@lodev09/react-native-true-sheet 4.0.0-beta.23 → 4.0.0-beta.25
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 +3 -2
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetModule.kt +13 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayView.kt +11 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +89 -9
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +100 -24
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +13 -10
- package/android/src/main/java/com/lodev09/truesheet/core/RNScreensEventObserver.kt +31 -6
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt +3 -8
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +14 -1
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDimView.kt +11 -17
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetGrabberView.kt +2 -4
- package/android/src/main/java/com/lodev09/truesheet/utils/ColorUtils.kt +19 -0
- package/ios/TrueSheetModule.mm +11 -2
- package/ios/TrueSheetView.h +2 -0
- package/ios/TrueSheetView.mm +62 -7
- package/ios/TrueSheetViewController.h +2 -0
- package/ios/TrueSheetViewController.mm +158 -4
- package/ios/core/RNScreensEventObserver.mm +18 -1
- package/ios/core/TrueSheetDimView.h +27 -0
- package/ios/core/TrueSheetDimView.mm +41 -0
- package/ios/core/TrueSheetKeyboardObserver.mm +11 -8
- package/lib/module/TrueSheet.js +88 -17
- package/lib/module/TrueSheet.js.map +1 -1
- package/lib/module/TrueSheet.web.js +150 -37
- package/lib/module/TrueSheet.web.js.map +1 -1
- package/lib/module/TrueSheetProvider.web.js +13 -9
- package/lib/module/TrueSheetProvider.web.js.map +1 -1
- package/lib/module/fabric/TrueSheetFooterViewNativeComponent.ts +2 -3
- package/lib/module/fabric/TrueSheetViewNativeComponent.ts +76 -71
- package/lib/module/navigation/createTrueSheetRouter.js +114 -91
- package/lib/module/navigation/createTrueSheetRouter.js.map +1 -1
- package/lib/module/navigation/expo-router/extendTrueSheetRouter.js +43 -0
- package/lib/module/navigation/expo-router/extendTrueSheetRouter.js.map +1 -0
- package/lib/module/navigation/expo-router/index.js +9 -4
- package/lib/module/navigation/expo-router/index.js.map +1 -1
- package/lib/module/scrollable.js +28 -0
- package/lib/module/scrollable.js.map +1 -0
- package/lib/module/web/keyboard.js +69 -0
- package/lib/module/web/keyboard.js.map +1 -0
- package/lib/module/web/layout.js +1 -1
- package/lib/module/web/layout.js.map +1 -1
- package/lib/module/web/vaul/context.js.map +1 -1
- package/lib/module/web/vaul/index.js +18 -8
- package/lib/module/web/vaul/index.js.map +1 -1
- package/lib/module/web/vaul/use-snap-points.js +15 -10
- package/lib/module/web/vaul/use-snap-points.js.map +1 -1
- package/lib/typescript/src/TrueSheet.d.ts +14 -1
- package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.types.d.ts +23 -3
- package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheetProvider.web.d.ts +5 -1
- package/lib/typescript/src/TrueSheetProvider.web.d.ts.map +1 -1
- package/lib/typescript/src/fabric/TrueSheetFooterViewNativeComponent.d.ts +2 -3
- package/lib/typescript/src/fabric/TrueSheetFooterViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +67 -66
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts +27 -0
- package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts.map +1 -1
- package/lib/typescript/src/navigation/expo-router/extendTrueSheetRouter.d.ts +29 -0
- package/lib/typescript/src/navigation/expo-router/extendTrueSheetRouter.d.ts.map +1 -0
- package/lib/typescript/src/navigation/expo-router/index.d.ts +3 -2
- package/lib/typescript/src/navigation/expo-router/index.d.ts.map +1 -1
- package/lib/typescript/src/navigation/expo-router/types.d.ts +1 -1
- package/lib/typescript/src/navigation/types.d.ts +3 -2
- package/lib/typescript/src/navigation/types.d.ts.map +1 -1
- package/lib/typescript/src/scrollable.d.ts +2 -0
- package/lib/typescript/src/scrollable.d.ts.map +1 -0
- package/lib/typescript/src/web/keyboard.d.ts +15 -0
- package/lib/typescript/src/web/keyboard.d.ts.map +1 -0
- package/lib/typescript/src/web/layout.d.ts +1 -0
- package/lib/typescript/src/web/layout.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/context.d.ts +2 -1
- package/lib/typescript/src/web/vaul/context.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/index.d.ts +8 -1
- package/lib/typescript/src/web/vaul/index.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/types.d.ts +9 -0
- package/lib/typescript/src/web/vaul/types.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/use-snap-points.d.ts +3 -2
- package/lib/typescript/src/web/vaul/use-snap-points.d.ts.map +1 -1
- package/package.json +5 -4
- package/src/TrueSheet.tsx +93 -14
- package/src/TrueSheet.types.ts +27 -2
- package/src/TrueSheet.web.tsx +170 -41
- package/src/TrueSheetProvider.web.tsx +22 -13
- package/src/fabric/TrueSheetFooterViewNativeComponent.ts +2 -3
- package/src/fabric/TrueSheetViewNativeComponent.ts +76 -71
- package/src/navigation/createTrueSheetRouter.ts +149 -113
- package/src/navigation/expo-router/extendTrueSheetRouter.ts +71 -0
- package/src/navigation/expo-router/index.ts +14 -6
- package/src/navigation/expo-router/types.ts +1 -1
- package/src/navigation/types.ts +5 -1
- package/src/scrollable.ts +25 -0
- package/src/web/keyboard.ts +79 -0
- package/src/web/layout.ts +1 -1
- package/src/web/vaul/context.ts +2 -1
- package/src/web/vaul/index.tsx +20 -5
- package/src/web/vaul/types.ts +10 -0
- package/src/web/vaul/use-snap-points.ts +19 -9
package/src/TrueSheet.web.tsx
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
useMemo,
|
|
11
11
|
useRef,
|
|
12
12
|
useState,
|
|
13
|
+
type RefObject,
|
|
13
14
|
} from 'react';
|
|
14
15
|
import type { DimensionValue, LayoutChangeEvent } from 'react-native';
|
|
15
16
|
import { StyleSheet, useColorScheme, useWindowDimensions, View } from 'react-native';
|
|
@@ -41,7 +42,12 @@ import {
|
|
|
41
42
|
TrueSheetPeekContext,
|
|
42
43
|
type TrueSheetPeekContextValue,
|
|
43
44
|
} from './TrueSheetPeek.web';
|
|
44
|
-
import {
|
|
45
|
+
import {
|
|
46
|
+
usePortalContainer,
|
|
47
|
+
useRegisterSheet,
|
|
48
|
+
useSheetStack,
|
|
49
|
+
type SheetHandle,
|
|
50
|
+
} from './TrueSheetProvider.web';
|
|
45
51
|
import {
|
|
46
52
|
COLOR_SURFACE_CONTAINER_LOW_DARK,
|
|
47
53
|
COLOR_SURFACE_CONTAINER_LOW_LIGHT,
|
|
@@ -60,18 +66,16 @@ import {
|
|
|
60
66
|
import { getDOMElement } from './web/dom';
|
|
61
67
|
import { TrueSheetBackground } from './TrueSheetBackground';
|
|
62
68
|
import { getDetentBackgroundIndex, normalizeDetentBackground } from './detentBackgrounds';
|
|
69
|
+
import { revealFocusedElement, useKeyboardViewport } from './web/keyboard';
|
|
63
70
|
import { observeSheetLayout } from './web/layout';
|
|
71
|
+
import { resolveScrollable } from './scrollable';
|
|
64
72
|
import { Drawer } from './web/vaul';
|
|
65
73
|
import { DEFAULT_PEEK_HEIGHT, DRAG_CLASS, TRANSITIONS } from './web/vaul/constants';
|
|
66
74
|
|
|
67
|
-
// Resolves the user's `scrollableRef` to its scrollable DOM element
|
|
68
|
-
//
|
|
69
|
-
// list refs (e.g. FlatList) expose it via getScrollableNode(). The node's
|
|
70
|
-
// first child is the content container.
|
|
75
|
+
// Resolves the user's `scrollableRef` to its scrollable DOM element. The
|
|
76
|
+
// node's first child is the content container.
|
|
71
77
|
const getScrollableElement = (scrollable: unknown): HTMLElement | null => {
|
|
72
|
-
|
|
73
|
-
if (scrollable instanceof HTMLElement) return scrollable;
|
|
74
|
-
const node = (scrollable as { getScrollableNode?: () => unknown }).getScrollableNode?.();
|
|
78
|
+
const node = resolveScrollable(scrollable);
|
|
75
79
|
return node instanceof HTMLElement ? node : null;
|
|
76
80
|
};
|
|
77
81
|
|
|
@@ -99,6 +103,8 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
99
103
|
accessibilityOptions,
|
|
100
104
|
detents = [0.5, 1],
|
|
101
105
|
dimmed = true,
|
|
106
|
+
dimColor,
|
|
107
|
+
dimOpacity,
|
|
102
108
|
dimmedDetentIndex = 0,
|
|
103
109
|
initialDetentIndex = -1,
|
|
104
110
|
header,
|
|
@@ -164,7 +170,19 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
164
170
|
const [isOpen, setIsOpen] = useState(shouldAutoPresent);
|
|
165
171
|
const isOpenRef = useRef(isOpen);
|
|
166
172
|
isOpenRef.current = isOpen;
|
|
173
|
+
const keyboardViewport = useKeyboardViewport(isOpen);
|
|
167
174
|
const isPresentingRef = useRef(shouldAutoPresent);
|
|
175
|
+
// Native's lifecycle state — updated synchronously so calls in the same tick see each other
|
|
176
|
+
const phaseRef = useRef<SheetPhase>(shouldAutoPresent ? 'presenting' : 'dismissed');
|
|
177
|
+
const resolvePresentRef = useRef<(() => void) | null>(null);
|
|
178
|
+
const resolveDismissRef = useRef<(() => void) | null>(null);
|
|
179
|
+
// Whether willPresent fired for the current open cycle — a present undone
|
|
180
|
+
// before it fires has nothing to pair a dismiss with.
|
|
181
|
+
const didEmitWillPresentRef = useRef(false);
|
|
182
|
+
// Whether the current open cycle came from initialDetentIndex. Native mounts those
|
|
183
|
+
// eagerly, so a dismiss before willPresent lands after the present instead of cancelling it.
|
|
184
|
+
const isAutoPresentRef = useRef(shouldAutoPresent);
|
|
185
|
+
const shouldDismissOnPresentRef = useRef(false);
|
|
168
186
|
const [displayedBackgroundIndex, setDisplayedBackgroundIndex] = useState(
|
|
169
187
|
shouldAutoPresent ? initialDetentIndex : 0
|
|
170
188
|
);
|
|
@@ -199,13 +217,31 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
199
217
|
);
|
|
200
218
|
}, []);
|
|
201
219
|
|
|
220
|
+
// Returns null when there's nothing to dismiss. Resolves on didDismiss.
|
|
221
|
+
const dismissSheet = useCallback((): Promise<void> | null => {
|
|
222
|
+
if (phaseRef.current === 'dismissed' || phaseRef.current === 'dismissing') return null;
|
|
223
|
+
settle(resolvePresentRef);
|
|
224
|
+
if (didEmitWillPresentRef.current) {
|
|
225
|
+
setIsOpen(false);
|
|
226
|
+
} else if (isAutoPresentRef.current) {
|
|
227
|
+
shouldDismissOnPresentRef.current = true;
|
|
228
|
+
} else {
|
|
229
|
+
// Mirror native's lazy-mount cancel: nothing shown yet, nothing to animate out
|
|
230
|
+
setIsOpen(false);
|
|
231
|
+
phaseRef.current = 'dismissed';
|
|
232
|
+
return Promise.resolve();
|
|
233
|
+
}
|
|
234
|
+
phaseRef.current = 'dismissing';
|
|
235
|
+
return new Promise((resolve) => {
|
|
236
|
+
resolveDismissRef.current = resolve;
|
|
237
|
+
});
|
|
238
|
+
}, []);
|
|
239
|
+
|
|
202
240
|
const handleOpenChange = useCallback(
|
|
203
241
|
(open: boolean) => {
|
|
204
|
-
if (!open
|
|
205
|
-
setIsOpen(false);
|
|
206
|
-
}
|
|
242
|
+
if (!open) dismissSheet();
|
|
207
243
|
},
|
|
208
|
-
[
|
|
244
|
+
[dismissSheet]
|
|
209
245
|
);
|
|
210
246
|
|
|
211
247
|
const portalContainer = usePortalContainer();
|
|
@@ -248,24 +284,38 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
248
284
|
const dismissAboveRef = useRef<(animated?: boolean) => Promise<void>>(async () => {});
|
|
249
285
|
const dismissDescendantsRef = useRef<() => void>(() => {});
|
|
250
286
|
|
|
287
|
+
const presentSheet = useCallback(async (index: number, auto: boolean) => {
|
|
288
|
+
const detent = validDetentsRef.current[index];
|
|
289
|
+
if (detent === undefined) {
|
|
290
|
+
throw new Error(
|
|
291
|
+
`TrueSheet: present index (${index}) is out of bounds. detents array has ${validDetentsRef.current.length} item(s)`
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
if (phaseRef.current !== 'dismissed') {
|
|
295
|
+
console.warn(ALREADY_PRESENTED_WARNING);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
phaseRef.current = 'presenting';
|
|
299
|
+
isAutoPresentRef.current = auto;
|
|
300
|
+
isPresentingRef.current = true;
|
|
301
|
+
setDisplayedBackgroundIndex(index);
|
|
302
|
+
setActiveSnapPoint(detent);
|
|
303
|
+
setIsOpen(true);
|
|
304
|
+
await new Promise<void>((resolve) => {
|
|
305
|
+
resolvePresentRef.current = resolve;
|
|
306
|
+
});
|
|
307
|
+
}, []);
|
|
308
|
+
|
|
251
309
|
const methods = useMemo<TrueSheetMethods>(
|
|
252
310
|
() => ({
|
|
253
|
-
present:
|
|
254
|
-
const detent = validDetentsRef.current[index];
|
|
255
|
-
if (detent === undefined) {
|
|
256
|
-
throw new Error(
|
|
257
|
-
`TrueSheet: present index (${index}) is out of bounds. detents array has ${validDetentsRef.current.length} item(s)`
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
if (!isOpenRef.current) {
|
|
261
|
-
isPresentingRef.current = true;
|
|
262
|
-
setDisplayedBackgroundIndex(index);
|
|
263
|
-
}
|
|
264
|
-
setActiveSnapPoint(detent);
|
|
265
|
-
setIsOpen(true);
|
|
266
|
-
},
|
|
311
|
+
present: (index = 0) => presentSheet(index, false),
|
|
267
312
|
dismiss: async () => {
|
|
268
|
-
|
|
313
|
+
const dismissal = dismissSheet();
|
|
314
|
+
if (!dismissal) {
|
|
315
|
+
console.warn(ALREADY_DISMISSED_WARNING);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
await dismissal;
|
|
269
319
|
},
|
|
270
320
|
resize: async (index) => {
|
|
271
321
|
const detent = validDetentsRef.current[index];
|
|
@@ -277,10 +327,14 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
277
327
|
setActiveSnapPoint(detent);
|
|
278
328
|
},
|
|
279
329
|
dismissStack: async (animated) => {
|
|
330
|
+
if (phaseRef.current === 'dismissed' || phaseRef.current === 'dismissing') {
|
|
331
|
+
console.warn(ALREADY_DISMISSED_WARNING);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
280
334
|
await dismissAboveRef.current(animated);
|
|
281
335
|
},
|
|
282
336
|
}),
|
|
283
|
-
[]
|
|
337
|
+
[presentSheet, dismissSheet]
|
|
284
338
|
);
|
|
285
339
|
|
|
286
340
|
useImperativeHandle(ref, () => methods, [methods]);
|
|
@@ -288,13 +342,27 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
288
342
|
useEffect(() => {
|
|
289
343
|
if (!shouldAutoPresent || didInitiallyPresentRef.current) return;
|
|
290
344
|
didInitiallyPresentRef.current = true;
|
|
291
|
-
if (
|
|
292
|
-
|
|
345
|
+
if (phaseRef.current === 'dismissed') {
|
|
346
|
+
presentSheet(initialDetentIndex, true);
|
|
293
347
|
}
|
|
294
|
-
}, [initialDetentIndex, shouldAutoPresent,
|
|
348
|
+
}, [initialDetentIndex, shouldAutoPresent, presentSheet]);
|
|
349
|
+
|
|
350
|
+
// Unmounted mid-transition: don't leave callers awaiting forever
|
|
351
|
+
useEffect(
|
|
352
|
+
() => () => {
|
|
353
|
+
settle(resolvePresentRef);
|
|
354
|
+
settle(resolveDismissRef);
|
|
355
|
+
},
|
|
356
|
+
[]
|
|
357
|
+
);
|
|
295
358
|
|
|
296
|
-
const
|
|
297
|
-
|
|
359
|
+
const handleRef = useRef<SheetHandle>({
|
|
360
|
+
methods,
|
|
361
|
+
dismissQuietly: async () => {
|
|
362
|
+
await dismissSheet();
|
|
363
|
+
},
|
|
364
|
+
});
|
|
365
|
+
useRegisterSheet(name, handleRef);
|
|
298
366
|
|
|
299
367
|
const drawerContentRef = useRef<HTMLDivElement | null>(null);
|
|
300
368
|
const [drawerContent, setDrawerContent] = useState<HTMLDivElement | null>(null);
|
|
@@ -447,6 +515,22 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
447
515
|
scrollableRef,
|
|
448
516
|
]);
|
|
449
517
|
|
|
518
|
+
// Re-reveal the focused field once the sheet settles above the keyboard
|
|
519
|
+
useEffect(() => {
|
|
520
|
+
const drawer = drawerContentRef.current;
|
|
521
|
+
if (!keyboardViewport || !drawer) return undefined;
|
|
522
|
+
let canceled = false;
|
|
523
|
+
// Flush styles so this commit's snap transition shows up in getAnimations()
|
|
524
|
+
// eslint-disable-next-line no-void
|
|
525
|
+
void drawer.offsetHeight;
|
|
526
|
+
Promise.allSettled(drawer.getAnimations().map((a) => a.finished)).then(() => {
|
|
527
|
+
if (!canceled) revealFocusedElement(drawer);
|
|
528
|
+
});
|
|
529
|
+
return () => {
|
|
530
|
+
canceled = true;
|
|
531
|
+
};
|
|
532
|
+
}, [keyboardViewport]);
|
|
533
|
+
|
|
450
534
|
// Below the last detent a vertical touch pan moves the sheet, not the
|
|
451
535
|
// content — `[data-vaul-scroll-locked]` disables vertical touch panning on
|
|
452
536
|
// the scroll container and everything inside it (see vaul/style.css).
|
|
@@ -519,6 +603,7 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
519
603
|
effectiveMaxContentHeight,
|
|
520
604
|
peekHeight,
|
|
521
605
|
peekContentHeight,
|
|
606
|
+
keyboardViewport,
|
|
522
607
|
});
|
|
523
608
|
geometryInputsRef.current = {
|
|
524
609
|
effectiveDetached,
|
|
@@ -526,14 +611,18 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
526
611
|
effectiveMaxContentHeight,
|
|
527
612
|
peekHeight,
|
|
528
613
|
peekContentHeight,
|
|
614
|
+
keyboardViewport,
|
|
529
615
|
};
|
|
530
616
|
|
|
531
617
|
const computeDetentGeometry = useCallback(() => {
|
|
532
618
|
const inputs = geometryInputsRef.current;
|
|
533
|
-
const windowH = window.innerHeight;
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
|
|
619
|
+
const windowH = inputs.keyboardViewport?.height ?? window.innerHeight;
|
|
620
|
+
const bottomGap = inputs.effectiveDetached ? inputs.effectiveDetachedOffset : 0;
|
|
621
|
+
const effectiveH = windowH - bottomGap;
|
|
622
|
+
// Above a keyboard, fractions keep their full-height size (see vaul).
|
|
623
|
+
const fractionH = inputs.keyboardViewport
|
|
624
|
+
? inputs.keyboardViewport.layoutHeight - bottomGap
|
|
625
|
+
: effectiveH;
|
|
537
626
|
// Matches vaul's height ceiling: min(effectiveH, maxContentHeight).
|
|
538
627
|
const ceiling =
|
|
539
628
|
inputs.effectiveMaxContentHeight !== undefined
|
|
@@ -569,7 +658,7 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
569
658
|
for (const d of validDetentsRef.current) {
|
|
570
659
|
const h =
|
|
571
660
|
typeof d === 'number'
|
|
572
|
-
? Math.min(d *
|
|
661
|
+
? Math.min(d * fractionH, ceiling)
|
|
573
662
|
: d === 'peek'
|
|
574
663
|
? Math.min(livePeekHeight, ceiling)
|
|
575
664
|
: autoHeight;
|
|
@@ -706,6 +795,9 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
706
795
|
// Mirror native: dismissing a sheet takes every sheet stacked above it
|
|
707
796
|
// (iOS dismisses from the presenter; Android runs dismissStack first).
|
|
708
797
|
dismissDescendantsRef.current();
|
|
798
|
+
// Mirror native's lazy-mount cancel: dismissed before willPresent, no events
|
|
799
|
+
if (!didEmitWillPresentRef.current) return undefined;
|
|
800
|
+
didEmitWillPresentRef.current = false;
|
|
709
801
|
// Pair willBlur with willDismiss on dismiss — mirrors native iOS
|
|
710
802
|
// emitWillDismissEvents (blur fires before dismiss). willPresent is
|
|
711
803
|
// deferred to `start()` below (needs the mounted drawer's geometry).
|
|
@@ -716,11 +808,20 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
716
808
|
const fireDone = () => {
|
|
717
809
|
if (present) {
|
|
718
810
|
isPresentingRef.current = false;
|
|
811
|
+
// A dismiss may already be underway (it lands on the next commit)
|
|
812
|
+
if (phaseRef.current === 'presenting') phaseRef.current = 'presented';
|
|
719
813
|
onDidPresentRef.current?.({ nativeEvent: computeDetentInfo() } as DidPresentEvent);
|
|
720
814
|
onDidFocusRef.current?.({ nativeEvent: null } as DidFocusEvent);
|
|
815
|
+
settle(resolvePresentRef);
|
|
816
|
+
if (shouldDismissOnPresentRef.current) {
|
|
817
|
+
shouldDismissOnPresentRef.current = false;
|
|
818
|
+
setIsOpen(false);
|
|
819
|
+
}
|
|
721
820
|
} else {
|
|
821
|
+
phaseRef.current = 'dismissed';
|
|
722
822
|
onDidBlurRef.current?.({ nativeEvent: null } as DidBlurEvent);
|
|
723
823
|
onDidDismissRef.current?.({ nativeEvent: null } as DidDismissEvent);
|
|
824
|
+
settle(resolveDismissRef);
|
|
724
825
|
}
|
|
725
826
|
};
|
|
726
827
|
|
|
@@ -746,6 +847,7 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
746
847
|
// willPresent mirrors native iOS where viewWillAppear dispatches
|
|
747
848
|
// both; the descendant-stack focus effect handles subsequent
|
|
748
849
|
// transitions.
|
|
850
|
+
didEmitWillPresentRef.current = true;
|
|
749
851
|
onWillPresentRef.current?.({ nativeEvent: computeDetentInfo() } as WillPresentEvent);
|
|
750
852
|
onWillFocusRef.current?.({ nativeEvent: null } as WillFocusEvent);
|
|
751
853
|
}
|
|
@@ -812,7 +914,7 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
812
914
|
}, [computeDetentInfo]);
|
|
813
915
|
|
|
814
916
|
const { isNested, dismissAbove, descendants } = useSheetStack(
|
|
815
|
-
|
|
917
|
+
handleRef,
|
|
816
918
|
drawerContentRef,
|
|
817
919
|
isOpen,
|
|
818
920
|
isFormSheet
|
|
@@ -822,7 +924,7 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
822
924
|
// already been popped from the stack, so `dismissAbove` can't find it.
|
|
823
925
|
dismissDescendantsRef.current = () => {
|
|
824
926
|
for (let i = descendants.length - 1; i >= 0; i--) {
|
|
825
|
-
descendants[i]!.ref.current?.
|
|
927
|
+
descendants[i]!.ref.current?.dismissQuietly();
|
|
826
928
|
}
|
|
827
929
|
};
|
|
828
930
|
|
|
@@ -1057,6 +1159,16 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
1057
1159
|
transition: isPresentingRef.current ? 'none' : 'background-color 200ms',
|
|
1058
1160
|
};
|
|
1059
1161
|
|
|
1162
|
+
// Vaul fades the overlay through its opacity, so the dim opacity goes into the color
|
|
1163
|
+
const resolvedOverlayStyle = useMemo<React.CSSProperties>(() => {
|
|
1164
|
+
if (dimColor == null && dimOpacity == null) return overlayStyle;
|
|
1165
|
+
const percentage = Math.min(1, Math.max(0, dimOpacity ?? DEFAULT_DIM_OPACITY)) * 100;
|
|
1166
|
+
return {
|
|
1167
|
+
...overlayStyle,
|
|
1168
|
+
backgroundColor: `color-mix(in srgb, ${String(dimColor ?? 'black')} ${percentage}%, transparent)`,
|
|
1169
|
+
};
|
|
1170
|
+
}, [dimColor, dimOpacity]);
|
|
1171
|
+
|
|
1060
1172
|
const mergedContentStyle = useMemo<React.CSSProperties>(
|
|
1061
1173
|
() => ({
|
|
1062
1174
|
position: 'fixed',
|
|
@@ -1225,12 +1337,13 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
1225
1337
|
initialAnimated={shouldAutoPresent ? initialDetentAnimated : true}
|
|
1226
1338
|
detachedWrapperStyle={wrapperStyle}
|
|
1227
1339
|
contentHeight={measuredContentHeight}
|
|
1340
|
+
keyboardViewport={keyboardViewport}
|
|
1228
1341
|
activeSnapPoint={activeSnapPoint}
|
|
1229
1342
|
setActiveSnapPoint={handleSetActiveSnapPoint}
|
|
1230
1343
|
{...snapPointsProps}
|
|
1231
1344
|
>
|
|
1232
1345
|
<Drawer.Portal container={portalContainer ?? undefined}>
|
|
1233
|
-
<Drawer.Overlay style={
|
|
1346
|
+
<Drawer.Overlay style={resolvedOverlayStyle} />
|
|
1234
1347
|
<Drawer.Content
|
|
1235
1348
|
ref={handleDrawerRef}
|
|
1236
1349
|
style={mergedContentStyle}
|
|
@@ -1281,6 +1394,8 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
1281
1394
|
);
|
|
1282
1395
|
});
|
|
1283
1396
|
|
|
1397
|
+
const DEFAULT_DIM_OPACITY = 0.5;
|
|
1398
|
+
|
|
1284
1399
|
const overlayStyle: React.CSSProperties = {
|
|
1285
1400
|
position: 'fixed',
|
|
1286
1401
|
inset: 0,
|
|
@@ -1329,6 +1444,20 @@ const visuallyHiddenStyle: React.CSSProperties = {
|
|
|
1329
1444
|
border: 0,
|
|
1330
1445
|
};
|
|
1331
1446
|
|
|
1447
|
+
type SheetPhase = 'dismissed' | 'presenting' | 'presented' | 'dismissing';
|
|
1448
|
+
|
|
1449
|
+
const settle = (resolveRef: RefObject<(() => void) | null>) => {
|
|
1450
|
+
const resolve = resolveRef.current;
|
|
1451
|
+
resolveRef.current = null;
|
|
1452
|
+
resolve?.();
|
|
1453
|
+
};
|
|
1454
|
+
|
|
1455
|
+
// Same as native
|
|
1456
|
+
const ALREADY_PRESENTED_WARNING =
|
|
1457
|
+
'TrueSheet: sheet is already presented. Use resize() to change detent.';
|
|
1458
|
+
const ALREADY_DISMISSED_WARNING =
|
|
1459
|
+
'TrueSheet: sheet is already dismissed. No need to dismiss it again.';
|
|
1460
|
+
|
|
1332
1461
|
const STATIC_METHOD_ERROR =
|
|
1333
1462
|
'Static methods are not supported on web. Use the useTrueSheet() hook instead.';
|
|
1334
1463
|
|
|
@@ -13,7 +13,13 @@ import {
|
|
|
13
13
|
|
|
14
14
|
import type { TrueSheetMethods, TrueSheetStaticMethods } from './TrueSheet.types';
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
export interface SheetHandle {
|
|
17
|
+
methods: TrueSheetMethods;
|
|
18
|
+
// dismiss() without the already-dismissed warning, for stack-wide dismissals
|
|
19
|
+
dismissQuietly: (animated?: boolean) => Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type SheetRef = RefObject<SheetHandle | null>;
|
|
17
23
|
type NodeRef = RefObject<HTMLDivElement | null>;
|
|
18
24
|
|
|
19
25
|
interface StackEntry {
|
|
@@ -23,7 +29,8 @@ interface StackEntry {
|
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
interface SheetContextValue {
|
|
26
|
-
|
|
32
|
+
registerSheet: (ref: SheetRef, name?: string) => () => void;
|
|
33
|
+
getSheets: () => ReadonlySet<SheetRef>;
|
|
27
34
|
resolveByName: (name: string) => TrueSheetMethods;
|
|
28
35
|
pushOpen: (entry: StackEntry) => void;
|
|
29
36
|
popOpen: (entry: StackEntry) => void;
|
|
@@ -44,6 +51,7 @@ export interface TrueSheetProviderProps {
|
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
export function TrueSheetProvider({ children }: TrueSheetProviderProps) {
|
|
54
|
+
const sheetsRef = useRef<Set<SheetRef>>(new Set());
|
|
47
55
|
const namedSheetsRef = useRef<Map<string, SheetRef>>(new Map());
|
|
48
56
|
const stackRef = useRef<readonly StackEntry[]>(EMPTY_STACK);
|
|
49
57
|
const listenersRef = useRef<Set<() => void>>(new Set());
|
|
@@ -72,17 +80,20 @@ export function TrueSheetProvider({ children }: TrueSheetProviderProps) {
|
|
|
72
80
|
const notify = () => listenersRef.current.forEach((listener) => listener());
|
|
73
81
|
|
|
74
82
|
return {
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
registerSheet: (ref, name) => {
|
|
84
|
+
sheetsRef.current.add(ref);
|
|
85
|
+
if (name) namedSheetsRef.current.set(name, ref);
|
|
77
86
|
return () => {
|
|
78
|
-
|
|
87
|
+
sheetsRef.current.delete(ref);
|
|
88
|
+
if (name && namedSheetsRef.current.get(name) === ref) {
|
|
79
89
|
namedSheetsRef.current.delete(name);
|
|
80
90
|
}
|
|
81
91
|
};
|
|
82
92
|
},
|
|
93
|
+
getSheets: () => sheetsRef.current,
|
|
83
94
|
resolveByName: (name) => {
|
|
84
95
|
const ref = namedSheetsRef.current.get(name);
|
|
85
|
-
const methods = ref?.current;
|
|
96
|
+
const methods = ref?.current?.methods;
|
|
86
97
|
if (!methods) {
|
|
87
98
|
throw new Error(`TrueSheet: no sheet registered with name "${name}"`);
|
|
88
99
|
}
|
|
@@ -145,11 +156,9 @@ export function useTrueSheet(): TrueSheetStaticMethods {
|
|
|
145
156
|
resize: (name, index) => ctx.resolveByName(name).resize(index),
|
|
146
157
|
dismiss: (name, animated) => ctx.resolveByName(name).dismiss(animated),
|
|
147
158
|
dismissStack: (name, animated) => ctx.resolveByName(name).dismissStack(animated),
|
|
159
|
+
// Every sheet, not just the open stack — it misses a present() that hasn't committed yet
|
|
148
160
|
dismissAll: async (animated) => {
|
|
149
|
-
|
|
150
|
-
await Promise.all(
|
|
151
|
-
[...stack].reverse().map((entry) => entry.ref.current?.dismiss(animated))
|
|
152
|
-
);
|
|
161
|
+
await Promise.all([...ctx.getSheets()].map((ref) => ref.current?.dismissQuietly(animated)));
|
|
153
162
|
},
|
|
154
163
|
};
|
|
155
164
|
}, [ctx]);
|
|
@@ -158,8 +167,8 @@ export function useTrueSheet(): TrueSheetStaticMethods {
|
|
|
158
167
|
export function useRegisterSheet(name: string | undefined, ref: SheetRef): void {
|
|
159
168
|
const ctx = useContext(SheetContext);
|
|
160
169
|
useEffect(() => {
|
|
161
|
-
if (!ctx
|
|
162
|
-
return ctx.
|
|
170
|
+
if (!ctx) return;
|
|
171
|
+
return ctx.registerSheet(ref, name);
|
|
163
172
|
}, [ctx, name, ref]);
|
|
164
173
|
}
|
|
165
174
|
|
|
@@ -211,7 +220,7 @@ export function useSheetStack(
|
|
|
211
220
|
const idx = snapshot.findIndex((e) => e.ref === ref);
|
|
212
221
|
if (idx < 0) return;
|
|
213
222
|
const above = snapshot.slice(idx + 1);
|
|
214
|
-
await Promise.all([...above].reverse().map((e) => e.ref.current?.
|
|
223
|
+
await Promise.all([...above].reverse().map((e) => e.ref.current?.dismissQuietly(animated)));
|
|
215
224
|
},
|
|
216
225
|
[ctx, ref]
|
|
217
226
|
);
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import type { ViewProps } from 'react-native';
|
|
2
|
-
import type { WithDefault } from 'react-native/Libraries/Types/CodegenTypes';
|
|
1
|
+
import type { CodegenTypes, ViewProps } from 'react-native';
|
|
3
2
|
import { codegenNativeComponent } from 'react-native';
|
|
4
3
|
|
|
5
4
|
export interface NativeProps extends ViewProps {
|
|
6
5
|
// Seeds the footer's first layout with the sheet's bottom safe-area inset
|
|
7
6
|
// (insetAdjustment: 'automatic') so a late-set footer doesn't need a second
|
|
8
7
|
// layout pass — and a visible sheet resize — once native pushes the inset
|
|
9
|
-
autoBottomInset?: WithDefault<boolean, false>;
|
|
8
|
+
autoBottomInset?: CodegenTypes.WithDefault<boolean, false>;
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
// interfaceOnly: shadow node/state/descriptor are custom (common/cpp) so the
|