@lodev09/react-native-true-sheet 3.2.2 → 3.3.0-beta.1

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.
Files changed (49) hide show
  1. package/lib/module/TrueSheet.js.map +1 -1
  2. package/lib/module/TrueSheet.web.js +348 -0
  3. package/lib/module/TrueSheet.web.js.map +1 -0
  4. package/lib/module/TrueSheetProvider.js +26 -0
  5. package/lib/module/TrueSheetProvider.js.map +1 -0
  6. package/lib/module/TrueSheetProvider.web.js +74 -0
  7. package/lib/module/TrueSheetProvider.web.js.map +1 -0
  8. package/lib/module/index.js +2 -1
  9. package/lib/module/index.js.map +1 -1
  10. package/lib/module/navigation/screen/ReanimatedTrueSheetScreen.js +1 -1
  11. package/lib/module/navigation/screen/ReanimatedTrueSheetScreen.js.map +1 -1
  12. package/lib/module/navigation/screen/TrueSheetScreen.js +1 -1
  13. package/lib/module/navigation/screen/TrueSheetScreen.js.map +1 -1
  14. package/lib/module/reanimated/ReanimatedTrueSheet.js +2 -2
  15. package/lib/module/reanimated/ReanimatedTrueSheet.js.map +1 -1
  16. package/lib/module/reanimated/ReanimatedTrueSheet.web.js +81 -0
  17. package/lib/module/reanimated/ReanimatedTrueSheet.web.js.map +1 -0
  18. package/lib/module/reanimated/index.js +2 -2
  19. package/lib/module/reanimated/index.js.map +1 -1
  20. package/lib/module/reanimated/useReanimatedPositionChangeHandler.web.js +21 -0
  21. package/lib/module/reanimated/useReanimatedPositionChangeHandler.web.js.map +1 -0
  22. package/lib/typescript/src/TrueSheet.d.ts +2 -2
  23. package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
  24. package/lib/typescript/src/TrueSheet.types.d.ts +72 -0
  25. package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
  26. package/lib/typescript/src/TrueSheet.web.d.ts +3 -0
  27. package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -0
  28. package/lib/typescript/src/TrueSheetProvider.d.ts +17 -0
  29. package/lib/typescript/src/TrueSheetProvider.d.ts.map +1 -0
  30. package/lib/typescript/src/TrueSheetProvider.web.d.ts +22 -0
  31. package/lib/typescript/src/TrueSheetProvider.web.d.ts.map +1 -0
  32. package/lib/typescript/src/index.d.ts +1 -0
  33. package/lib/typescript/src/index.d.ts.map +1 -1
  34. package/lib/typescript/src/navigation/types.d.ts +1 -1
  35. package/lib/typescript/src/navigation/types.d.ts.map +1 -1
  36. package/lib/typescript/src/reanimated/ReanimatedTrueSheet.web.d.ts +41 -0
  37. package/lib/typescript/src/reanimated/ReanimatedTrueSheet.web.d.ts.map +1 -0
  38. package/lib/typescript/src/reanimated/useReanimatedPositionChangeHandler.web.d.ts +16 -0
  39. package/lib/typescript/src/reanimated/useReanimatedPositionChangeHandler.web.d.ts.map +1 -0
  40. package/package.json +26 -13
  41. package/src/TrueSheet.tsx +5 -1
  42. package/src/TrueSheet.types.ts +76 -0
  43. package/src/TrueSheet.web.tsx +423 -0
  44. package/src/TrueSheetProvider.tsx +29 -0
  45. package/src/TrueSheetProvider.web.tsx +81 -0
  46. package/src/index.ts +1 -0
  47. package/src/navigation/types.ts +2 -0
  48. package/src/reanimated/ReanimatedTrueSheet.web.tsx +78 -0
  49. package/src/reanimated/useReanimatedPositionChangeHandler.web.ts +32 -0
@@ -40,6 +40,52 @@ export type WillFocusEvent = NativeSyntheticEvent<null>;
40
40
  export type WillBlurEvent = NativeSyntheticEvent<null>;
