react-native-reserved-regions 0.1.0-alpha.0 → 0.1.0-alpha.3
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 +61 -14
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/{reservedregions → appandflow/reservedregions}/RegionsChangeEvent.kt +3 -1
- package/android/src/main/java/com/{reservedregions → appandflow/reservedregions}/ReservedRegion.kt +1 -1
- package/android/src/main/java/com/{reservedregions → appandflow/reservedregions}/ReservedRegionsPackage.kt +1 -1
- package/android/src/main/java/com/{reservedregions → appandflow/reservedregions}/ReservedRegionsView.kt +64 -20
- package/android/src/main/java/com/{reservedregions → appandflow/reservedregions}/ReservedRegionsViewManager.kt +1 -1
- package/ios/ReservedRegionsView.mm +46 -38
- package/lib/module/ReservedRegionsView.js +9 -1
- package/lib/module/ReservedRegionsView.js.map +1 -1
- package/lib/module/ReservedRegionsViewNativeComponent.ts +1 -5
- package/lib/module/index.js +50 -23
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/ReservedRegionsView.d.ts +1 -1
- package/lib/typescript/src/ReservedRegionsView.d.ts.map +1 -1
- package/lib/typescript/src/ReservedRegionsViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +7 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +75 -78
- package/src/ReservedRegionsView.tsx +5 -4
- package/src/ReservedRegionsViewNativeComponent.ts +1 -5
- package/src/index.tsx +44 -37
package/README.md
CHANGED
|
@@ -5,15 +5,18 @@ Provider-scoped display divisions and occlusions for React Native's New Architec
|
|
|
5
5
|
## Requirements
|
|
6
6
|
|
|
7
7
|
- React Native New Architecture (Fabric)
|
|
8
|
-
- iOS 27.1 for UIKit reserved regions;
|
|
8
|
+
- iOS 27.1 SDK and runtime for UIKit reserved regions; older SDKs compile out observation and older runtimes report an empty list
|
|
9
9
|
- Android 7.0 (API 24) or newer; folding features require a device supported by Jetpack WindowManager
|
|
10
10
|
|
|
11
11
|
The example app uses React Native 0.88.0-rc.1.
|
|
12
|
+
Its iOS target adopts `UISceneDelegate` because the RC template currently crashes on iOS 27 without it ([React Native issue #58606](https://github.com/react/react-native/issues/58606)).
|
|
12
13
|
|
|
13
14
|
## Installation
|
|
14
15
|
|
|
16
|
+
`0.1.0-alpha.3` is an alpha release; its API may still change.
|
|
17
|
+
|
|
15
18
|
```sh
|
|
16
|
-
npm install react-native-reserved-regions
|
|
19
|
+
npm install react-native-reserved-regions@0.1.0-alpha.3
|
|
17
20
|
```
|
|
18
21
|
|
|
19
22
|
On iOS, install pods in your app's `ios` directory. Android links automatically through React Native autolinking.
|
|
@@ -21,13 +24,12 @@ On iOS, install pods in your app's `ios` directory. Android links automatically
|
|
|
21
24
|
## Usage
|
|
22
25
|
|
|
23
26
|
```tsx
|
|
24
|
-
import {
|
|
25
|
-
ReservedRegionsProvider,
|
|
26
|
-
useReservedRegions,
|
|
27
|
-
} from 'react-native-reserved-regions';
|
|
27
|
+
import { ReservedRegionsProvider, useReservedRegions, useReservedRegionsReady } from 'react-native-reserved-regions';
|
|
28
28
|
|
|
29
29
|
function Screen() {
|
|
30
30
|
const regions = useReservedRegions();
|
|
31
|
+
const isReady = useReservedRegionsReady();
|
|
32
|
+
if (!isReady) return null;
|
|
31
33
|
return regions.map((region, index) => {
|
|
32
34
|
if (region.kind === 'division') {
|
|
33
35
|
console.log(region.frame, region.occludesContent);
|
|
@@ -47,7 +49,7 @@ export default function App() {
|
|
|
47
49
|
}
|
|
48
50
|
```
|
|
49
51
|
|
|
50
|
-
`useReservedRegions()` returns an
|
|
52
|
+
`useReservedRegions()` always returns an array, initially `[]`. `useReservedRegionsReady()` is initially `false` and becomes `true` with the first measurement, including an empty result. It stays true for that provider’s lifetime. Unsupported platforms report a known empty result; readiness does not indicate hardware support or guarantee first-frame timing. Both hooks 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
53
|
|
|
52
54
|
The public type is a tagged union:
|
|
53
55
|
|
|
@@ -66,24 +68,69 @@ type ReservedRegion =
|
|
|
66
68
|
|
|
67
69
|
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
70
|
|
|
71
|
+
## Why a provider and a hook?
|
|
72
|
+
|
|
73
|
+
A reserved region needs a coordinate space: the same fold has different local
|
|
74
|
+
coordinates in a full-screen view and an inset panel. `ReservedRegionsProvider`
|
|
75
|
+
renders the native view that defines those bounds and shares its measurements
|
|
76
|
+
with descendants. `useReservedRegions()` reads the nearest provider, so several
|
|
77
|
+
consumers can use the same measurements without each adding a native view. Place
|
|
78
|
+
a provider around each area that needs its own coordinate space; it can replace
|
|
79
|
+
an existing container `View`.
|
|
80
|
+
|
|
81
|
+
[react-native-hinges](https://appandflow.github.io/react-native-hinges/docs/usage)
|
|
82
|
+
exposes a hook without a provider because posture and angle do not depend on a
|
|
83
|
+
child view's position or size. It observes the existing React root's hierarchy/window.
|
|
84
|
+
Use both libraries when layout and animation need those separate inputs.
|
|
85
|
+
|
|
69
86
|
## Native sources
|
|
70
87
|
|
|
71
|
-
- iOS queries UIKit's active `UIView` reserved division and occlusion regions on the provider view.
|
|
88
|
+
- iOS queries UIKit's active `UIView` reserved division and occlusion regions on the provider view. Typed UIKit calls are guarded by the SDK version and runtime availability. Building with an SDK older than iOS 27.1 compiles out this feature. [Apple's reserved regions overview](https://developer.apple.com/videos/play/tech-talks/111463/)
|
|
72
89
|
- 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
90
|
|
|
74
91
|
The library reports geometry and does not reposition content. Normal safe area insets, system bars, and keyboard insets remain separate concerns.
|
|
75
92
|
|
|
93
|
+
## Measurement timing
|
|
94
|
+
|
|
95
|
+
Native events request synchronous delivery. Android measures after Fabric mounting
|
|
96
|
+
and also observes pre-draw changes. The iOS example tests same-frame delivery with
|
|
97
|
+
an [upstream React Native event-beat patch](docs/workflow.md#react-native-event-beat-test-patch).
|
|
98
|
+
That example patch is not installed into consuming apps. Readiness indicates a
|
|
99
|
+
completed measurement, not a universal first-frame guarantee.
|
|
100
|
+
|
|
101
|
+
### Readiness-gated animated content
|
|
102
|
+
|
|
103
|
+
On the tested RN `0.88.0-rc.1` Android stack, conditionally mounting Reanimated
|
|
104
|
+
`4.7.0` content when `useReservedRegionsReady()` becomes true can throw
|
|
105
|
+
`__requestMapperRunFinalizer` is undefined: Worklets `0.13.0` can run synchronous
|
|
106
|
+
UI work ahead of queued mapper initialization. The combined hinges example uses
|
|
107
|
+
an [example-only Worklets FIFO patch](https://github.com/appandflow/react-native-hinges/blob/main/patches/react-native-worklets%400.13.0.patch).
|
|
108
|
+
Installing either library does not patch a consuming app's Worklets dependency.
|
|
109
|
+
Keeping the animated subtree mounted avoided this failure in the tested case;
|
|
110
|
+
apps that gate its mount need to apply the patch, rebuild the native app, and
|
|
111
|
+
validate it themselves.
|
|
112
|
+
|
|
113
|
+
The [performance guide](https://appandflow.github.io/react-native-reserved-regions/docs/performance)
|
|
114
|
+
explains provider placement and optional readiness gating. `ReservedRegionsGate`
|
|
115
|
+
is included starting with `0.1.0-alpha.3`.
|
|
116
|
+
|
|
76
117
|
## Development
|
|
77
118
|
|
|
78
119
|
```sh
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
120
|
+
pnpm install --frozen-lockfile
|
|
121
|
+
pnpm run format:check
|
|
122
|
+
pnpm run lint
|
|
123
|
+
pnpm run typecheck
|
|
124
|
+
pnpm test
|
|
125
|
+
pnpm run build
|
|
126
|
+
pnpm run docs:build
|
|
84
127
|
```
|
|
85
128
|
|
|
86
|
-
|
|
129
|
+
The example includes full-screen, inset and content-box providers with
|
|
130
|
+
`react-native-safe-area-context` values and overlays for comparison.
|
|
131
|
+
See [the development workflow](docs/workflow.md) for native runs,
|
|
132
|
+
[the docs website](website/README.md) for local previews, and
|
|
133
|
+
[the release process](RELEASE.md) for publishing.
|
|
87
134
|
|
|
88
135
|
## License
|
|
89
136
|
|
package/android/build.gradle
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package com.reservedregions
|
|
1
|
+
package com.appandflow.reservedregions
|
|
2
2
|
|
|
3
3
|
import com.facebook.react.bridge.Arguments
|
|
4
4
|
import com.facebook.react.bridge.WritableMap
|
|
@@ -12,6 +12,8 @@ internal class RegionsChangeEvent(
|
|
|
12
12
|
) : Event<RegionsChangeEvent>(surfaceId, viewTag) {
|
|
13
13
|
override fun getEventName() = NAME
|
|
14
14
|
|
|
15
|
+
override fun experimental_isSynchronous() = true
|
|
16
|
+
|
|
15
17
|
override fun getEventData(): WritableMap {
|
|
16
18
|
val payload = Arguments.createMap()
|
|
17
19
|
val regionArray = Arguments.createArray()
|
|
@@ -1,51 +1,96 @@
|
|
|
1
|
-
package com.reservedregions
|
|
1
|
+
package com.appandflow.reservedregions
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import android.graphics.Rect
|
|
5
5
|
import android.os.Build
|
|
6
6
|
import android.view.ViewTreeObserver
|
|
7
|
-
import android.view.WindowInsets
|
|
8
7
|
import androidx.core.content.ContextCompat
|
|
9
8
|
import androidx.core.util.Consumer
|
|
9
|
+
import androidx.window.WindowSdkExtensions
|
|
10
10
|
import androidx.window.java.layout.WindowInfoTrackerCallbackAdapter
|
|
11
11
|
import androidx.window.layout.FoldingFeature
|
|
12
12
|
import androidx.window.layout.WindowInfoTracker
|
|
13
13
|
import androidx.window.layout.WindowLayoutInfo
|
|
14
|
+
import com.facebook.react.bridge.UIManager
|
|
15
|
+
import com.facebook.react.bridge.UIManagerListener
|
|
16
|
+
import com.facebook.react.common.annotations.UnstableReactNativeAPI
|
|
14
17
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
18
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
15
19
|
import com.facebook.react.views.view.ReactViewGroup
|
|
16
20
|
import kotlin.math.max
|
|
17
21
|
import kotlin.math.min
|
|
18
22
|
|
|
19
23
|
internal typealias RegionsChangeHandler = (ReservedRegionsView, List<ReservedRegion>) -> Unit
|
|
20
24
|
|
|
21
|
-
class
|
|
25
|
+
@OptIn(UnstableReactNativeAPI::class)
|
|
26
|
+
class ReservedRegionsView(context: Context) : ReactViewGroup(context), ViewTreeObserver.OnPreDrawListener, UIManagerListener {
|
|
27
|
+
private var uiManager: UIManager? = null
|
|
22
28
|
private var tracker: WindowInfoTrackerCallbackAdapter? = null
|
|
23
29
|
private val layoutInfoConsumer = Consumer<WindowLayoutInfo> { info ->
|
|
24
30
|
foldingFeatures = info.displayFeatures.filterIsInstance<FoldingFeature>()
|
|
25
|
-
|
|
31
|
+
foldingFeaturesReady = true
|
|
32
|
+
invalidate()
|
|
26
33
|
}
|
|
27
34
|
private var foldingFeatures: List<FoldingFeature> = emptyList()
|
|
35
|
+
private var foldingFeaturesReady = false
|
|
28
36
|
private var lastRegions: List<ReservedRegion>? = null
|
|
37
|
+
private var awaitingFirstMount = true
|
|
29
38
|
private var onRegionsChange: RegionsChangeHandler? = null
|
|
30
39
|
|
|
40
|
+
override fun willDispatchViewUpdates(uiManager: UIManager) {}
|
|
41
|
+
|
|
42
|
+
override fun willMountItems(uiManager: UIManager) {}
|
|
43
|
+
|
|
44
|
+
override fun didScheduleMountItems(uiManager: UIManager) {}
|
|
45
|
+
|
|
46
|
+
override fun didDispatchMountItems(uiManager: UIManager) {}
|
|
47
|
+
|
|
48
|
+
override fun didMountItems(uiManager: UIManager) {
|
|
49
|
+
if (awaitingFirstMount) updateRegions()
|
|
50
|
+
}
|
|
51
|
+
|
|
31
52
|
override fun onAttachedToWindow() {
|
|
32
53
|
super.onAttachedToWindow()
|
|
54
|
+
uiManager = UIManagerHelper.getUIManagerForReactTag(UIManagerHelper.getReactContext(this), id)
|
|
55
|
+
awaitingFirstMount = true
|
|
56
|
+
// React Native's FabricMountingManager.cpp applies layout before installing a view's event
|
|
57
|
+
// emitter inside one mount batch, so onLayout cannot dispatch a synchronous event on first
|
|
58
|
+
// mount; didMountItems is the earliest point after the emitter exists and before the event
|
|
59
|
+
// beat. It runs for every mount batch in the app, so it is removed after the first delivery.
|
|
60
|
+
uiManager?.addUIManagerEventListener(this)
|
|
33
61
|
viewTreeObserver.addOnPreDrawListener(this)
|
|
62
|
+
foldingFeatures = emptyList()
|
|
63
|
+
foldingFeaturesReady = false
|
|
64
|
+
lastRegions = null
|
|
34
65
|
val activity = (context as? ThemedReactContext)?.currentActivity
|
|
35
66
|
if (activity != null) {
|
|
36
|
-
|
|
67
|
+
val windowTracker = WindowInfoTracker.getOrCreate(context)
|
|
68
|
+
if (WindowSdkExtensions.getInstance().extensionVersion >= 9) {
|
|
69
|
+
foldingFeatures = windowTracker.getCurrentWindowLayoutInfo(activity)
|
|
70
|
+
.displayFeatures.filterIsInstance<FoldingFeature>()
|
|
71
|
+
foldingFeaturesReady = true
|
|
72
|
+
}
|
|
73
|
+
tracker = WindowInfoTrackerCallbackAdapter(windowTracker)
|
|
37
74
|
tracker?.addWindowLayoutInfoListener(activity, ContextCompat.getMainExecutor(context), layoutInfoConsumer)
|
|
75
|
+
} else {
|
|
76
|
+
foldingFeaturesReady = true
|
|
38
77
|
}
|
|
39
|
-
updateRegions()
|
|
40
78
|
}
|
|
41
79
|
|
|
42
80
|
override fun onDetachedFromWindow() {
|
|
81
|
+
uiManager?.removeUIManagerEventListener(this)
|
|
82
|
+
uiManager = null
|
|
43
83
|
tracker?.removeWindowLayoutInfoListener(layoutInfoConsumer)
|
|
44
84
|
tracker = null
|
|
45
85
|
viewTreeObserver.removeOnPreDrawListener(this)
|
|
46
86
|
super.onDetachedFromWindow()
|
|
47
87
|
}
|
|
48
88
|
|
|
89
|
+
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
90
|
+
super.onLayout(changed, left, top, right, bottom)
|
|
91
|
+
if (!awaitingFirstMount) updateRegions()
|
|
92
|
+
}
|
|
93
|
+
|
|
49
94
|
override fun onPreDraw(): Boolean {
|
|
50
95
|
updateRegions()
|
|
51
96
|
return true
|
|
@@ -53,12 +98,13 @@ class ReservedRegionsView(context: Context) : ReactViewGroup(context), ViewTreeO
|
|
|
53
98
|
|
|
54
99
|
internal fun setOnRegionsChangeHandler(handler: RegionsChangeHandler) {
|
|
55
100
|
onRegionsChange = handler
|
|
56
|
-
|
|
101
|
+
lastRegions = null
|
|
102
|
+
invalidate()
|
|
57
103
|
}
|
|
58
104
|
|
|
59
|
-
private fun updateRegions() {
|
|
60
|
-
val handler = onRegionsChange ?: return
|
|
61
|
-
if (!isAttachedToWindow || width == 0 || height == 0) return
|
|
105
|
+
private fun updateRegions(): Boolean {
|
|
106
|
+
val handler = onRegionsChange ?: return false
|
|
107
|
+
if (!isAttachedToWindow || !foldingFeaturesReady || width == 0 || height == 0) return false
|
|
62
108
|
|
|
63
109
|
val windowLocation = IntArray(2)
|
|
64
110
|
getLocationInWindow(windowLocation)
|
|
@@ -70,13 +116,7 @@ class ReservedRegionsView(context: Context) : ReactViewGroup(context), ViewTreeO
|
|
|
70
116
|
regions.add(ReservedRegion("division", frame, feature.occlusionType == FoldingFeature.OcclusionType.FULL))
|
|
71
117
|
}
|
|
72
118
|
|
|
73
|
-
if (Build.VERSION.SDK_INT >=
|
|
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) {
|
|
119
|
+
if (Build.VERSION.SDK_INT >= 28) {
|
|
80
120
|
val screenLocation = IntArray(2)
|
|
81
121
|
getLocationOnScreen(screenLocation)
|
|
82
122
|
rootWindowInsets?.displayCutout?.boundingRects?.forEach { bounds ->
|
|
@@ -86,10 +126,14 @@ class ReservedRegionsView(context: Context) : ReactViewGroup(context), ViewTreeO
|
|
|
86
126
|
}
|
|
87
127
|
}
|
|
88
128
|
|
|
89
|
-
if (regions
|
|
90
|
-
|
|
91
|
-
|
|
129
|
+
if (regions == lastRegions) return false
|
|
130
|
+
lastRegions = regions
|
|
131
|
+
handler(this, regions)
|
|
132
|
+
if (awaitingFirstMount) {
|
|
133
|
+
awaitingFirstMount = false
|
|
134
|
+
uiManager?.removeUIManagerEventListener(this)
|
|
92
135
|
}
|
|
136
|
+
return true
|
|
93
137
|
}
|
|
94
138
|
|
|
95
139
|
private fun clippedFrame(bounds: Rect, originX: Int, originY: Int): Rect? {
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
#import "ReservedRegionsView.h"
|
|
2
2
|
|
|
3
|
-
#import <objc/message.h>
|
|
4
|
-
|
|
5
3
|
#import <react/renderer/components/ReservedRegionsViewSpec/ComponentDescriptors.h>
|
|
6
|
-
#import <react/renderer/components/ReservedRegionsViewSpec/EventEmitters.h>
|
|
7
4
|
#import <react/renderer/components/ReservedRegionsViewSpec/Props.h>
|
|
8
5
|
#import <react/renderer/components/ReservedRegionsViewSpec/RCTComponentViewHelpers.h>
|
|
9
6
|
|
|
@@ -25,6 +22,17 @@ using namespace facebook::react;
|
|
|
25
22
|
if (self = [super initWithFrame:frame]) {
|
|
26
23
|
static const auto defaultProps = std::make_shared<const ReservedRegionsViewProps>();
|
|
27
24
|
_props = defaultProps;
|
|
25
|
+
#if defined(__IPHONE_27_1) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_27_1
|
|
26
|
+
if (@available(iOS 27.1, *)) {
|
|
27
|
+
// UIKit posts no reserved-region change notification, so hinge updates schedule the re-query.
|
|
28
|
+
__weak __typeof(self) weakSelf = self;
|
|
29
|
+
UIHingeInteraction *hingeInteraction =
|
|
30
|
+
[[UIHingeInteraction alloc] initWithUpdateHandler:^(UIHingeInteraction *, UIHingeInteractionUpdate *) {
|
|
31
|
+
[weakSelf setNeedsLayout];
|
|
32
|
+
}];
|
|
33
|
+
[self addInteraction:hingeInteraction];
|
|
34
|
+
}
|
|
35
|
+
#endif
|
|
28
36
|
}
|
|
29
37
|
return self;
|
|
30
38
|
}
|
|
@@ -38,62 +46,62 @@ using namespace facebook::react;
|
|
|
38
46
|
- (void)didMoveToWindow
|
|
39
47
|
{
|
|
40
48
|
[super didMoveToWindow];
|
|
41
|
-
[self
|
|
49
|
+
[self setNeedsLayout];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
- (void)updateEventEmitter:(EventEmitter::Shared const &)eventEmitter
|
|
53
|
+
{
|
|
54
|
+
[super updateEventEmitter:eventEmitter];
|
|
55
|
+
[self setNeedsLayout];
|
|
42
56
|
}
|
|
43
57
|
|
|
44
58
|
- (void)updateRegions
|
|
45
59
|
{
|
|
46
|
-
if (self.window == nil || !_eventEmitter) {
|
|
60
|
+
if (self.window == nil || !_eventEmitter || CGSizeEqualToSize(self.bounds.size, CGSizeZero)) {
|
|
47
61
|
return;
|
|
48
62
|
}
|
|
49
63
|
|
|
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
64
|
NSMutableArray<NSDictionary *> *regions = [NSMutableArray new];
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
NSArray<
|
|
57
|
-
|
|
58
|
-
|
|
65
|
+
#if defined(__IPHONE_27_1) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_27_1
|
|
66
|
+
if (@available(iOS 27.1, *)) {
|
|
67
|
+
NSArray<UIViewReservedRegionKind *> *kinds = @[
|
|
68
|
+
UIViewReservedRegionKind.divisionRegionKind,
|
|
69
|
+
UIViewReservedRegionKind.occlusionRegionKind,
|
|
59
70
|
];
|
|
60
|
-
|
|
61
|
-
for (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
}];
|
|
71
|
+
NSArray<NSString *> *names = @[@"division", @"occlusion"];
|
|
72
|
+
for (NSUInteger index = 0; index < kinds.count; index++) {
|
|
73
|
+
for (UIViewReservedRegion *region in [self reservedRegionsOfKind:kinds[index]
|
|
74
|
+
options:UIViewReservedRegionQueryOptionsNone]) {
|
|
75
|
+
[regions addObject:@{@"kind": names[index], @"frame": [NSValue valueWithCGRect:region.frame]}];
|
|
78
76
|
}
|
|
79
77
|
}
|
|
80
78
|
}
|
|
79
|
+
#endif
|
|
81
80
|
|
|
82
81
|
if ([_currentRegions isEqualToArray:regions]) {
|
|
83
82
|
return;
|
|
84
83
|
}
|
|
85
84
|
_currentRegions = [regions copy];
|
|
86
85
|
|
|
87
|
-
|
|
86
|
+
auto regionPayloads = folly::dynamic::array();
|
|
88
87
|
for (NSDictionary *region in regions) {
|
|
89
88
|
CGRect frame = [region[@"frame"] CGRectValue];
|
|
90
|
-
|
|
91
|
-
[region[@"kind"] UTF8String]
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
89
|
+
regionPayloads.push_back(folly::dynamic::object
|
|
90
|
+
("kind", [region[@"kind"] UTF8String])
|
|
91
|
+
("frame", folly::dynamic::object
|
|
92
|
+
("x", frame.origin.x)
|
|
93
|
+
("y", frame.origin.y)
|
|
94
|
+
("width", frame.size.width)
|
|
95
|
+
("height", frame.size.height))
|
|
96
|
+
("occludesContent", false));
|
|
95
97
|
}
|
|
96
|
-
|
|
98
|
+
auto eventEmitter = _eventEmitter;
|
|
99
|
+
eventEmitter->experimental_flushSync([eventEmitter, regions = std::move(regionPayloads)]() mutable {
|
|
100
|
+
eventEmitter->dispatchEvent(
|
|
101
|
+
"regionsChange",
|
|
102
|
+
folly::dynamic::object("regions", std::move(regions)),
|
|
103
|
+
RawEvent::Category::Discrete);
|
|
104
|
+
});
|
|
97
105
|
}
|
|
98
106
|
|
|
99
107
|
- (void)prepareForRecycle
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
import { useEffect } from 'react';
|
|
3
4
|
import { View } from 'react-native';
|
|
4
5
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
5
6
|
export function ReservedRegionsView({
|
|
6
|
-
onRegionsChange
|
|
7
|
+
onRegionsChange,
|
|
7
8
|
...props
|
|
8
9
|
}) {
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
onRegionsChange?.({
|
|
12
|
+
nativeEvent: {
|
|
13
|
+
regions: []
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
}, [onRegionsChange]);
|
|
9
17
|
return /*#__PURE__*/_jsx(View, {
|
|
10
18
|
...props
|
|
11
19
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["View","jsx","_jsx","ReservedRegionsView","onRegionsChange","
|
|
1
|
+
{"version":3,"names":["useEffect","View","jsx","_jsx","ReservedRegionsView","onRegionsChange","props","nativeEvent","regions"],"sourceRoot":"../../src","sources":["ReservedRegionsView.tsx"],"mappings":";;AAAA,SAASA,SAAS,QAAQ,OAAO;AACjC,SAASC,IAAI,QAAwB,cAAc;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAOpD,OAAO,SAASC,mBAAmBA,CAAC;EAAEC,eAAe;EAAE,GAAGC;AAAgC,CAAC,EAAE;EAC3FN,SAAS,CAAC,MAAM;IACdK,eAAe,GAAG;MAAEE,WAAW,EAAE;QAAEC,OAAO,EAAE;MAAG;IAAE,CAAC,CAAC;EACrD,CAAC,EAAE,CAACH,eAAe,CAAC,CAAC;EACrB,oBAAOF,IAAA,CAACF,IAAI;IAAA,GAAKK;EAAK,CAAG,CAAC;AAC5B","ignoreList":[]}
|
package/lib/module/index.js
CHANGED
|
@@ -14,29 +14,37 @@ export function ReservedRegionsProvider({
|
|
|
14
14
|
children,
|
|
15
15
|
...props
|
|
16
16
|
}) {
|
|
17
|
-
const [
|
|
17
|
+
const [snapshot, setSnapshot] = React.useState({
|
|
18
|
+
regions: [],
|
|
19
|
+
isReady: false
|
|
20
|
+
});
|
|
21
|
+
const onRegionsChange = React.useCallback(event => {
|
|
22
|
+
const regions = event.nativeEvent.regions.flatMap(region => {
|
|
23
|
+
if (region.kind === 'division') {
|
|
24
|
+
return [{
|
|
25
|
+
kind: 'division',
|
|
26
|
+
frame: region.frame,
|
|
27
|
+
occludesContent: region.occludesContent
|
|
28
|
+
}];
|
|
29
|
+
}
|
|
30
|
+
if (region.kind === 'occlusion') {
|
|
31
|
+
return [{
|
|
32
|
+
kind: 'occlusion',
|
|
33
|
+
frame: region.frame
|
|
34
|
+
}];
|
|
35
|
+
}
|
|
36
|
+
return [];
|
|
37
|
+
});
|
|
38
|
+
setSnapshot({
|
|
39
|
+
regions,
|
|
40
|
+
isReady: true
|
|
41
|
+
});
|
|
42
|
+
}, []);
|
|
18
43
|
return /*#__PURE__*/_jsx(ReservedRegionsContext.Provider, {
|
|
19
|
-
value:
|
|
44
|
+
value: snapshot,
|
|
20
45
|
children: /*#__PURE__*/_jsx(ReservedRegionsView, {
|
|
21
46
|
...props,
|
|
22
|
-
onRegionsChange:
|
|
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
|
-
},
|
|
47
|
+
onRegionsChange: onRegionsChange,
|
|
40
48
|
children: children
|
|
41
49
|
})
|
|
42
50
|
});
|
|
@@ -44,10 +52,29 @@ export function ReservedRegionsProvider({
|
|
|
44
52
|
|
|
45
53
|
/** Returns active regions reported by the nearest ReservedRegionsProvider. */
|
|
46
54
|
export function useReservedRegions() {
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
55
|
+
const snapshot = React.useContext(ReservedRegionsContext);
|
|
56
|
+
if (snapshot === null) {
|
|
49
57
|
throw new Error('useReservedRegions must be used inside ReservedRegionsProvider');
|
|
50
58
|
}
|
|
51
|
-
return regions;
|
|
59
|
+
return snapshot.regions;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Whether the nearest provider has reported its first measurement, including an empty
|
|
64
|
+
* result. Remains true for that provider's lifetime; does not imply platform support.
|
|
65
|
+
*/
|
|
66
|
+
export function useReservedRegionsReady() {
|
|
67
|
+
const snapshot = React.useContext(ReservedRegionsContext);
|
|
68
|
+
if (snapshot === null) {
|
|
69
|
+
throw new Error('useReservedRegionsReady must be used inside ReservedRegionsProvider');
|
|
70
|
+
}
|
|
71
|
+
return snapshot.isReady;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Renders children after the nearest provider reports its first measurement. */
|
|
75
|
+
export function ReservedRegionsGate({
|
|
76
|
+
children
|
|
77
|
+
}) {
|
|
78
|
+
return useReservedRegionsReady() ? children : null;
|
|
52
79
|
}
|
|
53
80
|
//# sourceMappingURL=index.js.map
|
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["React","ReservedRegionsView","jsx","_jsx","ReservedRegionsContext","createContext","ReservedRegionsProvider","children","props","
|
|
1
|
+
{"version":3,"names":["React","ReservedRegionsView","jsx","_jsx","ReservedRegionsContext","createContext","ReservedRegionsProvider","children","props","snapshot","setSnapshot","useState","regions","isReady","onRegionsChange","useCallback","event","nativeEvent","flatMap","region","kind","frame","occludesContent","Provider","value","useReservedRegions","useContext","Error","useReservedRegionsReady","ReservedRegionsGate"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,OAAO,KAAKA,KAAK,MAAM,OAAO;AAE9B,SAASC,mBAAmB,QAAQ,uBAAuB;;AAG3D;;AAYA;AAAA,SAAAC,GAAA,IAAAC,IAAA;AAsBA,MAAMC,sBAAsB,gBAAGJ,KAAK,CAACK,aAAa,CAAiC,IAAI,CAAC;;AAExF;AACA,OAAO,SAASC,uBAAuBA,CAAC;EAAEC,QAAQ;EAAE,GAAGC;AAAiB,CAAC,EAAqB;EAC5F,MAAM,CAACC,QAAQ,EAAEC,WAAW,CAAC,GAAGV,KAAK,CAACW,QAAQ,CAA0B;IAAEC,OAAO,EAAE,EAAE;IAAEC,OAAO,EAAE;EAAM,CAAC,CAAC;EACxG,MAAMC,eAAe,GAAGd,KAAK,CAACe,WAAW,CAAEC,KAA0C,IAAK;IACxF,MAAMJ,OAAO,GAAGI,KAAK,CAACC,WAAW,CAACL,OAAO,CAACM,OAAO,CAAEC,MAAM,IAAuB;MAC9E,IAAIA,MAAM,CAACC,IAAI,KAAK,UAAU,EAAE;QAC9B,OAAO,CAAC;UAAEA,IAAI,EAAE,UAAU;UAAEC,KAAK,EAAEF,MAAM,CAACE,KAAK;UAAEC,eAAe,EAAEH,MAAM,CAACG;QAAgB,CAAC,CAAC;MAC7F;MACA,IAAIH,MAAM,CAACC,IAAI,KAAK,WAAW,EAAE;QAC/B,OAAO,CAAC;UAAEA,IAAI,EAAE,WAAW;UAAEC,KAAK,EAAEF,MAAM,CAACE;QAAM,CAAC,CAAC;MACrD;MACA,OAAO,EAAE;IACX,CAAC,CAAC;IACFX,WAAW,CAAC;MAAEE,OAAO;MAAEC,OAAO,EAAE;IAAK,CAAC,CAAC;EACzC,CAAC,EAAE,EAAE,CAAC;EAEN,oBACEV,IAAA,CAACC,sBAAsB,CAACmB,QAAQ;IAACC,KAAK,EAAEf,QAAS;IAAAF,QAAA,eAC/CJ,IAAA,CAACF,mBAAmB;MAAA,GAAKO,KAAK;MAAEM,eAAe,EAAEA,eAAgB;MAAAP,QAAA,EAC9DA;IAAQ,CACU;EAAC,CACS,CAAC;AAEtC;;AAEA;AACA,OAAO,SAASkB,kBAAkBA,CAAA,EAA8B;EAC9D,MAAMhB,QAAQ,GAAGT,KAAK,CAAC0B,UAAU,CAACtB,sBAAsB,CAAC;EACzD,IAAIK,QAAQ,KAAK,IAAI,EAAE;IACrB,MAAM,IAAIkB,KAAK,CAAC,gEAAgE,CAAC;EACnF;EACA,OAAOlB,QAAQ,CAACG,OAAO;AACzB;;AAEA;AACA;AACA;AACA;AACA,OAAO,SAASgB,uBAAuBA,CAAA,EAAY;EACjD,MAAMnB,QAAQ,GAAGT,KAAK,CAAC0B,UAAU,CAACtB,sBAAsB,CAAC;EACzD,IAAIK,QAAQ,KAAK,IAAI,EAAE;IACrB,MAAM,IAAIkB,KAAK,CAAC,qEAAqE,CAAC;EACxF;EACA,OAAOlB,QAAQ,CAACI,OAAO;AACzB;;AAEA;AACA,OAAO,SAASgB,mBAAmBA,CAAC;EAAEtB;AAAkC,CAAC,EAAmB;EAC1F,OAAOqB,uBAAuB,CAAC,CAAC,GAAGrB,QAAQ,GAAG,IAAI;AACpD","ignoreList":[]}
|
|
@@ -5,5 +5,5 @@ export type ReservedRegionsViewProps = ViewProps & {
|
|
|
5
5
|
nativeEvent: RegionsChangeEvent;
|
|
6
6
|
}) => void;
|
|
7
7
|
};
|
|
8
|
-
export declare function ReservedRegionsView({ onRegionsChange
|
|
8
|
+
export declare function ReservedRegionsView({ onRegionsChange, ...props }: ReservedRegionsViewProps): import("react").JSX.Element;
|
|
9
9
|
//# sourceMappingURL=ReservedRegionsView.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReservedRegionsView.d.ts","sourceRoot":"","sources":["../../../src/ReservedRegionsView.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ReservedRegionsView.d.ts","sourceRoot":"","sources":["../../../src/ReservedRegionsView.tsx"],"names":[],"mappings":"AACA,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,EAAE,eAAe,EAAE,GAAG,KAAK,EAAE,EAAE,wBAAwB,+BAK1F"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReservedRegionsViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../src/ReservedRegionsViewNativeComponent.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"ReservedRegionsViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../src/ReservedRegionsViewNativeComponent.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzF,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"}
|
|
@@ -29,4 +29,11 @@ export type ReservedRegion = Readonly<{
|
|
|
29
29
|
export declare function ReservedRegionsProvider({ children, ...props }: ViewProps): React.JSX.Element;
|
|
30
30
|
/** Returns active regions reported by the nearest ReservedRegionsProvider. */
|
|
31
31
|
export declare function useReservedRegions(): readonly ReservedRegion[];
|
|
32
|
+
/**
|
|
33
|
+
* Whether the nearest provider has reported its first measurement, including an empty
|
|
34
|
+
* result. Remains true for that provider's lifetime; does not imply platform support.
|
|
35
|
+
*/
|
|
36
|
+
export declare function useReservedRegionsReady(): boolean;
|
|
37
|
+
/** Renders children after the nearest provider reports its first measurement. */
|
|
38
|
+
export declare function ReservedRegionsGate({ children }: React.PropsWithChildren): React.ReactNode;
|
|
32
39
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +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;
|
|
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;AAI9C,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;AASP,sFAAsF;AACtF,wBAAgB,uBAAuB,CAAC,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAsB5F;AAED,8EAA8E;AAC9E,wBAAgB,kBAAkB,IAAI,SAAS,cAAc,EAAE,CAM9D;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,OAAO,CAMjD;AAED,iFAAiF;AACjF,wBAAgB,mBAAmB,CAAC,EAAE,QAAQ,EAAE,EAAE,KAAK,CAAC,iBAAiB,GAAG,KAAK,CAAC,SAAS,CAE1F"}
|
package/package.json
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-reserved-regions",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.3",
|
|
4
4
|
"description": "Provider-scoped reserved display regions for React Native",
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
5
|
+
"keywords": [
|
|
6
|
+
"android",
|
|
7
|
+
"ios",
|
|
8
|
+
"react-native"
|
|
9
|
+
],
|
|
10
|
+
"homepage": "https://github.com/appandflow/react-native-reserved-regions#readme",
|
|
11
|
+
"bugs": "https://github.com/appandflow/react-native-reserved-regions/issues",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"author": "Janic Duplessis <janicduplessis@gmail.com> (https://github.com/janicduplessis)",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/appandflow/react-native-reserved-regions.git"
|
|
14
17
|
},
|
|
15
18
|
"files": [
|
|
16
19
|
"src",
|
|
@@ -31,80 +34,63 @@
|
|
|
31
34
|
"!**/__mocks__",
|
|
32
35
|
"!**/.*"
|
|
33
36
|
],
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
"main": "./lib/module/index.js",
|
|
38
|
+
"types": "./lib/typescript/src/index.d.ts",
|
|
39
|
+
"exports": {
|
|
40
|
+
".": {
|
|
41
|
+
"react-native-reserved-regions-source": "./src/index.tsx",
|
|
42
|
+
"types": "./lib/typescript/src/index.d.ts",
|
|
43
|
+
"default": "./lib/module/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./package.json": "./package.json"
|
|
41
46
|
},
|
|
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
47
|
"publishConfig": {
|
|
50
48
|
"registry": "https://registry.npmjs.org/"
|
|
51
49
|
},
|
|
52
50
|
"devDependencies": {
|
|
53
|
-
"@eslint/compat": "^2.1.0",
|
|
54
|
-
"@eslint/eslintrc": "^3.3.6",
|
|
55
|
-
"@eslint/js": "^9.39.5",
|
|
56
51
|
"@jest/globals": "^29.7.0",
|
|
57
52
|
"@react-native/babel-preset": "0.88.0-rc.1",
|
|
58
|
-
"@react-native/eslint-config": "0.86.2",
|
|
59
53
|
"@react-native/jest-preset": "0.86.2",
|
|
60
54
|
"@types/react": "^19.2.0",
|
|
61
55
|
"@types/react-test-renderer": "^19.1.0",
|
|
62
56
|
"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
57
|
"jest": "^29.7.0",
|
|
68
|
-
"
|
|
58
|
+
"oxfmt": "0.65.0",
|
|
59
|
+
"oxlint": "1.80.0",
|
|
69
60
|
"react": "19.3.0",
|
|
70
61
|
"react-native": "0.88.0-rc.1",
|
|
71
62
|
"react-native-builder-bob": "^0.43.1",
|
|
72
63
|
"react-test-renderer": "19.3.0",
|
|
73
|
-
"
|
|
74
|
-
"typescript": "^6.0.3"
|
|
64
|
+
"typescript": "7.0.2"
|
|
75
65
|
},
|
|
76
66
|
"peerDependencies": {
|
|
77
67
|
"react": "*",
|
|
78
68
|
"react-native": "*"
|
|
79
69
|
},
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
"
|
|
87
|
-
"
|
|
88
|
-
[
|
|
89
|
-
"
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
],
|
|
94
|
-
[
|
|
95
|
-
"typescript",
|
|
96
|
-
{
|
|
97
|
-
"project": "tsconfig.build.json"
|
|
98
|
-
}
|
|
70
|
+
"jest": {
|
|
71
|
+
"modulePathIgnorePatterns": [
|
|
72
|
+
"<rootDir>/example/node_modules",
|
|
73
|
+
"<rootDir>/lib/",
|
|
74
|
+
"<rootDir>/website/"
|
|
75
|
+
],
|
|
76
|
+
"preset": "@react-native/jest-preset",
|
|
77
|
+
"testEnvironmentOptions": {
|
|
78
|
+
"customExportConditions": [
|
|
79
|
+
"require",
|
|
80
|
+
"react-native",
|
|
81
|
+
"react-native-reserved-regions-source"
|
|
99
82
|
]
|
|
100
|
-
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"engines": {
|
|
86
|
+
"node": ">=22.18.0"
|
|
101
87
|
},
|
|
102
88
|
"codegenConfig": {
|
|
103
89
|
"name": "ReservedRegionsViewSpec",
|
|
104
90
|
"type": "all",
|
|
105
91
|
"jsSrcsDir": "src",
|
|
106
92
|
"android": {
|
|
107
|
-
"javaPackageName": "com.reservedregions"
|
|
93
|
+
"javaPackageName": "com.appandflow.reservedregions"
|
|
108
94
|
},
|
|
109
95
|
"ios": {
|
|
110
96
|
"components": {
|
|
@@ -114,27 +100,6 @@
|
|
|
114
100
|
}
|
|
115
101
|
}
|
|
116
102
|
},
|
|
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
103
|
"create-react-native-library": {
|
|
139
104
|
"type": "fabric-view",
|
|
140
105
|
"languages": "kotlin-objc",
|
|
@@ -143,5 +108,37 @@
|
|
|
143
108
|
"jest"
|
|
144
109
|
],
|
|
145
110
|
"version": "0.63.1"
|
|
111
|
+
},
|
|
112
|
+
"react-native-builder-bob": {
|
|
113
|
+
"source": "src",
|
|
114
|
+
"output": "lib",
|
|
115
|
+
"targets": [
|
|
116
|
+
[
|
|
117
|
+
"module",
|
|
118
|
+
{
|
|
119
|
+
"esm": true
|
|
120
|
+
}
|
|
121
|
+
],
|
|
122
|
+
[
|
|
123
|
+
"typescript",
|
|
124
|
+
{
|
|
125
|
+
"project": "tsconfig.build.json"
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
]
|
|
129
|
+
},
|
|
130
|
+
"scripts": {
|
|
131
|
+
"example": "pnpm --filter react-native-reserved-regions-example",
|
|
132
|
+
"clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
|
|
133
|
+
"typecheck": "tsc --noEmit",
|
|
134
|
+
"lint": "oxlint .",
|
|
135
|
+
"test": "jest --runInBand --watchman=false",
|
|
136
|
+
"build": "bob build",
|
|
137
|
+
"lint:fix": "oxlint --fix .",
|
|
138
|
+
"format": "oxfmt .",
|
|
139
|
+
"format:check": "oxfmt --check .",
|
|
140
|
+
"e2e:android": "node e2e/android.mjs",
|
|
141
|
+
"docs:build": "pnpm --filter reserved-regions-docs build",
|
|
142
|
+
"docs:start": "pnpm --filter reserved-regions-docs start"
|
|
146
143
|
}
|
|
147
|
-
}
|
|
144
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
1
2
|
import { View, type ViewProps } from 'react-native';
|
|
2
3
|
import type { RegionsChangeEvent } from './ReservedRegionsViewNativeComponent';
|
|
3
4
|
|
|
@@ -5,9 +6,9 @@ export type ReservedRegionsViewProps = ViewProps & {
|
|
|
5
6
|
onRegionsChange?: (event: { nativeEvent: RegionsChangeEvent }) => void;
|
|
6
7
|
};
|
|
7
8
|
|
|
8
|
-
export function ReservedRegionsView({
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
9
|
+
export function ReservedRegionsView({ onRegionsChange, ...props }: ReservedRegionsViewProps) {
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
onRegionsChange?.({ nativeEvent: { regions: [] } });
|
|
12
|
+
}, [onRegionsChange]);
|
|
12
13
|
return <View {...props} />;
|
|
13
14
|
}
|
package/src/index.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import type { ViewProps } from 'react-native';
|
|
3
3
|
import { ReservedRegionsView } from './ReservedRegionsView';
|
|
4
|
+
import type { RegionsChangeEvent } from './ReservedRegionsViewNativeComponent';
|
|
4
5
|
|
|
5
6
|
/** A rectangle in logical points relative to the nearest ReservedRegionsProvider. */
|
|
6
7
|
export type ReservedRegionFrame = Readonly<{
|
|
@@ -31,41 +32,32 @@ export type ReservedRegion =
|
|
|
31
32
|
frame: ReservedRegionFrame;
|
|
32
33
|
}>;
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
readonly ReservedRegion[]
|
|
36
|
-
|
|
35
|
+
type ReservedRegionsSnapshot = Readonly<{
|
|
36
|
+
regions: readonly ReservedRegion[];
|
|
37
|
+
isReady: boolean;
|
|
38
|
+
}>;
|
|
39
|
+
|
|
40
|
+
const ReservedRegionsContext = React.createContext<ReservedRegionsSnapshot | null>(null);
|
|
37
41
|
|
|
38
42
|
/** Makes active reserved regions available to descendants in provider coordinates. */
|
|
39
|
-
export function ReservedRegionsProvider({
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
export function ReservedRegionsProvider({ children, ...props }: ViewProps): React.JSX.Element {
|
|
44
|
+
const [snapshot, setSnapshot] = React.useState<ReservedRegionsSnapshot>({ regions: [], isReady: false });
|
|
45
|
+
const onRegionsChange = React.useCallback((event: { nativeEvent: RegionsChangeEvent }) => {
|
|
46
|
+
const regions = event.nativeEvent.regions.flatMap((region): ReservedRegion[] => {
|
|
47
|
+
if (region.kind === 'division') {
|
|
48
|
+
return [{ kind: 'division', frame: region.frame, occludesContent: region.occludesContent }];
|
|
49
|
+
}
|
|
50
|
+
if (region.kind === 'occlusion') {
|
|
51
|
+
return [{ kind: 'occlusion', frame: region.frame }];
|
|
52
|
+
}
|
|
53
|
+
return [];
|
|
54
|
+
});
|
|
55
|
+
setSnapshot({ regions, isReady: true });
|
|
56
|
+
}, []);
|
|
44
57
|
|
|
45
58
|
return (
|
|
46
|
-
<ReservedRegionsContext.Provider value={
|
|
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
|
-
>
|
|
59
|
+
<ReservedRegionsContext.Provider value={snapshot}>
|
|
60
|
+
<ReservedRegionsView {...props} onRegionsChange={onRegionsChange}>
|
|
69
61
|
{children}
|
|
70
62
|
</ReservedRegionsView>
|
|
71
63
|
</ReservedRegionsContext.Provider>
|
|
@@ -74,11 +66,26 @@ export function ReservedRegionsProvider({
|
|
|
74
66
|
|
|
75
67
|
/** Returns active regions reported by the nearest ReservedRegionsProvider. */
|
|
76
68
|
export function useReservedRegions(): readonly ReservedRegion[] {
|
|
77
|
-
const
|
|
78
|
-
if (
|
|
79
|
-
throw new Error(
|
|
80
|
-
'useReservedRegions must be used inside ReservedRegionsProvider'
|
|
81
|
-
);
|
|
69
|
+
const snapshot = React.useContext(ReservedRegionsContext);
|
|
70
|
+
if (snapshot === null) {
|
|
71
|
+
throw new Error('useReservedRegions must be used inside ReservedRegionsProvider');
|
|
82
72
|
}
|
|
83
|
-
return regions;
|
|
73
|
+
return snapshot.regions;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Whether the nearest provider has reported its first measurement, including an empty
|
|
78
|
+
* result. Remains true for that provider's lifetime; does not imply platform support.
|
|
79
|
+
*/
|
|
80
|
+
export function useReservedRegionsReady(): boolean {
|
|
81
|
+
const snapshot = React.useContext(ReservedRegionsContext);
|
|
82
|
+
if (snapshot === null) {
|
|
83
|
+
throw new Error('useReservedRegionsReady must be used inside ReservedRegionsProvider');
|
|
84
|
+
}
|
|
85
|
+
return snapshot.isReady;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Renders children after the nearest provider reports its first measurement. */
|
|
89
|
+
export function ReservedRegionsGate({ children }: React.PropsWithChildren): React.ReactNode {
|
|
90
|
+
return useReservedRegionsReady() ? children : null;
|
|
84
91
|
}
|