@onekeyfe/react-native-native-sheet 3.0.128 → 3.0.130
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -8
- package/android/src/main/java/com/onekey/nativesheet/NativeSheetView.kt +610 -41
- package/ios/NativeSheetContainerView.swift +355 -45
- package/lib/module/NativeSheetHeight.js +43 -0
- package/lib/module/NativeSheetHeight.js.map +1 -0
- package/lib/module/NativeSheetRegistry.js +176 -0
- package/lib/module/NativeSheetRegistry.js.map +1 -0
- package/lib/module/index.js +180 -20
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/NativeSheetHeight.d.ts +20 -0
- package/lib/typescript/src/NativeSheetHeight.d.ts.map +1 -0
- package/lib/typescript/src/NativeSheetRegistry.d.ts +22 -0
- package/lib/typescript/src/NativeSheetRegistry.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +38 -12
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/NativeSheetHeight.ts +66 -0
- package/src/NativeSheetRegistry.ts +202 -0
- package/src/index.tsx +294 -19
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export interface ResolveNativeSheetHeightOptions {
|
|
2
|
+
currentHeight?: number;
|
|
3
|
+
explicitHeight?: number;
|
|
4
|
+
measuredHeight?: number;
|
|
5
|
+
maxHeight: number;
|
|
6
|
+
shouldAutoMeasure: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ResolveNativeSheetLayoutConstraintsOptions {
|
|
10
|
+
lockedHeight?: number;
|
|
11
|
+
maxHeight: number;
|
|
12
|
+
shouldAutoMeasure: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface NativeSheetLayoutConstraints {
|
|
16
|
+
hostHeight?: number;
|
|
17
|
+
hostMaxHeight?: number;
|
|
18
|
+
shouldFillHost: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function resolveNativeSheetLayoutConstraints({
|
|
22
|
+
lockedHeight,
|
|
23
|
+
maxHeight,
|
|
24
|
+
shouldAutoMeasure,
|
|
25
|
+
}: ResolveNativeSheetLayoutConstraintsOptions): NativeSheetLayoutConstraints {
|
|
26
|
+
if (shouldAutoMeasure) {
|
|
27
|
+
return {
|
|
28
|
+
hostMaxHeight: maxHeight,
|
|
29
|
+
shouldFillHost: false,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
if (lockedHeight !== undefined) {
|
|
33
|
+
return {
|
|
34
|
+
hostHeight: lockedHeight,
|
|
35
|
+
shouldFillHost: true,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
hostMaxHeight: maxHeight,
|
|
40
|
+
shouldFillHost: false,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function resolveNativeSheetHeight({
|
|
45
|
+
currentHeight,
|
|
46
|
+
explicitHeight,
|
|
47
|
+
measuredHeight,
|
|
48
|
+
maxHeight,
|
|
49
|
+
shouldAutoMeasure,
|
|
50
|
+
}: ResolveNativeSheetHeightOptions): number | undefined {
|
|
51
|
+
if (explicitHeight !== undefined && explicitHeight > 0) {
|
|
52
|
+
return Math.min(explicitHeight, maxHeight);
|
|
53
|
+
}
|
|
54
|
+
if (
|
|
55
|
+
explicitHeight === undefined &&
|
|
56
|
+
shouldAutoMeasure &&
|
|
57
|
+
measuredHeight !== undefined &&
|
|
58
|
+
measuredHeight > 0
|
|
59
|
+
) {
|
|
60
|
+
return Math.min(measuredHeight, maxHeight);
|
|
61
|
+
}
|
|
62
|
+
if (currentHeight !== undefined && currentHeight > maxHeight) {
|
|
63
|
+
return maxHeight;
|
|
64
|
+
}
|
|
65
|
+
return currentHeight;
|
|
66
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { NativeSheetDismissReason, NativeSheetShowOptions } from './index';
|
|
4
|
+
|
|
5
|
+
export interface NativeSheetRegistryEntry {
|
|
6
|
+
id: number;
|
|
7
|
+
open: boolean;
|
|
8
|
+
presentationRequested: boolean;
|
|
9
|
+
content: ReactNode;
|
|
10
|
+
options: NativeSheetShowOptions;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type RegistryListener = () => void;
|
|
14
|
+
|
|
15
|
+
const PRESENTATION_CLOSE_FALLBACK_MS = 5_000;
|
|
16
|
+
|
|
17
|
+
let nextId = 1;
|
|
18
|
+
let entries: readonly NativeSheetRegistryEntry[] = [];
|
|
19
|
+
const listeners = new Set<RegistryListener>();
|
|
20
|
+
let securityFallbackTimer: ReturnType<typeof setTimeout> | undefined;
|
|
21
|
+
const closeFallbackTimers = new Map<number, ReturnType<typeof setTimeout>>();
|
|
22
|
+
let registryBlocked = false;
|
|
23
|
+
|
|
24
|
+
function clearCloseFallbackTimer(id: number) {
|
|
25
|
+
const timer = closeFallbackTimers.get(id);
|
|
26
|
+
if (timer) {
|
|
27
|
+
clearTimeout(timer);
|
|
28
|
+
closeFallbackTimers.delete(id);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function emitChange() {
|
|
33
|
+
listeners.forEach((listener) => listener());
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function subscribeNativeSheetRegistry(listener: RegistryListener) {
|
|
37
|
+
listeners.add(listener);
|
|
38
|
+
return () => listeners.delete(listener);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getNativeSheetRegistrySnapshot() {
|
|
42
|
+
return entries;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function addNativeSheetRegistryEntry(
|
|
46
|
+
options: NativeSheetShowOptions,
|
|
47
|
+
content: ReactNode
|
|
48
|
+
) {
|
|
49
|
+
const id = nextId;
|
|
50
|
+
nextId += 1;
|
|
51
|
+
if (registryBlocked) {
|
|
52
|
+
options.onOpenChange?.(true);
|
|
53
|
+
options.onOpenChange?.(false);
|
|
54
|
+
options.onDismiss?.('security');
|
|
55
|
+
options.onAnimationComplete?.({ open: false });
|
|
56
|
+
return id;
|
|
57
|
+
}
|
|
58
|
+
const entry: NativeSheetRegistryEntry = {
|
|
59
|
+
id,
|
|
60
|
+
open: true,
|
|
61
|
+
presentationRequested: false,
|
|
62
|
+
content,
|
|
63
|
+
options,
|
|
64
|
+
};
|
|
65
|
+
entries = [...entries, entry];
|
|
66
|
+
options.onOpenChange?.(true);
|
|
67
|
+
emitChange();
|
|
68
|
+
return id;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function closeNativeSheetRegistryEntry(id: number) {
|
|
72
|
+
const entry = entries.find((item) => item.id === id);
|
|
73
|
+
if (!entry || !entry.open) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
entry.options.onOpenChange?.(false);
|
|
77
|
+
if (!entry.presentationRequested) {
|
|
78
|
+
entries = entries.filter((item) => item.id !== id);
|
|
79
|
+
entry.options.onDismiss?.('programmatic');
|
|
80
|
+
entry.options.onAnimationComplete?.({ open: false });
|
|
81
|
+
} else {
|
|
82
|
+
entries = entries.map((item) =>
|
|
83
|
+
item.id === id ? { ...item, open: false } : item
|
|
84
|
+
);
|
|
85
|
+
if (!closeFallbackTimers.has(id)) {
|
|
86
|
+
closeFallbackTimers.set(
|
|
87
|
+
id,
|
|
88
|
+
setTimeout(() => {
|
|
89
|
+
closeFallbackTimers.delete(id);
|
|
90
|
+
const pendingEntry = entries.find((item) => item.id === id);
|
|
91
|
+
if (!pendingEntry) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
finishNativeSheetRegistryEntry(id, 'programmatic');
|
|
95
|
+
pendingEntry.options.onAnimationComplete?.({ open: false });
|
|
96
|
+
}, PRESENTATION_CLOSE_FALLBACK_MS)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
emitChange();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function markNativeSheetRegistryEntryPresentationRequested(id: number) {
|
|
104
|
+
entries = entries.map((entry) =>
|
|
105
|
+
entry.id === id && !entry.presentationRequested
|
|
106
|
+
? { ...entry, presentationRequested: true }
|
|
107
|
+
: entry
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function finishNativeSheetRegistryEntry(
|
|
112
|
+
id: number,
|
|
113
|
+
reason: NativeSheetDismissReason
|
|
114
|
+
) {
|
|
115
|
+
const entry = entries.find((item) => item.id === id);
|
|
116
|
+
if (!entry) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
clearCloseFallbackTimer(id);
|
|
120
|
+
if (entry.open) {
|
|
121
|
+
entry.options.onOpenChange?.(false);
|
|
122
|
+
}
|
|
123
|
+
entries = entries.filter((item) => item.id !== id);
|
|
124
|
+
entry.options.onDismiss?.(reason);
|
|
125
|
+
emitChange();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function finishAllNativeSheetRegistryEntries(
|
|
129
|
+
reason: NativeSheetDismissReason
|
|
130
|
+
) {
|
|
131
|
+
const currentEntries = entries;
|
|
132
|
+
if (!currentEntries.length) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
entries = [];
|
|
136
|
+
currentEntries.forEach((entry) => {
|
|
137
|
+
clearCloseFallbackTimer(entry.id);
|
|
138
|
+
if (entry.open) {
|
|
139
|
+
entry.options.onOpenChange?.(false);
|
|
140
|
+
}
|
|
141
|
+
entry.options.onDismiss?.(reason);
|
|
142
|
+
entry.options.onAnimationComplete?.({ open: false });
|
|
143
|
+
});
|
|
144
|
+
emitChange();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function requestSecurityDismissAllNativeSheets() {
|
|
148
|
+
if (!entries.length) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const unpresentedEntries = entries.filter(
|
|
152
|
+
(entry) => !entry.presentationRequested
|
|
153
|
+
);
|
|
154
|
+
const presentedEntries = entries.filter(
|
|
155
|
+
(entry) => entry.presentationRequested
|
|
156
|
+
);
|
|
157
|
+
unpresentedEntries.forEach((entry) => {
|
|
158
|
+
if (entry.open) {
|
|
159
|
+
entry.options.onOpenChange?.(false);
|
|
160
|
+
}
|
|
161
|
+
entry.options.onDismiss?.('security');
|
|
162
|
+
entry.options.onAnimationComplete?.({ open: false });
|
|
163
|
+
});
|
|
164
|
+
entries = presentedEntries.map((entry) => {
|
|
165
|
+
if (entry.open) {
|
|
166
|
+
entry.options.onOpenChange?.(false);
|
|
167
|
+
return { ...entry, open: false };
|
|
168
|
+
}
|
|
169
|
+
return entry;
|
|
170
|
+
});
|
|
171
|
+
emitChange();
|
|
172
|
+
if (!entries.length) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (securityFallbackTimer) {
|
|
176
|
+
clearTimeout(securityFallbackTimer);
|
|
177
|
+
}
|
|
178
|
+
securityFallbackTimer = setTimeout(() => {
|
|
179
|
+
securityFallbackTimer = undefined;
|
|
180
|
+
finishAllNativeSheetRegistryEntries('security');
|
|
181
|
+
}, 500);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function setNativeSheetRegistryBlocked(blocked: boolean) {
|
|
185
|
+
registryBlocked = blocked;
|
|
186
|
+
if (blocked) {
|
|
187
|
+
requestSecurityDismissAllNativeSheets();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function resetNativeSheetRegistryForTests() {
|
|
192
|
+
if (securityFallbackTimer) {
|
|
193
|
+
clearTimeout(securityFallbackTimer);
|
|
194
|
+
securityFallbackTimer = undefined;
|
|
195
|
+
}
|
|
196
|
+
closeFallbackTimers.forEach(clearTimeout);
|
|
197
|
+
closeFallbackTimers.clear();
|
|
198
|
+
entries = [];
|
|
199
|
+
nextId = 1;
|
|
200
|
+
registryBlocked = false;
|
|
201
|
+
listeners.clear();
|
|
202
|
+
}
|
package/src/index.tsx
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createContext,
|
|
3
|
+
type ReactNode,
|
|
3
4
|
type PropsWithChildren,
|
|
4
5
|
useCallback,
|
|
5
6
|
useContext,
|
|
7
|
+
useEffect,
|
|
8
|
+
useMemo,
|
|
9
|
+
useRef,
|
|
10
|
+
useState,
|
|
11
|
+
useSyncExternalStore,
|
|
6
12
|
} from 'react';
|
|
7
13
|
|
|
8
14
|
import {
|
|
9
15
|
type ColorValue,
|
|
10
16
|
type NativeSyntheticEvent,
|
|
17
|
+
type LayoutChangeEvent,
|
|
11
18
|
StyleSheet,
|
|
12
19
|
useWindowDimensions,
|
|
13
20
|
View,
|
|
@@ -17,6 +24,19 @@ import RNCNativeSheet, {
|
|
|
17
24
|
type NativeSheetDismissEvent,
|
|
18
25
|
type NativeSheetPresentedEvent,
|
|
19
26
|
} from './NativeSheetNativeComponent';
|
|
27
|
+
import {
|
|
28
|
+
resolveNativeSheetHeight,
|
|
29
|
+
resolveNativeSheetLayoutConstraints,
|
|
30
|
+
} from './NativeSheetHeight';
|
|
31
|
+
import {
|
|
32
|
+
addNativeSheetRegistryEntry,
|
|
33
|
+
closeNativeSheetRegistryEntry,
|
|
34
|
+
finishNativeSheetRegistryEntry,
|
|
35
|
+
getNativeSheetRegistrySnapshot,
|
|
36
|
+
markNativeSheetRegistryEntryPresentationRequested,
|
|
37
|
+
setNativeSheetRegistryBlocked,
|
|
38
|
+
subscribeNativeSheetRegistry,
|
|
39
|
+
} from './NativeSheetRegistry';
|
|
20
40
|
|
|
21
41
|
export type NativeSheetDismissReason =
|
|
22
42
|
| 'back'
|
|
@@ -30,14 +50,27 @@ export interface NativeSheetProps extends PropsWithChildren {
|
|
|
30
50
|
/** Controls whether the sheet is presented. */
|
|
31
51
|
open: boolean;
|
|
32
52
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* native
|
|
53
|
+
* Outer height in points/dp. Changes while open animate the native surface
|
|
54
|
+
* from its bottom edge. When omitted, settled content-size changes update the
|
|
55
|
+
* native height automatically.
|
|
36
56
|
*/
|
|
37
|
-
height
|
|
57
|
+
height?: number;
|
|
58
|
+
/** Maximum auto-fit height. Defaults to 92% of the current window height. */
|
|
59
|
+
maxHeight?: number;
|
|
38
60
|
onDismiss?: (reason: NativeSheetDismissReason) => void;
|
|
39
61
|
onPresented?: (height: number) => void;
|
|
62
|
+
onAnimationComplete?: (info: NativeSheetAnimationInfo) => void;
|
|
63
|
+
/** Matches the React Native Sheet callback for controlled open state. */
|
|
64
|
+
onOpenChange?: (open: boolean) => void;
|
|
65
|
+
/** Matches the React Native Sheet drag-dismiss option. */
|
|
66
|
+
dismissOnSnapToBottom?: boolean;
|
|
67
|
+
/** Disables the native drag gesture. */
|
|
68
|
+
disableDrag?: boolean;
|
|
69
|
+
/** @deprecated Use dismissOnSnapToBottom. */
|
|
40
70
|
dismissOnPanDown?: boolean;
|
|
71
|
+
/** Matches the React Native Sheet overlay-dismiss option. */
|
|
72
|
+
dismissOnOverlayPress?: boolean;
|
|
73
|
+
/** @deprecated Use dismissOnOverlayPress. */
|
|
41
74
|
dismissOnBackdropPress?: boolean;
|
|
42
75
|
dismissOnBackPress?: boolean;
|
|
43
76
|
showHandle?: boolean;
|
|
@@ -47,6 +80,31 @@ export interface NativeSheetProps extends PropsWithChildren {
|
|
|
47
80
|
testID?: string;
|
|
48
81
|
}
|
|
49
82
|
|
|
83
|
+
export type NativeSheetAnimationInfo = Readonly<{ open: boolean }>;
|
|
84
|
+
|
|
85
|
+
export interface NativeSheetShowControls {
|
|
86
|
+
close: () => void;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface NativeSheetShowOptions
|
|
90
|
+
extends Omit<
|
|
91
|
+
NativeSheetProps,
|
|
92
|
+
'children' | 'open' | 'onDismiss' | 'onOpenChange'
|
|
93
|
+
> {
|
|
94
|
+
renderContent: ReactNode | ((controls: NativeSheetShowControls) => ReactNode);
|
|
95
|
+
onOpenChange?: (open: boolean) => void;
|
|
96
|
+
onDismiss?: (reason: NativeSheetDismissReason) => void;
|
|
97
|
+
onAnimationComplete?: (info: NativeSheetAnimationInfo) => void;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface NativeSheetShowHandle {
|
|
101
|
+
close: () => void;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
interface NativeSheetInternalProps extends NativeSheetProps {
|
|
105
|
+
onPresentationRequested?: () => void;
|
|
106
|
+
}
|
|
107
|
+
|
|
50
108
|
const NativeSheetSecurityContext = createContext(false);
|
|
51
109
|
|
|
52
110
|
/**
|
|
@@ -58,6 +116,10 @@ export function NativeSheetSecurityProvider({
|
|
|
58
116
|
blocked,
|
|
59
117
|
children,
|
|
60
118
|
}: PropsWithChildren<{ blocked: boolean }>) {
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
setNativeSheetRegistryBlocked(blocked);
|
|
121
|
+
return () => setNativeSheetRegistryBlocked(false);
|
|
122
|
+
}, [blocked]);
|
|
61
123
|
return (
|
|
62
124
|
<NativeSheetSecurityContext.Provider value={blocked}>
|
|
63
125
|
{children}
|
|
@@ -71,45 +133,161 @@ export function NativeSheetSecurityProvider({
|
|
|
71
133
|
* Fabric mounts the same child view into the platform sheet without serializing
|
|
72
134
|
* business content through the native bridge.
|
|
73
135
|
*/
|
|
74
|
-
|
|
136
|
+
function NativeSheetComponent({
|
|
75
137
|
open,
|
|
76
138
|
height,
|
|
139
|
+
maxHeight,
|
|
77
140
|
children,
|
|
78
141
|
onDismiss,
|
|
79
142
|
onPresented,
|
|
80
|
-
|
|
81
|
-
|
|
143
|
+
onAnimationComplete,
|
|
144
|
+
onOpenChange,
|
|
145
|
+
dismissOnSnapToBottom,
|
|
146
|
+
disableDrag = false,
|
|
147
|
+
dismissOnPanDown,
|
|
148
|
+
dismissOnOverlayPress,
|
|
149
|
+
dismissOnBackdropPress,
|
|
82
150
|
dismissOnBackPress = true,
|
|
83
151
|
showHandle = true,
|
|
84
152
|
cornerRadius = 32,
|
|
85
153
|
dimAmount = 0.4,
|
|
86
154
|
backgroundColor,
|
|
87
155
|
testID,
|
|
88
|
-
|
|
156
|
+
onPresentationRequested,
|
|
157
|
+
}: NativeSheetInternalProps) {
|
|
89
158
|
const securityBlocked = useContext(NativeSheetSecurityContext);
|
|
90
|
-
const { width } = useWindowDimensions();
|
|
159
|
+
const { height: windowHeight, width } = useWindowDimensions();
|
|
160
|
+
const [measurement, setMeasurement] = useState<{
|
|
161
|
+
height: number;
|
|
162
|
+
openCycle: number;
|
|
163
|
+
}>();
|
|
164
|
+
const lockedHeightRef = useRef<number | undefined>(undefined);
|
|
165
|
+
const dismissNotifiedRef = useRef(false);
|
|
166
|
+
const wasOpenRef = useRef(false);
|
|
167
|
+
const openCycleRef = useRef(0);
|
|
168
|
+
const explicitHeightForOpenRef = useRef(false);
|
|
169
|
+
if (open && !wasOpenRef.current) {
|
|
170
|
+
openCycleRef.current += 1;
|
|
171
|
+
dismissNotifiedRef.current = false;
|
|
172
|
+
explicitHeightForOpenRef.current = height !== undefined;
|
|
173
|
+
} else if (open && height !== undefined) {
|
|
174
|
+
explicitHeightForOpenRef.current = true;
|
|
175
|
+
}
|
|
176
|
+
wasOpenRef.current = open;
|
|
177
|
+
const shouldAutoMeasure = !explicitHeightForOpenRef.current;
|
|
178
|
+
const resolvedMaxHeight = maxHeight ?? Math.floor(windowHeight * 0.92);
|
|
179
|
+
if (open) {
|
|
180
|
+
lockedHeightRef.current = resolveNativeSheetHeight({
|
|
181
|
+
currentHeight: lockedHeightRef.current,
|
|
182
|
+
explicitHeight: height,
|
|
183
|
+
measuredHeight:
|
|
184
|
+
measurement?.openCycle === openCycleRef.current
|
|
185
|
+
? measurement.height
|
|
186
|
+
: undefined,
|
|
187
|
+
maxHeight: resolvedMaxHeight,
|
|
188
|
+
shouldAutoMeasure,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
const lockedHeight = lockedHeightRef.current;
|
|
192
|
+
const resolvedDismissOnPanDown =
|
|
193
|
+
!disableDrag && (dismissOnSnapToBottom ?? dismissOnPanDown ?? true);
|
|
194
|
+
const resolvedDismissOnBackdropPress =
|
|
195
|
+
dismissOnOverlayPress ?? dismissOnBackdropPress ?? false;
|
|
196
|
+
const layoutConstraints = useMemo(
|
|
197
|
+
() =>
|
|
198
|
+
resolveNativeSheetLayoutConstraints({
|
|
199
|
+
lockedHeight,
|
|
200
|
+
maxHeight: resolvedMaxHeight,
|
|
201
|
+
shouldAutoMeasure,
|
|
202
|
+
}),
|
|
203
|
+
[lockedHeight, resolvedMaxHeight, shouldAutoMeasure]
|
|
204
|
+
);
|
|
91
205
|
|
|
206
|
+
const notifyDismiss = useCallback(
|
|
207
|
+
(reason: NativeSheetDismissReason) => {
|
|
208
|
+
if (dismissNotifiedRef.current) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
dismissNotifiedRef.current = true;
|
|
212
|
+
lockedHeightRef.current = undefined;
|
|
213
|
+
onOpenChange?.(false);
|
|
214
|
+
onDismiss?.(reason);
|
|
215
|
+
onAnimationComplete?.({ open: false });
|
|
216
|
+
},
|
|
217
|
+
[onAnimationComplete, onDismiss, onOpenChange]
|
|
218
|
+
);
|
|
92
219
|
const handleDismiss = useCallback(
|
|
93
220
|
(event: NativeSyntheticEvent<NativeSheetDismissEvent>) => {
|
|
94
|
-
|
|
221
|
+
notifyDismiss(event.nativeEvent.reason as NativeSheetDismissReason);
|
|
95
222
|
},
|
|
96
|
-
[
|
|
223
|
+
[notifyDismiss]
|
|
97
224
|
);
|
|
225
|
+
useEffect(() => {
|
|
226
|
+
if (!securityBlocked || !open) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
// Native normally acknowledges the no-animation security dismissal. This
|
|
230
|
+
// fallback also covers an open request blocked before native presentation.
|
|
231
|
+
const timer = setTimeout(() => notifyDismiss('security'), 500);
|
|
232
|
+
return () => clearTimeout(timer);
|
|
233
|
+
}, [notifyDismiss, open, securityBlocked]);
|
|
234
|
+
useEffect(() => {
|
|
235
|
+
if (open && lockedHeight && !securityBlocked) {
|
|
236
|
+
onPresentationRequested?.();
|
|
237
|
+
}
|
|
238
|
+
}, [lockedHeight, onPresentationRequested, open, securityBlocked]);
|
|
98
239
|
const handlePresented = useCallback(
|
|
99
240
|
(event: NativeSyntheticEvent<NativeSheetPresentedEvent>) => {
|
|
100
241
|
onPresented?.(event.nativeEvent.height);
|
|
242
|
+
onAnimationComplete?.({ open: true });
|
|
101
243
|
},
|
|
102
|
-
[onPresented]
|
|
244
|
+
[onAnimationComplete, onPresented]
|
|
245
|
+
);
|
|
246
|
+
const handleContentLayout = useCallback(
|
|
247
|
+
(event: LayoutChangeEvent) => {
|
|
248
|
+
if (!open || !shouldAutoMeasure) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const nextHeight = Math.ceil(event.nativeEvent.layout.height);
|
|
252
|
+
if (nextHeight > 0) {
|
|
253
|
+
const nextMeasurement = {
|
|
254
|
+
height: Math.min(nextHeight, resolvedMaxHeight),
|
|
255
|
+
openCycle: openCycleRef.current,
|
|
256
|
+
};
|
|
257
|
+
setMeasurement((currentMeasurement) => {
|
|
258
|
+
if (
|
|
259
|
+
currentMeasurement?.height === nextMeasurement.height &&
|
|
260
|
+
currentMeasurement.openCycle === nextMeasurement.openCycle
|
|
261
|
+
) {
|
|
262
|
+
return currentMeasurement;
|
|
263
|
+
}
|
|
264
|
+
return nextMeasurement;
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
[open, resolvedMaxHeight, shouldAutoMeasure]
|
|
269
|
+
);
|
|
270
|
+
const hostStyle = useMemo(
|
|
271
|
+
() => [
|
|
272
|
+
styles.stagingHost,
|
|
273
|
+
{
|
|
274
|
+
width,
|
|
275
|
+
...(layoutConstraints.hostHeight !== undefined
|
|
276
|
+
? { height: layoutConstraints.hostHeight }
|
|
277
|
+
: { maxHeight: layoutConstraints.hostMaxHeight }),
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
[layoutConstraints, width]
|
|
103
281
|
);
|
|
104
282
|
|
|
105
283
|
return (
|
|
106
284
|
<RNCNativeSheet
|
|
107
285
|
testID={testID}
|
|
108
|
-
open={open}
|
|
109
|
-
sheetHeight={
|
|
286
|
+
open={open && Boolean(lockedHeight)}
|
|
287
|
+
sheetHeight={lockedHeight ?? 1}
|
|
110
288
|
securityBlocked={securityBlocked}
|
|
111
|
-
dismissOnPanDown={
|
|
112
|
-
dismissOnBackdropPress={
|
|
289
|
+
dismissOnPanDown={resolvedDismissOnPanDown}
|
|
290
|
+
dismissOnBackdropPress={resolvedDismissOnBackdropPress}
|
|
113
291
|
dismissOnBackPress={dismissOnBackPress}
|
|
114
292
|
showHandle={showHandle}
|
|
115
293
|
cornerRadius={cornerRadius}
|
|
@@ -117,15 +295,108 @@ export function NativeSheet({
|
|
|
117
295
|
sheetBackgroundColor={backgroundColor}
|
|
118
296
|
onDismiss={handleDismiss}
|
|
119
297
|
onPresented={handlePresented}
|
|
120
|
-
style={
|
|
298
|
+
style={hostStyle}
|
|
121
299
|
>
|
|
122
|
-
<View
|
|
123
|
-
{
|
|
300
|
+
<View
|
|
301
|
+
key={`open-cycle-${openCycleRef.current}`}
|
|
302
|
+
pointerEvents="auto"
|
|
303
|
+
style={layoutConstraints.shouldFillHost ? styles.content : undefined}
|
|
304
|
+
collapsable={false}
|
|
305
|
+
>
|
|
306
|
+
{shouldAutoMeasure ? (
|
|
307
|
+
<View
|
|
308
|
+
collapsable={false}
|
|
309
|
+
style={styles.autoMeasureContent}
|
|
310
|
+
onLayout={handleContentLayout}
|
|
311
|
+
>
|
|
312
|
+
{children}
|
|
313
|
+
</View>
|
|
314
|
+
) : (
|
|
315
|
+
children
|
|
316
|
+
)}
|
|
124
317
|
</View>
|
|
125
318
|
</RNCNativeSheet>
|
|
126
319
|
);
|
|
127
320
|
}
|
|
128
321
|
|
|
322
|
+
function showNativeSheet(
|
|
323
|
+
options: NativeSheetShowOptions
|
|
324
|
+
): NativeSheetShowHandle {
|
|
325
|
+
let id: number | undefined;
|
|
326
|
+
let closeRequestedBeforeRegistration = false;
|
|
327
|
+
const close = () => {
|
|
328
|
+
if (id === undefined) {
|
|
329
|
+
closeRequestedBeforeRegistration = true;
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
closeNativeSheetRegistryEntry(id);
|
|
333
|
+
};
|
|
334
|
+
const content =
|
|
335
|
+
typeof options.renderContent === 'function'
|
|
336
|
+
? options.renderContent({ close })
|
|
337
|
+
: options.renderContent;
|
|
338
|
+
id = addNativeSheetRegistryEntry(options, content);
|
|
339
|
+
if (closeRequestedBeforeRegistration) {
|
|
340
|
+
closeNativeSheetRegistryEntry(id);
|
|
341
|
+
}
|
|
342
|
+
return { close };
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function NativeSheetHostEntry({
|
|
346
|
+
entry,
|
|
347
|
+
}: {
|
|
348
|
+
entry: ReturnType<typeof getNativeSheetRegistrySnapshot>[number];
|
|
349
|
+
}) {
|
|
350
|
+
const handleOpenChange = useCallback(
|
|
351
|
+
(nextOpen: boolean) => {
|
|
352
|
+
if (!nextOpen) {
|
|
353
|
+
closeNativeSheetRegistryEntry(entry.id);
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
[entry.id]
|
|
357
|
+
);
|
|
358
|
+
const handleDismiss = useCallback(
|
|
359
|
+
(reason: NativeSheetDismissReason) =>
|
|
360
|
+
finishNativeSheetRegistryEntry(entry.id, reason),
|
|
361
|
+
[entry.id]
|
|
362
|
+
);
|
|
363
|
+
const handlePresentationRequested = useCallback(
|
|
364
|
+
() => markNativeSheetRegistryEntryPresentationRequested(entry.id),
|
|
365
|
+
[entry.id]
|
|
366
|
+
);
|
|
367
|
+
return (
|
|
368
|
+
<NativeSheetComponent
|
|
369
|
+
{...entry.options}
|
|
370
|
+
open={entry.open}
|
|
371
|
+
onOpenChange={handleOpenChange}
|
|
372
|
+
onPresented={entry.options.onPresented}
|
|
373
|
+
onPresentationRequested={handlePresentationRequested}
|
|
374
|
+
onDismiss={handleDismiss}
|
|
375
|
+
>
|
|
376
|
+
{entry.content}
|
|
377
|
+
</NativeSheetComponent>
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function NativeSheetHost() {
|
|
382
|
+
const entries = useSyncExternalStore(
|
|
383
|
+
subscribeNativeSheetRegistry,
|
|
384
|
+
getNativeSheetRegistrySnapshot,
|
|
385
|
+
getNativeSheetRegistrySnapshot
|
|
386
|
+
);
|
|
387
|
+
return (
|
|
388
|
+
<>
|
|
389
|
+
{entries.map((entry) => (
|
|
390
|
+
<NativeSheetHostEntry key={entry.id} entry={entry} />
|
|
391
|
+
))}
|
|
392
|
+
</>
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export const NativeSheet: ((props: NativeSheetProps) => ReactNode) & {
|
|
397
|
+
show: typeof showNativeSheet;
|
|
398
|
+
} = Object.assign(NativeSheetComponent, { show: showNativeSheet });
|
|
399
|
+
|
|
129
400
|
const styles = StyleSheet.create({
|
|
130
401
|
stagingHost: {
|
|
131
402
|
position: 'absolute',
|
|
@@ -136,6 +407,10 @@ const styles = StyleSheet.create({
|
|
|
136
407
|
content: {
|
|
137
408
|
flex: 1,
|
|
138
409
|
},
|
|
410
|
+
autoMeasureContent: {
|
|
411
|
+
alignSelf: 'stretch',
|
|
412
|
+
flexShrink: 0,
|
|
413
|
+
},
|
|
139
414
|
});
|
|
140
415
|
|
|
141
416
|
export type {
|