@lodev09/react-native-true-sheet 4.0.0-beta.3 → 4.0.0-beta.30
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 +28 -5
- package/android/build.gradle +23 -1
- package/android/proguard-rules.pro +3 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +50 -16
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +303 -184
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentViewManager.kt +5 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterView.kt +38 -109
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetModule.kt +13 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayView.kt +127 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetOverlayViewManager.kt +45 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetPackage.kt +2 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetStateUpdater.kt +29 -3
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +234 -66
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +491 -186
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +64 -10
- package/android/src/main/java/com/lodev09/truesheet/core/RNScreensEventObserver.kt +31 -14
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetBehavior.kt +135 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt +23 -23
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +130 -6
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +104 -39
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDimView.kt +14 -17
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetGrabberView.kt +2 -4
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetOverlayRootView.kt +148 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetStackManager.kt +33 -40
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetLifecycleEvents.kt +15 -0
- package/android/src/main/java/com/lodev09/truesheet/utils/ColorUtils.kt +19 -0
- package/android/src/main/java/com/lodev09/truesheet/utils/KeyboardUtils.kt +15 -2
- package/android/src/main/java/com/lodev09/truesheet/utils/ScreenUtils.kt +0 -12
- package/android/src/main/java/com/lodev09/truesheet/utils/ViewUtils.kt +19 -7
- package/android/src/main/jni/TrueSheetSpec.h +1 -0
- package/android/src/main/jni/TrueSheetStateUpdater.cpp +37 -6
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewComponentDescriptor.h +1 -9
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.cpp +114 -30
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.h +30 -7
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.cpp +2 -2
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.h +9 -8
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewComponentDescriptor.h +3 -5
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.cpp +14 -3
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.h +5 -6
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.cpp +1 -1
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.h +10 -6
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.cpp +1 -1
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.h +1 -1
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetLayoutUtils.h +43 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewComponentDescriptor.h +21 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.cpp +17 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetOverlayViewShadowNode.h +33 -0
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewComponentDescriptor.h +3 -5
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp +7 -40
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.h +3 -7
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.cpp +3 -5
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewState.h +4 -5
- package/ios/TrueSheetContainerView.h +23 -7
- package/ios/TrueSheetContainerView.mm +45 -25
- package/ios/TrueSheetContentView.h +44 -15
- package/ios/TrueSheetContentView.mm +221 -190
- package/ios/TrueSheetFooterView.mm +133 -28
- package/ios/TrueSheetModule.h +2 -7
- package/ios/TrueSheetModule.mm +18 -18
- package/ios/TrueSheetOverlayView.h +42 -0
- package/ios/TrueSheetOverlayView.mm +175 -0
- package/ios/TrueSheetView.h +2 -0
- package/ios/TrueSheetView.mm +268 -52
- package/ios/TrueSheetViewController.h +36 -3
- package/ios/TrueSheetViewController.mm +797 -102
- package/ios/core/RNScreensEventObserver.mm +31 -28
- package/ios/core/TrueSheetBlurView.h +0 -2
- package/ios/core/TrueSheetBlurView.mm +3 -36
- package/ios/core/TrueSheetDetentCalculator.h +18 -1
- package/ios/core/TrueSheetDetentCalculator.mm +44 -9
- package/ios/core/TrueSheetDimView.h +27 -0
- package/ios/core/TrueSheetDimView.mm +41 -0
- package/ios/core/TrueSheetGrabberView.mm +13 -3
- package/ios/core/TrueSheetKeyboardObserver.h +7 -0
- package/ios/core/TrueSheetKeyboardObserver.mm +107 -15
- package/ios/core/TrueSheetOverlayContainerView.h +31 -0
- package/ios/core/TrueSheetOverlayContainerView.mm +35 -0
- package/ios/events/TrueSheetLifecycleEvents.h +2 -0
- package/ios/events/TrueSheetLifecycleEvents.mm +8 -0
- package/ios/tvos/TrueSheetStubs.mm +12 -0
- package/ios/utils/UIView+ScrollEdgeInteraction.h +1 -0
- package/ios/utils/UIView+ScrollEdgeInteraction.mm +24 -2
- package/lib/module/TrueSheet.js +222 -54
- package/lib/module/TrueSheet.js.map +1 -1
- package/lib/module/TrueSheet.web.js +364 -275
- package/lib/module/TrueSheet.web.js.map +1 -1
- package/lib/module/TrueSheetBackground.js +15 -0
- package/lib/module/TrueSheetBackground.js.map +1 -0
- package/lib/module/TrueSheetOverlay.js +30 -0
- package/lib/module/TrueSheetOverlay.js.map +1 -0
- package/lib/module/TrueSheetOverlay.web.js +35 -0
- package/lib/module/TrueSheetOverlay.web.js.map +1 -0
- package/lib/module/TrueSheetPeek.web.js +20 -15
- package/lib/module/TrueSheetPeek.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/detentBackgrounds.js +21 -0
- package/lib/module/detentBackgrounds.js.map +1 -0
- package/lib/module/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
- package/lib/module/fabric/TrueSheetFooterViewNativeComponent.ts +2 -3
- package/lib/module/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
- package/lib/module/fabric/TrueSheetViewNativeComponent.ts +89 -73
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/mocks/index.js +18 -2
- package/lib/module/mocks/index.js.map +1 -1
- package/lib/module/mocks/navigationExpoRouter.js.map +1 -1
- package/lib/module/navigation/base-types.js +2 -0
- package/lib/module/navigation/base-types.js.map +1 -0
- package/lib/module/navigation/createTrueSheetRouter.js +158 -100
- package/lib/module/navigation/createTrueSheetRouter.js.map +1 -1
- package/lib/module/navigation/expo-router/base-types.js +2 -0
- package/lib/module/navigation/expo-router/base-types.js.map +1 -0
- package/lib/module/navigation/expo-router/extendTrueSheetRouter.js +71 -0
- package/lib/module/navigation/expo-router/extendTrueSheetRouter.js.map +1 -0
- package/lib/module/navigation/expo-router/index.js +18 -7
- package/lib/module/navigation/expo-router/index.js.map +1 -1
- package/lib/module/navigation/expo-router/types.js +4 -0
- package/lib/module/navigation/expo-router/types.js.map +1 -0
- package/lib/module/navigation/index.js.map +1 -1
- package/lib/module/navigation/screen/useSheetScreenState.js +1 -0
- package/lib/module/navigation/screen/useSheetScreenState.js.map +1 -1
- package/lib/module/scrollable.js +28 -0
- package/lib/module/scrollable.js.map +1 -0
- package/lib/module/web/constants.js +1 -1
- package/lib/module/web/constants.js.map +1 -1
- package/lib/module/web/keyboard.js +69 -0
- package/lib/module/web/keyboard.js.map +1 -0
- package/lib/module/web/layout.js +168 -0
- package/lib/module/web/layout.js.map +1 -0
- package/lib/module/web/vaul/constants.js +9 -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/helpers.js +1 -0
- package/lib/module/web/vaul/helpers.js.map +1 -1
- package/lib/module/web/vaul/index.js +83 -21
- package/lib/module/web/vaul/index.js.map +1 -1
- package/lib/module/web/vaul/use-snap-points.js +57 -28
- package/lib/module/web/vaul/use-snap-points.js.map +1 -1
- package/lib/typescript/src/TrueSheet.d.ts +18 -1
- package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.types.d.ts +209 -35
- 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/TrueSheetBackground.d.ts +3 -0
- package/lib/typescript/src/TrueSheetBackground.d.ts.map +1 -0
- package/lib/typescript/src/TrueSheetOverlay.d.ts +8 -0
- package/lib/typescript/src/TrueSheetOverlay.d.ts.map +1 -0
- package/lib/typescript/src/TrueSheetOverlay.web.d.ts +8 -0
- package/lib/typescript/src/TrueSheetOverlay.web.d.ts.map +1 -0
- package/lib/typescript/src/TrueSheetPeek.web.d.ts +8 -4
- package/lib/typescript/src/TrueSheetPeek.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/detentBackgrounds.d.ts +10 -0
- package/lib/typescript/src/detentBackgrounds.d.ts.map +1 -0
- 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/TrueSheetOverlayViewNativeComponent.d.ts +6 -0
- package/lib/typescript/src/fabric/TrueSheetOverlayViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +76 -65
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/mocks/index.d.ts +4 -0
- package/lib/typescript/src/mocks/index.d.ts.map +1 -1
- package/lib/typescript/src/mocks/navigationExpoRouter.d.ts +3 -3
- package/lib/typescript/src/mocks/navigationExpoRouter.d.ts.map +1 -1
- package/lib/typescript/src/navigation/base-types.d.ts +9 -0
- package/lib/typescript/src/navigation/base-types.d.ts.map +1 -0
- package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts +36 -10
- package/lib/typescript/src/navigation/createTrueSheetRouter.d.ts.map +1 -1
- package/lib/typescript/src/navigation/expo-router/base-types.d.ts +8 -0
- package/lib/typescript/src/navigation/expo-router/base-types.d.ts.map +1 -0
- package/lib/typescript/src/navigation/expo-router/extendTrueSheetRouter.d.ts +33 -0
- package/lib/typescript/src/navigation/expo-router/extendTrueSheetRouter.d.ts.map +1 -0
- package/lib/typescript/src/navigation/expo-router/index.d.ts +9 -6
- package/lib/typescript/src/navigation/expo-router/index.d.ts.map +1 -1
- package/lib/typescript/src/navigation/expo-router/types.d.ts +44 -0
- package/lib/typescript/src/navigation/expo-router/types.d.ts.map +1 -0
- package/lib/typescript/src/navigation/index.d.ts +3 -4
- package/lib/typescript/src/navigation/index.d.ts.map +1 -1
- package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts +2 -1
- package/lib/typescript/src/navigation/screen/useSheetScreenState.d.ts.map +1 -1
- package/lib/typescript/src/navigation/types.d.ts +43 -6
- 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/constants.d.ts +1 -1
- package/lib/typescript/src/web/constants.d.ts.map +1 -1
- 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 +20 -0
- package/lib/typescript/src/web/layout.d.ts.map +1 -0
- package/lib/typescript/src/web/vaul/constants.d.ts +3 -0
- package/lib/typescript/src/web/vaul/constants.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/context.d.ts +5 -1
- package/lib/typescript/src/web/vaul/context.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/helpers.d.ts +1 -0
- package/lib/typescript/src/web/vaul/helpers.d.ts.map +1 -1
- package/lib/typescript/src/web/vaul/index.d.ts +20 -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 +6 -4
- package/lib/typescript/src/web/vaul/use-snap-points.d.ts.map +1 -1
- package/package.json +18 -8
- package/react-native.config.js +1 -0
- package/src/TrueSheet.tsx +257 -62
- package/src/TrueSheet.types.ts +225 -35
- package/src/TrueSheet.web.tsx +445 -333
- package/src/TrueSheetBackground.tsx +17 -0
- package/src/TrueSheetOverlay.tsx +24 -0
- package/src/TrueSheetOverlay.web.tsx +33 -0
- package/src/TrueSheetPeek.web.tsx +25 -23
- package/src/TrueSheetProvider.web.tsx +22 -13
- package/src/detentBackgrounds.ts +24 -0
- package/src/fabric/TrueSheetContentViewNativeComponent.ts +1 -1
- package/src/fabric/TrueSheetFooterViewNativeComponent.ts +2 -3
- package/src/fabric/TrueSheetOverlayViewNativeComponent.ts +12 -0
- package/src/fabric/TrueSheetViewNativeComponent.ts +89 -73
- package/src/index.ts +1 -0
- package/src/mocks/index.ts +17 -2
- package/src/mocks/navigationExpoRouter.ts +6 -5
- package/src/navigation/base-types.ts +20 -0
- package/src/navigation/createTrueSheetRouter.ts +220 -144
- package/src/navigation/expo-router/base-types.ts +18 -0
- package/src/navigation/expo-router/extendTrueSheetRouter.ts +99 -0
- package/src/navigation/expo-router/index.ts +41 -30
- package/src/navigation/expo-router/types.ts +116 -0
- package/src/navigation/index.ts +6 -7
- package/src/navigation/screen/useSheetScreenState.ts +2 -0
- package/src/navigation/types.ts +81 -23
- package/src/scrollable.ts +25 -0
- package/src/web/constants.ts +1 -1
- package/src/web/keyboard.ts +79 -0
- package/src/web/layout.ts +196 -0
- package/src/web/vaul/constants.ts +9 -0
- package/src/web/vaul/context.ts +7 -1
- package/src/web/vaul/helpers.ts +5 -0
- package/src/web/vaul/index.tsx +113 -16
- package/src/web/vaul/types.ts +10 -0
- package/src/web/vaul/use-snap-points.ts +86 -33
package/src/web/vaul/context.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { DrawerDirection } from './types';
|
|
2
|
+
import type { DrawerDirection, KeyboardViewport } from './types';
|
|
3
3
|
|
|
4
4
|
export interface DragEvent {
|
|
5
5
|
target: EventTarget;
|
|
@@ -23,6 +23,7 @@ interface DrawerContextValue {
|
|
|
23
23
|
onNestedOpenChange: (o: boolean) => void;
|
|
24
24
|
onNestedRelease: (event: React.PointerEvent<HTMLDivElement>, open: boolean) => void;
|
|
25
25
|
dismissible: boolean;
|
|
26
|
+
onDismissAttempt?: () => void;
|
|
26
27
|
isOpen: boolean;
|
|
27
28
|
isDragging: boolean;
|
|
28
29
|
keyboardIsOpen: React.MutableRefObject<boolean>;
|
|
@@ -45,12 +46,15 @@ interface DrawerContextValue {
|
|
|
45
46
|
container?: HTMLElement | null;
|
|
46
47
|
autoFocus?: boolean;
|
|
47
48
|
shouldAnimate?: React.RefObject<boolean>;
|
|
49
|
+
initialAnimated: boolean;
|
|
50
|
+
hasPresented: React.RefObject<boolean>;
|
|
48
51
|
onPositionChangeRef: React.RefObject<((position: number) => void) | undefined>;
|
|
49
52
|
setContentHeight: (height: number) => void;
|
|
50
53
|
detached: boolean;
|
|
51
54
|
detachedOffset: number;
|
|
52
55
|
detachedRadius: number;
|
|
53
56
|
detachedWrapperStyle?: React.CSSProperties;
|
|
57
|
+
keyboardViewport?: KeyboardViewport;
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
export const DrawerContext = React.createContext<DrawerContextValue>({
|
|
@@ -80,6 +84,8 @@ export const DrawerContext = React.createContext<DrawerContextValue>({
|
|
|
80
84
|
closeDrawer: () => {},
|
|
81
85
|
direction: 'bottom',
|
|
82
86
|
shouldAnimate: { current: true },
|
|
87
|
+
initialAnimated: true,
|
|
88
|
+
hasPresented: { current: false },
|
|
83
89
|
shouldScaleBackground: false,
|
|
84
90
|
setBackgroundColorOnScale: true,
|
|
85
91
|
noBodyStyles: false,
|
package/src/web/vaul/helpers.ts
CHANGED
|
@@ -80,6 +80,11 @@ export const isVertical = (direction: DrawerDirection) => {
|
|
|
80
80
|
}
|
|
81
81
|
};
|
|
82
82
|
|
|
83
|
+
export const getSnapPointTransform = (direction: DrawerDirection) =>
|
|
84
|
+
isVertical(direction)
|
|
85
|
+
? 'translate3d(0, var(--snap-point-height), 0)'
|
|
86
|
+
: 'translate3d(var(--snap-point-height), 0, 0)';
|
|
87
|
+
|
|
83
88
|
export function getTranslate(element: HTMLElement, direction: DrawerDirection): number | null {
|
|
84
89
|
if (!element) {
|
|
85
90
|
return null;
|
package/src/web/vaul/index.tsx
CHANGED
|
@@ -12,6 +12,7 @@ import { isIOS, isMobileFirefox } from './browser';
|
|
|
12
12
|
import {
|
|
13
13
|
BORDER_RADIUS,
|
|
14
14
|
CLOSE_THRESHOLD,
|
|
15
|
+
DISMISS_ATTEMPT_THRESHOLD,
|
|
15
16
|
DRAG_CLASS,
|
|
16
17
|
NESTED_DISPLACEMENT,
|
|
17
18
|
SCROLL_LOCK_TIMEOUT,
|
|
@@ -19,8 +20,15 @@ import {
|
|
|
19
20
|
VELOCITY_THRESHOLD,
|
|
20
21
|
WINDOW_TOP_OFFSET,
|
|
21
22
|
} from './constants';
|
|
22
|
-
import {
|
|
23
|
-
|
|
23
|
+
import {
|
|
24
|
+
dampenValue,
|
|
25
|
+
getSnapPointTransform,
|
|
26
|
+
getTranslate,
|
|
27
|
+
isVertical,
|
|
28
|
+
reset,
|
|
29
|
+
set,
|
|
30
|
+
} from './helpers';
|
|
31
|
+
import type { DrawerDirection, KeyboardViewport } from './types';
|
|
24
32
|
import { useComposedRefs } from './use-composed-refs';
|
|
25
33
|
import { useControllableState } from './use-controllable-state';
|
|
26
34
|
import { usePositionFixed } from './use-position-fixed';
|
|
@@ -93,6 +101,18 @@ export type DialogProps = {
|
|
|
93
101
|
* @default true
|
|
94
102
|
*/
|
|
95
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';
|
|
110
|
+
/**
|
|
111
|
+
* Called when the user tries to close a non-dismissible drawer — dragging down
|
|
112
|
+
* at the first snap point, pressing escape, or tapping the handle of a
|
|
113
|
+
* single-snap-point drawer. Overlay clicks don't count, mirroring iOS.
|
|
114
|
+
*/
|
|
115
|
+
onDismissAttempt?: () => void;
|
|
96
116
|
/**
|
|
97
117
|
* When `false` the drawer can't be dragged by the user. Programmatic snap-point
|
|
98
118
|
* changes still animate. Defaults to `true`.
|
|
@@ -204,6 +224,12 @@ export type DialogProps = {
|
|
|
204
224
|
* from natural flow.
|
|
205
225
|
*/
|
|
206
226
|
contentHeight?: number;
|
|
227
|
+
/**
|
|
228
|
+
* Lays the drawer out in the visual viewport above a soft keyboard. The
|
|
229
|
+
* wrapper and snap offsets fit the visible area, while fractional snap
|
|
230
|
+
* points keep scaling against the full layout height.
|
|
231
|
+
*/
|
|
232
|
+
keyboardViewport?: KeyboardViewport;
|
|
207
233
|
} & (WithFadeFromProps | WithoutFadeFromProps);
|
|
208
234
|
|
|
209
235
|
export function Root({
|
|
@@ -218,6 +244,8 @@ export function Root({
|
|
|
218
244
|
closeThreshold = CLOSE_THRESHOLD,
|
|
219
245
|
scrollLockTimeout = SCROLL_LOCK_TIMEOUT,
|
|
220
246
|
dismissible = true,
|
|
247
|
+
dismissThreshold = 'half',
|
|
248
|
+
onDismissAttempt,
|
|
221
249
|
draggable = true,
|
|
222
250
|
handleOnly = false,
|
|
223
251
|
fadeFromIndex = snapPoints && snapPoints.length - 1,
|
|
@@ -247,6 +275,7 @@ export function Root({
|
|
|
247
275
|
initialAnimated = true,
|
|
248
276
|
onContentHeightChange,
|
|
249
277
|
contentHeight: contentHeightProp,
|
|
278
|
+
keyboardViewport,
|
|
250
279
|
}: DialogProps) {
|
|
251
280
|
const [isOpen = false, setIsOpen] = useControllableState({
|
|
252
281
|
defaultProp: defaultOpen,
|
|
@@ -274,8 +303,10 @@ export function Root({
|
|
|
274
303
|
const isAllowedToDrag = React.useRef<boolean>(false);
|
|
275
304
|
const nestedOpenChangeTimer = React.useRef<NodeJS.Timeout | null>(null);
|
|
276
305
|
const pointerStart = React.useRef(0);
|
|
306
|
+
const dismissAttempted = React.useRef(false);
|
|
277
307
|
const keyboardIsOpen = React.useRef(false);
|
|
278
308
|
const shouldAnimate = React.useRef(!defaultOpen);
|
|
309
|
+
const hasPresented = React.useRef(false);
|
|
279
310
|
const previousDiffFromInitial = React.useRef(0);
|
|
280
311
|
const drawerRef = React.useRef<HTMLDivElement>(null);
|
|
281
312
|
const drawerHeightRef = React.useRef(drawerRef.current?.getBoundingClientRect().height || 0);
|
|
@@ -292,10 +323,14 @@ export function Root({
|
|
|
292
323
|
onContentHeightChangeRef.current?.(measuredContentHeight);
|
|
293
324
|
}, [measuredContentHeight]);
|
|
294
325
|
|
|
295
|
-
const onSnapPointChange = React.useCallback((activeSnapPointIndex: number) => {
|
|
326
|
+
const onSnapPointChange = React.useCallback((activeSnapPointIndex: number, animated: boolean) => {
|
|
296
327
|
// Change openTime ref when we reach the last snap point to prevent dragging for 500ms incase it's scrollable.
|
|
297
328
|
if (snapPoints && activeSnapPointIndex === snapPointsOffset.length - 1)
|
|
298
329
|
openTime.current = new Date();
|
|
330
|
+
// Instant snaps do not emit transition events for position tracking.
|
|
331
|
+
if (!animated && drawerRef.current) {
|
|
332
|
+
onPositionChangeRef.current?.(drawerRef.current.getBoundingClientRect().top);
|
|
333
|
+
}
|
|
299
334
|
}, []);
|
|
300
335
|
|
|
301
336
|
const {
|
|
@@ -324,6 +359,7 @@ export function Root({
|
|
|
324
359
|
maxContentHeight,
|
|
325
360
|
peekHeight,
|
|
326
361
|
initialAnimated,
|
|
362
|
+
keyboardViewport,
|
|
327
363
|
});
|
|
328
364
|
|
|
329
365
|
usePreventScroll({
|
|
@@ -517,7 +553,17 @@ export function Root({
|
|
|
517
553
|
const noCloseSnapPointsPreCondition = snapPoints && !dismissible && !isDraggingInDirection;
|
|
518
554
|
|
|
519
555
|
// Disallow dragging down to close when first snap point is the active one and dismissible prop is set to false.
|
|
520
|
-
if (noCloseSnapPointsPreCondition && activeSnapPointIndex === 0)
|
|
556
|
+
if (noCloseSnapPointsPreCondition && activeSnapPointIndex === 0) {
|
|
557
|
+
// The drawer stays put. Remember a deliberate pull that would otherwise
|
|
558
|
+
// have dragged it (content at its top) so release() can report it.
|
|
559
|
+
if (
|
|
560
|
+
-draggedDistance > DISMISS_ATTEMPT_THRESHOLD &&
|
|
561
|
+
(isAllowedToDrag.current || shouldDrag(event.target, isDraggingInDirection))
|
|
562
|
+
) {
|
|
563
|
+
dismissAttempted.current = true;
|
|
564
|
+
}
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
521
567
|
|
|
522
568
|
// We need to capture last time when drag with scroll was triggered and have a timeout between
|
|
523
569
|
const absDraggedDistance = Math.abs(draggedDistance);
|
|
@@ -778,6 +824,21 @@ export function Root({
|
|
|
778
824
|
unfreezeScrollables();
|
|
779
825
|
setIsDragging(false);
|
|
780
826
|
dragEndTime.current = new Date();
|
|
827
|
+
|
|
828
|
+
if (dismissAttempted.current) {
|
|
829
|
+
dismissAttempted.current = false;
|
|
830
|
+
if (isOpen) onDismissAttempt?.();
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// The press that started this "drag" may have dismissed the drawer (e.g. a
|
|
834
|
+
// back button acting on pointerdown). Snapping now would overwrite the
|
|
835
|
+
// dismiss transform that closeDrawer just wrote and freeze the sheet in
|
|
836
|
+
// place until it unmounts.
|
|
837
|
+
if (!isOpen) {
|
|
838
|
+
if (pointerEvent) onReleaseProp?.(pointerEvent, false);
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
|
|
781
842
|
const swipeAmount = getTranslate(drawerRef.current, direction);
|
|
782
843
|
|
|
783
844
|
// Drag overshoot below the lowest snap point translates the detached
|
|
@@ -814,6 +875,7 @@ export function Root({
|
|
|
814
875
|
closeDrawer,
|
|
815
876
|
velocity,
|
|
816
877
|
dismissible,
|
|
878
|
+
dismissThreshold,
|
|
817
879
|
});
|
|
818
880
|
if (pointerEvent) onReleaseProp?.(pointerEvent, true);
|
|
819
881
|
return;
|
|
@@ -966,7 +1028,10 @@ export function Root({
|
|
|
966
1028
|
onTouchDrag: (event, pointerEvent) => drag(event, pointerEvent),
|
|
967
1029
|
onTouchRelease: (event, pointerEvent) => release(event, pointerEvent),
|
|
968
1030
|
dismissible,
|
|
1031
|
+
onDismissAttempt,
|
|
969
1032
|
shouldAnimate,
|
|
1033
|
+
initialAnimated,
|
|
1034
|
+
hasPresented,
|
|
970
1035
|
handleOnly,
|
|
971
1036
|
isOpen,
|
|
972
1037
|
isDragging,
|
|
@@ -992,6 +1057,7 @@ export function Root({
|
|
|
992
1057
|
detachedOffset: detached ? detachedOffset : 0,
|
|
993
1058
|
detachedRadius: detached ? detachedRadius : 0,
|
|
994
1059
|
detachedWrapperStyle,
|
|
1060
|
+
keyboardViewport,
|
|
995
1061
|
}}
|
|
996
1062
|
>
|
|
997
1063
|
{children}
|
|
@@ -1121,9 +1187,12 @@ export const Content = React.forwardRef<HTMLDivElement, ContentProps>(
|
|
|
1121
1187
|
snapPoints,
|
|
1122
1188
|
setActiveSnapPoint,
|
|
1123
1189
|
dismissible,
|
|
1190
|
+
onDismissAttempt,
|
|
1124
1191
|
container,
|
|
1125
1192
|
handleOnly,
|
|
1126
1193
|
shouldAnimate,
|
|
1194
|
+
initialAnimated,
|
|
1195
|
+
hasPresented,
|
|
1127
1196
|
autoFocus,
|
|
1128
1197
|
onPositionChangeRef,
|
|
1129
1198
|
setContentHeight,
|
|
@@ -1131,6 +1200,7 @@ export const Content = React.forwardRef<HTMLDivElement, ContentProps>(
|
|
|
1131
1200
|
detachedOffset,
|
|
1132
1201
|
detachedRadius,
|
|
1133
1202
|
detachedWrapperStyle: detachedWrapperStyleProp,
|
|
1203
|
+
keyboardViewport,
|
|
1134
1204
|
} = useDrawerContext();
|
|
1135
1205
|
// Always measure the inner wrapper's natural height. The drawer itself may
|
|
1136
1206
|
// be styled to a fixed viewport height, so we measure an inner wrapper
|
|
@@ -1265,11 +1335,17 @@ export const Content = React.forwardRef<HTMLDivElement, ContentProps>(
|
|
|
1265
1335
|
// same detent). Without this, position goes stale mid-animation because
|
|
1266
1336
|
// the drawer's `transitionrun` never fires.
|
|
1267
1337
|
const onTransitionRun = (e: TransitionEvent) => {
|
|
1268
|
-
if (
|
|
1338
|
+
if (
|
|
1339
|
+
(e.target === drawer || e.target === wrapper) &&
|
|
1340
|
+
(e.propertyName === 'transform' || e.propertyName === '--snap-point-height')
|
|
1341
|
+
)
|
|
1269
1342
|
state.start();
|
|
1270
1343
|
};
|
|
1271
1344
|
const onTransitionDone = (e: TransitionEvent) => {
|
|
1272
|
-
if (
|
|
1345
|
+
if (
|
|
1346
|
+
(e.target === drawer || e.target === wrapper) &&
|
|
1347
|
+
(e.propertyName === 'transform' || e.propertyName === '--snap-point-height')
|
|
1348
|
+
)
|
|
1273
1349
|
state.stop();
|
|
1274
1350
|
};
|
|
1275
1351
|
const onAnimationStart = (e: AnimationEvent) => {
|
|
@@ -1327,14 +1403,19 @@ export const Content = React.forwardRef<HTMLDivElement, ContentProps>(
|
|
|
1327
1403
|
// with a bottom gap and rounded bottom corners; otherwise it sits flush.
|
|
1328
1404
|
// Transform/transition are managed imperatively (via drag overshoot and the
|
|
1329
1405
|
// dismiss effect) so React doesn't skip DOM writes for values it thinks it
|
|
1330
|
-
// already owns.
|
|
1406
|
+
// already owns. Above a soft keyboard the wrapper fits the visual viewport,
|
|
1407
|
+
// which the full-height layout viewport extends behind.
|
|
1331
1408
|
const wrapperStyle = React.useMemo<React.CSSProperties>(
|
|
1332
1409
|
() => ({
|
|
1333
1410
|
position: 'fixed',
|
|
1334
|
-
top: 0,
|
|
1335
1411
|
left: 0,
|
|
1336
1412
|
right: 0,
|
|
1337
|
-
|
|
1413
|
+
...(keyboardViewport
|
|
1414
|
+
? {
|
|
1415
|
+
top: keyboardViewport.top,
|
|
1416
|
+
height: Math.max(0, keyboardViewport.height - (detached ? detachedOffset : 0)),
|
|
1417
|
+
}
|
|
1418
|
+
: { top: 0, bottom: detached ? detachedOffset : 0 }),
|
|
1338
1419
|
overflow: 'hidden',
|
|
1339
1420
|
contain: 'paint',
|
|
1340
1421
|
pointerEvents: 'none',
|
|
@@ -1342,7 +1423,7 @@ export const Content = React.forwardRef<HTMLDivElement, ContentProps>(
|
|
|
1342
1423
|
borderBottomRightRadius: detached ? detachedRadius : 0,
|
|
1343
1424
|
...detachedWrapperStyleProp,
|
|
1344
1425
|
}),
|
|
1345
|
-
[detached, detachedOffset, detachedRadius, detachedWrapperStyleProp]
|
|
1426
|
+
[detached, detachedOffset, detachedRadius, detachedWrapperStyleProp, keyboardViewport]
|
|
1346
1427
|
);
|
|
1347
1428
|
|
|
1348
1429
|
// Translate the wrapper off-screen on dismiss so the whole card slides out
|
|
@@ -1366,16 +1447,22 @@ export const Content = React.forwardRef<HTMLDivElement, ContentProps>(
|
|
|
1366
1447
|
wrapper.style.transform = `translate3d(0, ${viewportH}px, 0)`;
|
|
1367
1448
|
} else if (isOpen && !wasOpenRef.current) {
|
|
1368
1449
|
wrapper.style.transition = 'none';
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1450
|
+
if (hasPresented.current || initialAnimated) {
|
|
1451
|
+
wrapper.style.transform = `translate3d(0, ${viewportH}px, 0)`;
|
|
1452
|
+
// eslint-disable-next-line no-void
|
|
1453
|
+
void wrapper.offsetHeight;
|
|
1454
|
+
wrapper.style.transition = transition;
|
|
1455
|
+
}
|
|
1373
1456
|
wrapper.style.transform = 'translate3d(0, 0, 0)';
|
|
1457
|
+
hasPresented.current = true;
|
|
1374
1458
|
}
|
|
1375
1459
|
}
|
|
1376
1460
|
wasOpenRef.current = isOpen;
|
|
1377
|
-
}, [isOpen, detached, drawerRef]);
|
|
1461
|
+
}, [isOpen, detached, drawerRef, initialAnimated, hasPresented]);
|
|
1378
1462
|
|
|
1463
|
+
// Subsequent snap writes belong to useSnapPoints. A React style update on
|
|
1464
|
+
// release would overwrite the live drag value before its transition starts.
|
|
1465
|
+
const initialSnapPointOffset = React.useRef(snapPointsOffset?.[activeSnapPointIndex ?? 0] ?? 0);
|
|
1379
1466
|
const contentNode = (
|
|
1380
1467
|
<DialogPrimitive.Content
|
|
1381
1468
|
data-vaul-drawer-direction={direction}
|
|
@@ -1390,7 +1477,8 @@ export const Content = React.forwardRef<HTMLDivElement, ContentProps>(
|
|
|
1390
1477
|
style={
|
|
1391
1478
|
snapPointsOffset && snapPointsOffset.length > 0
|
|
1392
1479
|
? ({
|
|
1393
|
-
'--snap-point-height': `${
|
|
1480
|
+
'--snap-point-height': `${initialSnapPointOffset.current}px`,
|
|
1481
|
+
'transform': getSnapPointTransform(direction),
|
|
1394
1482
|
...style,
|
|
1395
1483
|
'pointerEvents': 'auto',
|
|
1396
1484
|
} as React.CSSProperties)
|
|
@@ -1453,6 +1541,8 @@ export const Content = React.forwardRef<HTMLDivElement, ContentProps>(
|
|
|
1453
1541
|
activeSnapPointIndex > 0
|
|
1454
1542
|
) {
|
|
1455
1543
|
setActiveSnapPoint(snapPoints![0]!);
|
|
1544
|
+
} else {
|
|
1545
|
+
onDismissAttempt?.();
|
|
1456
1546
|
}
|
|
1457
1547
|
}
|
|
1458
1548
|
}}
|
|
@@ -1594,6 +1684,7 @@ export const Handle = React.forwardRef<HTMLDivElement, HandleProps>(
|
|
|
1594
1684
|
activeSnapPoint,
|
|
1595
1685
|
setActiveSnapPoint,
|
|
1596
1686
|
dismissible,
|
|
1687
|
+
onDismissAttempt,
|
|
1597
1688
|
handleOnly,
|
|
1598
1689
|
isOpen,
|
|
1599
1690
|
onPress,
|
|
@@ -1637,6 +1728,12 @@ export const Handle = React.forwardRef<HTMLDivElement, HandleProps>(
|
|
|
1637
1728
|
return;
|
|
1638
1729
|
}
|
|
1639
1730
|
|
|
1731
|
+
// Mirrors native: a single-detent sheet's handle tap is a dismiss.
|
|
1732
|
+
if (isLastSnapPoint && snapPoints.length === 1) {
|
|
1733
|
+
onDismissAttempt?.();
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1640
1737
|
const currentSnapIndex = snapPoints.findIndex((point) => point === activeSnapPoint);
|
|
1641
1738
|
if (currentSnapIndex === -1) return; // activeSnapPoint not found in snapPoints
|
|
1642
1739
|
const nextSnapPoint = snapPoints[currentSnapIndex + 1];
|
package/src/web/vaul/types.ts
CHANGED
|
@@ -5,3 +5,13 @@ export interface SnapPoint {
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export type AnyFunction = (...args: any) => any;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The visual viewport while a soft keyboard shrinks it, in layout-viewport px.
|
|
11
|
+
* `layoutHeight` is the full (keyboard-free) height.
|
|
12
|
+
*/
|
|
13
|
+
export interface KeyboardViewport {
|
|
14
|
+
top: number;
|
|
15
|
+
height: number;
|
|
16
|
+
layoutHeight: number;
|
|
17
|
+
}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
// @ts-nocheck — vendored upstream, incompatible with noUncheckedIndexedAccess
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { set, isVertical } from './helpers';
|
|
4
|
-
import {
|
|
3
|
+
import { set, isVertical, getSnapPointTransform } from './helpers';
|
|
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
|
-
import type { DrawerDirection } from './types';
|
|
12
|
+
import type { DrawerDirection, KeyboardViewport } from './types';
|
|
7
13
|
|
|
8
14
|
export function useSnapPoints({
|
|
9
15
|
activeSnapPointProp,
|
|
@@ -22,6 +28,7 @@ export function useSnapPoints({
|
|
|
22
28
|
maxContentHeight,
|
|
23
29
|
peekHeight,
|
|
24
30
|
initialAnimated = true,
|
|
31
|
+
keyboardViewport,
|
|
25
32
|
}: {
|
|
26
33
|
activeSnapPointProp?: number | string | null;
|
|
27
34
|
setActiveSnapPointProp?(snapPoint: number | null | string): void;
|
|
@@ -29,7 +36,7 @@ export function useSnapPoints({
|
|
|
29
36
|
fadeFromIndex?: number;
|
|
30
37
|
drawerRef: React.RefObject<HTMLDivElement | null>;
|
|
31
38
|
overlayRef: React.RefObject<HTMLDivElement | null>;
|
|
32
|
-
onSnapPointChange(activeSnapPointIndex: number): void;
|
|
39
|
+
onSnapPointChange(activeSnapPointIndex: number, animated: boolean): void;
|
|
33
40
|
direction?: DrawerDirection;
|
|
34
41
|
container?: HTMLElement | null | undefined;
|
|
35
42
|
snapToSequentialPoint?: boolean;
|
|
@@ -39,6 +46,7 @@ export function useSnapPoints({
|
|
|
39
46
|
maxContentHeight?: number;
|
|
40
47
|
peekHeight?: number;
|
|
41
48
|
initialAnimated?: boolean;
|
|
49
|
+
keyboardViewport?: KeyboardViewport;
|
|
42
50
|
}) {
|
|
43
51
|
const [activeSnapPoint, setActiveSnapPoint] = useControllableState<string | number | null>({
|
|
44
52
|
prop: activeSnapPointProp,
|
|
@@ -92,13 +100,18 @@ export function useSnapPoints({
|
|
|
92
100
|
height: container.getBoundingClientRect().height,
|
|
93
101
|
}
|
|
94
102
|
: typeof window !== 'undefined'
|
|
95
|
-
? { width: window.innerWidth, height: window.innerHeight }
|
|
103
|
+
? { width: window.innerWidth, height: keyboardViewport?.height ?? window.innerHeight }
|
|
96
104
|
: { width: 0, height: 0 };
|
|
97
105
|
|
|
98
106
|
// Shrink the effective vertical area by detachedOffset so snap math
|
|
99
107
|
// reserves a fixed gap at the bottom — the floating card stays offset
|
|
100
108
|
// from the viewport edge through drag, snap and resize.
|
|
101
109
|
const effectiveHeight = Math.max(0, containerSize.height - (detachedOffset || 0));
|
|
110
|
+
// Above a keyboard, fractional snap points keep their full-height size and
|
|
111
|
+
// only cap at the visible area — mirrors native, where the sheet rises.
|
|
112
|
+
const fractionHeight = keyboardViewport
|
|
113
|
+
? Math.max(0, keyboardViewport.layoutHeight - (detachedOffset || 0))
|
|
114
|
+
: effectiveHeight;
|
|
102
115
|
|
|
103
116
|
return (
|
|
104
117
|
snapPoints?.map((snapPoint) => {
|
|
@@ -123,11 +136,11 @@ export function useSnapPoints({
|
|
|
123
136
|
const rawHeight = isPx
|
|
124
137
|
? snapPointAsNumber
|
|
125
138
|
: windowDimensions
|
|
126
|
-
? resolved *
|
|
139
|
+
? resolved * fractionHeight
|
|
127
140
|
: 0;
|
|
128
|
-
// Cap
|
|
129
|
-
// doesn't push the drawer past the top
|
|
130
|
-
//
|
|
141
|
+
// Cap snap points so content taller than the viewport (or a
|
|
142
|
+
// fraction above a keyboard) doesn't push the drawer past the top
|
|
143
|
+
// edge (offset would go negative).
|
|
131
144
|
// `maxContentHeight`, when set, caps the visible sheet further so
|
|
132
145
|
// full/'auto' detents respect the user's ceiling.
|
|
133
146
|
const ceiling =
|
|
@@ -164,8 +177,11 @@ export function useSnapPoints({
|
|
|
164
177
|
detachedOffset,
|
|
165
178
|
maxContentHeight,
|
|
166
179
|
peekHeight,
|
|
180
|
+
keyboardViewport,
|
|
167
181
|
]);
|
|
168
182
|
|
|
183
|
+
const getViewportHeight = () => keyboardViewport?.height ?? window.innerHeight;
|
|
184
|
+
|
|
169
185
|
const activeSnapPointOffset = React.useMemo(
|
|
170
186
|
() => (activeSnapPointIndex !== null ? snapPointsOffset?.[activeSnapPointIndex] : null),
|
|
171
187
|
[snapPointsOffset, activeSnapPointIndex]
|
|
@@ -187,33 +203,46 @@ export function useSnapPoints({
|
|
|
187
203
|
});
|
|
188
204
|
};
|
|
189
205
|
|
|
190
|
-
//
|
|
191
|
-
//
|
|
206
|
+
// Initial measurements can take several commits before the first paint.
|
|
207
|
+
// Keep all of them unanimated when initial presentation animation is disabled.
|
|
192
208
|
const hasSnappedRef = React.useRef(false);
|
|
209
|
+
const initialSnapFrameRef = React.useRef<number | null>(null);
|
|
210
|
+
React.useEffect(
|
|
211
|
+
() => () => {
|
|
212
|
+
if (initialSnapFrameRef.current !== null) {
|
|
213
|
+
window.cancelAnimationFrame(initialSnapFrameRef.current);
|
|
214
|
+
initialSnapFrameRef.current = null;
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
[]
|
|
218
|
+
);
|
|
193
219
|
const snapToPoint = React.useCallback(
|
|
194
220
|
(dimension: number) => {
|
|
221
|
+
if (!drawerRef.current) return;
|
|
195
222
|
const newSnapPointIndex =
|
|
196
223
|
snapPointsOffset?.findIndex((snapPointDim) => snapPointDim === dimension) ?? null;
|
|
197
|
-
onSnapPointChange(newSnapPointIndex);
|
|
198
224
|
|
|
199
225
|
const animateThisSnap = hasSnappedRef.current || initialAnimated;
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
226
|
+
if (initialSnapFrameRef.current === null) {
|
|
227
|
+
initialSnapFrameRef.current = window.requestAnimationFrame(() => {
|
|
228
|
+
hasSnappedRef.current = true;
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
// One animated value drives both the translation and the visible height.
|
|
204
232
|
set(drawerRef.current, {
|
|
205
233
|
'transition': animateThisSnap
|
|
206
|
-
?
|
|
234
|
+
? `--snap-point-height ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(',')})`
|
|
207
235
|
: 'none',
|
|
208
|
-
'transform':
|
|
209
|
-
? `translate3d(0, ${dimension}px, 0)`
|
|
210
|
-
: `translate3d(${dimension}px, 0, 0)`,
|
|
236
|
+
'transform': getSnapPointTransform(direction),
|
|
211
237
|
'--snap-point-height': `${dimension}px`,
|
|
212
238
|
});
|
|
213
239
|
|
|
214
240
|
// Snapping implies drag overshoot (if any) should be undone.
|
|
215
241
|
setDetachedWrapperTransform(0, animateThisSnap);
|
|
216
242
|
|
|
243
|
+
const overlayTransition = animateThisSnap
|
|
244
|
+
? `opacity ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(',')})`
|
|
245
|
+
: 'none';
|
|
217
246
|
if (
|
|
218
247
|
snapPointsOffset &&
|
|
219
248
|
newSnapPointIndex !== snapPointsOffset.length - 1 &&
|
|
@@ -222,24 +251,33 @@ export function useSnapPoints({
|
|
|
222
251
|
newSnapPointIndex < fadeFromIndex
|
|
223
252
|
) {
|
|
224
253
|
set(overlayRef.current, {
|
|
225
|
-
transition:
|
|
254
|
+
transition: overlayTransition,
|
|
226
255
|
opacity: '0',
|
|
227
256
|
pointerEvents: 'none',
|
|
228
257
|
});
|
|
229
258
|
} else {
|
|
230
259
|
set(overlayRef.current, {
|
|
231
|
-
transition:
|
|
260
|
+
transition: overlayTransition,
|
|
232
261
|
opacity: '1',
|
|
233
262
|
pointerEvents: 'auto',
|
|
234
263
|
});
|
|
235
264
|
}
|
|
236
265
|
|
|
266
|
+
onSnapPointChange(newSnapPointIndex, animateThisSnap);
|
|
237
267
|
setActiveSnapPoint(snapPoints?.[Math.max(newSnapPointIndex, 0)]);
|
|
238
268
|
},
|
|
239
|
-
[
|
|
269
|
+
[
|
|
270
|
+
drawerRef.current,
|
|
271
|
+
snapPoints,
|
|
272
|
+
snapPointsOffset,
|
|
273
|
+
fadeFromIndex,
|
|
274
|
+
overlayRef,
|
|
275
|
+
setActiveSnapPoint,
|
|
276
|
+
initialAnimated,
|
|
277
|
+
]
|
|
240
278
|
);
|
|
241
279
|
|
|
242
|
-
React.
|
|
280
|
+
React.useLayoutEffect(() => {
|
|
243
281
|
if (!isOpen) return;
|
|
244
282
|
if (activeSnapPoint || activeSnapPointProp) {
|
|
245
283
|
const newIndex =
|
|
@@ -257,11 +295,13 @@ export function useSnapPoints({
|
|
|
257
295
|
closeDrawer,
|
|
258
296
|
velocity,
|
|
259
297
|
dismissible,
|
|
298
|
+
dismissThreshold,
|
|
260
299
|
}: {
|
|
261
300
|
draggedDistance: number;
|
|
262
301
|
closeDrawer: () => void;
|
|
263
302
|
velocity: number;
|
|
264
303
|
dismissible: boolean;
|
|
304
|
+
dismissThreshold: 'half' | 'short';
|
|
265
305
|
}) {
|
|
266
306
|
if (fadeFromIndex === undefined) return;
|
|
267
307
|
|
|
@@ -279,6 +319,23 @@ export function useSnapPoints({
|
|
|
279
319
|
});
|
|
280
320
|
}
|
|
281
321
|
|
|
322
|
+
// Short threshold: below the lowest detent, close on a short drag or any
|
|
323
|
+
// downward fling, like Compose Material3's sheet.
|
|
324
|
+
if (
|
|
325
|
+
dismissThreshold === 'short' &&
|
|
326
|
+
dismissible &&
|
|
327
|
+
(direction === 'bottom' || direction === 'right') &&
|
|
328
|
+
currentPosition > snapPointsOffset[0]
|
|
329
|
+
) {
|
|
330
|
+
const overshoot = currentPosition - snapPointsOffset[0];
|
|
331
|
+
if (velocity > SHORT_DISMISS_VELOCITY || overshoot > SHORT_DISMISS_DISTANCE) {
|
|
332
|
+
closeDrawer();
|
|
333
|
+
} else {
|
|
334
|
+
snapToPoint(snapPointsOffset[0]);
|
|
335
|
+
}
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
282
339
|
// Distance-based dismiss: when overshooting past the lowest detent by more
|
|
283
340
|
// than half of its visible height, commit to dismiss regardless of
|
|
284
341
|
// velocity — lets a slow drag-down still close the sheet.
|
|
@@ -287,7 +344,7 @@ export function useSnapPoints({
|
|
|
287
344
|
(direction === 'bottom' || direction === 'right') &&
|
|
288
345
|
currentPosition > snapPointsOffset[0]
|
|
289
346
|
) {
|
|
290
|
-
const viewportDim = isVertical(direction) ?
|
|
347
|
+
const viewportDim = isVertical(direction) ? getViewportHeight() : window.innerWidth;
|
|
291
348
|
const effectiveDim = Math.max(0, viewportDim - (detachedOffset || 0));
|
|
292
349
|
const visibleAtLowest = Math.max(0, effectiveDim - snapPointsOffset[0]);
|
|
293
350
|
const overshoot = currentPosition - snapPointsOffset[0];
|
|
@@ -315,7 +372,7 @@ export function useSnapPoints({
|
|
|
315
372
|
return Math.abs(curr - currentPosition) < Math.abs(prev - currentPosition) ? curr : prev;
|
|
316
373
|
});
|
|
317
374
|
|
|
318
|
-
const dim = isVertical(direction) ?
|
|
375
|
+
const dim = isVertical(direction) ? getViewportHeight() : window.innerWidth;
|
|
319
376
|
if (velocity > VELOCITY_THRESHOLD && Math.abs(draggedDistance) < dim * 0.4) {
|
|
320
377
|
const dragDirection = hasDraggedUp ? 1 : -1; // 1 = up, -1 = down
|
|
321
378
|
|
|
@@ -364,9 +421,7 @@ export function useSnapPoints({
|
|
|
364
421
|
if ((direction === 'bottom' || direction === 'right') && newValue > snapPointsOffset[0]) {
|
|
365
422
|
const excess = newValue - snapPointsOffset[0];
|
|
366
423
|
set(drawerRef.current, {
|
|
367
|
-
'transform':
|
|
368
|
-
? `translate3d(0, ${snapPointsOffset[0]}px, 0)`
|
|
369
|
-
: `translate3d(${snapPointsOffset[0]}px, 0, 0)`,
|
|
424
|
+
'transform': getSnapPointTransform(direction),
|
|
370
425
|
'--snap-point-height': `${snapPointsOffset[0]}px`,
|
|
371
426
|
});
|
|
372
427
|
setDetachedWrapperTransform(excess, false);
|
|
@@ -378,9 +433,7 @@ export function useSnapPoints({
|
|
|
378
433
|
// derived from it (e.g. the scrollable fill) resize with the drawer
|
|
379
434
|
// instead of staying cut off at the last detent's visible height.
|
|
380
435
|
set(drawerRef.current, {
|
|
381
|
-
'transform':
|
|
382
|
-
? `translate3d(0, ${newValue}px, 0)`
|
|
383
|
-
: `translate3d(${newValue}px, 0, 0)`,
|
|
436
|
+
'transform': getSnapPointTransform(direction),
|
|
384
437
|
'--snap-point-height': `${newValue}px`,
|
|
385
438
|
});
|
|
386
439
|
}
|
|
@@ -401,7 +454,7 @@ export function useSnapPoints({
|
|
|
401
454
|
if (activeSnapPointIndex === 0 && !isDraggingDown) {
|
|
402
455
|
const activeOffset = snapPointsOffset[activeSnapPointIndex];
|
|
403
456
|
if (activeOffset === undefined) return 0;
|
|
404
|
-
const windowDim = isVertical(direction) ?
|
|
457
|
+
const windowDim = isVertical(direction) ? getViewportHeight() : window.innerWidth;
|
|
405
458
|
const dismissOffset =
|
|
406
459
|
direction === 'bottom' || direction === 'right' ? windowDim : -windowDim;
|
|
407
460
|
const dismissRange = Math.abs(dismissOffset - activeOffset);
|