react-native-reserved-regions 0.1.0-alpha.2 → 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 CHANGED
@@ -13,11 +13,10 @@ Its iOS target adopts `UISceneDelegate` because the RC template currently crashe
13
13
 
14
14
  ## Installation
15
15
 
16
- `0.1.0-alpha.2` is the functional release candidate. Install the explicit version
17
- once publication is verified; until then, run the repository example.
16
+ `0.1.0-alpha.3` is an alpha release; its API may still change.
18
17
 
19
18
  ```sh
20
- npm install react-native-reserved-regions@0.1.0-alpha.2
19
+ npm install react-native-reserved-regions@0.1.0-alpha.3
21
20
  ```
22
21
 
23
22
  On iOS, install pods in your app's `ios` directory. Android links automatically through React Native autolinking.
@@ -69,6 +68,21 @@ type ReservedRegion =
69
68
 
70
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.
71
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
+
72
86
  ## Native sources
73
87
 
74
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/)
@@ -96,6 +110,10 @@ Keeping the animated subtree mounted avoided this failure in the tested case;
96
110
  apps that gate its mount need to apply the patch, rebuild the native app, and
97
111
  validate it themselves.
98
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
+
99
117
  ## Development
100
118
 
101
119
  ```sh
@@ -34,6 +34,7 @@ class ReservedRegionsView(context: Context) : ReactViewGroup(context), ViewTreeO
34
34
  private var foldingFeatures: List<FoldingFeature> = emptyList()
35
35
  private var foldingFeaturesReady = false
36
36
  private var lastRegions: List<ReservedRegion>? = null
37
+ private var awaitingFirstMount = true
37
38
  private var onRegionsChange: RegionsChangeHandler? = null
38
39
 
39
40
  override fun willDispatchViewUpdates(uiManager: UIManager) {}
@@ -45,13 +46,17 @@ class ReservedRegionsView(context: Context) : ReactViewGroup(context), ViewTreeO
45
46
  override fun didDispatchMountItems(uiManager: UIManager) {}
46
47
 
47
48
  override fun didMountItems(uiManager: UIManager) {
48
- // Fabric installs event emitters after layout; its event beat runs before pre-draw.
49
- updateRegions()
49
+ if (awaitingFirstMount) updateRegions()
50
50
  }
51
51
 
52
52
  override fun onAttachedToWindow() {
53
53
  super.onAttachedToWindow()
54
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.
55
60
  uiManager?.addUIManagerEventListener(this)
56
61
  viewTreeObserver.addOnPreDrawListener(this)
57
62
  foldingFeatures = emptyList()
@@ -81,6 +86,11 @@ class ReservedRegionsView(context: Context) : ReactViewGroup(context), ViewTreeO
81
86
  super.onDetachedFromWindow()
82
87
  }
83
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
+
84
94
  override fun onPreDraw(): Boolean {
85
95
  updateRegions()
86
96
  return true
@@ -92,9 +102,9 @@ class ReservedRegionsView(context: Context) : ReactViewGroup(context), ViewTreeO
92
102
  invalidate()
93
103
  }
94
104
 
95
- private fun updateRegions() {
96
- val handler = onRegionsChange ?: return
97
- if (!isAttachedToWindow || !foldingFeaturesReady || 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
98
108
 
99
109
  val windowLocation = IntArray(2)
100
110
  getLocationInWindow(windowLocation)
@@ -116,10 +126,14 @@ class ReservedRegionsView(context: Context) : ReactViewGroup(context), ViewTreeO
116
126
  }
117
127
  }
118
128
 
119
- if (regions != lastRegions) {
120
- lastRegions = regions
121
- handler(this, regions)
129
+ if (regions == lastRegions) return false
130
+ lastRegions = regions
131
+ handler(this, regions)
132
+ if (awaitingFirstMount) {
133
+ awaitingFirstMount = false
134
+ uiManager?.removeUIManagerEventListener(this)
122
135
  }
136
+ return true
123
137
  }
124
138
 
125
139
  private fun clippedFrame(bounds: Rect, originX: Int, originY: Int): Rect? {
@@ -22,6 +22,17 @@ using namespace facebook::react;
22
22
  if (self = [super initWithFrame:frame]) {
23
23
  static const auto defaultProps = std::make_shared<const ReservedRegionsViewProps>();
24
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
25
36
  }
26
37
  return self;
27
38
  }
@@ -70,4 +70,11 @@ export function useReservedRegionsReady() {
70
70
  }
71
71
  return snapshot.isReady;
72
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;
79
+ }
73
80
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
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"],"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","ignoreList":[]}
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":[]}
@@ -34,4 +34,6 @@ export declare function useReservedRegions(): readonly ReservedRegion[];
34
34
  * result. Remains true for that provider's lifetime; does not imply platform support.
35
35
  */
36
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;
37
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;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"}
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,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-reserved-regions",
3
- "version": "0.1.0-alpha.2",
3
+ "version": "0.1.0-alpha.3",
4
4
  "description": "Provider-scoped reserved display regions for React Native",
5
5
  "keywords": [
6
6
  "android",
@@ -137,6 +137,7 @@
137
137
  "lint:fix": "oxlint --fix .",
138
138
  "format": "oxfmt .",
139
139
  "format:check": "oxfmt --check .",
140
+ "e2e:android": "node e2e/android.mjs",
140
141
  "docs:build": "pnpm --filter reserved-regions-docs build",
141
142
  "docs:start": "pnpm --filter reserved-regions-docs start"
142
143
  }
package/src/index.tsx CHANGED
@@ -84,3 +84,8 @@ export function useReservedRegionsReady(): boolean {
84
84
  }
85
85
  return snapshot.isReady;
86
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;
91
+ }