@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.
@@ -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
- * Fixed outer height for this presentation, in points/dp. Content updates do
34
- * not change this value, so asynchronously loaded children cannot resize the
35
- * native sheet mid-transition.
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: number;
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={blocked}>
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
- export function NativeSheet({
139
+ function NativeSheetComponent({
75
140
  open,
76
141
  height,
142
+ maxHeight,
77
143
  children,
78
144
  onDismiss,
79
145
  onPresented,
80
- dismissOnPanDown = true,
81
- dismissOnBackdropPress = false,
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
- }: NativeSheetProps) {
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
- onDismiss?.(event.nativeEvent.reason as NativeSheetDismissReason);
224
+ notifyDismiss(event.nativeEvent.reason as NativeSheetDismissReason);
95
225
  },
96
- [onDismiss]
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={height}
285
+ open={open && Boolean(lockedHeight)}
286
+ sheetHeight={lockedHeight ?? 1}
110
287
  securityBlocked={securityBlocked}
111
- dismissOnPanDown={dismissOnPanDown}
112
- dismissOnBackdropPress={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={[styles.stagingHost, { width, height }]}
297
+ style={hostStyle}
121
298
  >
122
- <View pointerEvents="auto" style={styles.content} collapsable={false}>
123
- {children}
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 {