@onekeyfe/react-native-native-overlay 3.0.162-alpha.270 → 6.7.0-8
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/android/build.gradle +6 -0
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayAccessibilityIsolation.kt +37 -0
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayHost.kt +27 -30
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayPageHostView.kt +21 -0
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayView.kt +7 -2
- package/android/src/test/java/com/onekey/nativeoverlay/NativeOverlayAccessibilityIsolationTest.kt +112 -0
- package/android/src/test/java/com/onekey/nativeoverlay/NativeOverlayBackTest.kt +157 -0
- package/docs/SPEC.md +148 -26
- package/ios/NativeOverlayContainerView.swift +20 -7
- package/ios/NativeOverlaySheetController.swift +35 -1
- package/lib/module/OverlayPageHost.web.js +19 -6
- package/lib/module/OverlayPageHost.web.js.map +1 -1
- package/lib/module/OverlayStore.js +13 -1
- package/lib/module/OverlayStore.js.map +1 -1
- package/lib/module/OverlayView.web.js +107 -25
- package/lib/module/OverlayView.web.js.map +1 -1
- package/lib/module/useOverlayController.js +11 -3
- package/lib/module/useOverlayController.js.map +1 -1
- package/lib/module/web/animateTransition.js +20 -12
- package/lib/module/web/animateTransition.js.map +1 -1
- package/lib/module/web/overlayLayers.js +27 -9
- package/lib/module/web/overlayLayers.js.map +1 -1
- package/lib/module/web/useKeyboardShift.js +1 -1
- package/lib/module/web/useKeyboardShift.js.map +1 -1
- package/lib/module/web/useSheetDrag.js +22 -2
- package/lib/module/web/useSheetDrag.js.map +1 -1
- package/lib/typescript/src/OverlayPageHost.web.d.ts.map +1 -1
- package/lib/typescript/src/OverlayStore.d.ts +2 -0
- package/lib/typescript/src/OverlayStore.d.ts.map +1 -1
- package/lib/typescript/src/OverlayView.web.d.ts.map +1 -1
- package/lib/typescript/src/useOverlayController.d.ts.map +1 -1
- package/lib/typescript/src/web/animateTransition.d.ts +2 -2
- package/lib/typescript/src/web/animateTransition.d.ts.map +1 -1
- package/lib/typescript/src/web/overlayLayers.d.ts +3 -0
- package/lib/typescript/src/web/overlayLayers.d.ts.map +1 -1
- package/lib/typescript/src/web/useSheetDrag.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/OverlayPageHost.web.tsx +24 -9
- package/src/OverlayStore.ts +13 -1
- package/src/OverlayView.web.tsx +197 -56
- package/src/useOverlayController.ts +11 -4
- package/src/web/animateTransition.ts +30 -11
- package/src/web/overlayLayers.ts +48 -10
- package/src/web/useKeyboardShift.ts +1 -1
- package/src/web/useSheetDrag.ts +23 -2
|
@@ -69,9 +69,7 @@ export function useOverlayController(
|
|
|
69
69
|
const pageScope = useOverlayPageScope();
|
|
70
70
|
const hostKey = props.hostKey ?? pageScope.hostKey;
|
|
71
71
|
const ownerKey = props.ownerKey ?? pageScope.ownerKey;
|
|
72
|
-
|
|
73
|
-
const scope =
|
|
74
|
-
props.scope === 'page' && hostKey && ownerKey ? 'page' : 'global';
|
|
72
|
+
const scope = props.scope ?? 'global';
|
|
75
73
|
const baseId = useId();
|
|
76
74
|
const cycleRef = useRef(0);
|
|
77
75
|
const [entryId, setEntryId] = useState<string | undefined>();
|
|
@@ -79,6 +77,7 @@ export function useOverlayController(
|
|
|
79
77
|
// still true, so the effect below does not immediately reopen it.
|
|
80
78
|
const closedByStoreRef = useRef(false);
|
|
81
79
|
const propsRef = useLatest(props);
|
|
80
|
+
const previousVisibleRef = useRef(visible);
|
|
82
81
|
|
|
83
82
|
const snapshot = useSyncExternalStore(
|
|
84
83
|
overlayStore.subscribe,
|
|
@@ -95,6 +94,9 @@ export function useOverlayController(
|
|
|
95
94
|
);
|
|
96
95
|
|
|
97
96
|
useEffect(() => {
|
|
97
|
+
const reopening = visible && !previousVisibleRef.current;
|
|
98
|
+
previousVisibleRef.current = visible;
|
|
99
|
+
if (reopening && entryId) overlayStore.reopen(entryId);
|
|
98
100
|
if (!visible) {
|
|
99
101
|
closedByStoreRef.current = false;
|
|
100
102
|
if (entryId) {
|
|
@@ -105,6 +107,11 @@ export function useOverlayController(
|
|
|
105
107
|
if (entryId || closedByStoreRef.current) {
|
|
106
108
|
return;
|
|
107
109
|
}
|
|
110
|
+
// Page context can become ready after the caller first opens the overlay.
|
|
111
|
+
// Keep the request in its page boundary instead of presenting globally.
|
|
112
|
+
if (scope === 'page' && (!hostKey || !ownerKey)) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
108
115
|
cycleRef.current += 1;
|
|
109
116
|
const id = `overlay-view${baseId}${cycleRef.current}`;
|
|
110
117
|
overlayStore.request(
|
|
@@ -134,7 +141,7 @@ export function useOverlayController(
|
|
|
134
141
|
setEntryId(id);
|
|
135
142
|
// Level / strategy changes apply to the next open cycle only.
|
|
136
143
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
137
|
-
}, [visible, entryId]);
|
|
144
|
+
}, [visible, entryId, scope, hostKey, ownerKey]);
|
|
138
145
|
|
|
139
146
|
useEffect(
|
|
140
147
|
() => () => {
|
|
@@ -100,7 +100,8 @@ export function animateTransition(
|
|
|
100
100
|
element: HTMLElement,
|
|
101
101
|
contentRoot: HTMLElement,
|
|
102
102
|
transition: IResolvedOverlayTransition,
|
|
103
|
-
direction: 'in' | 'out'
|
|
103
|
+
direction: 'in' | 'out',
|
|
104
|
+
interruptedFrames?: ReadonlyMap<HTMLElement, Keyframe>
|
|
104
105
|
): Animation | undefined {
|
|
105
106
|
if (transition.type === 'none') {
|
|
106
107
|
return undefined;
|
|
@@ -109,16 +110,32 @@ export function animateTransition(
|
|
|
109
110
|
? { ...transition, type: 'fade', fade: true }
|
|
110
111
|
: transition;
|
|
111
112
|
const extent = measureContentExtent(contentRoot);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
// The React Native root wraps the positioned panel. Animate that panel so its
|
|
114
|
+
// offset parent stays unscaled; multi-child roots retain their shared motion.
|
|
115
|
+
const anchoredContent =
|
|
116
|
+
effective.origin === 'anchor' && contentRoot.childElementCount === 1
|
|
117
|
+
? contentRoot.firstElementChild
|
|
118
|
+
: undefined;
|
|
119
|
+
const target =
|
|
120
|
+
anchoredContent instanceof HTMLElement ? anchoredContent : element;
|
|
121
|
+
const targetRect =
|
|
122
|
+
target === element ? undefined : target.getBoundingClientRect();
|
|
123
|
+
target.style.transformOrigin = `${
|
|
124
|
+
(extent.left + extent.right) / 2 - (targetRect?.left ?? 0)
|
|
125
|
+
}px ${(extent.top + extent.bottom) / 2 - (targetRect?.top ?? 0)}px`;
|
|
115
126
|
const hidden = hiddenKeyframe(effective, extent);
|
|
116
127
|
const visible: Keyframe = {};
|
|
117
128
|
Object.keys(hidden).forEach((key) => {
|
|
118
129
|
visible[key] = VISIBLE_KEYFRAME[key];
|
|
119
130
|
});
|
|
120
|
-
const
|
|
121
|
-
|
|
131
|
+
const sampled = interruptedFrames?.get(target);
|
|
132
|
+
const initial = direction === 'in' ? hidden : visible;
|
|
133
|
+
const start: Keyframe = {};
|
|
134
|
+
Object.keys(initial).forEach((key) => {
|
|
135
|
+
start[key] = sampled?.[key] ?? initial[key];
|
|
136
|
+
});
|
|
137
|
+
const keyframes = [start, direction === 'in' ? visible : hidden];
|
|
138
|
+
return target.animate(keyframes, {
|
|
122
139
|
...toTiming(effective.motion),
|
|
123
140
|
fill: 'forwards',
|
|
124
141
|
});
|
|
@@ -127,11 +144,13 @@ export function animateTransition(
|
|
|
127
144
|
export function animateBackdrop(
|
|
128
145
|
element: HTMLElement,
|
|
129
146
|
motion: IOverlayMotion,
|
|
130
|
-
direction: 'in' | 'out'
|
|
147
|
+
direction: 'in' | 'out',
|
|
148
|
+
interruptedFrames?: ReadonlyMap<HTMLElement, Keyframe>
|
|
131
149
|
): Animation {
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
150
|
+
const sampled = interruptedFrames?.get(element)?.opacity;
|
|
151
|
+
const keyframes = [
|
|
152
|
+
{ opacity: sampled ?? (direction === 'in' ? 0 : 1) },
|
|
153
|
+
{ opacity: direction === 'in' ? 1 : 0 },
|
|
154
|
+
];
|
|
136
155
|
return element.animate(keyframes, { ...toTiming(motion), fill: 'forwards' });
|
|
137
156
|
}
|
package/src/web/overlayLayers.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import { overlayStore } from '../OverlayStore';
|
|
7
7
|
|
|
8
8
|
import type { IOverlayRequestDismissReason } from '../OverlayViewTypes';
|
|
9
|
-
import type { IOverlayLevel } from '../types';
|
|
9
|
+
import type { IOverlayEntry, IOverlayLevel } from '../types';
|
|
10
10
|
|
|
11
11
|
const LAYER_ATTRIBUTE = 'data-onekey-overlay-layer';
|
|
12
12
|
export const ENTRY_ATTRIBUTE = 'data-onekey-overlay-entry';
|
|
@@ -15,6 +15,7 @@ const APP_ROOT_ID = 'root';
|
|
|
15
15
|
|
|
16
16
|
const layerRoots = new Map<IOverlayLevel, HTMLDivElement>();
|
|
17
17
|
const pageHosts = new Map<string, HTMLDivElement>();
|
|
18
|
+
const pageHostListeners = new Map<string, Set<() => void>>();
|
|
18
19
|
const dismissRequesters = new Map<
|
|
19
20
|
string,
|
|
20
21
|
(reason: IOverlayRequestDismissReason) => void
|
|
@@ -68,16 +69,27 @@ function siblingsOutside(host: HTMLElement, root: HTMLElement) {
|
|
|
68
69
|
return siblings;
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
function inertEntriesBelow(container: ParentNode,
|
|
72
|
+
function inertEntriesBelow(container: ParentNode, top?: IOverlayEntry) {
|
|
72
73
|
container
|
|
73
74
|
.querySelectorAll<HTMLElement>(`[${ENTRY_ATTRIBUTE}]`)
|
|
74
75
|
.forEach((node) => {
|
|
75
|
-
|
|
76
|
+
const level = Number(node.dataset.overlayLevelOrder);
|
|
77
|
+
const below =
|
|
78
|
+
!!top &&
|
|
79
|
+
(level < OVERLAY_LEVEL_ORDER[top.level] ||
|
|
80
|
+
(level === OVERLAY_LEVEL_ORDER[top.level] &&
|
|
81
|
+
Number(node.dataset.stackOrder) < top.seq));
|
|
82
|
+
setInert(
|
|
83
|
+
node,
|
|
84
|
+
below ||
|
|
85
|
+
node.style.visibility === 'hidden' ||
|
|
86
|
+
node.getAttribute('aria-hidden') === 'true'
|
|
87
|
+
);
|
|
76
88
|
});
|
|
77
89
|
}
|
|
78
90
|
|
|
79
91
|
/** Everything rendered below the topmost blocking entry becomes inert. */
|
|
80
|
-
function
|
|
92
|
+
export function syncOverlayInert() {
|
|
81
93
|
const top = overlayStore.getBlockingTop();
|
|
82
94
|
const appRoot = document.getElementById(APP_ROOT_ID);
|
|
83
95
|
let nextPageInert = new Set<HTMLElement>();
|
|
@@ -86,7 +98,6 @@ function syncInert() {
|
|
|
86
98
|
if (top && pageHost && appRoot?.contains(pageHost)) {
|
|
87
99
|
setInert(appRoot, false);
|
|
88
100
|
nextPageInert = siblingsOutside(pageHost, appRoot);
|
|
89
|
-
inertEntriesBelow(pageHost, top.seq);
|
|
90
101
|
} else {
|
|
91
102
|
setInert(appRoot, !!top);
|
|
92
103
|
}
|
|
@@ -98,15 +109,23 @@ function syncInert() {
|
|
|
98
109
|
nextPageInert.forEach((element) => setInert(element, true));
|
|
99
110
|
pageInertElements = nextPageInert;
|
|
100
111
|
|
|
112
|
+
for (const [hostKey, host] of pageHosts) {
|
|
113
|
+
inertEntriesBelow(
|
|
114
|
+
host,
|
|
115
|
+
top?.scope === 'page' && top.hostKey === hostKey ? top : undefined
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
101
119
|
for (const [level, root] of layerRoots) {
|
|
102
120
|
// Global layers render above every page overlay.
|
|
103
121
|
const below =
|
|
104
122
|
top?.scope === 'global' &&
|
|
105
123
|
OVERLAY_LEVEL_ORDER[level] < OVERLAY_LEVEL_ORDER[top.level];
|
|
106
124
|
setInert(root, below);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
125
|
+
inertEntriesBelow(
|
|
126
|
+
root,
|
|
127
|
+
top?.scope === 'global' && level === top.level ? top : undefined
|
|
128
|
+
);
|
|
110
129
|
}
|
|
111
130
|
}
|
|
112
131
|
|
|
@@ -131,7 +150,7 @@ function installOverlayWebManager() {
|
|
|
131
150
|
return;
|
|
132
151
|
}
|
|
133
152
|
installed = true;
|
|
134
|
-
overlayStore.subscribe(
|
|
153
|
+
overlayStore.subscribe(syncOverlayInert);
|
|
135
154
|
document.addEventListener('keydown', onKeyDown, true);
|
|
136
155
|
}
|
|
137
156
|
|
|
@@ -159,7 +178,7 @@ export function registerOverlayDismissRequester(
|
|
|
159
178
|
|
|
160
179
|
/** Re-run after entry DOM nodes mount, since the store emits before commit. */
|
|
161
180
|
export function scheduleOverlayInertSync() {
|
|
162
|
-
requestAnimationFrame(
|
|
181
|
+
requestAnimationFrame(syncOverlayInert);
|
|
163
182
|
}
|
|
164
183
|
|
|
165
184
|
export function registerOverlayPageHost(
|
|
@@ -168,13 +187,32 @@ export function registerOverlayPageHost(
|
|
|
168
187
|
): () => void {
|
|
169
188
|
installOverlayWebManager();
|
|
170
189
|
pageHosts.set(hostKey, element);
|
|
190
|
+
pageHostListeners.get(hostKey)?.forEach((listener) => listener());
|
|
171
191
|
return () => {
|
|
172
192
|
if (pageHosts.get(hostKey) === element) {
|
|
173
193
|
pageHosts.delete(hostKey);
|
|
194
|
+
pageHostListeners.get(hostKey)?.forEach((listener) => listener());
|
|
174
195
|
}
|
|
175
196
|
};
|
|
176
197
|
}
|
|
177
198
|
|
|
199
|
+
export function subscribeOverlayPageHost(
|
|
200
|
+
hostKey: string | undefined,
|
|
201
|
+
listener: () => void
|
|
202
|
+
): () => void {
|
|
203
|
+
if (!hostKey) return () => {};
|
|
204
|
+
let listeners = pageHostListeners.get(hostKey);
|
|
205
|
+
if (!listeners) {
|
|
206
|
+
listeners = new Set();
|
|
207
|
+
pageHostListeners.set(hostKey, listeners);
|
|
208
|
+
}
|
|
209
|
+
listeners.add(listener);
|
|
210
|
+
return () => {
|
|
211
|
+
listeners.delete(listener);
|
|
212
|
+
if (listeners.size === 0) pageHostListeners.delete(hostKey);
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
178
216
|
export function getOverlayPageHost(
|
|
179
217
|
hostKey: string | undefined
|
|
180
218
|
): HTMLDivElement | undefined {
|
|
@@ -99,7 +99,7 @@ export function useKeyboardShift({
|
|
|
99
99
|
top = origin + sheet.offsetTop;
|
|
100
100
|
bottom = top + sheet.offsetHeight;
|
|
101
101
|
} else {
|
|
102
|
-
const extent = layoutExtent(contentRef.current);
|
|
102
|
+
const extent = layoutExtent(sheet ?? contentRef.current);
|
|
103
103
|
if (extent) {
|
|
104
104
|
top = origin + extent.top;
|
|
105
105
|
bottom = origin + extent.bottom;
|
package/src/web/useSheetDrag.ts
CHANGED
|
@@ -129,15 +129,36 @@ export function useSheetDrag({
|
|
|
129
129
|
);
|
|
130
130
|
};
|
|
131
131
|
|
|
132
|
+
const onPointerCancel = (event: PointerEvent) => {
|
|
133
|
+
if (!tracking || event.pointerId !== pointerId) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
tracking = false;
|
|
137
|
+
dragging = false;
|
|
138
|
+
velocity = 0;
|
|
139
|
+
if (sheet.hasPointerCapture?.(pointerId)) {
|
|
140
|
+
sheet.releasePointerCapture(pointerId);
|
|
141
|
+
}
|
|
142
|
+
pointerId = -1;
|
|
143
|
+
sheet.style.transform = '';
|
|
144
|
+
sheet.style.transition = '';
|
|
145
|
+
if (backdropRef.current) {
|
|
146
|
+
backdropRef.current.style.opacity = '1';
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
132
150
|
sheet.addEventListener('pointerdown', onPointerDown);
|
|
133
151
|
sheet.addEventListener('pointermove', onPointerMove);
|
|
134
152
|
sheet.addEventListener('pointerup', onPointerUp);
|
|
135
|
-
sheet.addEventListener('pointercancel',
|
|
153
|
+
sheet.addEventListener('pointercancel', onPointerCancel);
|
|
136
154
|
return () => {
|
|
155
|
+
if (sheet.hasPointerCapture?.(pointerId)) {
|
|
156
|
+
sheet.releasePointerCapture(pointerId);
|
|
157
|
+
}
|
|
137
158
|
sheet.removeEventListener('pointerdown', onPointerDown);
|
|
138
159
|
sheet.removeEventListener('pointermove', onPointerMove);
|
|
139
160
|
sheet.removeEventListener('pointerup', onPointerUp);
|
|
140
|
-
sheet.removeEventListener('pointercancel',
|
|
161
|
+
sheet.removeEventListener('pointercancel', onPointerCancel);
|
|
141
162
|
};
|
|
142
163
|
}, [sheetRef, backdropRef, enabled]);
|
|
143
164
|
}
|