@lodev09/react-native-true-sheet 4.0.0-beta.6 → 4.0.0-beta.8
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 +1 -0
- package/android/build.gradle +22 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayView.kt +106 -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 +3 -25
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetOverlayRootView.kt +137 -0
- package/android/src/main/java/com/lodev09/truesheet/utils/ViewUtils.kt +19 -0
- package/android/src/main/jni/TrueSheetSpec.h +1 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetLayoutUtils.h +46 -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 +36 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp +4 -35
- package/ios/TrueSheetContentView.h +3 -1
- package/ios/TrueSheetContentView.mm +11 -0
- 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 +76 -5
- package/ios/TrueSheetViewController.mm +27 -13
- package/ios/core/TrueSheetOverlayContainerView.h +31 -0
- package/ios/core/TrueSheetOverlayContainerView.mm +35 -0
- package/ios/tvos/TrueSheetStubs.mm +12 -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/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/mocks/index.js +10 -0
- package/lib/module/mocks/index.js.map +1 -1
- 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/fabric/TrueSheetOverlayViewNativeComponent.d.ts +6 -0
- package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts.map +1 -0
- 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/package.json +9 -1
- package/react-native.config.js +1 -0
- package/src/TrueSheetOverlay.tsx +24 -0
- package/src/TrueSheetOverlay.web.tsx +33 -0
- package/src/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
- package/src/index.ts +1 -0
- package/src/mocks/index.ts +7 -0
package/README.md
CHANGED
|
@@ -32,6 +32,7 @@ The true native bottom sheet experience for your React Native Apps. 💩
|
|
|
32
32
|
* 📜 **`scrollableRef`** - Pass a `ScrollView`/`FlatList` ref to wire nested scrolling, keyboard insets, and `auto` detent sizing
|
|
33
33
|
* 🦶 **Relative footers** - Footers take up space below the content by default
|
|
34
34
|
* 🧭 **Expo Router** - First-class support via the `Sheet` layout from `/navigation/expo-router`
|
|
35
|
+
* 🍞 **`TrueSheetOverlay`** - Render toasts, dialogs, and other [overlays above your sheets](https://sheet.lodev09.com/guides/overlays) — no more `FullWindowOverlay`/`Modal` workarounds
|
|
35
36
|
|
|
36
37
|
```sh
|
|
37
38
|
npx expo install @lodev09/react-native-true-sheet@beta
|
package/android/build.gradle
CHANGED
|
@@ -17,7 +17,28 @@ buildscript {
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
apply plugin: "com.android.library"
|
|
20
|
-
|
|
20
|
+
// Android Gradle Plugin 9 ships built-in Kotlin support and applies the Kotlin
|
|
21
|
+
// plugin itself. Applying it a second time fails the configuration phase with
|
|
22
|
+
// "Cannot add extension with name 'kotlin'". Apply it only when AGP is not
|
|
23
|
+
// providing Kotlin: AGP 8 and older, or AGP 9 with android.builtInKotlin=false.
|
|
24
|
+
def shouldApplyKotlinPlugin() {
|
|
25
|
+
def agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0].toInteger()
|
|
26
|
+
if (agpMajor <= 8) {
|
|
27
|
+
return true
|
|
28
|
+
}
|
|
29
|
+
// AGP 10 removes the opt-out: built-in Kotlin is always active there.
|
|
30
|
+
if (agpMajor >= 10) {
|
|
31
|
+
return false
|
|
32
|
+
}
|
|
33
|
+
def propertyVal = providers.gradleProperty("android.builtInKotlin").orNull
|
|
34
|
+
def builtInKotlinEnabled = propertyVal != null ? propertyVal.toBoolean() : true
|
|
35
|
+
return !builtInKotlinEnabled
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (shouldApplyKotlinPlugin()) {
|
|
39
|
+
apply plugin: "kotlin-android"
|
|
40
|
+
}
|
|
41
|
+
|
|
21
42
|
apply plugin: "com.facebook.react"
|
|
22
43
|
|
|
23
44
|
def getExtOrIntegerDefault(name) {
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
package com.lodev09.truesheet
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.view.View
|
|
5
|
+
import android.view.ViewGroup
|
|
6
|
+
import android.view.accessibility.AccessibilityEvent
|
|
7
|
+
import com.facebook.react.uimanager.StateWrapper
|
|
8
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
9
|
+
import com.facebook.react.uimanager.events.EventDispatcher
|
|
10
|
+
import com.facebook.react.views.view.ReactViewGroup
|
|
11
|
+
import com.lodev09.truesheet.core.TrueSheetOverlayRootView
|
|
12
|
+
import com.lodev09.truesheet.core.TrueSheetOverlayRootViewDelegate
|
|
13
|
+
import com.lodev09.truesheet.utils.findRootContainerView
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Hidden host that renders its children in a root view attached to the
|
|
17
|
+
* activity's content view, above every presented sheet.
|
|
18
|
+
*/
|
|
19
|
+
@SuppressLint("ViewConstructor")
|
|
20
|
+
class TrueSheetOverlayView(private val reactContext: ThemedReactContext) :
|
|
21
|
+
ReactViewGroup(reactContext),
|
|
22
|
+
TrueSheetOverlayRootViewDelegate {
|
|
23
|
+
|
|
24
|
+
private val rootView = TrueSheetOverlayRootView(reactContext).apply {
|
|
25
|
+
delegate = this@TrueSheetOverlayView
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private var rootContainerView: ViewGroup? = null
|
|
29
|
+
|
|
30
|
+
override var eventDispatcher: EventDispatcher? = null
|
|
31
|
+
|
|
32
|
+
private var lastWidth = 0
|
|
33
|
+
private var lastHeight = 0
|
|
34
|
+
|
|
35
|
+
var stateWrapper: StateWrapper? = null
|
|
36
|
+
set(value) {
|
|
37
|
+
field = value
|
|
38
|
+
pushState()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
init {
|
|
42
|
+
// The host stays in the React tree for layout only — children render in the root view
|
|
43
|
+
visibility = GONE
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ==================== View Hierarchy Management ====================
|
|
47
|
+
|
|
48
|
+
override fun onAttachedToWindow() {
|
|
49
|
+
super.onAttachedToWindow()
|
|
50
|
+
attachRootView()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
override fun onDetachedFromWindow() {
|
|
54
|
+
detachRootView()
|
|
55
|
+
super.onDetachedFromWindow()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private fun attachRootView() {
|
|
59
|
+
if (rootView.parent != null) return
|
|
60
|
+
|
|
61
|
+
val container = findRootContainerView(reactContext) ?: return
|
|
62
|
+
container.addView(rootView)
|
|
63
|
+
rootContainerView = container
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private fun detachRootView() {
|
|
67
|
+
rootContainerView?.removeView(rootView)
|
|
68
|
+
rootContainerView = null
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
fun onDropInstance() {
|
|
72
|
+
detachRootView()
|
|
73
|
+
rootView.delegate = null
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
override fun addView(child: View?, index: Int) {
|
|
77
|
+
rootView.addView(child, index)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
override fun getChildCount(): Int = rootView.childCount
|
|
81
|
+
|
|
82
|
+
override fun getChildAt(index: Int): View? = rootView.getChildAt(index)
|
|
83
|
+
|
|
84
|
+
override fun removeViewAt(index: Int) {
|
|
85
|
+
rootView.removeViewAt(index)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Accessibility: children live in the root view since this view is hidden
|
|
89
|
+
override fun addChildrenForAccessibility(outChildren: ArrayList<View>) {}
|
|
90
|
+
override fun dispatchPopulateAccessibilityEvent(event: AccessibilityEvent): Boolean = false
|
|
91
|
+
|
|
92
|
+
// ==================== State ====================
|
|
93
|
+
|
|
94
|
+
override fun overlayRootViewDidChangeSize(width: Int, height: Int) {
|
|
95
|
+
if (width == lastWidth && height == lastHeight) return
|
|
96
|
+
|
|
97
|
+
lastWidth = width
|
|
98
|
+
lastHeight = height
|
|
99
|
+
pushState()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private fun pushState() {
|
|
103
|
+
if (lastWidth == 0 && lastHeight == 0) return
|
|
104
|
+
stateWrapper?.let { TrueSheetStateUpdater.updateContainerSize(it, lastWidth, lastHeight) }
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
package com.lodev09.truesheet
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
4
|
+
import com.facebook.react.uimanager.PointerEvents
|
|
5
|
+
import com.facebook.react.uimanager.ReactStylesDiffMap
|
|
6
|
+
import com.facebook.react.uimanager.StateWrapper
|
|
7
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
8
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
9
|
+
import com.facebook.react.uimanager.ViewGroupManager
|
|
10
|
+
import com.facebook.react.uimanager.annotations.ReactProp
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* ViewManager for TrueSheetOverlayView
|
|
14
|
+
* Renders content above every presented sheet
|
|
15
|
+
*/
|
|
16
|
+
@ReactModule(name = TrueSheetOverlayViewManager.REACT_CLASS)
|
|
17
|
+
class TrueSheetOverlayViewManager : ViewGroupManager<TrueSheetOverlayView>() {
|
|
18
|
+
|
|
19
|
+
override fun getName(): String = REACT_CLASS
|
|
20
|
+
|
|
21
|
+
override fun createViewInstance(reactContext: ThemedReactContext): TrueSheetOverlayView = TrueSheetOverlayView(reactContext)
|
|
22
|
+
|
|
23
|
+
override fun onDropViewInstance(view: TrueSheetOverlayView) {
|
|
24
|
+
super.onDropViewInstance(view)
|
|
25
|
+
view.onDropInstance()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
override fun addEventEmitters(reactContext: ThemedReactContext, view: TrueSheetOverlayView) {
|
|
29
|
+
view.eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, view.id)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
override fun updateState(view: TrueSheetOverlayView, props: ReactStylesDiffMap?, stateWrapper: StateWrapper?): Any? {
|
|
33
|
+
view.stateWrapper = stateWrapper
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@ReactProp(name = "pointerEvents")
|
|
38
|
+
fun setPointerEvents(view: TrueSheetOverlayView, pointerEventsStr: String?) {
|
|
39
|
+
view.pointerEvents = PointerEvents.parsePointerEvents(pointerEventsStr)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
companion object {
|
|
43
|
+
const val REACT_CLASS = "TrueSheetOverlayView"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
package com.lodev09.truesheet
|
|
2
2
|
|
|
3
|
+
import com.facebook.react.bridge.WritableNativeMap
|
|
4
|
+
import com.facebook.react.uimanager.PixelUtil.pxToDp
|
|
3
5
|
import com.facebook.react.uimanager.StateWrapper
|
|
4
6
|
|
|
5
7
|
/**
|
|
@@ -22,6 +24,22 @@ object TrueSheetStateUpdater {
|
|
|
22
24
|
fun updateState(stateWrapper: StateWrapper, widthDp: Float, heightDp: Float): Boolean =
|
|
23
25
|
isAvailable && nativeUpdateState(stateWrapper, widthDp, heightDp)
|
|
24
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Pushes the container size (px) into the Fabric state — synchronously when
|
|
29
|
+
* the bridge is available, async otherwise.
|
|
30
|
+
*/
|
|
31
|
+
fun updateContainerSize(stateWrapper: StateWrapper, widthPx: Int, heightPx: Int) {
|
|
32
|
+
val widthDp = widthPx.toFloat().pxToDp()
|
|
33
|
+
val heightDp = heightPx.toFloat().pxToDp()
|
|
34
|
+
|
|
35
|
+
if (updateState(stateWrapper, widthDp, heightDp)) return
|
|
36
|
+
|
|
37
|
+
val newStateData = WritableNativeMap()
|
|
38
|
+
newStateData.putDouble("containerWidth", widthDp.toDouble())
|
|
39
|
+
newStateData.putDouble("containerHeight", heightDp.toDouble())
|
|
40
|
+
stateWrapper.updateState(newStateData)
|
|
41
|
+
}
|
|
42
|
+
|
|
25
43
|
/** Returns false when unavailable — caller should fall back to async updateState. */
|
|
26
44
|
fun updateFooterState(stateWrapper: StateWrapper, bottomInsetDp: Float): Boolean =
|
|
27
45
|
isAvailable && nativeUpdateFooterState(stateWrapper, bottomInsetDp)
|
|
@@ -6,7 +6,6 @@ import android.view.ViewGroup
|
|
|
6
6
|
import android.view.accessibility.AccessibilityEvent
|
|
7
7
|
import androidx.annotation.UiThread
|
|
8
8
|
import com.facebook.react.bridge.LifecycleEventListener
|
|
9
|
-
import com.facebook.react.bridge.WritableNativeMap
|
|
10
9
|
import com.facebook.react.uimanager.PixelUtil.dpToPx
|
|
11
10
|
import com.facebook.react.uimanager.PixelUtil.pxToDp
|
|
12
11
|
import com.facebook.react.uimanager.StateWrapper
|
|
@@ -22,6 +21,7 @@ import com.lodev09.truesheet.core.RNScreensEventObserverDelegate
|
|
|
22
21
|
import com.lodev09.truesheet.core.TrueSheetStackManager
|
|
23
22
|
import com.lodev09.truesheet.events.*
|
|
24
23
|
import com.lodev09.truesheet.utils.KeyboardUtils
|
|
24
|
+
import com.lodev09.truesheet.utils.findRootContainerView
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Main TrueSheet host view that manages the sheet and dispatches events to JavaScript.
|
|
@@ -376,18 +376,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
376
376
|
lastContainerWidth = width
|
|
377
377
|
lastContainerHeight = height
|
|
378
378
|
|
|
379
|
-
|
|
380
|
-
val widthDp = width.toFloat().pxToDp()
|
|
381
|
-
val heightDp = height.toFloat().pxToDp()
|
|
382
|
-
|
|
383
|
-
// Synchronous update — commits and mounts within the same UI-thread frame
|
|
384
|
-
if (TrueSheetStateUpdater.updateState(sw, widthDp, heightDp)) return
|
|
385
|
-
|
|
386
|
-
// Fallback: async state update
|
|
387
|
-
val newStateData = WritableNativeMap()
|
|
388
|
-
newStateData.putDouble("containerWidth", widthDp.toDouble())
|
|
389
|
-
newStateData.putDouble("containerHeight", heightDp.toDouble())
|
|
390
|
-
sw.updateState(newStateData)
|
|
379
|
+
stateWrapper?.let { TrueSheetStateUpdater.updateContainerSize(it, width, height) }
|
|
391
380
|
}
|
|
392
381
|
|
|
393
382
|
// ==================== Sheet Actions ====================
|
|
@@ -731,16 +720,5 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
731
720
|
* of whichever window this view is in - whether that's the activity's window or a
|
|
732
721
|
* Modal's dialog window.
|
|
733
722
|
*/
|
|
734
|
-
override fun findRootContainerView(): ViewGroup?
|
|
735
|
-
var current: android.view.ViewParent? = parent
|
|
736
|
-
|
|
737
|
-
while (current != null) {
|
|
738
|
-
if (current is ViewGroup && current.id == android.R.id.content) {
|
|
739
|
-
return current
|
|
740
|
-
}
|
|
741
|
-
current = current.parent
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
return reactContext.currentActivity?.findViewById(android.R.id.content)
|
|
745
|
-
}
|
|
723
|
+
override fun findRootContainerView(): ViewGroup? = findRootContainerView(reactContext)
|
|
746
724
|
}
|
|
@@ -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
|
+
}
|
|
@@ -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
|
|
@@ -21,3 +23,20 @@ fun ViewGroup.smoothScrollTo(x: Int, y: Int) {
|
|
|
21
23
|
is NestedScrollView -> smoothScrollTo(x, y)
|
|
22
24
|
}
|
|
23
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 {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <react/renderer/components/view/conversions.h>
|
|
4
|
+
#include <yoga/node/Node.h>
|
|
5
|
+
#include <yoga/style/Style.h>
|
|
6
|
+
|
|
7
|
+
namespace facebook::react {
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* Overrides a node's Yoga dimensions with the container size reported from
|
|
11
|
+
* native. A zero dimension leaves that axis to Yoga.
|
|
12
|
+
*/
|
|
13
|
+
inline void applyContainerSizeToYogaNode(
|
|
14
|
+
yoga::Node &node,
|
|
15
|
+
const yoga::Style &baseStyle,
|
|
16
|
+
float containerWidth,
|
|
17
|
+
float containerHeight) {
|
|
18
|
+
if (containerWidth <= 0 && containerHeight <= 0) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
yoga::Style adjustedStyle = baseStyle;
|
|
23
|
+
const auto ¤tStyle = node.style();
|
|
24
|
+
bool needsUpdate = false;
|
|
25
|
+
|
|
26
|
+
if (containerWidth > 0) {
|
|
27
|
+
adjustedStyle.setDimension(yoga::Dimension::Width, yoga::StyleSizeLength::points(containerWidth));
|
|
28
|
+
if (adjustedStyle.dimension(yoga::Dimension::Width) != currentStyle.dimension(yoga::Dimension::Width)) {
|
|
29
|
+
needsUpdate = true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (containerHeight > 0) {
|
|
34
|
+
adjustedStyle.setDimension(yoga::Dimension::Height, yoga::StyleSizeLength::points(containerHeight));
|
|
35
|
+
if (adjustedStyle.dimension(yoga::Dimension::Height) != currentStyle.dimension(yoga::Dimension::Height)) {
|
|
36
|
+
needsUpdate = true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (needsUpdate) {
|
|
41
|
+
node.setStyle(adjustedStyle);
|
|
42
|
+
node.setDirty(true);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
} // namespace facebook::react
|
package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewComponentDescriptor.h
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.h>
|
|
4
|
+
#include <react/renderer/core/ConcreteComponentDescriptor.h>
|
|
5
|
+
|
|
6
|
+
namespace facebook::react {
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* Descriptor for <TrueSheetOverlayView> component.
|
|
10
|
+
*/
|
|
11
|
+
class TrueSheetOverlayViewComponentDescriptor final
|
|
12
|
+
: public ConcreteComponentDescriptor<TrueSheetOverlayViewShadowNode> {
|
|
13
|
+
using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
|
|
14
|
+
|
|
15
|
+
void adopt(ShadowNode &shadowNode) const override {
|
|
16
|
+
static_cast<TrueSheetOverlayViewShadowNode &>(shadowNode).adjustLayoutWithState();
|
|
17
|
+
ConcreteComponentDescriptor::adopt(shadowNode);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#include "TrueSheetOverlayViewShadowNode.h"
|
|
2
|
+
|
|
3
|
+
#include "TrueSheetLayoutUtils.h"
|
|
4
|
+
|
|
5
|
+
namespace facebook::react {
|
|
6
|
+
|
|
7
|
+
extern const char TrueSheetOverlayViewComponentName[] = "TrueSheetOverlayView";
|
|
8
|
+
|
|
9
|
+
void TrueSheetOverlayViewShadowNode::adjustLayoutWithState() {
|
|
10
|
+
ensureUnsealed();
|
|
11
|
+
|
|
12
|
+
const auto &stateData = getStateData();
|
|
13
|
+
applyContainerSizeToYogaNode(
|
|
14
|
+
yogaNode_, getConcreteProps().yogaStyle, stateData.containerWidth, stateData.containerHeight);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <jsi/jsi.h>
|
|
4
|
+
#include <react/renderer/components/TrueSheetSpec/EventEmitters.h>
|
|
5
|
+
#include <react/renderer/components/TrueSheetSpec/Props.h>
|
|
6
|
+
#include <react/renderer/components/TrueSheetSpec/TrueSheetViewState.h>
|
|
7
|
+
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
|
|
8
|
+
|
|
9
|
+
namespace facebook::react {
|
|
10
|
+
|
|
11
|
+
JSI_EXPORT extern const char TrueSheetOverlayViewComponentName[];
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
* `ShadowNode` for <TrueSheetOverlayView> component.
|
|
15
|
+
* Sized to the window from native (via TrueSheetViewState) so its children
|
|
16
|
+
* lay out against the full window like the sheet's container.
|
|
17
|
+
*/
|
|
18
|
+
class JSI_EXPORT TrueSheetOverlayViewShadowNode final
|
|
19
|
+
: public ConcreteViewShadowNode<
|
|
20
|
+
TrueSheetOverlayViewComponentName,
|
|
21
|
+
TrueSheetOverlayViewProps,
|
|
22
|
+
TrueSheetOverlayViewEventEmitter,
|
|
23
|
+
TrueSheetViewState> {
|
|
24
|
+
using ConcreteViewShadowNode::ConcreteViewShadowNode;
|
|
25
|
+
|
|
26
|
+
public:
|
|
27
|
+
static ShadowNodeTraits BaseTraits() {
|
|
28
|
+
auto traits = ConcreteViewShadowNode::BaseTraits();
|
|
29
|
+
traits.set(ShadowNodeTraits::Trait::RootNodeKind);
|
|
30
|
+
return traits;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
void adjustLayoutWithState();
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
} // namespace facebook::react
|
|
@@ -1,48 +1,17 @@
|
|
|
1
1
|
#include "TrueSheetViewShadowNode.h"
|
|
2
2
|
|
|
3
|
-
#include
|
|
3
|
+
#include "TrueSheetLayoutUtils.h"
|
|
4
4
|
|
|
5
5
|
namespace facebook::react {
|
|
6
6
|
|
|
7
|
-
using namespace yoga;
|
|
8
|
-
|
|
9
7
|
extern const char TrueSheetViewComponentName[] = "TrueSheetView";
|
|
10
8
|
|
|
11
9
|
void TrueSheetViewShadowNode::adjustLayoutWithState() {
|
|
12
10
|
ensureUnsealed();
|
|
13
11
|
|
|
14
|
-
auto
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
// If container dimensions are set from native, override Yoga's dimensions
|
|
19
|
-
if (stateData.containerWidth > 0 || stateData.containerHeight > 0) {
|
|
20
|
-
auto &props = getConcreteProps();
|
|
21
|
-
yoga::Style adjustedStyle = props.yogaStyle;
|
|
22
|
-
auto currentStyle = yogaNode_.style();
|
|
23
|
-
bool needsUpdate = false;
|
|
24
|
-
|
|
25
|
-
// Set width if provided
|
|
26
|
-
if (stateData.containerWidth > 0) {
|
|
27
|
-
adjustedStyle.setDimension(yoga::Dimension::Width, StyleSizeLength::points(stateData.containerWidth));
|
|
28
|
-
if (adjustedStyle.dimension(yoga::Dimension::Width) != currentStyle.dimension(yoga::Dimension::Width)) {
|
|
29
|
-
needsUpdate = true;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Set height if provided
|
|
34
|
-
if (stateData.containerHeight > 0) {
|
|
35
|
-
adjustedStyle.setDimension(yoga::Dimension::Height, StyleSizeLength::points(stateData.containerHeight));
|
|
36
|
-
if (adjustedStyle.dimension(yoga::Dimension::Height) != currentStyle.dimension(yoga::Dimension::Height)) {
|
|
37
|
-
needsUpdate = true;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (needsUpdate) {
|
|
42
|
-
yogaNode_.setStyle(adjustedStyle);
|
|
43
|
-
yogaNode_.setDirty(true);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
12
|
+
const auto &stateData = getStateData();
|
|
13
|
+
applyContainerSizeToYogaNode(
|
|
14
|
+
yogaNode_, getConcreteProps().yogaStyle, stateData.containerWidth, stateData.containerHeight);
|
|
46
15
|
}
|
|
47
16
|
|
|
48
17
|
#if !defined(ANDROID)
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
10
10
|
|
|
11
|
+
#import <React/RCTMountingTransactionObserving.h>
|
|
11
12
|
#import <React/RCTSurfaceTouchHandler.h>
|
|
12
13
|
#import <React/RCTViewComponentView.h>
|
|
13
14
|
#import <UIKit/UIKit.h>
|
|
@@ -28,7 +29,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
28
29
|
|
|
29
30
|
@end
|
|
30
31
|
|
|
31
|
-
@interface TrueSheetContentView
|
|
32
|
+
@interface TrueSheetContentView
|
|
33
|
+
: RCTViewComponentView <TrueSheetKeyboardObserverDelegate, RCTMountingTransactionObserving>
|
|
32
34
|
|
|
33
35
|
@property (nonatomic, weak, nullable) id<TrueSheetContentViewDelegate> delegate;
|
|
34
36
|
@property (nonatomic, assign) CGFloat keyboardScrollOffset;
|
|
@@ -159,6 +159,17 @@ using namespace facebook::react;
|
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
// A handle that commits in the same transaction as the ScrollView's insertion
|
|
163
|
+
// resolves to nothing — prop updates process before mount instructions, and a
|
|
164
|
+
// deep insertion doesn't pass through this view's mount hooks. Retry once the
|
|
165
|
+
// whole transaction has mounted.
|
|
166
|
+
- (void)mountingTransactionDidMount:(const MountingTransaction &)transaction
|
|
167
|
+
withSurfaceTelemetry:(const SurfaceTelemetry &)surfaceTelemetry {
|
|
168
|
+
if (_scrollableHandle > 0 && !_detectedScrollView) {
|
|
169
|
+
[self.delegate contentViewScrollViewDidChange];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
162
173
|
#pragma mark - Scrollable
|
|
163
174
|
|
|
164
175
|
// The scroll indicator follows automatically — UIKit derives its insets from
|