@onekeyfe/react-native-native-overlay 3.0.162-alpha.268
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NativeOverlay.podspec +30 -0
- package/README.md +35 -0
- package/android/build.gradle +82 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayAnimation.kt +176 -0
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayEntryRootView.kt +415 -0
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayEvents.kt +38 -0
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayHost.kt +174 -0
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayPackage.kt +16 -0
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayPageHostView.kt +122 -0
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayPageHostViewManager.kt +40 -0
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayView.kt +465 -0
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayViewManager.kt +156 -0
- package/android/src/main/jni/CMakeLists.txt +44 -0
- package/android/src/main/jni/RNCNativeOverlay.h +19 -0
- package/common/cpp/react/renderer/components/RNCNativeOverlay/NativeOverlayComponentDescriptor.h +12 -0
- package/common/cpp/react/renderer/components/RNCNativeOverlay/NativeOverlayShadowNode.cpp +12 -0
- package/common/cpp/react/renderer/components/RNCNativeOverlay/NativeOverlayShadowNode.h +41 -0
- package/common/cpp/react/renderer/components/RNCNativeOverlay/NativeOverlayState.h +40 -0
- package/docs/SPEC.md +693 -0
- package/ios/NativeOverlayAnimation.swift +160 -0
- package/ios/NativeOverlayContainerView.swift +688 -0
- package/ios/NativeOverlayKeyboard.swift +88 -0
- package/ios/NativeOverlayPageHostView.swift +98 -0
- package/ios/NativeOverlaySheetController.swift +391 -0
- package/ios/NativeOverlayWindowManager.swift +194 -0
- package/ios/RCTNativeOverlayComponentView.h +11 -0
- package/ios/RCTNativeOverlayComponentView.mm +193 -0
- package/ios/RCTNativeOverlayPageHostComponentView.h +11 -0
- package/ios/RCTNativeOverlayPageHostComponentView.mm +64 -0
- package/lib/module/NativeOverlayNativeComponent.ts +61 -0
- package/lib/module/NativeOverlayPageHostNativeComponent.ts +13 -0
- package/lib/module/OverlayErrors.js +6 -0
- package/lib/module/OverlayErrors.js.map +1 -0
- package/lib/module/OverlayLevelContext.js +27 -0
- package/lib/module/OverlayLevelContext.js.map +1 -0
- package/lib/module/OverlayLevels.js +33 -0
- package/lib/module/OverlayLevels.js.map +1 -0
- package/lib/module/OverlayPageHost.js +24 -0
- package/lib/module/OverlayPageHost.js.map +1 -0
- package/lib/module/OverlayPageHost.web.js +49 -0
- package/lib/module/OverlayPageHost.web.js.map +1 -0
- package/lib/module/OverlayPageScope.js +41 -0
- package/lib/module/OverlayPageScope.js.map +1 -0
- package/lib/module/OverlayStore.js +305 -0
- package/lib/module/OverlayStore.js.map +1 -0
- package/lib/module/OverlayView.js +141 -0
- package/lib/module/OverlayView.js.map +1 -0
- package/lib/module/OverlayView.web.js +290 -0
- package/lib/module/OverlayView.web.js.map +1 -0
- package/lib/module/OverlayViewTypes.js +4 -0
- package/lib/module/OverlayViewTypes.js.map +1 -0
- package/lib/module/animation/motionPresets.js +57 -0
- package/lib/module/animation/motionPresets.js.map +1 -0
- package/lib/module/animation/platformMotionPresets.js +4 -0
- package/lib/module/animation/platformMotionPresets.js.map +1 -0
- package/lib/module/animation/platformMotionPresets.web.js +4 -0
- package/lib/module/animation/platformMotionPresets.web.js.map +1 -0
- package/lib/module/animation/resolveAnimation.js +80 -0
- package/lib/module/animation/resolveAnimation.js.map +1 -0
- package/lib/module/animation/spring.js +69 -0
- package/lib/module/animation/spring.js.map +1 -0
- package/lib/module/animation/types.js +2 -0
- package/lib/module/animation/types.js.map +1 -0
- package/lib/module/index.js +16 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/useOverlayController.js +127 -0
- package/lib/module/useOverlayController.js.map +1 -0
- package/lib/module/useSuspendedPageOwners.js +16 -0
- package/lib/module/useSuspendedPageOwners.js.map +1 -0
- package/lib/module/web/animateTransition.js +115 -0
- package/lib/module/web/animateTransition.js.map +1 -0
- package/lib/module/web/overlayLayers.js +147 -0
- package/lib/module/web/overlayLayers.js.map +1 -0
- package/lib/module/web/useKeyboardShift.js +110 -0
- package/lib/module/web/useKeyboardShift.js.map +1 -0
- package/lib/module/web/useSheetDrag.js +126 -0
- package/lib/module/web/useSheetDrag.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeOverlayNativeComponent.d.ts +41 -0
- package/lib/typescript/src/NativeOverlayNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/NativeOverlayPageHostNativeComponent.d.ts +9 -0
- package/lib/typescript/src/NativeOverlayPageHostNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/OverlayErrors.d.ts +4 -0
- package/lib/typescript/src/OverlayErrors.d.ts.map +1 -0
- package/lib/typescript/src/OverlayLevelContext.d.ts +12 -0
- package/lib/typescript/src/OverlayLevelContext.d.ts.map +1 -0
- package/lib/typescript/src/OverlayLevels.d.ts +13 -0
- package/lib/typescript/src/OverlayLevels.d.ts.map +1 -0
- package/lib/typescript/src/OverlayPageHost.d.ts +9 -0
- package/lib/typescript/src/OverlayPageHost.d.ts.map +1 -0
- package/lib/typescript/src/OverlayPageHost.web.d.ts +11 -0
- package/lib/typescript/src/OverlayPageHost.web.d.ts.map +1 -0
- package/lib/typescript/src/OverlayPageScope.d.ts +17 -0
- package/lib/typescript/src/OverlayPageScope.d.ts.map +1 -0
- package/lib/typescript/src/OverlayStore.d.ts +72 -0
- package/lib/typescript/src/OverlayStore.d.ts.map +1 -0
- package/lib/typescript/src/OverlayView.d.ts +3 -0
- package/lib/typescript/src/OverlayView.d.ts.map +1 -0
- package/lib/typescript/src/OverlayView.web.d.ts +3 -0
- package/lib/typescript/src/OverlayView.web.d.ts.map +1 -0
- package/lib/typescript/src/OverlayViewTypes.d.ts +68 -0
- package/lib/typescript/src/OverlayViewTypes.d.ts.map +1 -0
- package/lib/typescript/src/animation/motionPresets.d.ts +13 -0
- package/lib/typescript/src/animation/motionPresets.d.ts.map +1 -0
- package/lib/typescript/src/animation/platformMotionPresets.d.ts +2 -0
- package/lib/typescript/src/animation/platformMotionPresets.d.ts.map +1 -0
- package/lib/typescript/src/animation/platformMotionPresets.web.d.ts +2 -0
- package/lib/typescript/src/animation/platformMotionPresets.web.d.ts.map +1 -0
- package/lib/typescript/src/animation/resolveAnimation.d.ts +23 -0
- package/lib/typescript/src/animation/resolveAnimation.d.ts.map +1 -0
- package/lib/typescript/src/animation/spring.d.ts +24 -0
- package/lib/typescript/src/animation/spring.d.ts.map +1 -0
- package/lib/typescript/src/animation/types.d.ts +56 -0
- package/lib/typescript/src/animation/types.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +18 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +59 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/useOverlayController.d.ts +29 -0
- package/lib/typescript/src/useOverlayController.d.ts.map +1 -0
- package/lib/typescript/src/useSuspendedPageOwners.d.ts +7 -0
- package/lib/typescript/src/useSuspendedPageOwners.d.ts.map +1 -0
- package/lib/typescript/src/web/animateTransition.d.ts +18 -0
- package/lib/typescript/src/web/animateTransition.d.ts.map +1 -0
- package/lib/typescript/src/web/overlayLayers.d.ts +10 -0
- package/lib/typescript/src/web/overlayLayers.d.ts.map +1 -0
- package/lib/typescript/src/web/useKeyboardShift.d.ts +29 -0
- package/lib/typescript/src/web/useKeyboardShift.d.ts.map +1 -0
- package/lib/typescript/src/web/useSheetDrag.d.ts +13 -0
- package/lib/typescript/src/web/useSheetDrag.d.ts.map +1 -0
- package/package.json +188 -0
- package/react-native.config.js +16 -0
- package/src/NativeOverlayNativeComponent.ts +61 -0
- package/src/NativeOverlayPageHostNativeComponent.ts +13 -0
- package/src/OverlayErrors.ts +3 -0
- package/src/OverlayLevelContext.tsx +32 -0
- package/src/OverlayLevels.ts +40 -0
- package/src/OverlayPageHost.tsx +21 -0
- package/src/OverlayPageHost.web.tsx +53 -0
- package/src/OverlayPageScope.tsx +42 -0
- package/src/OverlayStore.ts +401 -0
- package/src/OverlayView.tsx +165 -0
- package/src/OverlayView.web.tsx +356 -0
- package/src/OverlayViewTypes.ts +81 -0
- package/src/animation/motionPresets.ts +52 -0
- package/src/animation/platformMotionPresets.ts +1 -0
- package/src/animation/platformMotionPresets.web.ts +1 -0
- package/src/animation/resolveAnimation.ts +103 -0
- package/src/animation/spring.ts +80 -0
- package/src/animation/types.ts +71 -0
- package/src/index.ts +55 -0
- package/src/types.ts +79 -0
- package/src/useOverlayController.ts +199 -0
- package/src/useSuspendedPageOwners.ts +26 -0
- package/src/web/animateTransition.ts +137 -0
- package/src/web/overlayLayers.ts +182 -0
- package/src/web/useKeyboardShift.ts +130 -0
- package/src/web/useSheetDrag.ts +143 -0
package/docs/SPEC.md
ADDED
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
# react-native-native-overlay SPEC
|
|
2
|
+
|
|
3
|
+
Status: Implemented through the P5 caller migration in app-monorepo. Android
|
|
4
|
+
and Web runtime checks passed there; iOS runtime, physical-device and iPad
|
|
5
|
+
acceptance remain unverified. Packaging in app-modules is validated separately.
|
|
6
|
+
|
|
7
|
+
The implementation is migrated from app-monorepo commit
|
|
8
|
+
`376633bd9f1c410dbccb01fb6a0492be9a5c3f38`, package
|
|
9
|
+
`packages/react-native-native-overlay`. This independent package owns the native
|
|
10
|
+
and Web overlay hosts. Caller migrations remain in app-monorepo.
|
|
11
|
+
|
|
12
|
+
### Template exception and migration boundary
|
|
13
|
+
|
|
14
|
+
The package was initialized with `yarn create:view native-overlay` on
|
|
15
|
+
2026-10-01, from app-modules main `5b4574bf0d2a5fdd394b7bd81dd1c8c0d15d101f`.
|
|
16
|
+
The checked-in template is Nitro; the verified implementation uses Fabric
|
|
17
|
+
codegen components and custom shadow nodes to preserve React subtrees, native
|
|
18
|
+
hit testing and `measureInWindow` across overlay windows. Rewriting it to Nitro
|
|
19
|
+
would change those boundaries and requires separate design/runtime validation.
|
|
20
|
+
This SPEC records the explicit template exception allowed by
|
|
21
|
+
`docs/NATIVE_MODULE_DEVELOPMENT.md` section 3.
|
|
22
|
+
|
|
23
|
+
Keep the generated package/configuration structure, but replace the template's
|
|
24
|
+
Nitro sample view, nitrogen config/bindings and CMake adapter with the existing
|
|
25
|
+
Fabric Android, iOS and custom C++ implementation. The package has no Nitro
|
|
26
|
+
peer dependency or nitrogen build step. Builder Bob emits JavaScript and
|
|
27
|
+
TypeScript declarations; React Native generates the Fabric bindings at app
|
|
28
|
+
build time. Publish `common/cpp`, `src`, `lib`, Android/iOS sources, podspec and
|
|
29
|
+
autolinking configuration. Web consumers additionally provide `react-dom`.
|
|
30
|
+
|
|
31
|
+
`react-native-native-sheet` remains separately published for existing consumers;
|
|
32
|
+
its compatibility shim/deprecation is a subsequent migration step, not a
|
|
33
|
+
silent API change in this release. Sheet behavior derived from that package
|
|
34
|
+
retains its original implementation attribution in source comments.
|
|
35
|
+
|
|
36
|
+
### Release boundary
|
|
37
|
+
|
|
38
|
+
This branch aligns all 42 publishable workspaces to `3.0.162-alpha.268`.
|
|
39
|
+
Publish with the existing `package-publish` workflow dispatch, `npm_dist_tag=next`
|
|
40
|
+
and an empty `only_workspace`, which publishes and registry-verifies all 42.
|
|
41
|
+
Do not merge either repository PR or change the `latest` tag. The app-monorepo
|
|
42
|
+
workspace copy is removed only after this release succeeds and every target
|
|
43
|
+
version and tarball is available. Versions already published on npm are immutable.
|
|
44
|
+
|
|
45
|
+
## 5.1 Purpose, scope, and non-goals
|
|
46
|
+
|
|
47
|
+
The package owns **where an overlay is drawn, which overlay is on top, and how
|
|
48
|
+
it enters and exits** on iOS, Android, and Web (web, desktop, extension).
|
|
49
|
+
|
|
50
|
+
Callers are `@onekeyhq/components` (Dialog, Toast, Popover, ActionList, Select,
|
|
51
|
+
DatePicker) and `@onekeyhq/kit` global containers: hardware stage and dialogs,
|
|
52
|
+
password verify, DialogLoading, app lock, Spotlight, in-app notification.
|
|
53
|
+
|
|
54
|
+
Non-goals:
|
|
55
|
+
|
|
56
|
+
- Business UI. Titles, buttons, forms, toast cards, the hardware stage morph and
|
|
57
|
+
the lock screen UI stay React content owned by components and kit.
|
|
58
|
+
- Navigation. Screens and navigation modals stay in react-navigation and
|
|
59
|
+
react-native-screens.
|
|
60
|
+
- Third-party windows such as RN `Modal`, WalletConnect, and system dialogs.
|
|
61
|
+
They are only handled defensively by the `lock` level (§5.7).
|
|
62
|
+
|
|
63
|
+
## 5.2 Definitions and ownership boundaries
|
|
64
|
+
|
|
65
|
+
| Term | Meaning |
|
|
66
|
+
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
67
|
+
| **scope** | `global`: attached to the app window. `page`: owned by one screen, rendered in that screen's root-route host. |
|
|
68
|
+
| **root-route host** | A single `PageOverlayHost` rendered as the last child of each root-stack route (Main tabs, Modal, Onboarding, FullScreenPush, WebView). Because it sits there, page overlays cover native headers and the tab bar. |
|
|
69
|
+
| **owner page** | The nearest screen that requested a page overlay. The overlay is visible only while its owner is on top inside its root route. |
|
|
70
|
+
| **level** | Fixed layer. `modal` 100 < `hardware` 200 < `secure` 300 < `toast` 400 < `lock` 500 < `debug` 900. |
|
|
71
|
+
| **lane** | The entries sharing (scope, host, level). Strategies apply per lane. |
|
|
72
|
+
| **strategy** | What a new entry does to its lane. `stack` (default) appears on top. `queue` waits until the lane is empty and is ordered by `priority`. `replace` closes the top entry, or the entries sharing `replaceKey`. |
|
|
73
|
+
| **presentation** | Frame and default motion: `sheet`, `center`, `fullscreen`, `toast`, `anchored`. |
|
|
74
|
+
|
|
75
|
+
Ordering rule:
|
|
76
|
+
|
|
77
|
+
- Every page entry renders below every global entry.
|
|
78
|
+
- Inside a scope, entries are ordered by level, then by request order.
|
|
79
|
+
- Nothing else affects order: not mount order, portal order, or window add
|
|
80
|
+
order.
|
|
81
|
+
|
|
82
|
+
Ownership:
|
|
83
|
+
|
|
84
|
+
- **JS store** (`OverlayStore`, one per JS runtime, `main` only) owns ordering,
|
|
85
|
+
queues, page visibility, and back/Escape resolution.
|
|
86
|
+
- **Native hosts** own drawing, hit testing, animation, keyboard, and back-key
|
|
87
|
+
capture, and report lifecycle back to the store.
|
|
88
|
+
- **Content** is the caller's React subtree. The overlay view stays where the
|
|
89
|
+
caller rendered it. Native code reparents its content into the host, so React
|
|
90
|
+
context is preserved; there is no root-siblings portal.
|
|
91
|
+
|
|
92
|
+
The package MUST NOT infer anything from product data. Levels and strategies
|
|
93
|
+
come only from props.
|
|
94
|
+
|
|
95
|
+
## 5.3 Public API and defaults
|
|
96
|
+
|
|
97
|
+
### Store (implemented)
|
|
98
|
+
|
|
99
|
+
- `overlayStore.request(request, { onRemoved })` returns an `IOverlayEntry`.
|
|
100
|
+
- Defaults: `scope: 'global'`, `level: 'modal'`, `strategy: 'stack'`,
|
|
101
|
+
`priority: 0`.
|
|
102
|
+
- `blocking` defaults to false for `toast` and `debug`, and true for every
|
|
103
|
+
other level.
|
|
104
|
+
- `dismissible` defaults to the value of `blocking`.
|
|
105
|
+
- `page` scope requires `hostKey` and `ownerKey`. Missing either throws
|
|
106
|
+
`OverlayRequestError`.
|
|
107
|
+
- Re-requesting an id that already exists is a no-op.
|
|
108
|
+
- `dismiss(id, reason)` moves an entry to `closing`. `finalize(id)` removes it
|
|
109
|
+
after the native exit animation. A closing entry that is never finalized is
|
|
110
|
+
removed after 5 s.
|
|
111
|
+
- `dismissAll({ scope, belowLevel, reason })`, `setPageVisible(ownerKey,
|
|
112
|
+
visible)`, `removePage(ownerKey)`.
|
|
113
|
+
- `resolveBack(focusedHostKey?)` returns `dismiss(id)`, `block`, or `pass`.
|
|
114
|
+
- `getBlockingTop()` returns the entry that makes everything below it inert.
|
|
115
|
+
- `subscribe` and `getSnapshot` are compatible with `useSyncExternalStore`.
|
|
116
|
+
|
|
117
|
+
### Component (P1)
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
<OverlayView
|
|
121
|
+
open
|
|
122
|
+
scope="global" | "page"
|
|
123
|
+
level="modal"
|
|
124
|
+
strategy="stack"
|
|
125
|
+
priority={0}
|
|
126
|
+
replaceKey?
|
|
127
|
+
presentation="sheet" | "center" | "fullscreen" | "toast" | "anchored"
|
|
128
|
+
animation?={{ enter, exit, backdrop }}
|
|
129
|
+
backdrop?={{ opacity, dismissOnPress }}
|
|
130
|
+
dismissOnBackPress
|
|
131
|
+
dismissOnPanDown // sheet and toast only
|
|
132
|
+
anchor? // anchored only: a trigger ref or a window rect
|
|
133
|
+
onPresented onDismissRequest onDismissed(reason)
|
|
134
|
+
/>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
- `usePageOverlayHost()` resolves `hostKey` and `ownerKey` from navigation
|
|
138
|
+
context.
|
|
139
|
+
- `useInPageDialog`, `useInModalDialog`, and `useInTabDialog` become aliases of
|
|
140
|
+
page scope. Tab scope is removed.
|
|
141
|
+
|
|
142
|
+
### Animation (implemented model)
|
|
143
|
+
|
|
144
|
+
- `animation.enter` and `animation.exit` are each
|
|
145
|
+
`{ type: none|fade|slide|scale, edge, distance, scale, offsetY, fade, origin: center|anchor, motion }`.
|
|
146
|
+
- `exit` defaults to `enter`.
|
|
147
|
+
- `motion` is a preset name or a concrete `{ type: 'spring', spring: { mass, stiffness, damping, overshootClamping } }`
|
|
148
|
+
or `{ type: 'timing', timing: { durationMs, easing: cubicBezier } }`.
|
|
149
|
+
- Presets are resolved per platform:
|
|
150
|
+
- Native mirrors the Reanimated driver: `quick` is a spring with m 0.1,
|
|
151
|
+
k 100, c 20 (damping ratio ≈ 3.16).
|
|
152
|
+
- Web mirrors the CSS driver: `quick` is 150 ms `cubic-bezier(.25,.1,.25,1)`.
|
|
153
|
+
- Converting native springs for web is opt-in (`springToCssLinear`), because
|
|
154
|
+
it would slow web overlays down 4–9×.
|
|
155
|
+
- Presentation defaults (`DEFAULT_OVERLAY_ANIMATIONS`):
|
|
156
|
+
|
|
157
|
+
| Presentation | Enter / exit | Backdrop |
|
|
158
|
+
| ------------ | -------------------------------------------------------------------------------- | ------------- |
|
|
159
|
+
| sheet | slide from bottom, `quick` | fade, `quick` |
|
|
160
|
+
| center | scale 0.85 + fade, `quick` | fade, `quick` |
|
|
161
|
+
| toast | slide from top + fade, `toastSlide` (native 300 ms quad in-out, web 400 ms ease) | none |
|
|
162
|
+
| fullscreen | fade, `quick` | none |
|
|
163
|
+
| anchored | scale 0.95 + fade from the resolved anchor placement, `popoverQuick` | fade |
|
|
164
|
+
|
|
165
|
+
- Special cases the callers set explicitly:
|
|
166
|
+
- App lock: `enter: none`, `exit: fade lockFade`. The web tamper check reloads
|
|
167
|
+
the page if the lock fades in from opacity 0.
|
|
168
|
+
- Hardware stage (MorphOverlay), TradingView fullscreen, ScreenshotBranding,
|
|
169
|
+
DevOverlay: `presentation: fullscreen`, `enter`/`exit: none`, transparent,
|
|
170
|
+
touches pass through where content is `box-none`. MorphOverlay keeps its
|
|
171
|
+
Reanimated springs. The container must not animate alpha, transform, or
|
|
172
|
+
frame while its content is animating.
|
|
173
|
+
- Custom toast (`Toast.show`): `scale 0.8 + offsetY -20`, `quick`, with an
|
|
174
|
+
opt-in blocking backdrop.
|
|
175
|
+
|
|
176
|
+
## 5.4 Lifecycle and concurrency
|
|
177
|
+
|
|
178
|
+
Lifecycle:
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
request ─► queued ─► active ─► (enter anim) presented
|
|
182
|
+
│
|
|
183
|
+
dismiss / replace / page-removed / back / backdrop / pan
|
|
184
|
+
▼
|
|
185
|
+
closing ─► (exit anim) onDismissed ─► finalize ─► onRemoved
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
- Content stays mounted until `finalize`. This replaces today's hardcoded
|
|
189
|
+
timers: the Dialog 300 ms portal destroy, the ColorPicker 300 ms, the
|
|
190
|
+
ActionList and Select 150 ms waits, and the DialogLoading 50 ms delays.
|
|
191
|
+
- A queued entry is promoted only after the previous entry in its lane is
|
|
192
|
+
finalized, so exit and enter never overlap. `replace` is the exception: the
|
|
193
|
+
new entry enters while the replaced one exits.
|
|
194
|
+
- Reopening an entry that is still closing retargets the running animation from
|
|
195
|
+
its current value. It does not restart.
|
|
196
|
+
- `onRemoved` fires exactly once per entry, even when a finalize and the
|
|
197
|
+
timeout race.
|
|
198
|
+
- Page entries:
|
|
199
|
+
- They are suspended (hidden but kept) while the owner page is covered or
|
|
200
|
+
detached.
|
|
201
|
+
- Queued page entries are not promoted into a hidden page.
|
|
202
|
+
- `removePage` closes them with reason `page-removed`.
|
|
203
|
+
- Hide signals must come from native, because tab stacks use `freezeOnBlur`
|
|
204
|
+
and frozen React trees receive no updates:
|
|
205
|
+
- iOS: the owner `RNSScreen`'s `viewWillDisappear` / `viewWillAppear`.
|
|
206
|
+
- Android: the owner `ScreenFragment`'s view detach / attach.
|
|
207
|
+
- Web: navigation focus.
|
|
208
|
+
- Threads: store mutations happen on the JS thread. Native animation and layout
|
|
209
|
+
run on the UI thread. Events are delivered as direct events.
|
|
210
|
+
|
|
211
|
+
## 5.5 Data, cache, and identity
|
|
212
|
+
|
|
213
|
+
- Identity is the entry `id`: caller-provided, otherwise `overlay-<seq>`.
|
|
214
|
+
- Nothing is persisted. The store is per runtime and empty on reload.
|
|
215
|
+
- `replaceKey` is the only identity shared across entries.
|
|
216
|
+
|
|
217
|
+
## 5.6 Platform contract
|
|
218
|
+
|
|
219
|
+
| Concern | iOS | Android | Web |
|
|
220
|
+
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
221
|
+
| Global level host | One passthrough `UIWindow` per active level per `UIWindowScene`, ordered by `windowLevel`. `lock` sits above RN Alert (2001). | One `OverlayHost` FrameLayout in `android.R.id.content` above the ReactRootView, with one child container per level. | Sibling `div[data-onekey-layer]` roots under `body`, one z-index band per level (`OVERLAY_WEB_Z_INDEX_BASE`). |
|
|
222
|
+
| Order within a level | The window's presentation chain (sheet) and subview order (other presentations). | Child order. | DOM order. |
|
|
223
|
+
| Global sheet | `UISheetPresentationController`, presented from the level window's root VC (reuses native-sheet). | View sheet: CoordinatorLayout + BottomSheetBehavior + dimming view (native-sheet reworked off `BottomSheetDialog`). | Custom sheet: pointer drag, snap by velocity, `visualViewport` keyboard. |
|
|
224
|
+
| Page host | Fabric `PageOverlayHost` as the last child of the root-route screen. Custom-drawn sheet, because UIKit sheets cannot be page-scoped. | The same host inside the root-route screen. The ReactRootView already dispatches touches, so no nested RootView. | `position: absolute` host inside the root-route card. |
|
|
225
|
+
| Header / tab bar coverage (page) | Covered: the host is above the tab controller and navigation bars. | Covered. | Covered. |
|
|
226
|
+
| Touch passthrough (toast, debug, box-none) | `hitTest` returns nil on the window or root view. | A non-blocking entry hit-tests its React content with `TouchTargetHelper` (honoring `box-none`) and falls through to the FrameLayout sibling on a miss. | `pointer-events: none` root, `auto` per entry. |
|
|
227
|
+
| Back / Escape | Escape via `accessibilityPerformEscape`. The interactive pop gesture is disabled while a blocking page overlay is shown. | A `Window.Callback` wrapper consumes `KEYCODE_BACK` while a blocking overlay is shown. `ReactActivity.onBackPressed` hands back to JS BackHandler (react-navigation) before the `OnBackPressedDispatcher`, and the app opts out of predictive back, so a dispatcher callback alone never runs first. The dispatcher callback is kept for the predictive-back path. The IME still gets back first to close the keyboard. | Capture-phase `keydown` that ignores IME composition. |
|
|
228
|
+
| Modality / accessibility | `accessibilityViewIsModal` on the blocking level window; `.screenChanged` posted on present. | `IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS` on the content below. | `inert` on the app root and lower layers, `role=dialog`, `aria-modal`, focus trap and restore. |
|
|
229
|
+
| Keyboard | System sheet avoidance; `keyboardLayoutGuide` for custom presentations. The main window regains key status on dismiss. | `WindowInsetsAnimationCompat` per sheet (the app uses `adjustPan`). | `visualViewport`. |
|
|
230
|
+
| Animation engine | `UIViewPropertyAnimator` + `UISpringTimingParameters(mass:stiffness:damping:)`, which maps 1:1 and supports damping ratio > 1. | `SpringAnimation` with `stiffness = k/m` and `dampingRatio`; `PathInterpolator` for timing curves. | WAAPI `element.animate`. Timing presets use `cubic-bezier`; custom springs use `linear()`. |
|
|
231
|
+
| Content hosting | `RCTSurfaceTouchHandler` on the host root. A state-driven shadow node sizes Yoga, which replaces native-sheet's forced `frame`. A nested `SafeAreaProvider`. | A `RootView` + `JSTouchDispatcher` container for global levels. A state-driven shadow node, like RN Modal. | `createPortal`. The Tamagui theme class is set on the layer root, and React event bubbling stops at the entry root. |
|
|
232
|
+
|
|
233
|
+
Known divergences:
|
|
234
|
+
|
|
235
|
+
| Divergence | Status |
|
|
236
|
+
| -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
|
237
|
+
| iOS global sheet (system physics) vs. iOS page sheet (custom physics) | Accepted (decision A). Revisit after on-device comparison. |
|
|
238
|
+
| RN `Modal` and third-party dialogs on Android are windows above every layer | Accepted, mitigated by the lock policy (§5.7). |
|
|
239
|
+
| Web anchored origin follows the resolved placement after flip; today's code uses the requested placement | Intentional fix. |
|
|
240
|
+
|
|
241
|
+
## 5.7 Failure, fallback, and safety
|
|
242
|
+
|
|
243
|
+
- A page request without host or owner keys throws `OverlayRequestError`. This
|
|
244
|
+
is a programming error; there is no silent global fallback, unlike today's
|
|
245
|
+
`Portal.Render` retry.
|
|
246
|
+
- A closing entry is removed after 5 s even if native never reports the end of
|
|
247
|
+
its exit animation.
|
|
248
|
+
- Lock level:
|
|
249
|
+
- iOS: the lock window sits above everything this app creates.
|
|
250
|
+
- Android: when lock shows, the app's foreign windows are found with
|
|
251
|
+
`WindowInspector.getGlobalWindowViews()` (API 29+) and hidden or
|
|
252
|
+
dismissed. An alternative is to make lock a focus-owning `ComponentDialog`
|
|
253
|
+
that re-raises itself.
|
|
254
|
+
- Web: layers below lock get `inert`. Third-party roots are left alone.
|
|
255
|
+
- System view controllers (document picker, PHPicker, share sheet,
|
|
256
|
+
ASWebAuthenticationSession anchor) must be presented through
|
|
257
|
+
`OverlayPresenterResolver` on iOS, so they appear above the overlay windows.
|
|
258
|
+
`lock` is never lowered.
|
|
259
|
+
- No secrets or content are logged. Logs carry only entry id, level, scope and
|
|
260
|
+
reason.
|
|
261
|
+
|
|
262
|
+
## 5.8 Performance and resource budget
|
|
263
|
+
|
|
264
|
+
- iOS level windows are created lazily per active level and hidden when empty.
|
|
265
|
+
There are at most 6 per scene.
|
|
266
|
+
- Android layers are views, not windows: no extra surfaces or WindowManager IPC.
|
|
267
|
+
- Store operations are O(n) in the number of live entries. Snapshots are
|
|
268
|
+
rebuilt once per mutation.
|
|
269
|
+
- Animations run entirely on the UI thread. JS receives only lifecycle events:
|
|
270
|
+
no per-frame bridge traffic.
|
|
271
|
+
- Content height changes animate only between settled measurements (the
|
|
272
|
+
two-frame stable pattern from Popover). Continuous per-frame height changes,
|
|
273
|
+
such as the DatePicker month-row interpolation, are clipped inside the
|
|
274
|
+
content.
|
|
275
|
+
|
|
276
|
+
## 5.9 Conformance and acceptance
|
|
277
|
+
|
|
278
|
+
Automated (P0):
|
|
279
|
+
|
|
280
|
+
- `src/__tests__/OverlayStore.test.ts` covers:
|
|
281
|
+
- level ordering regardless of request order;
|
|
282
|
+
- page below global;
|
|
283
|
+
- `stack`, `queue` (priority, lane isolation, promotion after finalize) and
|
|
284
|
+
`replace` (top entry and `replaceKey`);
|
|
285
|
+
- the closing timeout and single `onRemoved`;
|
|
286
|
+
- page suspend, restore and removal;
|
|
287
|
+
- back resolution (global first, `block`, `pass`, focused host) and the
|
|
288
|
+
blocking top.
|
|
289
|
+
- `src/__tests__/animation.test.ts` covers:
|
|
290
|
+
- the spring response in all damping regimes, overshoot clamping and settle
|
|
291
|
+
time;
|
|
292
|
+
- Android `SpringForce` conversion and CSS `linear()` sampling;
|
|
293
|
+
- presentation defaults and per-platform preset resolution.
|
|
294
|
+
|
|
295
|
+
Runtime matrix, required per platform before migrating callers (P1–P3):
|
|
296
|
+
|
|
297
|
+
1. Show sheet, toast, hardware dialog, password and lock in all 5! orders. The
|
|
298
|
+
final z-order is always level order.
|
|
299
|
+
2. A queue of 3 hardware dialogs with a priority insert.
|
|
300
|
+
3. `replace` DialogLoading text updates without flicker.
|
|
301
|
+
4. Page overlay:
|
|
302
|
+
- push another screen: the overlay hides;
|
|
303
|
+
- pop: it restores;
|
|
304
|
+
- switch tab and come back: it restores;
|
|
305
|
+
- remove the page: it closes with `page-removed`;
|
|
306
|
+
- the native header and tab bar are covered.
|
|
307
|
+
5. Back/Escape reaches the topmost blocking overlay; the lock swallows it.
|
|
308
|
+
6. Keyboard: an input in a global sheet, a page sheet and a secure dialog.
|
|
309
|
+
7. iOS: a system picker opened from an overlay appears above it.
|
|
310
|
+
8. Split view: page overlays stay in their own pane.
|
|
311
|
+
9. Reduce motion degrades to fade.
|
|
312
|
+
|
|
313
|
+
P3 page scope notes:
|
|
314
|
+
|
|
315
|
+
- kit wraps every root route component (`withOverlayPageHost`) with
|
|
316
|
+
`OverlayPageHostScope` and renders `OverlayPageHost` after the navigator,
|
|
317
|
+
keyed by the root route key. components `Page` wraps its content in
|
|
318
|
+
`PageOverlayOwner`, keyed by the screen's route key.
|
|
319
|
+
- Visibility: `PageOverlayOwner` listens to the screen's `focus` / `blur`
|
|
320
|
+
events (they fire even while `freezeOnBlur` freezes the React tree) and
|
|
321
|
+
reports `setPageVisible(owner, activeWithinHost)`. "Active within host"
|
|
322
|
+
checks every navigator up to, but not including, the root stack, so a root
|
|
323
|
+
modal over Main does not hide Main's page overlays; a push or tab switch
|
|
324
|
+
does. Unmounting the page calls `removePage`.
|
|
325
|
+
- The owner may be frozen, so the host applies suspension: `OverlayPageHost`
|
|
326
|
+
subscribes to the store and hides entries of suspended owners natively
|
|
327
|
+
(`suspendedOwners`) or in the DOM (`visibility: hidden` + `inert`). Page
|
|
328
|
+
entries keep `visible=true` while suspended; they are not dismissed.
|
|
329
|
+
- Page entries live inside the React surface: iOS does not attach the
|
|
330
|
+
overlay's own `RCTSurfaceTouchHandler`, Android disables the entry's
|
|
331
|
+
`JSTouchDispatcher` (the ReactRootView already dispatches), otherwise every
|
|
332
|
+
touch would be delivered twice.
|
|
333
|
+
- iOS page sheets are drawn by the entry (bottom-pinned content view, pan to
|
|
334
|
+
close) because UIKit sheets always cover the window. Android and Web reuse
|
|
335
|
+
their global sheet implementation inside the page host.
|
|
336
|
+
- Android back also resolves page entries: global overlays first, then the
|
|
337
|
+
topmost visible page overlay.
|
|
338
|
+
- A page request without a resolvable host or owner falls back to global
|
|
339
|
+
scope.
|
|
340
|
+
|
|
341
|
+
P2 sheet notes:
|
|
342
|
+
|
|
343
|
+
- iOS presents the sheet (`.pageSheet` + one custom detent, ported from
|
|
344
|
+
native-sheet) from the top of its level window's presentation chain.
|
|
345
|
+
Non-sheet entries are subviews of the same window, so within a level,
|
|
346
|
+
sheets and center / toast entries stack by request order. Dismissing a
|
|
347
|
+
lower sheet makes UIKit dismiss the sheets presented above it; those report
|
|
348
|
+
`onDismissed('system')` and the store closes them.
|
|
349
|
+
- Android hosts the sheet in a CoordinatorLayout + BottomSheetBehavior inside
|
|
350
|
+
the level container (no BottomSheetDialog window). Enter / exit use the
|
|
351
|
+
overlay motion; dragging fades the backdrop; a drag to hidden reports
|
|
352
|
+
`pan`.
|
|
353
|
+
- Web renders a bottom surface with pointer drag (40% distance or 0.5 px/ms
|
|
354
|
+
flick dismisses, never from scrolled content).
|
|
355
|
+
- A fitted sheet measures its content with `onLayout` before presenting.
|
|
356
|
+
Hidden content stays mounted inside the zero-width host (clipped on iOS,
|
|
357
|
+
INVISIBLE on Android), and the host height stays auto: a fixed 0 height
|
|
358
|
+
makes Yoga measure the content at most 0 tall.
|
|
359
|
+
- `keepContentMounted` renders content while closed so callers can measure it
|
|
360
|
+
before opening (Popover waits for header / scroll measurements).
|
|
361
|
+
- `pan` dismissals cannot be vetoed; `onRequestDismiss('pan')` is informative.
|
|
362
|
+
|
|
363
|
+
P4a toast notes:
|
|
364
|
+
|
|
365
|
+
- Message toasts (`Toast.success` / `message` / `error` …) keep their
|
|
366
|
+
libraries (sonner on web, `@backpackapp-io/react-native-toast` on native)
|
|
367
|
+
and render inside one persistent, non-blocking `toast`-level fullscreen
|
|
368
|
+
entry (kit `ToastOverlayContainer`). The iOS raise tokens are gone.
|
|
369
|
+
- Custom toasts (`Toast.show` / `ShowCustom`) are their own `toast` entries:
|
|
370
|
+
transparent blocking backdrop (tap closes when `dismissOnOverlayPress`),
|
|
371
|
+
`scale 0.8 + offsetY -20 + fade` on `quick`, PanResponder swipe up to
|
|
372
|
+
close unless `disableSwipeGesture` (the Tamagui toast ignored the flag). `Toast.show` unmounts its portal on the overlay's `onClose`, after
|
|
373
|
+
the exit animation, instead of a 300 ms timer.
|
|
374
|
+
|
|
375
|
+
P4b dialog loading notes:
|
|
376
|
+
|
|
377
|
+
- Dialog takes an internal `overlayLevel` prop. With it, `DialogFrame`
|
|
378
|
+
renders through `OverlayDialogPresentation` on every platform: a sheet
|
|
379
|
+
(24 pt corners, no handle) on narrow windows, a centered 400 pt card
|
|
380
|
+
otherwise, the theme `$bgBackdrop`, Escape never closes on web, Android
|
|
381
|
+
back closes unless `disableSystemClose`.
|
|
382
|
+
- The kit DialogLoading container uses `overlayLevel="modal"`: request order
|
|
383
|
+
puts it above earlier dialogs, and hardware / password prompts stay above
|
|
384
|
+
it. Its 50 ms delays and the iOS remount key are gone; text updates in
|
|
385
|
+
place.
|
|
386
|
+
- Android sheets pad their content by the navigation-bar inset, matching the
|
|
387
|
+
UIKit sheet's home-indicator inset.
|
|
388
|
+
|
|
389
|
+
P1 implementation map:
|
|
390
|
+
|
|
391
|
+
| Contract | iOS | Android | Web |
|
|
392
|
+
| ---------------------------- | -------------------------------------------------------------- | ---------------------------------------------------------- | --------------------------------------------- |
|
|
393
|
+
| Level host | `NativeOverlayWindowManager.swift` | `NativeOverlayHost.kt` | `web/overlayLayers.ts` |
|
|
394
|
+
| Entry, hit testing, backdrop | `NativeOverlayEntryView` in `NativeOverlayContainerView.swift` | `NativeOverlayEntryRootView.kt` | `WebOverlayEntry` in `OverlayView.web.tsx` |
|
|
395
|
+
| Page host | `NativeOverlayPageHostView.swift` | `NativeOverlayPageHostView.kt` | `OverlayPageHost.web.tsx` |
|
|
396
|
+
| Sheet | `NativeOverlaySheetController.swift` | `NativeOverlayEntryRootView.configureSheet` | `OverlayView.web.tsx` + `web/useSheetDrag.ts` |
|
|
397
|
+
| Enter / exit animation | `NativeOverlayAnimation.swift` (`UIViewPropertyAnimator`) | `NativeOverlayAnimation.kt` (analytic spring interpolator) | `web/animateTransition.ts` (WAAPI) |
|
|
398
|
+
| Store bridge | `useOverlayController.ts` | same | same |
|
|
399
|
+
|
|
400
|
+
P4c dialog notes:
|
|
401
|
+
|
|
402
|
+
- Every Dialog renders through `OverlayDialogPresentation` (sheet on narrow
|
|
403
|
+
windows, centered card otherwise); the Tamagui Sheet / Dialog and the
|
|
404
|
+
NativeSheetPresentation branches of `DialogFrame` are gone. Removed
|
|
405
|
+
`nativeSheet`, `sheetOverlayProps` and JS portal/layering options throw at
|
|
406
|
+
the component and imperative API entry points, including object spreads.
|
|
407
|
+
- `floatingPanelProps` style the centered card (width, maxWidth, maxHeight,
|
|
408
|
+
bg, overflow, radius…); `zIndex`, `sheetOverlayProps`, `sheetProps`
|
|
409
|
+
(except `disableDrag`), `modal` and `forceMount` no longer apply.
|
|
410
|
+
- Level: `overlayLevel` prop, else from the portal container (`lock` for the
|
|
411
|
+
lock screen's container, `secure` for the password prompt's, otherwise
|
|
412
|
+
`modal`). `isOverTopAllViews` is redundant: every level is above app
|
|
413
|
+
content.
|
|
414
|
+
- `useInPageDialog` / `useInTabDialog` pass the page's host and owner keys:
|
|
415
|
+
in-page dialogs are page overlays (hidden while the page is covered).
|
|
416
|
+
- `Dialog.show` unmounts its portal when the exit animation ends
|
|
417
|
+
(`onExited`), with a 1.5 s fallback, instead of a 300 ms timer.
|
|
418
|
+
- Web: a blocking page overlay inerts the siblings on the path from its page
|
|
419
|
+
host to the app root, not the app root that contains it.
|
|
420
|
+
- Native sheets pad the home indicator / navigation bar and lift above the
|
|
421
|
+
keyboard natively; the dialog adds no keyboard padding on native (the
|
|
422
|
+
bounded login layout still caps its scroll view by the keyboard height).
|
|
423
|
+
|
|
424
|
+
P4d hardware / password notes:
|
|
425
|
+
|
|
426
|
+
- The hardware stage (MorphOverlay / DeviceStage) renders its portal inside
|
|
427
|
+
one persistent, non-blocking `hardware`-level fullscreen entry (kit
|
|
428
|
+
`HardwareStageOverlayContainer`); the stage's own wall blocks the app
|
|
429
|
+
while it is up, and taps pass through once it is gone. The iOS raise
|
|
430
|
+
tokens and the Android split-view offset are gone.
|
|
431
|
+
- Hardware dialogs (device confirm, errors, BLE permission, third-party
|
|
432
|
+
install / permission) use `overlayLevel: 'hardware'`: above every modal
|
|
433
|
+
dialog, ordered by request with the stage.
|
|
434
|
+
- Password setup / verify prompts use `overlayLevel: 'secure'`, above the
|
|
435
|
+
stage and every dialog; their web z-index and portal workarounds are gone.
|
|
436
|
+
|
|
437
|
+
P4e lock screen notes:
|
|
438
|
+
|
|
439
|
+
- kit `AppStateContainer` (one file for every platform) hosts the lock
|
|
440
|
+
screen and its dialog portal in a persistent blocking `lock`-level
|
|
441
|
+
fullscreen entry with no overlay animation (the lock screen keeps its own
|
|
442
|
+
AnimatePresence fade). Everything below it stays open and is covered.
|
|
443
|
+
- `setSecurityBlocked`, the `security` dismiss reason and
|
|
444
|
+
`snapshot.securityBlockedBelow` are removed.
|
|
445
|
+
- Dialogs opened into the lock screen's container (forgot passcode, export
|
|
446
|
+
logs) take the `lock` level from the container, so they render above the
|
|
447
|
+
lock screen; on web in the same `document.body` child the lock screen keeps
|
|
448
|
+
interactive (OK-62416).
|
|
449
|
+
- Android back is swallowed on the lock screen (blocking, not dismissible);
|
|
450
|
+
it no longer reaches navigation behind the lock.
|
|
451
|
+
- The web lock tamper check and the lock screen's body-level inert pass are
|
|
452
|
+
unchanged; the lock layer root is a `document.body` child.
|
|
453
|
+
|
|
454
|
+
P5a native popover notes:
|
|
455
|
+
|
|
456
|
+
- Every native Popover / ActionList / Select sheet is a native overlay sheet
|
|
457
|
+
(`NativeSheetPresentation`); the Tamagui Popover sheet (`TMPopover.Adapt`),
|
|
458
|
+
its external backdrop, z-index and back handler are gone, and
|
|
459
|
+
the removed `nativeSheet` option throws. iPad / wide windows keep the 400–480 pt centered
|
|
460
|
+
frame inside the sheet. `usingSheet={false}` still renders no panel on
|
|
461
|
+
native (web / desktop floating panels).
|
|
462
|
+
- The native popover renders inline in the caller's tree (its contexts
|
|
463
|
+
apply) instead of through the full-window portal. Every popover remains
|
|
464
|
+
mounted until the exit callback (1 s fallback); content then unmounts
|
|
465
|
+
unless `keepChildrenMounted`. Reopening cancels pending teardown.
|
|
466
|
+
- `closePopover()` waits for exit completion; repeated requests share the
|
|
467
|
+
same completion promise, and completion or unmount clears its timer.
|
|
468
|
+
- Sheet options are package-owned. Unsupported legacy `sheetProps` keys
|
|
469
|
+
and floating-panel `zIndex` / `portalProps` throw instead of being ignored.
|
|
470
|
+
|
|
471
|
+
P5b web popover notes:
|
|
472
|
+
|
|
473
|
+
- Web Popover / ActionList / Select render in `OverlayView` at the `modal`
|
|
474
|
+
level; the Tamagui Popover and `PopoverContent` are gone.
|
|
475
|
+
- Below `md` with `usingSheet` (the default): an overlay sheet with a
|
|
476
|
+
header and a dismissing backdrop.
|
|
477
|
+
- Otherwise: `presentation="anchored"`, non-blocking and without a backdrop.
|
|
478
|
+
Floating UI (`@floating-ui/dom`) places the panel: offset, flip, shift,
|
|
479
|
+
and a max height from the available space. `autoUpdate` follows scroll
|
|
480
|
+
and resize.
|
|
481
|
+
- The enter animation scales an ancestor of the panel, so the panel measures
|
|
482
|
+
smaller while it runs. The panel is positioned again on `onPresented`.
|
|
483
|
+
- Anchored panels close on an outside press. Presses on the trigger, on the
|
|
484
|
+
panel, or inside an overlay ranked above it do not close it, so a nested
|
|
485
|
+
Select or a Dialog opened from the panel keeps it open. They also close on
|
|
486
|
+
Escape, unless `hoverable`.
|
|
487
|
+
- `hoverable` opens and closes on mouse enter / leave of the trigger and
|
|
488
|
+
panel. The default close delay is 100 ms, so the pointer can cross the gap.
|
|
489
|
+
- `keepChildrenMounted` maps to `keepContentMounted`. On web a closed entry
|
|
490
|
+
stays in its layer, `visibility: hidden` and `inert`.
|
|
491
|
+
- `OverlayStore.resolveBack` also resolves non-blocking entries that are
|
|
492
|
+
dismissible, so Escape reaches an anchored panel.
|
|
493
|
+
|
|
494
|
+
P5c tooltip and nested-level notes:
|
|
495
|
+
|
|
496
|
+
- `OverlayView` provides its level to its content.
|
|
497
|
+
- `useEnclosingOverlayLevel()` reads that level.
|
|
498
|
+
- `useNestedOverlayLevel(minimum = 'modal')` returns the higher of it and
|
|
499
|
+
`minimum`.
|
|
500
|
+
- Popover (web anchored and sheet), the native popover sheet
|
|
501
|
+
(`NativeSheetPresentation`) and Tooltip open at that level. A popover in a
|
|
502
|
+
password prompt or on the lock screen then renders above it, not beneath.
|
|
503
|
+
- Web Tooltip renders in `OverlayView`: anchored, non-blocking, no backdrop.
|
|
504
|
+
Floating UI places it with offset 6, flip and shift. The Tamagui tooltip is
|
|
505
|
+
gone; the trigger is still a Tamagui `View`, so `triggerAsChild` works.
|
|
506
|
+
- Hover and keyboard focus (`:focus-visible`) open it; leaving or blurring
|
|
507
|
+
the trigger closes it.
|
|
508
|
+
- A tap toggles it on touch.
|
|
509
|
+
- A mouse/pen press closes it until the pointer leaves (unchanged).
|
|
510
|
+
- Escape and an outside press close it.
|
|
511
|
+
- Non-`hovering` tooltips let the pointer pass through. `hovering` ones
|
|
512
|
+
stay open while the pointer is over the content.
|
|
513
|
+
- A controlled `open` wins; requests go to `onOpenChange`.
|
|
514
|
+
- Native Tooltip still renders only the trigger.
|
|
515
|
+
- iOS: an overlay whose host view lives inside a level window resolves that
|
|
516
|
+
window back to the app window. Before this, closing a sheet opened from a
|
|
517
|
+
`secure` overlay recursed forever in `preferredStatusBarStyle`
|
|
518
|
+
(`_appearingOrAppearedChildModalViewController` stack overflow). The level
|
|
519
|
+
root controller also never forwards to itself.
|
|
520
|
+
|
|
521
|
+
P5d Spotlight notes:
|
|
522
|
+
|
|
523
|
+
- Spotlight renders in a blocking `OverlayView`: `presentation="fullscreen"`
|
|
524
|
+
(fade), a 0.3 black backdrop, and the nested level. Back and Escape are
|
|
525
|
+
swallowed, and the backdrop does not dismiss it; only "Done" ends the tour.
|
|
526
|
+
- It renders inline, so the highlighted copy of the trigger keeps its
|
|
527
|
+
contexts. The deferred-trigger plumbing and `SPOTLIGHT_OVERLAY_PORTAL`
|
|
528
|
+
(container and enum member) are gone.
|
|
529
|
+
- The Windows / Linux desktop -30 px title-bar offset is dropped: the overlay
|
|
530
|
+
layer covers the whole window, so `measureInWindow` coordinates line up.
|
|
531
|
+
Not yet checked on a Windows / Linux desktop build.
|
|
532
|
+
|
|
533
|
+
P5e full-window portal notes:
|
|
534
|
+
|
|
535
|
+
- `FULL_WINDOW_OVERLAY_PORTAL` and `TOASTER_OVERLAY_PORTAL` are only mount
|
|
536
|
+
roots for the imperative APIs (`Dialog.show`, `Toast.show`,
|
|
537
|
+
`ActionList.show`, and other `Portal.Render` callers). What mounts there
|
|
538
|
+
renders its own `OverlayView`. `Portal.Body` into either one throws.
|
|
539
|
+
- These now render their `OverlayView`-backed dialogs and toasts inline
|
|
540
|
+
instead of through `Portal.Body`: LocalDbDowngrade, the hardware and
|
|
541
|
+
third-party hardware UI state containers, and ColorPicker's mobile dialog.
|
|
542
|
+
- The Perp OrderBook hover summary is a non-blocking, backdrop-less,
|
|
543
|
+
unanimated `fullscreen` overlay at `modal`.
|
|
544
|
+
- The Market inline action bar (long-press on a watchlist row) is a
|
|
545
|
+
`fullscreen` overlay with a transparent, dismissing backdrop. Back now
|
|
546
|
+
dismisses it as well.
|
|
547
|
+
- `DialogFrame` defaults its level to the nested level, so a Dialog declared
|
|
548
|
+
inside another overlay opens above it. `Dialog.show` keeps the level of its
|
|
549
|
+
portal container.
|
|
550
|
+
|
|
551
|
+
P5f full-window container notes:
|
|
552
|
+
|
|
553
|
+
- `OverlayContainer` (react-native-screens `FullWindowOverlay` on iOS) and
|
|
554
|
+
`useOverlayZIndex` are removed. `Dialog.show`'s `isOverTopAllViews` is
|
|
555
|
+
removed: a dialog's level decides its stacking, and on iOS the flag mounted
|
|
556
|
+
the dialog twice. `FullWindowOverlayContainer` now only mounts
|
|
557
|
+
self-hosting overlays and the imperative mount roots.
|
|
558
|
+
- The TradingView native fullscreen chart keeps its global host, because the
|
|
559
|
+
chart owner's page freezes while fullscreen. The host presents each layer
|
|
560
|
+
in a non-blocking `fullscreen` overlay at `modal`, without animation, and
|
|
561
|
+
back is not dismissed. Insets come from the app's safe-area context.
|
|
562
|
+
- ScreenshotBranding is a non-interactive, unanimated overlay at `toast`.
|
|
563
|
+
- The dev overlay window and the TradingView debug panel are non-interactive
|
|
564
|
+
hosts at `debug`.
|
|
565
|
+
- The Market desktop fullscreen chart stays in place (a remount would reload
|
|
566
|
+
TradingView), fixed at `OVERLAY_WEB_Z_INDEX_BASE.modal - 1`.
|
|
567
|
+
- iOS: a touched level window becomes key, and React Native reads its window
|
|
568
|
+
dimensions from the key window. After a rotation while one was key (the
|
|
569
|
+
fullscreen chart), JS kept the landscape size, so the page stayed in its
|
|
570
|
+
wide layout and the native header stayed hidden. A level window now
|
|
571
|
+
re-reports the dimensions when it resigns key or resizes while key, by
|
|
572
|
+
re-assigning the app window's frame (React Native observes it).
|
|
573
|
+
|
|
574
|
+
Measurement:
|
|
575
|
+
|
|
576
|
+
- `RNCNativeOverlay` has a hand-written shadow node (`common/cpp`, codegen
|
|
577
|
+
`interfaceOnly`), like `<Modal>`: it is a `RootNodeKind` (measurements stop
|
|
578
|
+
at it) and `Unstable_uncullableView`. Its state holds the window origin of
|
|
579
|
+
the view the content currently lives in (level window, sheet, page host,
|
|
580
|
+
or the host itself while staged), returned from
|
|
581
|
+
`getContentOriginOffset`, so Fabric `measure` / `measureInWindow` report
|
|
582
|
+
on-screen frames.
|
|
583
|
+
- iOS reports the origin after presenting, on entry / sheet layout, and after
|
|
584
|
+
restaging; Android on the same events plus container layout changes. Both
|
|
585
|
+
read model geometry, so a running enter / exit transform is ignored.
|
|
586
|
+
- Web measures the DOM and needs nothing extra.
|
|
587
|
+
- Android builds the codegen library from `android/src/main/jni/CMakeLists.txt`
|
|
588
|
+
(as react-native-screens does); `jni/RNCNativeOverlay.h` shadows the
|
|
589
|
+
generated header so autolinking sees the custom descriptor.
|
|
590
|
+
|
|
591
|
+
Keyboard avoidance:
|
|
592
|
+
|
|
593
|
+
- One rule on every platform (`computeKeyboardShift` on web,
|
|
594
|
+
`NativeOverlayKeyboardShift` on iOS, `updateKeyboardShift` on Android): a
|
|
595
|
+
sheet rises until its content bottom clears the keyboard; other content
|
|
596
|
+
rises until its bottom is 16 pt above it; nothing rises above the top safe
|
|
597
|
+
area, so full-window content (toaster host, lock screen) never moves.
|
|
598
|
+
- The lift lives on a keyboard layer between the entry and its animated
|
|
599
|
+
content, so it composes with enter / exit transforms, and `measure`
|
|
600
|
+
includes it.
|
|
601
|
+
- iOS: a shared tracker follows `keyboardWillChangeFrame` from the first
|
|
602
|
+
overlay host on, animated with the keyboard's own curve; UIKit sheets keep
|
|
603
|
+
their native avoidance.
|
|
604
|
+
- Android: each entry reads IME insets (applied insets at rest, the insets
|
|
605
|
+
animation per frame, nothing consumed); the app runs edge-to-edge through
|
|
606
|
+
react-native-keyboard-controller, so the window never pans for overlays.
|
|
607
|
+
- Web: `visualViewport` resize / scroll on mobile browsers.
|
|
608
|
+
- Center dialogs put the card itself at the overlay root (auto margins), so
|
|
609
|
+
the native content extent is the card, not a full-window wrapper.
|
|
610
|
+
|
|
611
|
+
Status bar:
|
|
612
|
+
|
|
613
|
+
- `statusBarStyle` (`light` | `dark`) on an overlay; the topmost shown,
|
|
614
|
+
unsuspended entry that sets one owns the status bar
|
|
615
|
+
(`findStatusBarOwner`, render order: level, then request order).
|
|
616
|
+
- The owner mounts a React Native `<StatusBar>`, the same stack the pages
|
|
617
|
+
use: the app runs without view-controller-based status bar appearance on
|
|
618
|
+
iOS, and Android sets the window appearance. Web has no status bar.
|
|
619
|
+
- iOS asks the root view controller of level windows above the status bar
|
|
620
|
+
(`lock`, `debug`) anyway; without view-controller-based appearance it
|
|
621
|
+
answers with the app-wide style, so the StatusBar stack still decides.
|
|
622
|
+
|
|
623
|
+
Migration rules (decided 2026-09-28):
|
|
624
|
+
|
|
625
|
+
- No transition period. Every overlay (Dialog, Sheet, Toast, DialogLoading,
|
|
626
|
+
hardware stage and dialogs, password prompts, lock screen, Popover,
|
|
627
|
+
ActionList, Select, Spotlight) moves onto this package, and the JS
|
|
628
|
+
overlays (Tamagui Dialog / Sheet / Popover portals, `Portal.Container`
|
|
629
|
+
overlay hosts, `useOverlayZIndex`) are removed. No lint rule: each old
|
|
630
|
+
entry point that has to stay exported throws at runtime once its last
|
|
631
|
+
caller migrates, so a leftover or new use fails at once (2026-09-29).
|
|
632
|
+
- The global Portal exists only for the JS overlays; it is removed once
|
|
633
|
+
its last caller migrates.
|
|
634
|
+
- The app lock is unified with the rest: the lock screen renders at the
|
|
635
|
+
`lock` level and covers everything below it. Overlays are no longer
|
|
636
|
+
closed on lock (done in P4e).
|
|
637
|
+
- Every presentation avoids the keyboard (center, sheet, page sheet) on
|
|
638
|
+
iOS, Android, and web.
|
|
639
|
+
- Fabric `measure` / `measureInWindow` inside an overlay must report the
|
|
640
|
+
on-screen frame.
|
|
641
|
+
- The status bar follows the topmost full-screen overlay (`statusBarStyle`).
|
|
642
|
+
- Custom toasts honor `disableSwipeGesture`.
|
|
643
|
+
|
|
644
|
+
Accepted limitations:
|
|
645
|
+
|
|
646
|
+
- The iOS page sheet is drawn by the entry (UIKit sheets always cover the
|
|
647
|
+
window): it has no grabber and does not coordinate its pan with inner
|
|
648
|
+
scroll views. Page sheets should keep scrollable content short or pass
|
|
649
|
+
`dismissOnPanDown={false}`.
|
|
650
|
+
- Custom toasts stack by request order and overlap at the top, as before;
|
|
651
|
+
message toasts stack inside their libraries.
|
|
652
|
+
|
|
653
|
+
Future work (better to have):
|
|
654
|
+
|
|
655
|
+
- VoiceOver / TalkBack cross-window modality, verified on device.
|
|
656
|
+
- Screen-reader toasts: the native toast library renders nothing under a
|
|
657
|
+
screen reader today; the toast host should announce content and extend
|
|
658
|
+
its timer instead.
|
|
659
|
+
|
|
660
|
+
Known gaps:
|
|
661
|
+
|
|
662
|
+
- `statusBarStyle` follows React Native's StatusBar stack, so a page that
|
|
663
|
+
mounts a new `<StatusBar>` while an overlay owns the style, or the
|
|
664
|
+
imperative theme calls (`StatusBar.setBarStyle`), take over until the owner
|
|
665
|
+
changes; rare while a full-screen overlay covers the page.
|
|
666
|
+
- The podspec normalizes an npm prerelease version to its stable numeric core.
|
|
667
|
+
npm remains pinned to the exact prerelease; local CocoaPods never uses a
|
|
668
|
+
prerelease version string in its path-based dependency resolver.
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
## Package migration conformance audit (2026-10-01)
|
|
672
|
+
|
|
673
|
+
- Template deviations: Fabric codegen/custom shadow nodes replace Nitro sample
|
|
674
|
+
code and bindings as documented above. The template's ESLint configuration
|
|
675
|
+
uses an unregistered Prettier plugin; use RN's exported ESLint 9 flat config
|
|
676
|
+
and a separate Prettier check. Import CodegenTypes from RN's public namespace
|
|
677
|
+
so the package typechecks under the template's strict API condition.
|
|
678
|
+
- Behavioral contract: no new overlay behavior in this migration. Formatting,
|
|
679
|
+
public codegen type imports and build metadata preserve the implementation
|
|
680
|
+
from the recorded app-monorepo commit. Native-sheet API remains intact.
|
|
681
|
+
- Passed: standalone typecheck; three Jest suites / 39 tests; lint (zero errors,
|
|
682
|
+
11 advisory style warnings); Builder Bob JS/declaration build; npm tarball
|
|
683
|
+
contents including custom C++, Web JS, declarations and autolinking files;
|
|
684
|
+
36 release-tool tests including all four overlay consumer migrations;
|
|
685
|
+
repository test-integrity check; immutable Yarn installation.
|
|
686
|
+
- Passed: full Android example `:app:assembleDebug` for arm64-v8a, including
|
|
687
|
+
the overlay Fabric schema, Kotlin and custom C++ autolinking.
|
|
688
|
+
- Passed: example CocoaPods install with NativeOverlay and the synchronized
|
|
689
|
+
prerelease packages. This is dependency/codegen evidence, not iOS execution.
|
|
690
|
+
- Previous app-monorepo Android/Web interaction evidence remains recorded in
|
|
691
|
+
the handoff. iOS runtime, physical devices, iPad, screen-reader cross-window
|
|
692
|
+
modality and real password-lock flows remain outstanding; CI adds packaging
|
|
693
|
+
checks and builds this pod through the example application.
|