@onekeyfe/react-native-native-sheet 3.0.129 → 3.0.131
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/NativeSheet.podspec +1 -0
- package/README.md +41 -8
- package/android/src/main/java/com/onekey/nativesheet/NativeSheetView.kt +610 -41
- package/ios/NativeSheetContainerView.swift +355 -46
- package/lib/module/NativeSheetHeight.js +43 -0
- package/lib/module/NativeSheetHeight.js.map +1 -0
- package/lib/module/NativeSheetRegistry.js +181 -0
- package/lib/module/NativeSheetRegistry.js.map +1 -0
- package/lib/module/index.js +180 -21
- 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 +210 -0
- package/src/index.tsx +294 -20
|
@@ -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,210 @@
|
|
|
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
|
+
const registryBlockers = new Set<symbol>();
|
|
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 (registryBlockers.size > 0) {
|
|
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(
|
|
185
|
+
blockerId: symbol,
|
|
186
|
+
blocked: boolean
|
|
187
|
+
) {
|
|
188
|
+
const wasBlocked = registryBlockers.size > 0;
|
|
189
|
+
if (blocked) {
|
|
190
|
+
registryBlockers.add(blockerId);
|
|
191
|
+
} else {
|
|
192
|
+
registryBlockers.delete(blockerId);
|
|
193
|
+
}
|
|
194
|
+
if (!wasBlocked && registryBlockers.size > 0) {
|
|
195
|
+
requestSecurityDismissAllNativeSheets();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function resetNativeSheetRegistryForTests() {
|
|
200
|
+
if (securityFallbackTimer) {
|
|
201
|
+
clearTimeout(securityFallbackTimer);
|
|
202
|
+
securityFallbackTimer = undefined;
|
|
203
|
+
}
|
|
204
|
+
closeFallbackTimers.forEach(clearTimeout);
|
|
205
|
+
closeFallbackTimers.clear();
|
|
206
|
+
entries = [];
|
|
207
|
+
nextId = 1;
|
|
208
|
+
registryBlockers.clear();
|
|
209
|
+
listeners.clear();
|
|
210
|
+
}
|
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,8 +116,15 @@ export function NativeSheetSecurityProvider({
|
|
|
58
116
|
blocked,
|
|
59
117
|
children,
|
|
60
118
|
}: PropsWithChildren<{ blocked: boolean }>) {
|
|
119
|
+
const parentBlocked = useContext(NativeSheetSecurityContext);
|
|
120
|
+
const blockerId = useRef(Symbol('NativeSheetSecurityProvider')).current;
|
|
121
|
+
const effectiveBlocked = parentBlocked || blocked;
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
setNativeSheetRegistryBlocked(blockerId, blocked);
|
|
124
|
+
return () => setNativeSheetRegistryBlocked(blockerId, false);
|
|
125
|
+
}, [blocked, blockerId]);
|
|
61
126
|
return (
|
|
62
|
-
<NativeSheetSecurityContext.Provider value={
|
|
127
|
+
<NativeSheetSecurityContext.Provider value={effectiveBlocked}>
|
|
63
128
|
{children}
|
|
64
129
|
</NativeSheetSecurityContext.Provider>
|
|
65
130
|
);
|
|
@@ -71,45 +136,157 @@ export function NativeSheetSecurityProvider({
|
|
|
71
136
|
* Fabric mounts the same child view into the platform sheet without serializing
|
|
72
137
|
* business content through the native bridge.
|
|
73
138
|
*/
|
|
74
|
-
|
|
139
|
+
function NativeSheetComponent({
|
|
75
140
|
open,
|
|
76
141
|
height,
|
|
142
|
+
maxHeight,
|
|
77
143
|
children,
|
|
78
144
|
onDismiss,
|
|
79
145
|
onPresented,
|
|
80
|
-
|
|
81
|
-
|
|
146
|
+
onAnimationComplete,
|
|
147
|
+
onOpenChange,
|
|
148
|
+
dismissOnSnapToBottom,
|
|
149
|
+
disableDrag = false,
|
|
150
|
+
dismissOnPanDown,
|
|
151
|
+
dismissOnOverlayPress,
|
|
152
|
+
dismissOnBackdropPress,
|
|
82
153
|
dismissOnBackPress = true,
|
|
83
154
|
showHandle = true,
|
|
84
155
|
cornerRadius = 32,
|
|
85
156
|
dimAmount = 0.4,
|
|
86
157
|
backgroundColor,
|
|
87
158
|
testID,
|
|
88
|
-
|
|
159
|
+
onPresentationRequested,
|
|
160
|
+
}: NativeSheetInternalProps) {
|
|
89
161
|
const securityBlocked = useContext(NativeSheetSecurityContext);
|
|
90
|
-
const { width } = useWindowDimensions();
|
|
162
|
+
const { height: windowHeight, width } = useWindowDimensions();
|
|
163
|
+
const [measurement, setMeasurement] = useState<{
|
|
164
|
+
height: number;
|
|
165
|
+
openCycle: number;
|
|
166
|
+
}>();
|
|
167
|
+
const lockedHeightRef = useRef<number | undefined>(undefined);
|
|
168
|
+
const dismissNotifiedRef = useRef(false);
|
|
169
|
+
const wasOpenRef = useRef(false);
|
|
170
|
+
const openCycleRef = useRef(0);
|
|
171
|
+
const explicitHeightForOpenRef = useRef(false);
|
|
172
|
+
if (open && !wasOpenRef.current) {
|
|
173
|
+
openCycleRef.current += 1;
|
|
174
|
+
dismissNotifiedRef.current = false;
|
|
175
|
+
explicitHeightForOpenRef.current = height !== undefined;
|
|
176
|
+
} else if (open && height !== undefined) {
|
|
177
|
+
explicitHeightForOpenRef.current = true;
|
|
178
|
+
}
|
|
179
|
+
wasOpenRef.current = open;
|
|
180
|
+
const shouldAutoMeasure = !explicitHeightForOpenRef.current;
|
|
181
|
+
const resolvedMaxHeight = maxHeight ?? Math.floor(windowHeight * 0.92);
|
|
182
|
+
if (open) {
|
|
183
|
+
lockedHeightRef.current = resolveNativeSheetHeight({
|
|
184
|
+
currentHeight: lockedHeightRef.current,
|
|
185
|
+
explicitHeight: height,
|
|
186
|
+
measuredHeight:
|
|
187
|
+
measurement?.openCycle === openCycleRef.current
|
|
188
|
+
? measurement.height
|
|
189
|
+
: undefined,
|
|
190
|
+
maxHeight: resolvedMaxHeight,
|
|
191
|
+
shouldAutoMeasure,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
const lockedHeight = lockedHeightRef.current;
|
|
195
|
+
const resolvedDismissOnPanDown =
|
|
196
|
+
!disableDrag && (dismissOnSnapToBottom ?? dismissOnPanDown ?? true);
|
|
197
|
+
const resolvedDismissOnBackdropPress =
|
|
198
|
+
dismissOnOverlayPress ?? dismissOnBackdropPress ?? false;
|
|
199
|
+
const layoutConstraints = useMemo(
|
|
200
|
+
() =>
|
|
201
|
+
resolveNativeSheetLayoutConstraints({
|
|
202
|
+
lockedHeight,
|
|
203
|
+
maxHeight: resolvedMaxHeight,
|
|
204
|
+
shouldAutoMeasure,
|
|
205
|
+
}),
|
|
206
|
+
[lockedHeight, resolvedMaxHeight, shouldAutoMeasure]
|
|
207
|
+
);
|
|
91
208
|
|
|
209
|
+
const notifyDismiss = useCallback(
|
|
210
|
+
(reason: NativeSheetDismissReason) => {
|
|
211
|
+
if (dismissNotifiedRef.current) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
dismissNotifiedRef.current = true;
|
|
215
|
+
lockedHeightRef.current = undefined;
|
|
216
|
+
onOpenChange?.(false);
|
|
217
|
+
onDismiss?.(reason);
|
|
218
|
+
onAnimationComplete?.({ open: false });
|
|
219
|
+
},
|
|
220
|
+
[onAnimationComplete, onDismiss, onOpenChange]
|
|
221
|
+
);
|
|
92
222
|
const handleDismiss = useCallback(
|
|
93
223
|
(event: NativeSyntheticEvent<NativeSheetDismissEvent>) => {
|
|
94
|
-
|
|
224
|
+
notifyDismiss(event.nativeEvent.reason as NativeSheetDismissReason);
|
|
95
225
|
},
|
|
96
|
-
[
|
|
226
|
+
[notifyDismiss]
|
|
97
227
|
);
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
if (!securityBlocked || !open) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
// Native normally acknowledges the no-animation security dismissal. This
|
|
233
|
+
// fallback also covers an open request blocked before native presentation.
|
|
234
|
+
const timer = setTimeout(() => notifyDismiss('security'), 500);
|
|
235
|
+
return () => clearTimeout(timer);
|
|
236
|
+
}, [notifyDismiss, open, securityBlocked]);
|
|
98
237
|
const handlePresented = useCallback(
|
|
99
238
|
(event: NativeSyntheticEvent<NativeSheetPresentedEvent>) => {
|
|
239
|
+
onPresentationRequested?.();
|
|
100
240
|
onPresented?.(event.nativeEvent.height);
|
|
241
|
+
onAnimationComplete?.({ open: true });
|
|
101
242
|
},
|
|
102
|
-
[onPresented]
|
|
243
|
+
[onAnimationComplete, onPresentationRequested, onPresented]
|
|
244
|
+
);
|
|
245
|
+
const handleContentLayout = useCallback(
|
|
246
|
+
(event: LayoutChangeEvent) => {
|
|
247
|
+
if (!open || !shouldAutoMeasure) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
const nextHeight = Math.ceil(event.nativeEvent.layout.height);
|
|
251
|
+
if (nextHeight > 0) {
|
|
252
|
+
const nextMeasurement = {
|
|
253
|
+
height: Math.min(nextHeight, resolvedMaxHeight),
|
|
254
|
+
openCycle: openCycleRef.current,
|
|
255
|
+
};
|
|
256
|
+
setMeasurement((currentMeasurement) => {
|
|
257
|
+
if (
|
|
258
|
+
currentMeasurement?.height === nextMeasurement.height &&
|
|
259
|
+
currentMeasurement.openCycle === nextMeasurement.openCycle
|
|
260
|
+
) {
|
|
261
|
+
return currentMeasurement;
|
|
262
|
+
}
|
|
263
|
+
return nextMeasurement;
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
[open, resolvedMaxHeight, shouldAutoMeasure]
|
|
268
|
+
);
|
|
269
|
+
const hostStyle = useMemo(
|
|
270
|
+
() => [
|
|
271
|
+
styles.stagingHost,
|
|
272
|
+
{
|
|
273
|
+
width,
|
|
274
|
+
...(layoutConstraints.hostHeight !== undefined
|
|
275
|
+
? { height: layoutConstraints.hostHeight }
|
|
276
|
+
: { maxHeight: layoutConstraints.hostMaxHeight }),
|
|
277
|
+
},
|
|
278
|
+
],
|
|
279
|
+
[layoutConstraints, width]
|
|
103
280
|
);
|
|
104
281
|
|
|
105
282
|
return (
|
|
106
283
|
<RNCNativeSheet
|
|
107
284
|
testID={testID}
|
|
108
|
-
open={open}
|
|
109
|
-
sheetHeight={
|
|
285
|
+
open={open && Boolean(lockedHeight)}
|
|
286
|
+
sheetHeight={lockedHeight ?? 1}
|
|
110
287
|
securityBlocked={securityBlocked}
|
|
111
|
-
dismissOnPanDown={
|
|
112
|
-
dismissOnBackdropPress={
|
|
288
|
+
dismissOnPanDown={resolvedDismissOnPanDown}
|
|
289
|
+
dismissOnBackdropPress={resolvedDismissOnBackdropPress}
|
|
113
290
|
dismissOnBackPress={dismissOnBackPress}
|
|
114
291
|
showHandle={showHandle}
|
|
115
292
|
cornerRadius={cornerRadius}
|
|
@@ -117,15 +294,108 @@ export function NativeSheet({
|
|
|
117
294
|
sheetBackgroundColor={backgroundColor}
|
|
118
295
|
onDismiss={handleDismiss}
|
|
119
296
|
onPresented={handlePresented}
|
|
120
|
-
style={
|
|
297
|
+
style={hostStyle}
|
|
121
298
|
>
|
|
122
|
-
<View
|
|
123
|
-
{
|
|
299
|
+
<View
|
|
300
|
+
key={`open-cycle-${openCycleRef.current}`}
|
|
301
|
+
pointerEvents="auto"
|
|
302
|
+
style={layoutConstraints.shouldFillHost ? styles.content : undefined}
|
|
303
|
+
collapsable={false}
|
|
304
|
+
>
|
|
305
|
+
{shouldAutoMeasure ? (
|
|
306
|
+
<View
|
|
307
|
+
collapsable={false}
|
|
308
|
+
style={styles.autoMeasureContent}
|
|
309
|
+
onLayout={handleContentLayout}
|
|
310
|
+
>
|
|
311
|
+
{children}
|
|
312
|
+
</View>
|
|
313
|
+
) : (
|
|
314
|
+
children
|
|
315
|
+
)}
|
|
124
316
|
</View>
|
|
125
317
|
</RNCNativeSheet>
|
|
126
318
|
);
|
|
127
319
|
}
|
|
128
320
|
|
|
321
|
+
function showNativeSheet(
|
|
322
|
+
options: NativeSheetShowOptions
|
|
323
|
+
): NativeSheetShowHandle {
|
|
324
|
+
let id: number | undefined;
|
|
325
|
+
let closeRequestedBeforeRegistration = false;
|
|
326
|
+
const close = () => {
|
|
327
|
+
if (id === undefined) {
|
|
328
|
+
closeRequestedBeforeRegistration = true;
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
closeNativeSheetRegistryEntry(id);
|
|
332
|
+
};
|
|
333
|
+
const content =
|
|
334
|
+
typeof options.renderContent === 'function'
|
|
335
|
+
? options.renderContent({ close })
|
|
336
|
+
: options.renderContent;
|
|
337
|
+
id = addNativeSheetRegistryEntry(options, content);
|
|
338
|
+
if (closeRequestedBeforeRegistration) {
|
|
339
|
+
closeNativeSheetRegistryEntry(id);
|
|
340
|
+
}
|
|
341
|
+
return { close };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function NativeSheetHostEntry({
|
|
345
|
+
entry,
|
|
346
|
+
}: {
|
|
347
|
+
entry: ReturnType<typeof getNativeSheetRegistrySnapshot>[number];
|
|
348
|
+
}) {
|
|
349
|
+
const handleOpenChange = useCallback(
|
|
350
|
+
(nextOpen: boolean) => {
|
|
351
|
+
if (!nextOpen) {
|
|
352
|
+
closeNativeSheetRegistryEntry(entry.id);
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
[entry.id]
|
|
356
|
+
);
|
|
357
|
+
const handleDismiss = useCallback(
|
|
358
|
+
(reason: NativeSheetDismissReason) =>
|
|
359
|
+
finishNativeSheetRegistryEntry(entry.id, reason),
|
|
360
|
+
[entry.id]
|
|
361
|
+
);
|
|
362
|
+
const handlePresentationRequested = useCallback(
|
|
363
|
+
() => markNativeSheetRegistryEntryPresentationRequested(entry.id),
|
|
364
|
+
[entry.id]
|
|
365
|
+
);
|
|
366
|
+
return (
|
|
367
|
+
<NativeSheetComponent
|
|
368
|
+
{...entry.options}
|
|
369
|
+
open={entry.open}
|
|
370
|
+
onOpenChange={handleOpenChange}
|
|
371
|
+
onPresented={entry.options.onPresented}
|
|
372
|
+
onPresentationRequested={handlePresentationRequested}
|
|
373
|
+
onDismiss={handleDismiss}
|
|
374
|
+
>
|
|
375
|
+
{entry.content}
|
|
376
|
+
</NativeSheetComponent>
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export function NativeSheetHost() {
|
|
381
|
+
const entries = useSyncExternalStore(
|
|
382
|
+
subscribeNativeSheetRegistry,
|
|
383
|
+
getNativeSheetRegistrySnapshot,
|
|
384
|
+
getNativeSheetRegistrySnapshot
|
|
385
|
+
);
|
|
386
|
+
return (
|
|
387
|
+
<>
|
|
388
|
+
{entries.map((entry) => (
|
|
389
|
+
<NativeSheetHostEntry key={entry.id} entry={entry} />
|
|
390
|
+
))}
|
|
391
|
+
</>
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export const NativeSheet: ((props: NativeSheetProps) => ReactNode) & {
|
|
396
|
+
show: typeof showNativeSheet;
|
|
397
|
+
} = Object.assign(NativeSheetComponent, { show: showNativeSheet });
|
|
398
|
+
|
|
129
399
|
const styles = StyleSheet.create({
|
|
130
400
|
stagingHost: {
|
|
131
401
|
position: 'absolute',
|
|
@@ -136,6 +406,10 @@ const styles = StyleSheet.create({
|
|
|
136
406
|
content: {
|
|
137
407
|
flex: 1,
|
|
138
408
|
},
|
|
409
|
+
autoMeasureContent: {
|
|
410
|
+
alignSelf: 'stretch',
|
|
411
|
+
flexShrink: 0,
|
|
412
|
+
},
|
|
139
413
|
});
|
|
140
414
|
|
|
141
415
|
export type {
|