react-native-reserved-regions 0.1.0-alpha.0
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/LICENSE +20 -0
- package/README.md +90 -0
- package/ReservedRegions.podspec +20 -0
- package/android/build.gradle +57 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/reservedregions/RegionsChangeEvent.kt +37 -0
- package/android/src/main/java/com/reservedregions/ReservedRegion.kt +9 -0
- package/android/src/main/java/com/reservedregions/ReservedRegionsPackage.kt +17 -0
- package/android/src/main/java/com/reservedregions/ReservedRegionsView.kt +103 -0
- package/android/src/main/java/com/reservedregions/ReservedRegionsViewManager.kt +37 -0
- package/ios/ReservedRegionsView.h +14 -0
- package/ios/ReservedRegionsView.mm +105 -0
- package/lib/module/ReservedRegionsView.js +13 -0
- package/lib/module/ReservedRegionsView.js.map +1 -0
- package/lib/module/ReservedRegionsView.native.js +4 -0
- package/lib/module/ReservedRegionsView.native.js.map +1 -0
- package/lib/module/ReservedRegionsViewNativeComponent.ts +24 -0
- package/lib/module/index.js +53 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/ReservedRegionsView.d.ts +9 -0
- package/lib/typescript/src/ReservedRegionsView.d.ts.map +1 -0
- package/lib/typescript/src/ReservedRegionsView.native.d.ts +2 -0
- package/lib/typescript/src/ReservedRegionsView.native.d.ts.map +1 -0
- package/lib/typescript/src/ReservedRegionsViewNativeComponent.d.ts +19 -0
- package/lib/typescript/src/ReservedRegionsViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +32 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/package.json +147 -0
- package/src/ReservedRegionsView.native.tsx +1 -0
- package/src/ReservedRegionsView.tsx +13 -0
- package/src/ReservedRegionsViewNativeComponent.ts +24 -0
- package/src/index.tsx +84 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Janic Duplessis
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# react-native-reserved-regions
|
|
2
|
+
|
|
3
|
+
Provider-scoped display divisions and occlusions for React Native's New Architecture.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- React Native New Architecture (Fabric)
|
|
8
|
+
- iOS 27.1 for UIKit reserved regions; earlier iOS versions report an empty list
|
|
9
|
+
- Android 7.0 (API 24) or newer; folding features require a device supported by Jetpack WindowManager
|
|
10
|
+
|
|
11
|
+
The example app uses React Native 0.88.0-rc.1.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npm install react-native-reserved-regions
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
On iOS, install pods in your app's `ios` directory. Android links automatically through React Native autolinking.
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import {
|
|
25
|
+
ReservedRegionsProvider,
|
|
26
|
+
useReservedRegions,
|
|
27
|
+
} from 'react-native-reserved-regions';
|
|
28
|
+
|
|
29
|
+
function Screen() {
|
|
30
|
+
const regions = useReservedRegions();
|
|
31
|
+
return regions.map((region, index) => {
|
|
32
|
+
if (region.kind === 'division') {
|
|
33
|
+
console.log(region.frame, region.occludesContent);
|
|
34
|
+
} else {
|
|
35
|
+
console.log(region.frame);
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default function App() {
|
|
42
|
+
return (
|
|
43
|
+
<ReservedRegionsProvider style={{ flex: 1 }}>
|
|
44
|
+
<Screen />
|
|
45
|
+
</ReservedRegionsProvider>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`useReservedRegions()` returns an empty array until the native view reports its first layout and when no active regions overlap the provider. It must be called beneath a `ReservedRegionsProvider`. Each region's `frame` uses logical points relative to that provider's top-left corner. A nested provider establishes its own coordinate space. The provider accepts standard React Native `View` props and should cover the content whose reserved regions you want to inspect.
|
|
51
|
+
|
|
52
|
+
The public type is a tagged union:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
type ReservedRegion =
|
|
56
|
+
| {
|
|
57
|
+
kind: 'division';
|
|
58
|
+
frame: { x: number; y: number; width: number; height: number };
|
|
59
|
+
occludesContent: boolean;
|
|
60
|
+
}
|
|
61
|
+
| {
|
|
62
|
+
kind: 'occlusion';
|
|
63
|
+
frame: { x: number; y: number; width: number; height: number };
|
|
64
|
+
};
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
A division splits the available display. `occludesContent` tells you whether content under that division is hidden. An occlusion is an area where content is hidden. On iOS, division regions currently report `occludesContent: false`. The TypeScript declarations in `src/index.tsx` document every field.
|
|
68
|
+
|
|
69
|
+
## Native sources
|
|
70
|
+
|
|
71
|
+
- iOS queries UIKit's active `UIView` reserved division and occlusion regions on the provider view. The selectors are resolved at runtime so the library can compile with an SDK older than iOS 27.1. [Apple's reserved regions overview](https://developer.apple.com/videos/play/tech-talks/111463/)
|
|
72
|
+
- Android observes Jetpack WindowManager `FoldingFeature` values and Android display cutout rectangles. A separating or fully occluding fold becomes a division; `OcclusionType.FULL` sets `occludesContent` to `true`. Display cutouts become occlusions. [FoldingFeature reference](https://developer.android.com/reference/androidx/window/layout/FoldingFeature), [WindowInsets reference](https://developer.android.com/reference/android/view/WindowInsets)
|
|
73
|
+
|
|
74
|
+
The library reports geometry and does not reposition content. Normal safe area insets, system bars, and keyboard insets remain separate concerns.
|
|
75
|
+
|
|
76
|
+
## Development
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
yarn install
|
|
80
|
+
yarn typecheck
|
|
81
|
+
yarn lint
|
|
82
|
+
yarn test --watch=false
|
|
83
|
+
yarn prepare
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
To run the example, use `yarn example start`, then `yarn example ios` or `yarn example android`.
|
|
87
|
+
|
|
88
|
+
## License
|
|
89
|
+
|
|
90
|
+
MIT
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "ReservedRegions"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
14
|
+
s.source = { :git => "https://github.com/AppAndFlow/react-native-reserved-regions.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
|
|
17
|
+
s.private_header_files = "ios/**/*.h"
|
|
18
|
+
|
|
19
|
+
install_modules_dependencies(s)
|
|
20
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.ReservedRegions = [
|
|
3
|
+
kotlinVersion: "2.0.21",
|
|
4
|
+
minSdkVersion: 24,
|
|
5
|
+
compileSdkVersion: 36
|
|
6
|
+
]
|
|
7
|
+
|
|
8
|
+
ext.getExtOrDefault = { prop ->
|
|
9
|
+
if (rootProject.ext.has(prop)) {
|
|
10
|
+
return rootProject.ext.get(prop)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return ReservedRegions[prop]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
repositories {
|
|
17
|
+
google()
|
|
18
|
+
mavenCentral()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
dependencies {
|
|
22
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
23
|
+
// noinspection DifferentKotlinGradleVersion
|
|
24
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
apply plugin: "com.android.library"
|
|
30
|
+
|
|
31
|
+
if (project.extensions.findByName("kotlin") == null) {
|
|
32
|
+
apply plugin: "kotlin-android"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
apply plugin: "com.facebook.react"
|
|
37
|
+
|
|
38
|
+
android {
|
|
39
|
+
namespace "com.reservedregions"
|
|
40
|
+
|
|
41
|
+
compileSdkVersion getExtOrDefault("compileSdkVersion")
|
|
42
|
+
|
|
43
|
+
defaultConfig {
|
|
44
|
+
minSdkVersion getExtOrDefault("minSdkVersion")
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
compileOptions {
|
|
48
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
49
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
dependencies {
|
|
54
|
+
implementation "com.facebook.react:react-android"
|
|
55
|
+
implementation "androidx.window:window:1.5.1"
|
|
56
|
+
implementation "androidx.window:window-java:1.5.1"
|
|
57
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
package com.reservedregions
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.WritableMap
|
|
5
|
+
import com.facebook.react.uimanager.PixelUtil
|
|
6
|
+
import com.facebook.react.uimanager.events.Event
|
|
7
|
+
|
|
8
|
+
internal class RegionsChangeEvent(
|
|
9
|
+
surfaceId: Int,
|
|
10
|
+
viewTag: Int,
|
|
11
|
+
private val regions: List<ReservedRegion>,
|
|
12
|
+
) : Event<RegionsChangeEvent>(surfaceId, viewTag) {
|
|
13
|
+
override fun getEventName() = NAME
|
|
14
|
+
|
|
15
|
+
override fun getEventData(): WritableMap {
|
|
16
|
+
val payload = Arguments.createMap()
|
|
17
|
+
val regionArray = Arguments.createArray()
|
|
18
|
+
for (region in regions) {
|
|
19
|
+
val frame = Arguments.createMap()
|
|
20
|
+
frame.putDouble("x", PixelUtil.toDIPFromPixel(region.frame.left.toFloat()).toDouble())
|
|
21
|
+
frame.putDouble("y", PixelUtil.toDIPFromPixel(region.frame.top.toFloat()).toDouble())
|
|
22
|
+
frame.putDouble("width", PixelUtil.toDIPFromPixel(region.frame.width().toFloat()).toDouble())
|
|
23
|
+
frame.putDouble("height", PixelUtil.toDIPFromPixel(region.frame.height().toFloat()).toDouble())
|
|
24
|
+
val item = Arguments.createMap()
|
|
25
|
+
item.putString("kind", region.kind)
|
|
26
|
+
item.putMap("frame", frame)
|
|
27
|
+
item.putBoolean("occludesContent", region.occludesContent)
|
|
28
|
+
regionArray.pushMap(item)
|
|
29
|
+
}
|
|
30
|
+
payload.putArray("regions", regionArray)
|
|
31
|
+
return payload
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
companion object {
|
|
35
|
+
const val NAME = "topRegionsChange"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
package com.reservedregions
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
7
|
+
import com.facebook.react.uimanager.ViewManager
|
|
8
|
+
|
|
9
|
+
class ReservedRegionsViewPackage : BaseReactPackage() {
|
|
10
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
11
|
+
return listOf(ReservedRegionsViewManager())
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? = null
|
|
15
|
+
|
|
16
|
+
override fun getReactModuleInfoProvider() = ReactModuleInfoProvider { emptyMap() }
|
|
17
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
package com.reservedregions
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.Rect
|
|
5
|
+
import android.os.Build
|
|
6
|
+
import android.view.ViewTreeObserver
|
|
7
|
+
import android.view.WindowInsets
|
|
8
|
+
import androidx.core.content.ContextCompat
|
|
9
|
+
import androidx.core.util.Consumer
|
|
10
|
+
import androidx.window.java.layout.WindowInfoTrackerCallbackAdapter
|
|
11
|
+
import androidx.window.layout.FoldingFeature
|
|
12
|
+
import androidx.window.layout.WindowInfoTracker
|
|
13
|
+
import androidx.window.layout.WindowLayoutInfo
|
|
14
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
15
|
+
import com.facebook.react.views.view.ReactViewGroup
|
|
16
|
+
import kotlin.math.max
|
|
17
|
+
import kotlin.math.min
|
|
18
|
+
|
|
19
|
+
internal typealias RegionsChangeHandler = (ReservedRegionsView, List<ReservedRegion>) -> Unit
|
|
20
|
+
|
|
21
|
+
class ReservedRegionsView(context: Context) : ReactViewGroup(context), ViewTreeObserver.OnPreDrawListener {
|
|
22
|
+
private var tracker: WindowInfoTrackerCallbackAdapter? = null
|
|
23
|
+
private val layoutInfoConsumer = Consumer<WindowLayoutInfo> { info ->
|
|
24
|
+
foldingFeatures = info.displayFeatures.filterIsInstance<FoldingFeature>()
|
|
25
|
+
updateRegions()
|
|
26
|
+
}
|
|
27
|
+
private var foldingFeatures: List<FoldingFeature> = emptyList()
|
|
28
|
+
private var lastRegions: List<ReservedRegion>? = null
|
|
29
|
+
private var onRegionsChange: RegionsChangeHandler? = null
|
|
30
|
+
|
|
31
|
+
override fun onAttachedToWindow() {
|
|
32
|
+
super.onAttachedToWindow()
|
|
33
|
+
viewTreeObserver.addOnPreDrawListener(this)
|
|
34
|
+
val activity = (context as? ThemedReactContext)?.currentActivity
|
|
35
|
+
if (activity != null) {
|
|
36
|
+
tracker = WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(context))
|
|
37
|
+
tracker?.addWindowLayoutInfoListener(activity, ContextCompat.getMainExecutor(context), layoutInfoConsumer)
|
|
38
|
+
}
|
|
39
|
+
updateRegions()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
override fun onDetachedFromWindow() {
|
|
43
|
+
tracker?.removeWindowLayoutInfoListener(layoutInfoConsumer)
|
|
44
|
+
tracker = null
|
|
45
|
+
viewTreeObserver.removeOnPreDrawListener(this)
|
|
46
|
+
super.onDetachedFromWindow()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
override fun onPreDraw(): Boolean {
|
|
50
|
+
updateRegions()
|
|
51
|
+
return true
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
internal fun setOnRegionsChangeHandler(handler: RegionsChangeHandler) {
|
|
55
|
+
onRegionsChange = handler
|
|
56
|
+
updateRegions()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private fun updateRegions() {
|
|
60
|
+
val handler = onRegionsChange ?: return
|
|
61
|
+
if (!isAttachedToWindow || width == 0 || height == 0) return
|
|
62
|
+
|
|
63
|
+
val windowLocation = IntArray(2)
|
|
64
|
+
getLocationInWindow(windowLocation)
|
|
65
|
+
val regions = mutableListOf<ReservedRegion>()
|
|
66
|
+
|
|
67
|
+
for (feature in foldingFeatures) {
|
|
68
|
+
if (!feature.isSeparating && feature.occlusionType != FoldingFeature.OcclusionType.FULL) continue
|
|
69
|
+
val frame = clippedFrame(feature.bounds, windowLocation[0], windowLocation[1]) ?: continue
|
|
70
|
+
regions.add(ReservedRegion("division", frame, feature.occlusionType == FoldingFeature.OcclusionType.FULL))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (Build.VERSION.SDK_INT >= 35) {
|
|
74
|
+
rootWindowInsets?.getBoundingRects(WindowInsets.Type.displayCutout())?.forEach { bounds ->
|
|
75
|
+
clippedFrame(bounds, windowLocation[0], windowLocation[1])?.let { frame ->
|
|
76
|
+
regions.add(ReservedRegion("occlusion", frame))
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
} else if (Build.VERSION.SDK_INT >= 28) {
|
|
80
|
+
val screenLocation = IntArray(2)
|
|
81
|
+
getLocationOnScreen(screenLocation)
|
|
82
|
+
rootWindowInsets?.displayCutout?.boundingRects?.forEach { bounds ->
|
|
83
|
+
clippedFrame(bounds, screenLocation[0], screenLocation[1])?.let { frame ->
|
|
84
|
+
regions.add(ReservedRegion("occlusion", frame))
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (regions != lastRegions) {
|
|
90
|
+
lastRegions = regions
|
|
91
|
+
handler(this, regions)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private fun clippedFrame(bounds: Rect, originX: Int, originY: Int): Rect? {
|
|
96
|
+
val left = max(0, bounds.left - originX)
|
|
97
|
+
val top = max(0, bounds.top - originY)
|
|
98
|
+
val right = min(width, bounds.right - originX)
|
|
99
|
+
val bottom = min(height, bounds.bottom - originY)
|
|
100
|
+
if (right < left || bottom < top || (right == left && bottom == top)) return null
|
|
101
|
+
return Rect(left, top, right, bottom)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
package com.reservedregions
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReactContext
|
|
4
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
5
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
6
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
7
|
+
import com.facebook.react.uimanager.ViewGroupManager
|
|
8
|
+
import com.facebook.react.viewmanagers.ReservedRegionsViewManagerInterface
|
|
9
|
+
import com.facebook.react.viewmanagers.ReservedRegionsViewManagerDelegate
|
|
10
|
+
|
|
11
|
+
@ReactModule(name = ReservedRegionsViewManager.NAME)
|
|
12
|
+
class ReservedRegionsViewManager : ViewGroupManager<ReservedRegionsView>(),
|
|
13
|
+
ReservedRegionsViewManagerInterface<ReservedRegionsView> {
|
|
14
|
+
private val delegate = ReservedRegionsViewManagerDelegate(this)
|
|
15
|
+
|
|
16
|
+
override fun getDelegate() = delegate
|
|
17
|
+
|
|
18
|
+
override fun getName() = NAME
|
|
19
|
+
|
|
20
|
+
override fun createViewInstance(context: ThemedReactContext) = ReservedRegionsView(context)
|
|
21
|
+
|
|
22
|
+
override fun getExportedCustomDirectEventTypeConstants() =
|
|
23
|
+
mutableMapOf(RegionsChangeEvent.NAME to mutableMapOf("registrationName" to "onRegionsChange"))
|
|
24
|
+
|
|
25
|
+
override fun addEventEmitters(reactContext: ThemedReactContext, view: ReservedRegionsView) {
|
|
26
|
+
super.addEventEmitters(reactContext, view)
|
|
27
|
+
view.setOnRegionsChangeHandler { source, regions ->
|
|
28
|
+
val sourceContext = source.context as ReactContext
|
|
29
|
+
UIManagerHelper.getEventDispatcherForReactTag(sourceContext, source.id)
|
|
30
|
+
?.dispatchEvent(RegionsChangeEvent(UIManagerHelper.getSurfaceId(sourceContext), source.id, regions))
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
companion object {
|
|
35
|
+
const val NAME = "ReservedRegionsView"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#import <React/RCTViewComponentView.h>
|
|
2
|
+
#import <UIKit/UIKit.h>
|
|
3
|
+
|
|
4
|
+
#ifndef ReservedRegionsViewNativeComponent_h
|
|
5
|
+
#define ReservedRegionsViewNativeComponent_h
|
|
6
|
+
|
|
7
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
8
|
+
|
|
9
|
+
@interface ReservedRegionsView : RCTViewComponentView
|
|
10
|
+
@end
|
|
11
|
+
|
|
12
|
+
NS_ASSUME_NONNULL_END
|
|
13
|
+
|
|
14
|
+
#endif /* ReservedRegionsViewNativeComponent_h */
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#import "ReservedRegionsView.h"
|
|
2
|
+
|
|
3
|
+
#import <objc/message.h>
|
|
4
|
+
|
|
5
|
+
#import <react/renderer/components/ReservedRegionsViewSpec/ComponentDescriptors.h>
|
|
6
|
+
#import <react/renderer/components/ReservedRegionsViewSpec/EventEmitters.h>
|
|
7
|
+
#import <react/renderer/components/ReservedRegionsViewSpec/Props.h>
|
|
8
|
+
#import <react/renderer/components/ReservedRegionsViewSpec/RCTComponentViewHelpers.h>
|
|
9
|
+
|
|
10
|
+
#import "RCTFabricComponentsPlugins.h"
|
|
11
|
+
|
|
12
|
+
using namespace facebook::react;
|
|
13
|
+
|
|
14
|
+
@implementation ReservedRegionsView {
|
|
15
|
+
NSArray<NSDictionary *> *_currentRegions;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
+ (ComponentDescriptorProvider)componentDescriptorProvider
|
|
19
|
+
{
|
|
20
|
+
return concreteComponentDescriptorProvider<ReservedRegionsViewComponentDescriptor>();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
- (instancetype)initWithFrame:(CGRect)frame
|
|
24
|
+
{
|
|
25
|
+
if (self = [super initWithFrame:frame]) {
|
|
26
|
+
static const auto defaultProps = std::make_shared<const ReservedRegionsViewProps>();
|
|
27
|
+
_props = defaultProps;
|
|
28
|
+
}
|
|
29
|
+
return self;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
- (void)layoutSubviews
|
|
33
|
+
{
|
|
34
|
+
[super layoutSubviews];
|
|
35
|
+
[self updateRegions];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
- (void)didMoveToWindow
|
|
39
|
+
{
|
|
40
|
+
[super didMoveToWindow];
|
|
41
|
+
[self updateRegions];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
- (void)updateRegions
|
|
45
|
+
{
|
|
46
|
+
if (self.window == nil || !_eventEmitter) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// UIView reserved region selectors first ship in the iOS 27.1 SDK.
|
|
51
|
+
SEL querySelector = NSSelectorFromString(@"reservedRegionsOfKind:options:");
|
|
52
|
+
Class kindClass = NSClassFromString(@"UIViewReservedRegionKind");
|
|
53
|
+
NSMutableArray<NSDictionary *> *regions = [NSMutableArray new];
|
|
54
|
+
|
|
55
|
+
if ([self respondsToSelector:querySelector] && kindClass != nil) {
|
|
56
|
+
NSArray<NSDictionary<NSString *, NSString *> *> *kinds = @[
|
|
57
|
+
@{@"selector": @"divisionRegionKind", @"kind": @"division"},
|
|
58
|
+
@{@"selector": @"occlusionRegionKind", @"kind": @"occlusion"},
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
for (NSDictionary<NSString *, NSString *> *entry in kinds) {
|
|
62
|
+
SEL kindSelector = NSSelectorFromString(entry[@"selector"]);
|
|
63
|
+
if (![kindClass respondsToSelector:kindSelector]) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
id kind = ((id (*)(id, SEL))objc_msgSend)(kindClass, kindSelector);
|
|
68
|
+
NSArray *results = ((NSArray *(*)(id, SEL, id, NSUInteger))objc_msgSend)(self, querySelector, kind, 0);
|
|
69
|
+
for (id region in results) {
|
|
70
|
+
if (![region respondsToSelector:@selector(frame)]) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
CGRect frame = [[region valueForKey:@"frame"] CGRectValue];
|
|
74
|
+
[regions addObject:@{
|
|
75
|
+
@"kind": entry[@"kind"],
|
|
76
|
+
@"frame": [NSValue valueWithCGRect:frame],
|
|
77
|
+
}];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if ([_currentRegions isEqualToArray:regions]) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
_currentRegions = [regions copy];
|
|
86
|
+
|
|
87
|
+
ReservedRegionsViewEventEmitter::OnRegionsChange event;
|
|
88
|
+
for (NSDictionary *region in regions) {
|
|
89
|
+
CGRect frame = [region[@"frame"] CGRectValue];
|
|
90
|
+
event.regions.push_back({
|
|
91
|
+
[region[@"kind"] UTF8String],
|
|
92
|
+
{frame.origin.x, frame.origin.y, frame.size.width, frame.size.height},
|
|
93
|
+
false,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
std::static_pointer_cast<ReservedRegionsViewEventEmitter const>(_eventEmitter)->onRegionsChange(event);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
- (void)prepareForRecycle
|
|
100
|
+
{
|
|
101
|
+
[super prepareForRecycle];
|
|
102
|
+
_currentRegions = nil;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { View } from 'react-native';
|
|
4
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
5
|
+
export function ReservedRegionsView({
|
|
6
|
+
onRegionsChange: _onRegionsChange,
|
|
7
|
+
...props
|
|
8
|
+
}) {
|
|
9
|
+
return /*#__PURE__*/_jsx(View, {
|
|
10
|
+
...props
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=ReservedRegionsView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["View","jsx","_jsx","ReservedRegionsView","onRegionsChange","_onRegionsChange","props"],"sourceRoot":"../../src","sources":["ReservedRegionsView.tsx"],"mappings":";;AAAA,SAASA,IAAI,QAAwB,cAAc;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAOpD,OAAO,SAASC,mBAAmBA,CAAC;EAClCC,eAAe,EAAEC,gBAAgB;EACjC,GAAGC;AACqB,CAAC,EAAE;EAC3B,oBAAOJ,IAAA,CAACF,IAAI;IAAA,GAAKM;EAAK,CAAG,CAAC;AAC5B","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["default","ReservedRegionsView"],"sourceRoot":"../../src","sources":["ReservedRegionsView.native.tsx"],"mappings":";;AAAA,SAASA,OAAO,IAAIC,mBAAmB,QAAQ,sCAAsC","ignoreList":[]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
codegenNativeComponent,
|
|
3
|
+
type CodegenTypes,
|
|
4
|
+
type ViewProps,
|
|
5
|
+
} from 'react-native';
|
|
6
|
+
|
|
7
|
+
export type RegionsChangeEvent = Readonly<{
|
|
8
|
+
regions: {
|
|
9
|
+
kind: string;
|
|
10
|
+
frame: {
|
|
11
|
+
x: CodegenTypes.Double;
|
|
12
|
+
y: CodegenTypes.Double;
|
|
13
|
+
width: CodegenTypes.Double;
|
|
14
|
+
height: CodegenTypes.Double;
|
|
15
|
+
};
|
|
16
|
+
occludesContent: boolean;
|
|
17
|
+
}[];
|
|
18
|
+
}>;
|
|
19
|
+
|
|
20
|
+
export interface NativeProps extends ViewProps {
|
|
21
|
+
onRegionsChange?: CodegenTypes.DirectEventHandler<RegionsChangeEvent>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default codegenNativeComponent<NativeProps>('ReservedRegionsView');
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { ReservedRegionsView } from './ReservedRegionsView';
|
|
5
|
+
|
|
6
|
+
/** A rectangle in logical points relative to the nearest ReservedRegionsProvider. */
|
|
7
|
+
|
|
8
|
+
/** An active area reserved by the display or system UI. */
|
|
9
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
10
|
+
const ReservedRegionsContext = /*#__PURE__*/React.createContext(null);
|
|
11
|
+
|
|
12
|
+
/** Makes active reserved regions available to descendants in provider coordinates. */
|
|
13
|
+
export function ReservedRegionsProvider({
|
|
14
|
+
children,
|
|
15
|
+
...props
|
|
16
|
+
}) {
|
|
17
|
+
const [regions, setRegions] = React.useState([]);
|
|
18
|
+
return /*#__PURE__*/_jsx(ReservedRegionsContext.Provider, {
|
|
19
|
+
value: regions,
|
|
20
|
+
children: /*#__PURE__*/_jsx(ReservedRegionsView, {
|
|
21
|
+
...props,
|
|
22
|
+
onRegionsChange: event => {
|
|
23
|
+
setRegions(event.nativeEvent.regions.flatMap(region => {
|
|
24
|
+
if (region.kind === 'division') {
|
|
25
|
+
return [{
|
|
26
|
+
kind: 'division',
|
|
27
|
+
frame: region.frame,
|
|
28
|
+
occludesContent: region.occludesContent
|
|
29
|
+
}];
|
|
30
|
+
}
|
|
31
|
+
if (region.kind === 'occlusion') {
|
|
32
|
+
return [{
|
|
33
|
+
kind: 'occlusion',
|
|
34
|
+
frame: region.frame
|
|
35
|
+
}];
|
|
36
|
+
}
|
|
37
|
+
return [];
|
|
38
|
+
}));
|
|
39
|
+
},
|
|
40
|
+
children: children
|
|
41
|
+
})
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Returns active regions reported by the nearest ReservedRegionsProvider. */
|
|
46
|
+
export function useReservedRegions() {
|
|
47
|
+
const regions = React.useContext(ReservedRegionsContext);
|
|
48
|
+
if (regions === null) {
|
|
49
|
+
throw new Error('useReservedRegions must be used inside ReservedRegionsProvider');
|
|
50
|
+
}
|
|
51
|
+
return regions;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["React","ReservedRegionsView","jsx","_jsx","ReservedRegionsContext","createContext","ReservedRegionsProvider","children","props","regions","setRegions","useState","Provider","value","onRegionsChange","event","nativeEvent","flatMap","region","kind","frame","occludesContent","useReservedRegions","useContext","Error"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,OAAO,KAAKA,KAAK,MAAM,OAAO;AAE9B,SAASC,mBAAmB,QAAQ,uBAAuB;;AAE3D;;AAYA;AAAA,SAAAC,GAAA,IAAAC,IAAA;AAiBA,MAAMC,sBAAsB,gBAAGJ,KAAK,CAACK,aAAa,CAEhD,IAAI,CAAC;;AAEP;AACA,OAAO,SAASC,uBAAuBA,CAAC;EACtCC,QAAQ;EACR,GAAGC;AACM,CAAC,EAAqB;EAC/B,MAAM,CAACC,OAAO,EAAEC,UAAU,CAAC,GAAGV,KAAK,CAACW,QAAQ,CAA4B,EAAE,CAAC;EAE3E,oBACER,IAAA,CAACC,sBAAsB,CAACQ,QAAQ;IAACC,KAAK,EAAEJ,OAAQ;IAAAF,QAAA,eAC9CJ,IAAA,CAACF,mBAAmB;MAAA,GACdO,KAAK;MACTM,eAAe,EAAGC,KAAK,IAAK;QAC1BL,UAAU,CACRK,KAAK,CAACC,WAAW,CAACP,OAAO,CAACQ,OAAO,CAAEC,MAAM,IAAuB;UAC9D,IAAIA,MAAM,CAACC,IAAI,KAAK,UAAU,EAAE;YAC9B,OAAO,CACL;cACEA,IAAI,EAAE,UAAmB;cACzBC,KAAK,EAAEF,MAAM,CAACE,KAAK;cACnBC,eAAe,EAAEH,MAAM,CAACG;YAC1B,CAAC,CACF;UACH;UACA,IAAIH,MAAM,CAACC,IAAI,KAAK,WAAW,EAAE;YAC/B,OAAO,CAAC;cAAEA,IAAI,EAAE,WAAoB;cAAEC,KAAK,EAAEF,MAAM,CAACE;YAAM,CAAC,CAAC;UAC9D;UACA,OAAO,EAAE;QACX,CAAC,CACH,CAAC;MACH,CAAE;MAAAb,QAAA,EAEDA;IAAQ,CACU;EAAC,CACS,CAAC;AAEtC;;AAEA;AACA,OAAO,SAASe,kBAAkBA,CAAA,EAA8B;EAC9D,MAAMb,OAAO,GAAGT,KAAK,CAACuB,UAAU,CAACnB,sBAAsB,CAAC;EACxD,IAAIK,OAAO,KAAK,IAAI,EAAE;IACpB,MAAM,IAAIe,KAAK,CACb,gEACF,CAAC;EACH;EACA,OAAOf,OAAO;AAChB","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ViewProps } from 'react-native';
|
|
2
|
+
import type { RegionsChangeEvent } from './ReservedRegionsViewNativeComponent';
|
|
3
|
+
export type ReservedRegionsViewProps = ViewProps & {
|
|
4
|
+
onRegionsChange?: (event: {
|
|
5
|
+
nativeEvent: RegionsChangeEvent;
|
|
6
|
+
}) => void;
|
|
7
|
+
};
|
|
8
|
+
export declare function ReservedRegionsView({ onRegionsChange: _onRegionsChange, ...props }: ReservedRegionsViewProps): import("react").JSX.Element;
|
|
9
|
+
//# sourceMappingURL=ReservedRegionsView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReservedRegionsView.d.ts","sourceRoot":"","sources":["../../../src/ReservedRegionsView.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAE/E,MAAM,MAAM,wBAAwB,GAAG,SAAS,GAAG;IACjD,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,WAAW,EAAE,kBAAkB,CAAA;KAAE,KAAK,IAAI,CAAC;CACxE,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,EAClC,eAAe,EAAE,gBAAgB,EACjC,GAAG,KAAK,EACT,EAAE,wBAAwB,+BAE1B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReservedRegionsView.native.d.ts","sourceRoot":"","sources":["../../../src/ReservedRegionsView.native.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,sCAAsC,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type CodegenTypes, type ViewProps } from 'react-native';
|
|
2
|
+
export type RegionsChangeEvent = Readonly<{
|
|
3
|
+
regions: {
|
|
4
|
+
kind: string;
|
|
5
|
+
frame: {
|
|
6
|
+
x: CodegenTypes.Double;
|
|
7
|
+
y: CodegenTypes.Double;
|
|
8
|
+
width: CodegenTypes.Double;
|
|
9
|
+
height: CodegenTypes.Double;
|
|
10
|
+
};
|
|
11
|
+
occludesContent: boolean;
|
|
12
|
+
}[];
|
|
13
|
+
}>;
|
|
14
|
+
export interface NativeProps extends ViewProps {
|
|
15
|
+
onRegionsChange?: CodegenTypes.DirectEventHandler<RegionsChangeEvent>;
|
|
16
|
+
}
|
|
17
|
+
declare const _default: import("react-native").HostComponent<NativeProps>;
|
|
18
|
+
export default _default;
|
|
19
|
+
//# sourceMappingURL=ReservedRegionsViewNativeComponent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReservedRegionsViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../src/ReservedRegionsViewNativeComponent.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAEtB,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;IACxC,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE;YACL,CAAC,EAAE,YAAY,CAAC,MAAM,CAAC;YACvB,CAAC,EAAE,YAAY,CAAC,MAAM,CAAC;YACvB,KAAK,EAAE,YAAY,CAAC,MAAM,CAAC;YAC3B,MAAM,EAAE,YAAY,CAAC,MAAM,CAAC;SAC7B,CAAC;QACF,eAAe,EAAE,OAAO,CAAC;KAC1B,EAAE,CAAC;CACL,CAAC,CAAC;AAEH,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC5C,eAAe,CAAC,EAAE,YAAY,CAAC,kBAAkB,CAAC,kBAAkB,CAAC,CAAC;CACvE;;AAED,wBAA0E"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { ViewProps } from 'react-native';
|
|
3
|
+
/** A rectangle in logical points relative to the nearest ReservedRegionsProvider. */
|
|
4
|
+
export type ReservedRegionFrame = Readonly<{
|
|
5
|
+
/** Distance from the provider's left edge. */
|
|
6
|
+
x: number;
|
|
7
|
+
/** Distance from the provider's top edge. */
|
|
8
|
+
y: number;
|
|
9
|
+
/** Horizontal extent of the reserved area. */
|
|
10
|
+
width: number;
|
|
11
|
+
/** Vertical extent of the reserved area. */
|
|
12
|
+
height: number;
|
|
13
|
+
}>;
|
|
14
|
+
/** An active area reserved by the display or system UI. */
|
|
15
|
+
export type ReservedRegion = Readonly<{
|
|
16
|
+
/** A seam or fold that divides the usable display. */
|
|
17
|
+
kind: 'division';
|
|
18
|
+
/** The division's bounds in the provider's coordinate space. */
|
|
19
|
+
frame: ReservedRegionFrame;
|
|
20
|
+
/** Whether content underneath the division is hidden. */
|
|
21
|
+
occludesContent: boolean;
|
|
22
|
+
}> | Readonly<{
|
|
23
|
+
/** An area where content is hidden, such as a camera cutout. */
|
|
24
|
+
kind: 'occlusion';
|
|
25
|
+
/** The occlusion's bounds in the provider's coordinate space. */
|
|
26
|
+
frame: ReservedRegionFrame;
|
|
27
|
+
}>;
|
|
28
|
+
/** Makes active reserved regions available to descendants in provider coordinates. */
|
|
29
|
+
export declare function ReservedRegionsProvider({ children, ...props }: ViewProps): React.JSX.Element;
|
|
30
|
+
/** Returns active regions reported by the nearest ReservedRegionsProvider. */
|
|
31
|
+
export declare function useReservedRegions(): readonly ReservedRegion[];
|
|
32
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAG9C,qFAAqF;AACrF,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,8CAA8C;IAC9C,CAAC,EAAE,MAAM,CAAC;IACV,6CAA6C;IAC7C,CAAC,EAAE,MAAM,CAAC;IACV,8CAA8C;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC,CAAC;AAEH,2DAA2D;AAC3D,MAAM,MAAM,cAAc,GACtB,QAAQ,CAAC;IACP,sDAAsD;IACtD,IAAI,EAAE,UAAU,CAAC;IACjB,gEAAgE;IAChE,KAAK,EAAE,mBAAmB,CAAC;IAC3B,yDAAyD;IACzD,eAAe,EAAE,OAAO,CAAC;CAC1B,CAAC,GACF,QAAQ,CAAC;IACP,gEAAgE;IAChE,IAAI,EAAE,WAAW,CAAC;IAClB,iEAAiE;IACjE,KAAK,EAAE,mBAAmB,CAAC;CAC5B,CAAC,CAAC;AAMP,sFAAsF;AACtF,wBAAgB,uBAAuB,CAAC,EACtC,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CA+B/B;AAED,8EAA8E;AAC9E,wBAAgB,kBAAkB,IAAI,SAAS,cAAc,EAAE,CAQ9D"}
|
package/package.json
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-native-reserved-regions",
|
|
3
|
+
"version": "0.1.0-alpha.0",
|
|
4
|
+
"description": "Provider-scoped reserved display regions for React Native",
|
|
5
|
+
"main": "./lib/module/index.js",
|
|
6
|
+
"types": "./lib/typescript/src/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"react-native-reserved-regions-source": "./src/index.tsx",
|
|
10
|
+
"types": "./lib/typescript/src/index.d.ts",
|
|
11
|
+
"default": "./lib/module/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"src",
|
|
17
|
+
"lib",
|
|
18
|
+
"android",
|
|
19
|
+
"ios",
|
|
20
|
+
"cpp",
|
|
21
|
+
"*.podspec",
|
|
22
|
+
"react-native.config.js",
|
|
23
|
+
"!ios/build",
|
|
24
|
+
"!android/build",
|
|
25
|
+
"!android/gradle",
|
|
26
|
+
"!android/gradlew",
|
|
27
|
+
"!android/gradlew.bat",
|
|
28
|
+
"!android/local.properties",
|
|
29
|
+
"!**/__tests__",
|
|
30
|
+
"!**/__fixtures__",
|
|
31
|
+
"!**/__mocks__",
|
|
32
|
+
"!**/.*"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"example": "yarn workspace react-native-reserved-regions-example",
|
|
36
|
+
"clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
|
|
37
|
+
"prepare": "bob build",
|
|
38
|
+
"typecheck": "tsc",
|
|
39
|
+
"lint": "eslint \"**/*.{js,ts,tsx}\"",
|
|
40
|
+
"test": "jest"
|
|
41
|
+
},
|
|
42
|
+
"keywords": [
|
|
43
|
+
"react-native",
|
|
44
|
+
"ios",
|
|
45
|
+
"android"
|
|
46
|
+
],
|
|
47
|
+
"author": "Janic Duplessis <janicduplessis@gmail.com> (https://github.com/janicduplessis)",
|
|
48
|
+
"license": "MIT",
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"registry": "https://registry.npmjs.org/"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@eslint/compat": "^2.1.0",
|
|
54
|
+
"@eslint/eslintrc": "^3.3.6",
|
|
55
|
+
"@eslint/js": "^9.39.5",
|
|
56
|
+
"@jest/globals": "^29.7.0",
|
|
57
|
+
"@react-native/babel-preset": "0.88.0-rc.1",
|
|
58
|
+
"@react-native/eslint-config": "0.86.2",
|
|
59
|
+
"@react-native/jest-preset": "0.86.2",
|
|
60
|
+
"@types/react": "^19.2.0",
|
|
61
|
+
"@types/react-test-renderer": "^19.1.0",
|
|
62
|
+
"del-cli": "^7.0.0",
|
|
63
|
+
"eslint": "^9.39.5",
|
|
64
|
+
"eslint-config-prettier": "^10.1.8",
|
|
65
|
+
"eslint-plugin-ft-flow": "^3.0.11",
|
|
66
|
+
"eslint-plugin-prettier": "^5.5.6",
|
|
67
|
+
"jest": "^29.7.0",
|
|
68
|
+
"prettier": "^3.9.6",
|
|
69
|
+
"react": "19.3.0",
|
|
70
|
+
"react-native": "0.88.0-rc.1",
|
|
71
|
+
"react-native-builder-bob": "^0.43.1",
|
|
72
|
+
"react-test-renderer": "19.3.0",
|
|
73
|
+
"turbo": "^2.10.8",
|
|
74
|
+
"typescript": "^6.0.3"
|
|
75
|
+
},
|
|
76
|
+
"peerDependencies": {
|
|
77
|
+
"react": "*",
|
|
78
|
+
"react-native": "*"
|
|
79
|
+
},
|
|
80
|
+
"workspaces": [
|
|
81
|
+
"example"
|
|
82
|
+
],
|
|
83
|
+
"packageManager": "yarn@4.11.0",
|
|
84
|
+
"react-native-builder-bob": {
|
|
85
|
+
"source": "src",
|
|
86
|
+
"output": "lib",
|
|
87
|
+
"targets": [
|
|
88
|
+
[
|
|
89
|
+
"module",
|
|
90
|
+
{
|
|
91
|
+
"esm": true
|
|
92
|
+
}
|
|
93
|
+
],
|
|
94
|
+
[
|
|
95
|
+
"typescript",
|
|
96
|
+
{
|
|
97
|
+
"project": "tsconfig.build.json"
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
]
|
|
101
|
+
},
|
|
102
|
+
"codegenConfig": {
|
|
103
|
+
"name": "ReservedRegionsViewSpec",
|
|
104
|
+
"type": "all",
|
|
105
|
+
"jsSrcsDir": "src",
|
|
106
|
+
"android": {
|
|
107
|
+
"javaPackageName": "com.reservedregions"
|
|
108
|
+
},
|
|
109
|
+
"ios": {
|
|
110
|
+
"components": {
|
|
111
|
+
"ReservedRegionsView": {
|
|
112
|
+
"className": "ReservedRegionsView"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"prettier": {
|
|
118
|
+
"quoteProps": "consistent",
|
|
119
|
+
"singleQuote": true,
|
|
120
|
+
"tabWidth": 2,
|
|
121
|
+
"trailingComma": "es5",
|
|
122
|
+
"useTabs": false
|
|
123
|
+
},
|
|
124
|
+
"jest": {
|
|
125
|
+
"preset": "@react-native/jest-preset",
|
|
126
|
+
"testEnvironmentOptions": {
|
|
127
|
+
"customExportConditions": [
|
|
128
|
+
"require",
|
|
129
|
+
"react-native",
|
|
130
|
+
"react-native-reserved-regions-source"
|
|
131
|
+
]
|
|
132
|
+
},
|
|
133
|
+
"modulePathIgnorePatterns": [
|
|
134
|
+
"<rootDir>/example/node_modules",
|
|
135
|
+
"<rootDir>/lib/"
|
|
136
|
+
]
|
|
137
|
+
},
|
|
138
|
+
"create-react-native-library": {
|
|
139
|
+
"type": "fabric-view",
|
|
140
|
+
"languages": "kotlin-objc",
|
|
141
|
+
"tools": [
|
|
142
|
+
"eslint",
|
|
143
|
+
"jest"
|
|
144
|
+
],
|
|
145
|
+
"version": "0.63.1"
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as ReservedRegionsView } from './ReservedRegionsViewNativeComponent';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { View, type ViewProps } from 'react-native';
|
|
2
|
+
import type { RegionsChangeEvent } from './ReservedRegionsViewNativeComponent';
|
|
3
|
+
|
|
4
|
+
export type ReservedRegionsViewProps = ViewProps & {
|
|
5
|
+
onRegionsChange?: (event: { nativeEvent: RegionsChangeEvent }) => void;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function ReservedRegionsView({
|
|
9
|
+
onRegionsChange: _onRegionsChange,
|
|
10
|
+
...props
|
|
11
|
+
}: ReservedRegionsViewProps) {
|
|
12
|
+
return <View {...props} />;
|
|
13
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
codegenNativeComponent,
|
|
3
|
+
type CodegenTypes,
|
|
4
|
+
type ViewProps,
|
|
5
|
+
} from 'react-native';
|
|
6
|
+
|
|
7
|
+
export type RegionsChangeEvent = Readonly<{
|
|
8
|
+
regions: {
|
|
9
|
+
kind: string;
|
|
10
|
+
frame: {
|
|
11
|
+
x: CodegenTypes.Double;
|
|
12
|
+
y: CodegenTypes.Double;
|
|
13
|
+
width: CodegenTypes.Double;
|
|
14
|
+
height: CodegenTypes.Double;
|
|
15
|
+
};
|
|
16
|
+
occludesContent: boolean;
|
|
17
|
+
}[];
|
|
18
|
+
}>;
|
|
19
|
+
|
|
20
|
+
export interface NativeProps extends ViewProps {
|
|
21
|
+
onRegionsChange?: CodegenTypes.DirectEventHandler<RegionsChangeEvent>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default codegenNativeComponent<NativeProps>('ReservedRegionsView');
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { ViewProps } from 'react-native';
|
|
3
|
+
import { ReservedRegionsView } from './ReservedRegionsView';
|
|
4
|
+
|
|
5
|
+
/** A rectangle in logical points relative to the nearest ReservedRegionsProvider. */
|
|
6
|
+
export type ReservedRegionFrame = Readonly<{
|
|
7
|
+
/** Distance from the provider's left edge. */
|
|
8
|
+
x: number;
|
|
9
|
+
/** Distance from the provider's top edge. */
|
|
10
|
+
y: number;
|
|
11
|
+
/** Horizontal extent of the reserved area. */
|
|
12
|
+
width: number;
|
|
13
|
+
/** Vertical extent of the reserved area. */
|
|
14
|
+
height: number;
|
|
15
|
+
}>;
|
|
16
|
+
|
|
17
|
+
/** An active area reserved by the display or system UI. */
|
|
18
|
+
export type ReservedRegion =
|
|
19
|
+
| Readonly<{
|
|
20
|
+
/** A seam or fold that divides the usable display. */
|
|
21
|
+
kind: 'division';
|
|
22
|
+
/** The division's bounds in the provider's coordinate space. */
|
|
23
|
+
frame: ReservedRegionFrame;
|
|
24
|
+
/** Whether content underneath the division is hidden. */
|
|
25
|
+
occludesContent: boolean;
|
|
26
|
+
}>
|
|
27
|
+
| Readonly<{
|
|
28
|
+
/** An area where content is hidden, such as a camera cutout. */
|
|
29
|
+
kind: 'occlusion';
|
|
30
|
+
/** The occlusion's bounds in the provider's coordinate space. */
|
|
31
|
+
frame: ReservedRegionFrame;
|
|
32
|
+
}>;
|
|
33
|
+
|
|
34
|
+
const ReservedRegionsContext = React.createContext<
|
|
35
|
+
readonly ReservedRegion[] | null
|
|
36
|
+
>(null);
|
|
37
|
+
|
|
38
|
+
/** Makes active reserved regions available to descendants in provider coordinates. */
|
|
39
|
+
export function ReservedRegionsProvider({
|
|
40
|
+
children,
|
|
41
|
+
...props
|
|
42
|
+
}: ViewProps): React.JSX.Element {
|
|
43
|
+
const [regions, setRegions] = React.useState<readonly ReservedRegion[]>([]);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<ReservedRegionsContext.Provider value={regions}>
|
|
47
|
+
<ReservedRegionsView
|
|
48
|
+
{...props}
|
|
49
|
+
onRegionsChange={(event) => {
|
|
50
|
+
setRegions(
|
|
51
|
+
event.nativeEvent.regions.flatMap((region): ReservedRegion[] => {
|
|
52
|
+
if (region.kind === 'division') {
|
|
53
|
+
return [
|
|
54
|
+
{
|
|
55
|
+
kind: 'division' as const,
|
|
56
|
+
frame: region.frame,
|
|
57
|
+
occludesContent: region.occludesContent,
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
if (region.kind === 'occlusion') {
|
|
62
|
+
return [{ kind: 'occlusion' as const, frame: region.frame }];
|
|
63
|
+
}
|
|
64
|
+
return [];
|
|
65
|
+
})
|
|
66
|
+
);
|
|
67
|
+
}}
|
|
68
|
+
>
|
|
69
|
+
{children}
|
|
70
|
+
</ReservedRegionsView>
|
|
71
|
+
</ReservedRegionsContext.Provider>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Returns active regions reported by the nearest ReservedRegionsProvider. */
|
|
76
|
+
export function useReservedRegions(): readonly ReservedRegion[] {
|
|
77
|
+
const regions = React.useContext(ReservedRegionsContext);
|
|
78
|
+
if (regions === null) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
'useReservedRegions must be used inside ReservedRegionsProvider'
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return regions;
|
|
84
|
+
}
|