41
41
  export type BackPressEvent = NativeSyntheticEvent<null>;
42
42
 
43
+ /**
44
+ * Ref methods exposed by a TrueSheet instance.
45
+ */
46
+ export interface TrueSheetRef {
47
+ /**
48
+ * Present the sheet at a given detent index.
49
+ * @param index - The detent index to present at (default: 0)
50
+ * @param animated - Whether to animate the presentation (default: true)
51
+ */
52
+ present: (index?: number, animated?: boolean) => Promise<void>;
53
+ /**
54
+ * Dismiss the sheet.
55
+ * @param animated - Whether to animate the dismissal (default: true)
56
+ */
57
+ dismiss: (animated?: boolean) => Promise<void>;
58
+ /**
59
+ * Resize the sheet to a given detent index.
60
+ * @param index - The detent index to resize to
61
+ */
62
+ resize: (index: number) => Promise<void>;
63
+ }
64
+
65
+ /**
66
+ * Methods for controlling TrueSheet instances by name.
67
+ * Returned by the `useTrueSheet` hook.
68
+ */
69
+ export interface TrueSheetContextMethods {
70
+ /**
71
+ * Present a sheet by name.
72
+ * @param name - The name of the sheet to present
73
+ * @param index - The detent index to present at (default: 0)
74
+ */
75
+ present: (name: string, index?: number) => Promise<void>;
76
+ /**
77
+ * Dismiss a sheet by name.
78
+ * @param name - The name of the sheet to dismiss
79
+ */
80
+ dismiss: (name: string) => Promise<void>;
81
+ /**
82
+ * Resize a sheet by name.
83
+ * @param name - The name of the sheet to resize
84
+ * @param index - The detent index to resize to
85
+ */
86
+ resize: (name: string, index: number) => Promise<void>;
87
+ }
88
+
43
89
  /**
44
90
  * Options for customizing the grabber (drag handle) appearance.
45
91
  */
@@ -97,6 +143,25 @@ export interface BlurOptions {
97
143
  interaction?: boolean;
98
144
  }
99
145
 
146
+ /**
147
+ * Defines the stack behavior when a modal is presented on web.
148
+ *
149
+ * @platform web
150
+ */
151
+ export type StackBehavior =
152
+ /**
153
+ * Mount the modal on top of the current one.
154
+ */
155
+ | 'push'
156
+ /**
157
+ * Minimize the current modal then mount the new one.
158
+ */
159
+ | 'switch'
160
+ /**
161
+ * Dismiss the current modal then mount the new one.
162
+ */
163
+ | 'replace';
164
+
100
165
  /**
101
166
  * Inset adjustment behavior for the sheet content.
102
167
  */
