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.
Files changed (34) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +90 -0
  3. package/ReservedRegions.podspec +20 -0
  4. package/android/build.gradle +57 -0
  5. package/android/src/main/AndroidManifest.xml +2 -0
  6. package/android/src/main/java/com/reservedregions/RegionsChangeEvent.kt +37 -0
  7. package/android/src/main/java/com/reservedregions/ReservedRegion.kt +9 -0
  8. package/android/src/main/java/com/reservedregions/ReservedRegionsPackage.kt +17 -0
  9. package/android/src/main/java/com/reservedregions/ReservedRegionsView.kt +103 -0
  10. package/android/src/main/java/com/reservedregions/ReservedRegionsViewManager.kt +37 -0
  11. package/ios/ReservedRegionsView.h +14 -0
  12. package/ios/ReservedRegionsView.mm +105 -0
  13. package/lib/module/ReservedRegionsView.js +13 -0
  14. package/lib/module/ReservedRegionsView.js.map +1 -0
  15. package/lib/module/ReservedRegionsView.native.js +4 -0
  16. package/lib/module/ReservedRegionsView.native.js.map +1 -0
  17. package/lib/module/ReservedRegionsViewNativeComponent.ts +24 -0
  18. package/lib/module/index.js +53 -0
  19. package/lib/module/index.js.map +1 -0
  20. package/lib/module/package.json +1 -0
  21. package/lib/typescript/package.json +1 -0
  22. package/lib/typescript/src/ReservedRegionsView.d.ts +9 -0
  23. package/lib/typescript/src/ReservedRegionsView.d.ts.map +1 -0
  24. package/lib/typescript/src/ReservedRegionsView.native.d.ts +2 -0
  25. package/lib/typescript/src/ReservedRegionsView.native.d.ts.map +1 -0
  26. package/lib/typescript/src/ReservedRegionsViewNativeComponent.d.ts +19 -0
  27. package/lib/typescript/src/ReservedRegionsViewNativeComponent.d.ts.map +1 -0
  28. package/lib/typescript/src/index.d.ts +32 -0
  29. package/lib/typescript/src/index.d.ts.map +1 -0
  30. package/package.json +147 -0
  31. package/src/ReservedRegionsView.native.tsx +1 -0
  32. package/src/ReservedRegionsView.tsx +13 -0
  33. package/src/ReservedRegionsViewNativeComponent.ts +24 -0
  34. 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,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -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,9 @@
1
+ package com.reservedregions
2
+
3
+ import android.graphics.Rect
4
+
5
+ internal data class ReservedRegion(
6
+ val kind: String,
7
+ val frame: Rect,
8
+ val occludesContent: Boolean = false,
9
+ )
@@ -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,4 @@
1
+ "use strict";
2
+
3
+ export { default as ReservedRegionsView } from './ReservedRegionsViewNativeComponent';
4
+ //# sourceMappingURL=ReservedRegionsView.native.js.map
@@ -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,2 @@
1
+ export { default as ReservedRegionsView } from './ReservedRegionsViewNativeComponent';
2
+ //# sourceMappingURL=ReservedRegionsView.native.d.ts.map
@@ -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
+ }