@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
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Physical spring, identical to Reanimated / Tamagui:
|
|
3
|
+
* mass * x'' + damping * x' + stiffness * x = 0.
|
|
4
|
+
* Springs with a damping ratio above 1 are valid and common (`quick`).
|
|
5
|
+
*/
|
|
6
|
+
export interface IOverlaySpring {
|
|
7
|
+
mass: number;
|
|
8
|
+
stiffness: number;
|
|
9
|
+
damping: number;
|
|
10
|
+
/** Clamp the value at the target instead of overshooting. */
|
|
11
|
+
overshootClamping?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type IOverlayCubicBezier = readonly [number, number, number, number];
|
|
15
|
+
|
|
16
|
+
export interface IOverlayTiming {
|
|
17
|
+
durationMs: number;
|
|
18
|
+
easing: IOverlayCubicBezier;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type IOverlayMotion =
|
|
22
|
+
| { type: 'spring'; spring: IOverlaySpring }
|
|
23
|
+
| { type: 'timing'; timing: IOverlayTiming };
|
|
24
|
+
|
|
25
|
+
/** Named motion tokens; each platform maps them to its own curve. */
|
|
26
|
+
export type IOverlayMotionPreset =
|
|
27
|
+
| 'quick'
|
|
28
|
+
| 'popoverQuick'
|
|
29
|
+
| 'smooth'
|
|
30
|
+
| 'fast'
|
|
31
|
+
| 'medium'
|
|
32
|
+
| 'slow'
|
|
33
|
+
| 'toastSlide'
|
|
34
|
+
| 'lockFade';
|
|
35
|
+
|
|
36
|
+
export type IOverlayMotionInput = IOverlayMotionPreset | IOverlayMotion;
|
|
37
|
+
|
|
38
|
+
export type IOverlayEdge = 'top' | 'bottom' | 'left' | 'right';
|
|
39
|
+
|
|
40
|
+
export interface IOverlayTransition {
|
|
41
|
+
/** `none` shows / hides immediately; content may animate itself. */
|
|
42
|
+
type: 'none' | 'fade' | 'slide' | 'scale';
|
|
43
|
+
/** `slide`: the edge the overlay travels from (enter) or to (exit). */
|
|
44
|
+
edge?: IOverlayEdge;
|
|
45
|
+
/** `slide`: travel in points; defaults to off-screen. */
|
|
46
|
+
distance?: number;
|
|
47
|
+
/** `scale`: start (enter) or end (exit) scale. */
|
|
48
|
+
scale?: number;
|
|
49
|
+
/** `scale`: extra translate along Y in points, e.g. -20 for custom toasts. */
|
|
50
|
+
offsetY?: number;
|
|
51
|
+
/** Fade opacity alongside slide / scale. Defaults to true for scale. */
|
|
52
|
+
fade?: boolean;
|
|
53
|
+
/** `scale`: `anchor` scales from the resolved anchor placement. */
|
|
54
|
+
origin?: 'center' | 'anchor';
|
|
55
|
+
motion?: IOverlayMotionInput;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface IOverlayAnimation {
|
|
59
|
+
enter?: IOverlayTransition;
|
|
60
|
+
/** Defaults to the reverse of `enter`. */
|
|
61
|
+
exit?: IOverlayTransition;
|
|
62
|
+
/** Backdrop fade; runs on the same clock as the content by default. */
|
|
63
|
+
backdrop?: { motion?: IOverlayMotionInput };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type IOverlayPresentation =
|
|
67
|
+
| 'sheet'
|
|
68
|
+
| 'center'
|
|
69
|
+
| 'fullscreen'
|
|
70
|
+
| 'toast'
|
|
71
|
+
| 'anchored';
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export * from './types';
|
|
2
|
+
export {
|
|
3
|
+
OVERLAY_LEVELS,
|
|
4
|
+
OVERLAY_LEVEL_ORDER,
|
|
5
|
+
OVERLAY_WEB_PAGE_Z_INDEX_BASE,
|
|
6
|
+
OVERLAY_WEB_Z_INDEX_BASE,
|
|
7
|
+
isBlockingLevel,
|
|
8
|
+
} from './OverlayLevels';
|
|
9
|
+
export { OverlayRequestError } from './OverlayErrors';
|
|
10
|
+
export { OverlayStore, findStatusBarOwner, overlayStore } from './OverlayStore';
|
|
11
|
+
export type {
|
|
12
|
+
IOverlayBackResolution,
|
|
13
|
+
IOverlayEntryHandlers,
|
|
14
|
+
IOverlayStoreOptions,
|
|
15
|
+
} from './OverlayStore';
|
|
16
|
+
export * from './animation/types';
|
|
17
|
+
export {
|
|
18
|
+
NATIVE_MOTION_PRESETS,
|
|
19
|
+
WEB_MOTION_PRESETS,
|
|
20
|
+
} from './animation/motionPresets';
|
|
21
|
+
export { OVERLAY_MOTION_PRESETS } from './animation/platformMotionPresets';
|
|
22
|
+
export {
|
|
23
|
+
DEFAULT_OVERLAY_ANIMATIONS,
|
|
24
|
+
resolveOverlayAnimation,
|
|
25
|
+
} from './animation/resolveAnimation';
|
|
26
|
+
export type {
|
|
27
|
+
IResolvedOverlayAnimation,
|
|
28
|
+
IResolvedOverlayTransition,
|
|
29
|
+
} from './animation/resolveAnimation';
|
|
30
|
+
export {
|
|
31
|
+
springDampingRatio,
|
|
32
|
+
springNaturalFrequency,
|
|
33
|
+
springProgressAt,
|
|
34
|
+
springSettleTimeMs,
|
|
35
|
+
springToAndroidSpringForce,
|
|
36
|
+
springToCssLinear,
|
|
37
|
+
} from './animation/spring';
|
|
38
|
+
export { OverlayView } from './OverlayView';
|
|
39
|
+
export {
|
|
40
|
+
useEnclosingOverlayLevel,
|
|
41
|
+
useNestedOverlayLevel,
|
|
42
|
+
} from './OverlayLevelContext';
|
|
43
|
+
export type {
|
|
44
|
+
IOverlayBackdrop,
|
|
45
|
+
IOverlayRequestDismissReason,
|
|
46
|
+
IOverlaySheetOptions,
|
|
47
|
+
IOverlayViewProps,
|
|
48
|
+
} from './OverlayViewTypes';
|
|
49
|
+
export {
|
|
50
|
+
OverlayPageHostScope,
|
|
51
|
+
OverlayPageOwnerScope,
|
|
52
|
+
useOverlayPageScope,
|
|
53
|
+
} from './OverlayPageScope';
|
|
54
|
+
export type { IOverlayPageScope } from './OverlayPageScope';
|
|
55
|
+
export { OverlayPageHost } from './OverlayPageHost';
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export type IOverlayScope = 'global' | 'page';
|
|
2
|
+
|
|
3
|
+
export type IOverlayLevel =
|
|
4
|
+
| 'modal'
|
|
5
|
+
| 'hardware'
|
|
6
|
+
| 'secure'
|
|
7
|
+
| 'toast'
|
|
8
|
+
| 'lock'
|
|
9
|
+
| 'debug';
|
|
10
|
+
|
|
11
|
+
export type IOverlayStrategy = 'stack' | 'queue' | 'replace';
|
|
12
|
+
|
|
13
|
+
export type IOverlayPhase = 'queued' | 'active' | 'closing';
|
|
14
|
+
|
|
15
|
+
export type IOverlayDismissReason =
|
|
16
|
+
| 'programmatic'
|
|
17
|
+
| 'back'
|
|
18
|
+
| 'backdrop'
|
|
19
|
+
| 'pan'
|
|
20
|
+
| 'replaced'
|
|
21
|
+
| 'page-removed'
|
|
22
|
+
| 'system';
|
|
23
|
+
|
|
24
|
+
/** `light`: light content for dark overlays; `dark`: dark content. */
|
|
25
|
+
export type IOverlayStatusBarStyle = 'light' | 'dark';
|
|
26
|
+
|
|
27
|
+
export interface IOverlayRequest {
|
|
28
|
+
/** Stable id; generated when omitted. Re-requesting an existing id is a no-op. */
|
|
29
|
+
id?: string;
|
|
30
|
+
scope?: IOverlayScope;
|
|
31
|
+
level?: IOverlayLevel;
|
|
32
|
+
strategy?: IOverlayStrategy;
|
|
33
|
+
/** Queue ordering only: higher runs first, ties keep request order. */
|
|
34
|
+
priority?: number;
|
|
35
|
+
/**
|
|
36
|
+
* `replace` only: entries in the same lane with this key are replaced.
|
|
37
|
+
* When omitted, the topmost active entry of the lane is replaced.
|
|
38
|
+
*/
|
|
39
|
+
replaceKey?: string;
|
|
40
|
+
/** Required for `page` scope: the root-route host that renders the entry. */
|
|
41
|
+
hostKey?: string;
|
|
42
|
+
/** Required for `page` scope: the nearest page that owns the entry. */
|
|
43
|
+
ownerKey?: string;
|
|
44
|
+
/** Receives back / Escape. Defaults to true for blocking levels. */
|
|
45
|
+
dismissible?: boolean;
|
|
46
|
+
/** Blocks interaction with everything below. Defaults by level. */
|
|
47
|
+
blocking?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Status bar content while this is the topmost styled overlay; unset
|
|
50
|
+
* leaves the status bar alone.
|
|
51
|
+
*/
|
|
52
|
+
statusBarStyle?: IOverlayStatusBarStyle;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface IOverlayEntry {
|
|
56
|
+
id: string;
|
|
57
|
+
scope: IOverlayScope;
|
|
58
|
+
level: IOverlayLevel;
|
|
59
|
+
strategy: IOverlayStrategy;
|
|
60
|
+
priority: number;
|
|
61
|
+
replaceKey: string | undefined;
|
|
62
|
+
hostKey: string | undefined;
|
|
63
|
+
ownerKey: string | undefined;
|
|
64
|
+
dismissible: boolean;
|
|
65
|
+
blocking: boolean;
|
|
66
|
+
statusBarStyle: IOverlayStatusBarStyle | undefined;
|
|
67
|
+
phase: IOverlayPhase;
|
|
68
|
+
/** True while the owning page is covered or detached; page scope only. */
|
|
69
|
+
suspended: boolean;
|
|
70
|
+
dismissReason: IOverlayDismissReason | undefined;
|
|
71
|
+
/** Monotonic request order. */
|
|
72
|
+
seq: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface IOverlaySnapshot {
|
|
76
|
+
/** Every non-queued entry in bottom-to-top render order. */
|
|
77
|
+
entries: readonly IOverlayEntry[];
|
|
78
|
+
queued: readonly IOverlayEntry[];
|
|
79
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useId,
|
|
5
|
+
useMemo,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
useSyncExternalStore,
|
|
9
|
+
} from 'react';
|
|
10
|
+
|
|
11
|
+
import { OVERLAY_MOTION_PRESETS } from './animation/platformMotionPresets';
|
|
12
|
+
import { resolveOverlayAnimation } from './animation/resolveAnimation';
|
|
13
|
+
import { useOverlayPageScope } from './OverlayPageScope';
|
|
14
|
+
import { findStatusBarOwner, overlayStore } from './OverlayStore';
|
|
15
|
+
|
|
16
|
+
import type { IResolvedOverlayAnimation } from './animation/resolveAnimation';
|
|
17
|
+
import type {
|
|
18
|
+
IOverlayRequestDismissReason,
|
|
19
|
+
IOverlayViewProps,
|
|
20
|
+
} from './OverlayViewTypes';
|
|
21
|
+
import type { IOverlayEntry } from './types';
|
|
22
|
+
|
|
23
|
+
export interface IOverlayController {
|
|
24
|
+
entry: IOverlayEntry | undefined;
|
|
25
|
+
/** Render the content (entry is active or closing). */
|
|
26
|
+
mounted: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Ask the host to show the content. Page entries stay presented while
|
|
29
|
+
* suspended; the page host hides them instead (the owner may be frozen).
|
|
30
|
+
*/
|
|
31
|
+
presented: boolean;
|
|
32
|
+
scope: 'global' | 'page';
|
|
33
|
+
/** This entry's `statusBarStyle` currently drives the status bar. */
|
|
34
|
+
ownsStatusBar: boolean;
|
|
35
|
+
hostKey: string | undefined;
|
|
36
|
+
ownerKey: string | undefined;
|
|
37
|
+
animation: IResolvedOverlayAnimation;
|
|
38
|
+
onHostPresented: () => void;
|
|
39
|
+
onHostDismissed: () => void;
|
|
40
|
+
onHostRequestDismiss: (reason: IOverlayRequestDismissReason) => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function useLatest<T>(value: T) {
|
|
44
|
+
const ref = useRef(value);
|
|
45
|
+
ref.current = value;
|
|
46
|
+
return ref;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Bridges a declarative overlay to the shared `overlayStore`: requests an
|
|
51
|
+
* entry while `visible`, follows the store's phase, and finalizes the entry
|
|
52
|
+
* once the host reports the end of the exit animation.
|
|
53
|
+
*/
|
|
54
|
+
export function useOverlayController(
|
|
55
|
+
props: IOverlayViewProps
|
|
56
|
+
): IOverlayController {
|
|
57
|
+
const {
|
|
58
|
+
visible,
|
|
59
|
+
level,
|
|
60
|
+
strategy,
|
|
61
|
+
priority,
|
|
62
|
+
replaceKey,
|
|
63
|
+
blocking,
|
|
64
|
+
dismissOnBackPress,
|
|
65
|
+
presentation = 'center',
|
|
66
|
+
animation,
|
|
67
|
+
statusBarStyle,
|
|
68
|
+
} = props;
|
|
69
|
+
const pageScope = useOverlayPageScope();
|
|
70
|
+
const hostKey = props.hostKey ?? pageScope.hostKey;
|
|
71
|
+
const ownerKey = props.ownerKey ?? pageScope.ownerKey;
|
|
72
|
+
// Without a host or owner there is nowhere to anchor a page overlay.
|
|
73
|
+
const scope =
|
|
74
|
+
props.scope === 'page' && hostKey && ownerKey ? 'page' : 'global';
|
|
75
|
+
const baseId = useId();
|
|
76
|
+
const cycleRef = useRef(0);
|
|
77
|
+
const [entryId, setEntryId] = useState<string | undefined>();
|
|
78
|
+
// Set when the store closed the entry (replace, back) while `visible` is
|
|
79
|
+
// still true, so the effect below does not immediately reopen it.
|
|
80
|
+
const closedByStoreRef = useRef(false);
|
|
81
|
+
const propsRef = useLatest(props);
|
|
82
|
+
|
|
83
|
+
const snapshot = useSyncExternalStore(
|
|
84
|
+
overlayStore.subscribe,
|
|
85
|
+
overlayStore.getSnapshot,
|
|
86
|
+
overlayStore.getSnapshot
|
|
87
|
+
);
|
|
88
|
+
const entry = useMemo(
|
|
89
|
+
() =>
|
|
90
|
+
entryId
|
|
91
|
+
? snapshot.entries.find((e) => e.id === entryId) ??
|
|
92
|
+
snapshot.queued.find((e) => e.id === entryId)
|
|
93
|
+
: undefined,
|
|
94
|
+
[entryId, snapshot]
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (!visible) {
|
|
99
|
+
closedByStoreRef.current = false;
|
|
100
|
+
if (entryId) {
|
|
101
|
+
overlayStore.dismiss(entryId, 'programmatic');
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (entryId || closedByStoreRef.current) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
cycleRef.current += 1;
|
|
109
|
+
const id = `overlay-view${baseId}${cycleRef.current}`;
|
|
110
|
+
overlayStore.request(
|
|
111
|
+
{
|
|
112
|
+
id,
|
|
113
|
+
scope,
|
|
114
|
+
hostKey,
|
|
115
|
+
ownerKey,
|
|
116
|
+
level,
|
|
117
|
+
strategy,
|
|
118
|
+
priority,
|
|
119
|
+
replaceKey,
|
|
120
|
+
blocking,
|
|
121
|
+
dismissible: dismissOnBackPress,
|
|
122
|
+
statusBarStyle,
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
onRemoved: (reason) => {
|
|
126
|
+
setEntryId((current) => (current === id ? undefined : current));
|
|
127
|
+
if (reason !== 'programmatic') {
|
|
128
|
+
closedByStoreRef.current = true;
|
|
129
|
+
}
|
|
130
|
+
propsRef.current.onClose?.(reason);
|
|
131
|
+
},
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
setEntryId(id);
|
|
135
|
+
// Level / strategy changes apply to the next open cycle only.
|
|
136
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
137
|
+
}, [visible, entryId]);
|
|
138
|
+
|
|
139
|
+
useEffect(
|
|
140
|
+
() => () => {
|
|
141
|
+
if (entryId) {
|
|
142
|
+
overlayStore.dismiss(entryId, 'system');
|
|
143
|
+
overlayStore.finalize(entryId);
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
[entryId]
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const resolvedAnimation = useMemo(
|
|
150
|
+
() =>
|
|
151
|
+
resolveOverlayAnimation(presentation, animation, OVERLAY_MOTION_PRESETS),
|
|
152
|
+
[presentation, animation]
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const onHostPresented = useCallback(() => {
|
|
156
|
+
propsRef.current.onPresented?.();
|
|
157
|
+
}, [propsRef]);
|
|
158
|
+
|
|
159
|
+
const onHostDismissed = useCallback(() => {
|
|
160
|
+
if (!entryId) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const phase = overlayStore.getEntry(entryId)?.phase;
|
|
164
|
+
if (phase === 'active') {
|
|
165
|
+
// Native closed it on its own, e.g. a lower sheet in the same level
|
|
166
|
+
// window was dismissed and UIKit took the ones above with it.
|
|
167
|
+
overlayStore.dismiss(entryId, 'system');
|
|
168
|
+
}
|
|
169
|
+
overlayStore.finalize(entryId);
|
|
170
|
+
}, [entryId]);
|
|
171
|
+
|
|
172
|
+
const onHostRequestDismiss = useCallback(
|
|
173
|
+
(reason: IOverlayRequestDismissReason) => {
|
|
174
|
+
const handler = propsRef.current.onRequestDismiss;
|
|
175
|
+
handler?.(reason);
|
|
176
|
+
if (entryId && (!handler || reason === 'pan')) {
|
|
177
|
+
overlayStore.dismiss(entryId, reason);
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
[entryId, propsRef]
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
entry,
|
|
185
|
+
mounted: !!entry && entry.phase !== 'queued',
|
|
186
|
+
presented:
|
|
187
|
+
!!entry &&
|
|
188
|
+
entry.phase === 'active' &&
|
|
189
|
+
(scope === 'page' || !entry.suspended),
|
|
190
|
+
scope,
|
|
191
|
+
ownsStatusBar: !!entryId && findStatusBarOwner(snapshot)?.id === entryId,
|
|
192
|
+
hostKey: scope === 'page' ? hostKey : undefined,
|
|
193
|
+
ownerKey: scope === 'page' ? ownerKey : undefined,
|
|
194
|
+
animation: resolvedAnimation,
|
|
195
|
+
onHostPresented,
|
|
196
|
+
onHostDismissed,
|
|
197
|
+
onHostRequestDismiss,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useMemo, useSyncExternalStore } from 'react';
|
|
2
|
+
|
|
3
|
+
import { overlayStore } from './OverlayStore';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Owners whose page overlays in `hostKey` must be hidden. The page host
|
|
7
|
+
* applies this natively: the owning page may be frozen (freezeOnBlur) and
|
|
8
|
+
* cannot re-render its own overlays.
|
|
9
|
+
*/
|
|
10
|
+
export function useSuspendedPageOwners(hostKey: string): string[] {
|
|
11
|
+
const snapshot = useSyncExternalStore(
|
|
12
|
+
overlayStore.subscribe,
|
|
13
|
+
overlayStore.getSnapshot,
|
|
14
|
+
overlayStore.getSnapshot
|
|
15
|
+
);
|
|
16
|
+
const joined = snapshot.entries
|
|
17
|
+
.filter((e) => e.scope === 'page' && e.hostKey === hostKey && e.suspended)
|
|
18
|
+
.map((e) => e.ownerKey ?? '')
|
|
19
|
+
.filter(Boolean)
|
|
20
|
+
.toSorted()
|
|
21
|
+
.join('\n');
|
|
22
|
+
return useMemo(
|
|
23
|
+
() => (joined ? [...new Set(joined.split('\n'))] : []),
|
|
24
|
+
[joined]
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { springToCssLinear } from '../animation/spring';
|
|
2
|
+
|
|
3
|
+
import type { IResolvedOverlayTransition } from '../animation/resolveAnimation';
|
|
4
|
+
import type { IOverlayMotion } from '../animation/types';
|
|
5
|
+
|
|
6
|
+
export interface IContentExtent {
|
|
7
|
+
top: number;
|
|
8
|
+
bottom: number;
|
|
9
|
+
left: number;
|
|
10
|
+
right: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Union of the rendered children rects; the content wrapper is full-window. */
|
|
14
|
+
export function measureContentExtent(container: HTMLElement): IContentExtent {
|
|
15
|
+
const rects = Array.from(container.children).map((child) =>
|
|
16
|
+
child.getBoundingClientRect()
|
|
17
|
+
);
|
|
18
|
+
if (rects.length === 0) {
|
|
19
|
+
return {
|
|
20
|
+
top: 0,
|
|
21
|
+
bottom: window.innerHeight,
|
|
22
|
+
left: 0,
|
|
23
|
+
right: window.innerWidth,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
top: Math.min(...rects.map((r) => r.top)),
|
|
28
|
+
bottom: Math.max(...rects.map((r) => r.bottom)),
|
|
29
|
+
left: Math.min(...rects.map((r) => r.left)),
|
|
30
|
+
right: Math.max(...rects.map((r) => r.right)),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function toTiming(motion: IOverlayMotion): {
|
|
35
|
+
duration: number;
|
|
36
|
+
easing: string;
|
|
37
|
+
} {
|
|
38
|
+
if (motion.type === 'timing') {
|
|
39
|
+
const [x1, y1, x2, y2] = motion.timing.easing;
|
|
40
|
+
return {
|
|
41
|
+
duration: motion.timing.durationMs,
|
|
42
|
+
easing: `cubic-bezier(${x1}, ${y1}, ${x2}, ${y2})`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const { durationMs, easing } = springToCssLinear(motion.spring);
|
|
46
|
+
return { duration: durationMs, easing };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function prefersReducedMotion(): boolean {
|
|
50
|
+
return (
|
|
51
|
+
typeof globalThis.matchMedia === 'function' &&
|
|
52
|
+
globalThis.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The off-stage keyframe for a transition: where enter starts / exit ends. */
|
|
57
|
+
function hiddenKeyframe(
|
|
58
|
+
transition: IResolvedOverlayTransition,
|
|
59
|
+
extent: IContentExtent
|
|
60
|
+
): Keyframe {
|
|
61
|
+
const frame: Keyframe = {};
|
|
62
|
+
if (transition.fade || transition.type === 'fade') {
|
|
63
|
+
frame.opacity = 0;
|
|
64
|
+
}
|
|
65
|
+
if (transition.type === 'slide') {
|
|
66
|
+
const edge = transition.edge ?? 'bottom';
|
|
67
|
+
const distance =
|
|
68
|
+
transition.distance ??
|
|
69
|
+
{
|
|
70
|
+
top: extent.bottom,
|
|
71
|
+
bottom: window.innerHeight - extent.top,
|
|
72
|
+
left: extent.right,
|
|
73
|
+
right: window.innerWidth - extent.left,
|
|
74
|
+
}[edge];
|
|
75
|
+
frame.transform = {
|
|
76
|
+
top: `translate3d(0, ${-distance}px, 0)`,
|
|
77
|
+
bottom: `translate3d(0, ${distance}px, 0)`,
|
|
78
|
+
left: `translate3d(${-distance}px, 0, 0)`,
|
|
79
|
+
right: `translate3d(${distance}px, 0, 0)`,
|
|
80
|
+
}[edge];
|
|
81
|
+
} else if (transition.type === 'scale') {
|
|
82
|
+
frame.transform = `translate3d(0, ${transition.offsetY ?? 0}px, 0) scale(${
|
|
83
|
+
transition.scale ?? 0.95
|
|
84
|
+
})`;
|
|
85
|
+
}
|
|
86
|
+
return frame;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const VISIBLE_KEYFRAME: Keyframe = {
|
|
90
|
+
opacity: 1,
|
|
91
|
+
transform: 'translate3d(0, 0, 0) scale(1)',
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Runs an enter (`direction: 'in'`) or exit transition on `element`. The
|
|
96
|
+
* slide distance and scale origin come from the children of `contentRoot`,
|
|
97
|
+
* because the content wrapper itself always fills the window.
|
|
98
|
+
*/
|
|
99
|
+
export function animateTransition(
|
|
100
|
+
element: HTMLElement,
|
|
101
|
+
contentRoot: HTMLElement,
|
|
102
|
+
transition: IResolvedOverlayTransition,
|
|
103
|
+
direction: 'in' | 'out'
|
|
104
|
+
): Animation | undefined {
|
|
105
|
+
if (transition.type === 'none') {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
const effective: IResolvedOverlayTransition = prefersReducedMotion()
|
|
109
|
+
? { ...transition, type: 'fade', fade: true }
|
|
110
|
+
: transition;
|
|
111
|
+
const extent = measureContentExtent(contentRoot);
|
|
112
|
+
element.style.transformOrigin = `${(extent.left + extent.right) / 2}px ${
|
|
113
|
+
(extent.top + extent.bottom) / 2
|
|
114
|
+
}px`;
|
|
115
|
+
const hidden = hiddenKeyframe(effective, extent);
|
|
116
|
+
const visible: Keyframe = {};
|
|
117
|
+
Object.keys(hidden).forEach((key) => {
|
|
118
|
+
visible[key] = VISIBLE_KEYFRAME[key];
|
|
119
|
+
});
|
|
120
|
+
const keyframes = direction === 'in' ? [hidden, visible] : [visible, hidden];
|
|
121
|
+
return element.animate(keyframes, {
|
|
122
|
+
...toTiming(effective.motion),
|
|
123
|
+
fill: 'forwards',
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function animateBackdrop(
|
|
128
|
+
element: HTMLElement,
|
|
129
|
+
motion: IOverlayMotion,
|
|
130
|
+
direction: 'in' | 'out'
|
|
131
|
+
): Animation {
|
|
132
|
+
const keyframes =
|
|
133
|
+
direction === 'in'
|
|
134
|
+
? [{ opacity: 0 }, { opacity: 1 }]
|
|
135
|
+
: [{ opacity: 1 }, { opacity: 0 }];
|
|
136
|
+
return element.animate(keyframes, { ...toTiming(motion), fill: 'forwards' });
|
|
137
|
+
}
|