@@ -355,6 +420,17 @@ export interface TrueSheetProps extends ViewProps {
355
420
  */
356
421
  keyboardMode?: 'resize' | 'pan';
357
422
 
423
+ /**
424
+ * Defines the stack behavior when a modal is presented.
425
+ * - `push`: Mount the modal on top of the current one.
426
+ * - `switch`: Minimize the current modal then mount the new one.
427
+ * - `replace`: Dismiss the current modal then mount the new one.
428
+ *
429
+ * @platform web
430
+ * @default 'switch'
431
+ */
432
+ stackBehavior?: StackBehavior;
433
+
358
434
  /**
359
435
  * Called when the sheet's content is mounted and ready.
360
436
  * The sheet automatically waits for this event before presenting.
@@ -0,0 +1,423 @@
1
+ import {
2
+ createElement,
3
+ Fragment,
4
+ forwardRef,
5
+ isValidElement,
6
+ useCallback,
7
+ useContext,
8
+ useEffect,
9
+ useId,
10
+ useImperativeHandle,
11
+ useMemo,
12
+ useRef,
13
+ useState,
14
+ } from 'react';
15
+ import { View, StyleSheet, useWindowDimensions } from 'react-native';
16
+
17
+ import {
18
+ BottomSheetBackdrop,
19
+ type BottomSheetBackdropProps,
20
+ BottomSheetFooter,
21
+ type BottomSheetFooterProps,
22
+ BottomSheetHandle,
23
+ type BottomSheetHandleProps,
24
+ BottomSheetModal,
25
+ BottomSheetView,
26
+ type SNAP_POINT_TYPE,
27
+ } from '@gorhom/bottom-sheet';
28
+ import { useDerivedValue, useSharedValue } from 'react-native-reanimated';
29
+
30
+ import { BottomSheetContext } from './TrueSheetProvider.web';
31
+ import type {
32
+ TrueSheetProps,
33
+ TrueSheetRef,
34
+ DetentChangeEvent,
35
+ DidBlurEvent,
36
+ DidDismissEvent,
37
+ DidFocusEvent,
38
+ DidPresentEvent,
39
+ MountEvent,
40
+ PositionChangeEvent,
41
+ WillBlurEvent,
42
+ WillDismissEvent,
43
+ WillFocusEvent,
44
+ WillPresentEvent,
45
+ DragBeginEvent,
46
+ DragChangeEvent,
47
+ DragEndEvent,
48
+ } from './TrueSheet.types';
49
+
50
+ const DEFAULT_CORNER_RADIUS = 16;
51
+
52
+ const DEFAULT_GRABBER_COLOR = 'rgba(0, 0, 0, 0.3)';
53
+ const DEFAULT_GRABBER_WIDTH = 32;
54
+ const DEFAULT_GRABBER_HEIGHT = 4;
55
+
56
+ const renderSlot = (slot: TrueSheetProps['header'] | TrueSheetProps['footer']) => {
57
+ if (!slot) return null;
58
+ if (isValidElement(slot)) return slot;
59
+ return createElement(slot);
60
+ };
61
+
62
+ export const TrueSheet = forwardRef<TrueSheetRef, TrueSheetProps>((props, ref) => {
63
+ const {
64
+ name,
65
+ detents = [0.5, 1],
66
+ dismissible = true,
67
+ draggable = true,
68
+ dimmed = true,
69
+ dimmedDetentIndex = 0,
70
+ children,
71
+ scrollable = false,
72
+ initialDetentIndex = -1,
73
+ backgroundColor = '#ffffff',
74
+ cornerRadius = DEFAULT_CORNER_RADIUS,
75
+ grabber = true,
76
+ grabberOptions,
77
+ maxHeight,
78
+ header,
79
+ footer,
80
+ onMount,
81
+ onWillPresent,
82
+ onDidPresent,
83
+ onWillDismiss,
84
+ onDidDismiss,
85
+ onDetentChange,
86
+ onPositionChange,
87
+ onDragBegin,
88
+ onDragChange,
89
+ onDragEnd,
90
+ onWillFocus,
91
+ onDidFocus,
92
+ onWillBlur,
93
+ onDidBlur,
94
+ stackBehavior = 'switch',
95
+ style,
96
+ } = props;
97
+
98
+ const { height: windowHeight } = useWindowDimensions();
99
+ const defaultName = useId();
100
+ const sheetName = name ?? defaultName;
101
+ const bottomSheetContext = useContext(BottomSheetContext);
102
+ const modalRef = useRef<BottomSheetModal>(null);
103
+ const initialDetentIndexRef = useRef(initialDetentIndex);
104
+ const currentIndexRef = useRef(0);
105
+ const isPresenting = useRef(false);
106
+ const isDismissing = useRef(false);
107
+ const isMinimized = useRef(false);
108
+ const isDragging = useRef(false);
109
+
110
+ const presentResolver = useRef<(() => void) | null>(null);
111
+ const dismissResolver = useRef<(() => void) | null>(null);
112
+
113
+ const animatedPosition = useSharedValue(windowHeight);
114
+ const animatedIndex = useSharedValue(0);
115
+
116
+ const [snapIndex, setSnapIndex] = useState(initialDetentIndex);
117
+ const [isMounted, setIsMounted] = useState(false);
118
+
119
+ useDerivedValue(() => {
120
+ onPositionChange?.({
121
+ nativeEvent: {
122
+ position: animatedPosition.value,
123
+ index: animatedIndex.value,
124
+ detent: detents[animatedIndex.value] ?? 0,
125
+ realtime: true,
126
+ },
127
+ } as PositionChangeEvent);
128
+ });
129
+
130
+ const hasAutoDetent = detents.includes('auto');
131
+
132
+ const containerHeight = maxHeight ?? windowHeight;
133
+ const snapPoints = useMemo(
134
+ () =>
135
+ detents
136
+ .filter((detent): detent is number => detent !== 'auto' && typeof detent === 'number')
137
+ .map((detent) => Math.min(1, Math.max(0.1, detent)) * containerHeight),
138
+ [detents, containerHeight]
139
+ );
140
+
141
+ const handleChange = useCallback(
142
+ (index: number, _position: number, _type: SNAP_POINT_TYPE) => {
143
+ const previousIndex = currentIndexRef.current;
144
+ currentIndexRef.current = index;
145
+
146
+ // Handle drag end
147
+ if (isDragging.current && !isPresenting.current) {
148
+ isDragging.current = false;
149
+ onDragEnd?.({
150
+ nativeEvent: {
151
+ index,
152
+ position: animatedPosition.value,
153
+ detent: detents[index] ?? 0,
154
+ },
155
+ } as DragEndEvent);
156
+ }
157
+
158
+ if (!isPresenting.current && !isMinimized.current && previousIndex !== index && index >= 0) {
159
+ onDetentChange?.({
160
+ nativeEvent: {
161
+ index,
162
+ position: animatedPosition.value,
163
+ detent: detents[index] ?? 0,
164
+ },
165
+ } as DetentChangeEvent);
166
+ }
167
+
168
+ if (isPresenting.current) {
169
+ isPresenting.current = false;
170
+
171
+ // Resolve present promise
172
+ if (presentResolver.current) {
173
+ presentResolver.current();
174
+ presentResolver.current = null;
175
+ }
176
+
177
+ onDidPresent?.({
178
+ nativeEvent: {
179
+ index,
180
+ position: animatedPosition.value,
181
+ detent: detents[index] ?? 0,
182
+ },
183
+ } as DidPresentEvent);
184
+
185
+ onDidFocus?.({ nativeEvent: null } as DidFocusEvent);
186
+ }
187
+
188
+ // Fire onDidBlur when sheet reaches minimized state (index -1 but still mounted)
189
+ if (isMinimized.current && index === -1) {
190
+ onDidBlur?.({ nativeEvent: null } as DidBlurEvent);
191
+ }
192
+
193
+ // Fire onDidFocus when sheet is restored from minimized state
194
+ if (isMinimized.current && index >= 0) {
195
+ isMinimized.current = false;
196
+ onDidFocus?.({ nativeEvent: null } as DidFocusEvent);
197
+ }
198
+ },
199
+ [detents, animatedPosition]
200
+ );
201
+
202
+ const handleDismiss = useCallback(() => {
203
+ // Resolve dismiss promise
204
+ if (dismissResolver.current) {
205
+ dismissResolver.current();
206
+ dismissResolver.current = null;
207
+ }
208
+
209
+ onDidDismiss?.({ nativeEvent: null } as DidDismissEvent);
210
+
211
+ // Reset states since sheet is being dismissed
212
+ isMinimized.current = false;
213
+ isDismissing.current = false;
214
+ isDragging.current = false;
215
+ }, []);
216
+
217
+ const handleAnimate = useCallback(
218
+ (_fromIndex: number, toIndex: number) => {
219
+ // Detect drag begin (when not presenting or dismissing)
220
+ if (!isPresenting.current && !isDismissing.current && !isDragging.current && toIndex >= 0) {
221
+ isDragging.current = true;
222
+ onDragBegin?.({
223
+ nativeEvent: {
224
+ index: currentIndexRef.current,
225
+ position: animatedPosition.value,
226
+ detent: detents[currentIndexRef.current] ?? 0,
227
+ },
228
+ } as DragBeginEvent);
229
+ }
230
+
231
+ // Drag change during animation
232
+ if (isDragging.current && toIndex >= 0) {
233
+ onDragChange?.({
234
+ nativeEvent: {
235
+ index: toIndex,
236
+ position: animatedPosition.value,
237
+ detent: detents[toIndex] ?? 0,
238
+ },
239
+ } as DragChangeEvent);
240
+ }
241
+
242
+ if (isPresenting.current) {
243
+ onWillPresent?.({
244
+ nativeEvent: {
245
+ index: toIndex,
246
+ position: animatedPosition.value,
247
+ detent: detents[toIndex] ?? 0,
248
+ },
249
+ } as WillPresentEvent);
250
+
251
+ // Focus events fire together with present events
252
+ onWillFocus?.({ nativeEvent: null } as WillFocusEvent);
253
+ }
254
+
255
+ // Detect if sheet is being restored (will focus)
256
+ if (isMinimized.current && toIndex >= 0) {
257
+ onWillFocus?.({ nativeEvent: null } as WillFocusEvent);
258
+ }
259
+
260
+ if (toIndex === -1 && !isPresenting.current) {
261
+ // Will be handled as blur if the sheet doesn't actually dismiss
262
+ isMinimized.current = true;
263
+ onWillBlur?.({ nativeEvent: null } as WillBlurEvent);
264
+
265
+ if (isDismissing.current) {
266
+ onWillDismiss?.({ nativeEvent: null } as WillDismissEvent);
267
+ }
268
+ }
269
+ },
270
+ [detents, animatedPosition]
271
+ );
272
+
273
+ const backdropComponent = useCallback(
274
+ (backdropProps: BottomSheetBackdropProps) => {
275
+ if (!dimmed) {
276
+ return null;
277
+ }
278
+ return (
279
+ <BottomSheetBackdrop
280
+ {...backdropProps}
281
+ opacity={0.5}
282
+ appearsOnIndex={dimmedDetentIndex}
283
+ disappearsOnIndex={dimmedDetentIndex - 1}
284
+ pressBehavior={dismissible ? 'close' : 'none'}
285
+ />
286
+ );
287
+ },
288
+ [dimmed, dimmedDetentIndex, dismissible]
289
+ );
290
+
291
+ const handleComponent = useCallback(
292
+ (handleProps: BottomSheetHandleProps) => {
293
+ if (!grabber) {
294
+ return null;
295
+ }
296
+
297
+ const height = grabberOptions?.height ?? DEFAULT_GRABBER_HEIGHT;
298
+ const borderRadius = grabberOptions?.cornerRadius ?? height / 2;
299
+
300
+ return (
301
+ <BottomSheetHandle
302
+ {...handleProps}
303
+ style={[styles.handle, { paddingTop: grabberOptions?.topMargin }]}
304
+ indicatorStyle={{
305
+ height,
306
+ borderRadius,
307
+ width: grabberOptions?.width ?? DEFAULT_GRABBER_WIDTH,
308
+ backgroundColor: grabberOptions?.color ?? DEFAULT_GRABBER_COLOR,
309
+ }}
310
+ />
311
+ );
312
+ },
313
+ [grabber, grabberOptions]
314
+ );
315
+
316
+ const footerComponent = useMemo(
317
+ () =>
318
+ footer
319
+ ? (footerProps: BottomSheetFooterProps) => (
320
+ <BottomSheetFooter {...footerProps}>{renderSlot(footer)}</BottomSheetFooter>
321
+ )
322
+ : undefined,
323
+ [footer]
324
+ );
325
+
326
+ // For scrollable, we render the child directly
327
+ const ContainerComponent = scrollable ? Fragment : BottomSheetView;
328
+
329
+ const sheetMethodsRef = useRef<TrueSheetRef>({
330
+ present: (index = 0) => {
331
+ return new Promise<void>((resolve) => {
332
+ presentResolver.current = resolve;
333
+ setSnapIndex(index);
334
+ isPresenting.current = true;
335
+ modalRef.current?.present();
336
+ });
337
+ },
338
+ dismiss: () => {
339
+ return new Promise<void>((resolve) => {
340
+ dismissResolver.current = resolve;
341
+ isDismissing.current = true;
342
+ modalRef.current?.dismiss();
343
+ });
344
+ },
345
+ resize: async (index: number) => {
346
+ modalRef.current?.snapToIndex(index);
347
+ },
348
+ });
349
+
350
+ useImperativeHandle(ref, () => sheetMethodsRef.current);
351
+
352
+ // Register with context provider
353
+ useEffect(() => {
354
+ bottomSheetContext?.register(sheetName, sheetMethodsRef);
355
+ return () => {
356
+ bottomSheetContext?.unregister(sheetName);
357
+ };
358
+ }, [sheetName]);
359
+
360
+ // Auto-present on mount if initialDetentIndex is set
361
+ useEffect(() => {
362
+ if (initialDetentIndexRef.current >= 0) {
363
+ sheetMethodsRef.current.present(initialDetentIndexRef.current);
364
+ }
365
+ }, []);
366
+
367
+ // Handle mount event after first render
368
+ useEffect(() => {
369
+ if (!isMounted) {
370
+ setIsMounted(true);
371
+ onMount?.({ nativeEvent: null } as MountEvent);
372
+ }
373
+ }, [isMounted, onMount]);
374
+
375
+ return (
376
+ <BottomSheetModal
377
+ ref={modalRef}
378
+ name={sheetName}
379
+ style={[
380
+ styles.root,
381
+ { backgroundColor, borderTopLeftRadius: cornerRadius, borderTopRightRadius: cornerRadius },
382
+ ]}
383
+ index={snapIndex}
384
+ animateOnMount
385
+ enablePanDownToClose={dismissible}
386
+ enableContentPanningGesture={draggable}
387
+ enableHandlePanningGesture={draggable}
388
+ animatedPosition={animatedPosition}
389
+ animatedIndex={animatedIndex}
390
+ handleComponent={handleComponent}
391
+ onChange={handleChange}
392
+ onAnimate={handleAnimate}
393
+ enableDynamicSizing={hasAutoDetent}
394
+ maxDynamicContentSize={maxHeight}
395
+ snapPoints={snapPoints.length > 0 ? snapPoints : undefined}
396
+ onDismiss={handleDismiss}
397
+ stackBehavior={stackBehavior}
398
+ backdropComponent={backdropComponent}
399
+ backgroundComponent={null}
400
+ footerComponent={footerComponent}
401
+ >
402
+ <ContainerComponent>
403
+ {renderSlot(header)}
404
+ <View style={style}>{children}</View>
405
+ </ContainerComponent>
406
+ </BottomSheetModal>
407
+ );
408
+ });
409
+
410
+ const styles = StyleSheet.create({
411
+ root: {
412
+ overflow: 'hidden',
413
+ },
414
+ handle: {
415
+ position: 'absolute',
416
+ top: 0,
417
+ left: 0,
418
+ right: 0,
419
+ zIndex: 999,
420
+ paddingVertical: 10,
421
+ pointerEvents: 'none',
422
+ },
423
+ });
@@ -0,0 +1,29 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ import { TrueSheet } from './TrueSheet';
4
+ import type { TrueSheetContextMethods } from './TrueSheet.types';
5
+
6
+ export interface TrueSheetProviderProps {
7
+ children: ReactNode;
8
+ }
9
+
10
+ /**
11
+ * Provider for TrueSheet on native platforms.
12
+ * This is a pass-through component - no context is needed on native
13
+ * since TrueSheet uses static instance methods internally.
14
+ */
15
+ export function TrueSheetProvider({ children }: TrueSheetProviderProps) {
16
+ return children;
17
+ }
18
+
19
+ /**
20
+ * Hook to control TrueSheet instances by name.
21
+ * On native, this maps directly to TrueSheet static methods.
22
+ */
23
+ export function useTrueSheet(): TrueSheetContextMethods {
24
+ return {
25
+ present: TrueSheet.present,
26
+ dismiss: TrueSheet.dismiss,
27
+ resize: TrueSheet.resize,
28
+ };
29
+ }
@@ -0,0 +1,81 @@
1
+ import { createContext, useContext, useRef, type ReactNode, type RefObject } from 'react';
2
+ import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
3
+ import type { TrueSheetContextMethods, TrueSheetRef } from './TrueSheet.types';
4
+
5
+ interface BottomSheetContextValue extends TrueSheetContextMethods {
6
+ register: (name: string, methods: RefObject<TrueSheetRef>) => void;
7
+ unregister: (name: string) => void;
8
+ }
9
+
10
+ export const BottomSheetContext = createContext<BottomSheetContextValue | null>(null);
11
+
12
+ export interface TrueSheetProviderProps {
13
+ children: ReactNode;
14
+ }
15
+
16
+ /**
17
+ * Provider for TrueSheet on web.
18
+ * Required to wrap your app for sheet management via useTrueSheet hook.
19
+ */
20
+ export function TrueSheetProvider({ children }: TrueSheetProviderProps) {
21
+ const sheetsRef = useRef<Map<string, RefObject<TrueSheetRef>>>(new Map());
22
+
23
+ const register = (name: string, methods: RefObject<TrueSheetRef>) => {
24
+ sheetsRef.current.set(name, methods);
25
+ };
26
+
27
+ const unregister = (name: string) => {
28
+ sheetsRef.current.delete(name);
29
+ };
30
+
31
+ const present = async (name: string, index: number = 0) => {
32
+ const sheet = sheetsRef.current.get(name);
33
+ if (!sheet?.current) {
34
+ console.warn(`TrueSheet: Could not find sheet with name "${name}"`);
35
+ return;
36
+ }
37
+ return sheet.current.present(index);
38
+ };
39
+
40
+ const dismiss = async (name: string) => {
41
+ const sheet = sheetsRef.current.get(name);
42
+ if (!sheet?.current) {
43
+ console.warn(`TrueSheet: Could not find sheet with name "${name}"`);
44
+ return;
45
+ }
46
+ return sheet.current.dismiss();
47
+ };
48
+
49
+ const resize = async (name: string, index: number) => {
50
+ const sheet = sheetsRef.current.get(name);
51
+ if (!sheet?.current) {
52
+ console.warn(`TrueSheet: Could not find sheet with name "${name}"`);
53
+ return;
54
+ }
55
+ return sheet.current.resize(index);
56
+ };
57
+
58
+ return (
59
+ <BottomSheetContext.Provider value={{ register, unregister, present, dismiss, resize }}>
60
+ <BottomSheetModalProvider>{children}</BottomSheetModalProvider>
61
+ </BottomSheetContext.Provider>
62
+ );
63
+ }
64
+
65
+ /**
66
+ * Hook to control TrueSheet instances by name.
67
+ * On web, this uses the TrueSheetContext from TrueSheetProvider.
68
+ */
69
+ export function useTrueSheet(): TrueSheetContextMethods {
70
+ const context = useContext(BottomSheetContext);
71
+
72
+ if (!context) {
73
+ throw new Error('useTrueSheet must be used within a TrueSheetProvider');
74
+ }
75
+
76
+ return {
77
+ present: context.present,
78
+ dismiss: context.dismiss,
79
+ resize: context.resize,
80
+ };
81
+ }
package/src/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './TrueSheet';
2
2
  export * from './TrueSheet.types';
3
+ export * from './TrueSheetProvider';
@@ -142,6 +142,8 @@ export type TrueSheetNavigationOptions = Pick<
142
142
  | 'pageSizing'
143
143
  | 'header'
144
144
  | 'footer'
145
+ | 'insetAdjustment'
146
+ | 'stackBehavior'
145
147
  > & {
146
148
  /**
147
149
  * The detent index to present at.