@lodev09/react-native-true-sheet 4.0.0-beta.20 → 4.0.0-beta.22
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/android/build.gradle +1 -0
- package/android/proguard-rules.pro +3 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +1 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +5 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +9 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +5 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetBehavior.kt +126 -0
- package/ios/TrueSheetView.mm +63 -1
- package/lib/module/TrueSheet.js +3 -2
- package/lib/module/TrueSheet.js.map +1 -1
- package/lib/module/TrueSheet.web.js +41 -40
- package/lib/module/TrueSheet.web.js.map +1 -1
- package/lib/module/fabric/TrueSheetViewNativeComponent.ts +1 -0
- package/lib/module/web/vaul/constants.js +5 -0
- package/lib/module/web/vaul/constants.js.map +1 -1
- package/lib/module/web/vaul/context.js +4 -0
- package/lib/module/web/vaul/context.js.map +1 -1
- package/lib/module/web/vaul/index.js +21 -7
- package/lib/module/web/vaul/index.js.map +1 -1
- package/lib/module/web/vaul/use-snap-points.js +36 -10
- package/lib/module/web/vaul/use-snap-points.js.map +1 -1
- package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.types.d.ts +22 -0
- 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/fabric/TrueSheetViewNativeComponent.d.ts +1 -0
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/constants.d.ts +2 -0
- package/lib/typescript/src/web/vaul/constants.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/context.d.ts +2 -0
- package/lib/typescript/src/web/vaul/context.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/index.d.ts +7 -1
- package/lib/typescript/src/web/vaul/index.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 +1 -1
- package/src/TrueSheet.tsx +3 -2
- package/src/TrueSheet.types.ts +24 -0
- package/src/TrueSheet.web.tsx +46 -43
- package/src/fabric/TrueSheetViewNativeComponent.ts +1 -0
- package/src/web/vaul/constants.ts +5 -0
- package/src/web/vaul/context.ts +4 -0
- package/src/web/vaul/index.tsx +26 -6
- package/src/web/vaul/use-snap-points.ts +61 -10
package/src/TrueSheet.web.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
useCallback,
|
|
7
7
|
useEffect,
|
|
8
8
|
useImperativeHandle,
|
|
9
|
+
useLayoutEffect,
|
|
9
10
|
useMemo,
|
|
10
11
|
useRef,
|
|
11
12
|
useState,
|
|
@@ -80,6 +81,7 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
80
81
|
name,
|
|
81
82
|
dismissible = true,
|
|
82
83
|
draggable = true,
|
|
84
|
+
dismissThreshold = 'half',
|
|
83
85
|
cornerRadius,
|
|
84
86
|
style,
|
|
85
87
|
backgroundColor: backgroundColorProp,
|
|
@@ -158,6 +160,7 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
158
160
|
|
|
159
161
|
const colorScheme = useColorScheme();
|
|
160
162
|
const shouldAutoPresent = initialDetentIndex >= 0 && initialDetentIndex < validDetents.length;
|
|
163
|
+
const didInitiallyPresentRef = useRef(shouldAutoPresent);
|
|
161
164
|
const [isOpen, setIsOpen] = useState(shouldAutoPresent);
|
|
162
165
|
const isOpenRef = useRef(isOpen);
|
|
163
166
|
isOpenRef.current = isOpen;
|
|
@@ -282,10 +285,23 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
282
285
|
|
|
283
286
|
useImperativeHandle(ref, () => methods, [methods]);
|
|
284
287
|
|
|
288
|
+
useEffect(() => {
|
|
289
|
+
if (!shouldAutoPresent || didInitiallyPresentRef.current) return;
|
|
290
|
+
didInitiallyPresentRef.current = true;
|
|
291
|
+
if (!isOpenRef.current) {
|
|
292
|
+
methods.present(initialDetentIndex);
|
|
293
|
+
}
|
|
294
|
+
}, [initialDetentIndex, shouldAutoPresent, methods]);
|
|
295
|
+
|
|
285
296
|
const methodsRef = useRef<TrueSheetMethods | null>(methods);
|
|
286
297
|
useRegisterSheet(name, methodsRef);
|
|
287
298
|
|
|
288
299
|
const drawerContentRef = useRef<HTMLDivElement | null>(null);
|
|
300
|
+
const [drawerContent, setDrawerContent] = useState<HTMLDivElement | null>(null);
|
|
301
|
+
const handleDrawerRef = useCallback((node: HTMLDivElement | null) => {
|
|
302
|
+
drawerContentRef.current = node;
|
|
303
|
+
setDrawerContent(node);
|
|
304
|
+
}, []);
|
|
289
305
|
|
|
290
306
|
// Measured header/footer heights drive the 'peek' snap point — mirrors
|
|
291
307
|
// native, where the controller tracks headerHeight/footerHeight.
|
|
@@ -394,50 +410,36 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
394
410
|
(insetBehavior === 'automatic' || insetBehavior === 'footer');
|
|
395
411
|
const measureContent = validDetents.includes('auto') || isFormSheet;
|
|
396
412
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
absoluteHeader,
|
|
422
|
-
absoluteFooter,
|
|
423
|
-
footerInsetAdjustment,
|
|
424
|
-
measureContent,
|
|
425
|
-
onHeightChange: (height) => {
|
|
426
|
-
naturalHeightRef.current = height;
|
|
427
|
-
setMeasuredContentHeight(height);
|
|
428
|
-
},
|
|
429
|
-
});
|
|
430
|
-
};
|
|
431
|
-
|
|
432
|
-
rafId = window.requestAnimationFrame(attach);
|
|
413
|
+
useLayoutEffect(() => {
|
|
414
|
+
const layoutEl = sizedLayoutRef.current;
|
|
415
|
+
if (!isOpen || !drawerContent || !layoutEl?.isConnected) return undefined;
|
|
416
|
+
|
|
417
|
+
setHeaderHeight(getDOMElement(headerElRef.current)?.offsetHeight ?? 0);
|
|
418
|
+
setFooterHeight(getDOMElement(footerElRef.current)?.offsetHeight ?? 0);
|
|
419
|
+
measurePeek();
|
|
420
|
+
|
|
421
|
+
const observer = observeSheetLayout({
|
|
422
|
+
drawer: drawerContent,
|
|
423
|
+
layout: layoutEl,
|
|
424
|
+
getContent: () => getDOMElement(contentRef.current),
|
|
425
|
+
getHeader: () => getDOMElement(headerElRef.current),
|
|
426
|
+
getFooter: () => getDOMElement(footerElRef.current),
|
|
427
|
+
getScrollable: () => getScrollableElement(scrollableRef?.current),
|
|
428
|
+
absoluteHeader,
|
|
429
|
+
absoluteFooter,
|
|
430
|
+
footerInsetAdjustment,
|
|
431
|
+
measureContent,
|
|
432
|
+
onHeightChange: (height) => {
|
|
433
|
+
naturalHeightRef.current = height;
|
|
434
|
+
setMeasuredContentHeight(height);
|
|
435
|
+
},
|
|
436
|
+
});
|
|
433
437
|
|
|
434
|
-
return () =>
|
|
435
|
-
canceled = true;
|
|
436
|
-
window.cancelAnimationFrame(rafId);
|
|
437
|
-
observer?.disconnect();
|
|
438
|
-
};
|
|
438
|
+
return () => observer.disconnect();
|
|
439
439
|
}, [
|
|
440
440
|
isOpen,
|
|
441
|
+
drawerContent,
|
|
442
|
+
measurePeek,
|
|
441
443
|
absoluteHeader,
|
|
442
444
|
absoluteFooter,
|
|
443
445
|
footerInsetAdjustment,
|
|
@@ -1209,6 +1211,7 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
1209
1211
|
onDrag={handleDrag}
|
|
1210
1212
|
onRelease={handleRelease}
|
|
1211
1213
|
dismissible={dismissible}
|
|
1214
|
+
dismissThreshold={dismissThreshold}
|
|
1212
1215
|
onDismissAttempt={handleDismissAttempt}
|
|
1213
1216
|
draggable={draggable}
|
|
1214
1217
|
repositionInputs={false}
|
|
@@ -1219,7 +1222,7 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
1219
1222
|
detachedRadius={effectiveCornerRadius}
|
|
1220
1223
|
maxContentHeight={effectiveMaxContentHeight}
|
|
1221
1224
|
peekHeight={peekHeight}
|
|
1222
|
-
initialAnimated={initialDetentAnimated}
|
|
1225
|
+
initialAnimated={shouldAutoPresent ? initialDetentAnimated : true}
|
|
1223
1226
|
detachedWrapperStyle={wrapperStyle}
|
|
1224
1227
|
contentHeight={measuredContentHeight}
|
|
1225
1228
|
activeSnapPoint={activeSnapPoint}
|
|
@@ -1229,7 +1232,7 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
|
|
|
1229
1232
|
<Drawer.Portal container={portalContainer ?? undefined}>
|
|
1230
1233
|
<Drawer.Overlay style={overlayStyle} />
|
|
1231
1234
|
<Drawer.Content
|
|
1232
|
-
ref={
|
|
1235
|
+
ref={handleDrawerRef}
|
|
1233
1236
|
style={mergedContentStyle}
|
|
1234
1237
|
onPointerDownOutside={handlePointerDownOutside}
|
|
1235
1238
|
detachedSiblings={
|
|
@@ -114,6 +114,7 @@ export interface NativeProps extends ViewProps {
|
|
|
114
114
|
placement?: WithDefault<'automatic' | 'leading' | 'center' | 'trailing', 'automatic'>;
|
|
115
115
|
placementOffset?: WithDefault<Double, 16>;
|
|
116
116
|
insetAdjustment?: WithDefault<'automatic' | 'never', 'automatic'>;
|
|
117
|
+
dismissThreshold?: WithDefault<'half' | 'short', 'half'>;
|
|
117
118
|
|
|
118
119
|
// Boolean properties - match defaults from TrueSheet.types.ts
|
|
119
120
|
grabber?: WithDefault<boolean, true>;
|
|
@@ -9,6 +9,11 @@ export const DEFAULT_PEEK_HEIGHT = 150;
|
|
|
9
9
|
|
|
10
10
|
export const CLOSE_THRESHOLD = 0.25;
|
|
11
11
|
|
|
12
|
+
// Compose Material3 SheetState's dismiss thresholds, used by the 'short'
|
|
13
|
+
// dismiss threshold: velocity in px/ms, distance in px.
|
|
14
|
+
export const SHORT_DISMISS_VELOCITY = 0.125;
|
|
15
|
+
export const SHORT_DISMISS_DISTANCE = 56;
|
|
16
|
+
|
|
12
17
|
// Minimum downward pull (px) on a non-dismissible drawer at its first snap
|
|
13
18
|
// point before the release counts as a dismiss attempt.
|
|
14
19
|
export const DISMISS_ATTEMPT_THRESHOLD = 24;
|
package/src/web/vaul/context.ts
CHANGED
|
@@ -46,6 +46,8 @@ interface DrawerContextValue {
|
|
|
46
46
|
container?: HTMLElement | null;
|
|
47
47
|
autoFocus?: boolean;
|
|
48
48
|
shouldAnimate?: React.RefObject<boolean>;
|
|
49
|
+
initialAnimated: boolean;
|
|
50
|
+
hasPresented: React.RefObject<boolean>;
|
|
49
51
|
onPositionChangeRef: React.RefObject<((position: number) => void) | undefined>;
|
|
50
52
|
setContentHeight: (height: number) => void;
|
|
51
53
|
detached: boolean;
|
|
@@ -81,6 +83,8 @@ export const DrawerContext = React.createContext<DrawerContextValue>({
|
|
|
81
83
|
closeDrawer: () => {},
|
|
82
84
|
direction: 'bottom',
|
|
83
85
|
shouldAnimate: { current: true },
|
|
86
|
+
initialAnimated: true,
|
|
87
|
+
hasPresented: { current: false },
|
|
84
88
|
shouldScaleBackground: false,
|
|
85
89
|
setBackgroundColorOnScale: true,
|
|
86
90
|
noBodyStyles: false,
|
package/src/web/vaul/index.tsx
CHANGED
|
@@ -101,6 +101,12 @@ export type DialogProps = {
|
|
|
101
101
|
* @default true
|
|
102
102
|
*/
|
|
103
103
|
dismissible?: boolean;
|
|
104
|
+
/**
|
|
105
|
+
* How far the drawer must be dragged past its first snap point to close.
|
|
106
|
+
* `'half'` closes past half of it, `'short'` on a short drag or any fling.
|
|
107
|
+
* @default 'half'
|
|
108
|
+
*/
|
|
109
|
+
dismissThreshold?: 'half' | 'short';
|
|
104
110
|
/**
|
|
105
111
|
* Called when the user tries to close a non-dismissible drawer — dragging down
|
|
106
112
|
* at the first snap point, pressing escape, or tapping the handle of a
|
|
@@ -232,6 +238,7 @@ export function Root({
|
|
|
232
238
|
closeThreshold = CLOSE_THRESHOLD,
|
|
233
239
|
scrollLockTimeout = SCROLL_LOCK_TIMEOUT,
|
|
234
240
|
dismissible = true,
|
|
241
|
+
dismissThreshold = 'half',
|
|
235
242
|
onDismissAttempt,
|
|
236
243
|
draggable = true,
|
|
237
244
|
handleOnly = false,
|
|
@@ -292,6 +299,7 @@ export function Root({
|
|
|
292
299
|
const dismissAttempted = React.useRef(false);
|
|
293
300
|
const keyboardIsOpen = React.useRef(false);
|
|
294
301
|
const shouldAnimate = React.useRef(!defaultOpen);
|
|
302
|
+
const hasPresented = React.useRef(false);
|
|
295
303
|
const previousDiffFromInitial = React.useRef(0);
|
|
296
304
|
const drawerRef = React.useRef<HTMLDivElement>(null);
|
|
297
305
|
const drawerHeightRef = React.useRef(drawerRef.current?.getBoundingClientRect().height || 0);
|
|
@@ -308,10 +316,14 @@ export function Root({
|
|
|
308
316
|
onContentHeightChangeRef.current?.(measuredContentHeight);
|
|
309
317
|
}, [measuredContentHeight]);
|
|
310
318
|
|
|
311
|
-
const onSnapPointChange = React.useCallback((activeSnapPointIndex: number) => {
|
|
319
|
+
const onSnapPointChange = React.useCallback((activeSnapPointIndex: number, animated: boolean) => {
|
|
312
320
|
// Change openTime ref when we reach the last snap point to prevent dragging for 500ms incase it's scrollable.
|
|
313
321
|
if (snapPoints && activeSnapPointIndex === snapPointsOffset.length - 1)
|
|
314
322
|
openTime.current = new Date();
|
|
323
|
+
// Instant snaps do not emit transition events for position tracking.
|
|
324
|
+
if (!animated && drawerRef.current) {
|
|
325
|
+
onPositionChangeRef.current?.(drawerRef.current.getBoundingClientRect().top);
|
|
326
|
+
}
|
|
315
327
|
}, []);
|
|
316
328
|
|
|
317
329
|
const {
|
|
@@ -855,6 +867,7 @@ export function Root({
|
|
|
855
867
|
closeDrawer,
|
|
856
868
|
velocity,
|
|
857
869
|
dismissible,
|
|
870
|
+
dismissThreshold,
|
|
858
871
|
});
|
|
859
872
|
if (pointerEvent) onReleaseProp?.(pointerEvent, true);
|
|
860
873
|
return;
|
|
@@ -1009,6 +1022,8 @@ export function Root({
|
|
|
1009
1022
|
dismissible,
|
|
1010
1023
|
onDismissAttempt,
|
|
1011
1024
|
shouldAnimate,
|
|
1025
|
+
initialAnimated,
|
|
1026
|
+
hasPresented,
|
|
1012
1027
|
handleOnly,
|
|
1013
1028
|
isOpen,
|
|
1014
1029
|
isDragging,
|
|
@@ -1167,6 +1182,8 @@ export const Content = React.forwardRef<HTMLDivElement, ContentProps>(
|
|
|
1167
1182
|
container,
|
|
1168
1183
|
handleOnly,
|
|
1169
1184
|
shouldAnimate,
|
|
1185
|
+
initialAnimated,
|
|
1186
|
+
hasPresented,
|
|
1170
1187
|
autoFocus,
|
|
1171
1188
|
onPositionChangeRef,
|
|
1172
1189
|
setContentHeight,
|
|
@@ -1415,15 +1432,18 @@ export const Content = React.forwardRef<HTMLDivElement, ContentProps>(
|
|
|
1415
1432
|
wrapper.style.transform = `translate3d(0, ${viewportH}px, 0)`;
|
|
1416
1433
|
} else if (isOpen && !wasOpenRef.current) {
|
|
1417
1434
|
wrapper.style.transition = 'none';
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1435
|
+
if (hasPresented.current || initialAnimated) {
|
|
1436
|
+
wrapper.style.transform = `translate3d(0, ${viewportH}px, 0)`;
|
|
1437
|
+
// eslint-disable-next-line no-void
|
|
1438
|
+
void wrapper.offsetHeight;
|
|
1439
|
+
wrapper.style.transition = transition;
|
|
1440
|
+
}
|
|
1422
1441
|
wrapper.style.transform = 'translate3d(0, 0, 0)';
|
|
1442
|
+
hasPresented.current = true;
|
|
1423
1443
|
}
|
|
1424
1444
|
}
|
|
1425
1445
|
wasOpenRef.current = isOpen;
|
|
1426
|
-
}, [isOpen, detached, drawerRef]);
|
|
1446
|
+
}, [isOpen, detached, drawerRef, initialAnimated, hasPresented]);
|
|
1427
1447
|
|
|
1428
1448
|
// Subsequent snap writes belong to useSnapPoints. A React style update on
|
|
1429
1449
|
// release would overwrite the live drag value before its transition starts.
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
// @ts-nocheck — vendored upstream, incompatible with noUncheckedIndexedAccess
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { set, isVertical, getSnapPointTransform } from './helpers';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_PEEK_HEIGHT,
|
|
6
|
+
SHORT_DISMISS_DISTANCE,
|
|
7
|
+
SHORT_DISMISS_VELOCITY,
|
|
8
|
+
TRANSITIONS,
|
|
9
|
+
VELOCITY_THRESHOLD,
|
|
10
|
+
} from './constants';
|
|
5
11
|
import { useControllableState } from './use-controllable-state';
|
|
6
12
|
import type { DrawerDirection } from './types';
|
|
7
13
|
|
|
@@ -29,7 +35,7 @@ export function useSnapPoints({
|
|
|
29
35
|
fadeFromIndex?: number;
|
|
30
36
|
drawerRef: React.RefObject<HTMLDivElement | null>;
|
|
31
37
|
overlayRef: React.RefObject<HTMLDivElement | null>;
|
|
32
|
-
onSnapPointChange(activeSnapPointIndex: number): void;
|
|
38
|
+
onSnapPointChange(activeSnapPointIndex: number, animated: boolean): void;
|
|
33
39
|
direction?: DrawerDirection;
|
|
34
40
|
container?: HTMLElement | null | undefined;
|
|
35
41
|
snapToSequentialPoint?: boolean;
|
|
@@ -187,17 +193,31 @@ export function useSnapPoints({
|
|
|
187
193
|
});
|
|
188
194
|
};
|
|
189
195
|
|
|
190
|
-
//
|
|
191
|
-
//
|
|
196
|
+
// Initial measurements can take several commits before the first paint.
|
|
197
|
+
// Keep all of them unanimated when initial presentation animation is disabled.
|
|
192
198
|
const hasSnappedRef = React.useRef(false);
|
|
199
|
+
const initialSnapFrameRef = React.useRef<number | null>(null);
|
|
200
|
+
React.useEffect(
|
|
201
|
+
() => () => {
|
|
202
|
+
if (initialSnapFrameRef.current !== null) {
|
|
203
|
+
window.cancelAnimationFrame(initialSnapFrameRef.current);
|
|
204
|
+
initialSnapFrameRef.current = null;
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
[]
|
|
208
|
+
);
|
|
193
209
|
const snapToPoint = React.useCallback(
|
|
194
210
|
(dimension: number) => {
|
|
211
|
+
if (!drawerRef.current) return;
|
|
195
212
|
const newSnapPointIndex =
|
|
196
213
|
snapPointsOffset?.findIndex((snapPointDim) => snapPointDim === dimension) ?? null;
|
|
197
|
-
onSnapPointChange(newSnapPointIndex);
|
|
198
214
|
|
|
199
215
|
const animateThisSnap = hasSnappedRef.current || initialAnimated;
|
|
200
|
-
|
|
216
|
+
if (initialSnapFrameRef.current === null) {
|
|
217
|
+
initialSnapFrameRef.current = window.requestAnimationFrame(() => {
|
|
218
|
+
hasSnappedRef.current = true;
|
|
219
|
+
});
|
|
220
|
+
}
|
|
201
221
|
// One animated value drives both the translation and the visible height.
|
|
202
222
|
set(drawerRef.current, {
|
|
203
223
|
'transition': animateThisSnap
|
|
@@ -210,6 +230,9 @@ export function useSnapPoints({
|
|
|
210
230
|
// Snapping implies drag overshoot (if any) should be undone.
|
|
211
231
|
setDetachedWrapperTransform(0, animateThisSnap);
|
|
212
232
|
|
|
233
|
+
const overlayTransition = animateThisSnap
|
|
234
|
+
? `opacity ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(',')})`
|
|
235
|
+
: 'none';
|
|
213
236
|
if (
|
|
214
237
|
snapPointsOffset &&
|
|
215
238
|
newSnapPointIndex !== snapPointsOffset.length - 1 &&
|
|
@@ -218,24 +241,33 @@ export function useSnapPoints({
|
|
|
218
241
|
newSnapPointIndex < fadeFromIndex
|
|
219
242
|
) {
|
|
220
243
|
set(overlayRef.current, {
|
|
221
|
-
transition:
|
|
244
|
+
transition: overlayTransition,
|
|
222
245
|
opacity: '0',
|
|
223
246
|
pointerEvents: 'none',
|
|
224
247
|
});
|
|
225
248
|
} else {
|
|
226
249
|
set(overlayRef.current, {
|
|
227
|
-
transition:
|
|
250
|
+
transition: overlayTransition,
|
|
228
251
|
opacity: '1',
|
|
229
252
|
pointerEvents: 'auto',
|
|
230
253
|
});
|
|
231
254
|
}
|
|
232
255
|
|
|
256
|
+
onSnapPointChange(newSnapPointIndex, animateThisSnap);
|
|
233
257
|
setActiveSnapPoint(snapPoints?.[Math.max(newSnapPointIndex, 0)]);
|
|
234
258
|
},
|
|
235
|
-
[
|
|
259
|
+
[
|
|
260
|
+
drawerRef.current,
|
|
261
|
+
snapPoints,
|
|
262
|
+
snapPointsOffset,
|
|
263
|
+
fadeFromIndex,
|
|
264
|
+
overlayRef,
|
|
265
|
+
setActiveSnapPoint,
|
|
266
|
+
initialAnimated,
|
|
267
|
+
]
|
|
236
268
|
);
|
|
237
269
|
|
|
238
|
-
React.
|
|
270
|
+
React.useLayoutEffect(() => {
|
|
239
271
|
if (!isOpen) return;
|
|
240
272
|
if (activeSnapPoint || activeSnapPointProp) {
|
|
241
273
|
const newIndex =
|
|
@@ -253,11 +285,13 @@ export function useSnapPoints({
|
|
|
253
285
|
closeDrawer,
|
|
254
286
|
velocity,
|
|
255
287
|
dismissible,
|
|
288
|
+
dismissThreshold,
|
|
256
289
|
}: {
|
|
257
290
|
draggedDistance: number;
|
|
258
291
|
closeDrawer: () => void;
|
|
259
292
|
velocity: number;
|
|
260
293
|
dismissible: boolean;
|
|
294
|
+
dismissThreshold: 'half' | 'short';
|
|
261
295
|
}) {
|
|
262
296
|
if (fadeFromIndex === undefined) return;
|
|
263
297
|
|
|
@@ -275,6 +309,23 @@ export function useSnapPoints({
|
|
|
275
309
|
});
|
|
276
310
|
}
|
|
277
311
|
|
|
312
|
+
// Short threshold: below the lowest detent, close on a short drag or any
|
|
313
|
+
// downward fling, like Compose Material3's sheet.
|
|
314
|
+
if (
|
|
315
|
+
dismissThreshold === 'short' &&
|
|
316
|
+
dismissible &&
|
|
317
|
+
(direction === 'bottom' || direction === 'right') &&
|
|
318
|
+
currentPosition > snapPointsOffset[0]
|
|
319
|
+
) {
|
|
320
|
+
const overshoot = currentPosition - snapPointsOffset[0];
|
|
321
|
+
if (velocity > SHORT_DISMISS_VELOCITY || overshoot > SHORT_DISMISS_DISTANCE) {
|
|
322
|
+
closeDrawer();
|
|
323
|
+
} else {
|
|
324
|
+
snapToPoint(snapPointsOffset[0]);
|
|
325
|
+
}
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
278
329
|
// Distance-based dismiss: when overshooting past the lowest detent by more
|
|
279
330
|
// than half of its visible height, commit to dismiss regardless of
|
|
280
331
|
// velocity — lets a slow drag-down still close the sheet.
|