@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
|
@@ -1,27 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
/// <reference lib="dom" />
|
|
4
|
-
import { createElement, forwardRef, isValidElement, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { createElement, forwardRef, isValidElement, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
5
5
|
import { StyleSheet, useColorScheme, useWindowDimensions, View } from 'react-native';
|
|
6
6
|
import { measurePeekContentHeight, TrueSheetPeekContext } from "./TrueSheetPeek.web.js";
|
|
7
7
|
import { usePortalContainer, useRegisterSheet, useSheetStack } from "./TrueSheetProvider.web.js";
|
|
8
|
-
import { COLOR_SURFACE_CONTAINER_LOW_DARK, COLOR_SURFACE_CONTAINER_LOW_LIGHT,
|
|
8
|
+
import { COLOR_SURFACE_CONTAINER_LOW_DARK, COLOR_SURFACE_CONTAINER_LOW_LIGHT, DEFAULT_PLACEMENT_OFFSET, DEFAULT_CORNER_RADIUS, DEFAULT_DETACHED_OFFSET, DEFAULT_FORM_SHEET_HEIGHT_RATIO, DEFAULT_FORM_SHEET_WIDTH, DEFAULT_GRABBER_COLOR_DARK, DEFAULT_GRABBER_COLOR_LIGHT, DEFAULT_GRABBER_HEIGHT, DEFAULT_GRABBER_TOP_MARGIN, DEFAULT_GRABBER_WIDTH, DEFAULT_MAX_WIDTH } from "./web/constants.js";
|
|
9
9
|
import { getDOMElement } from "./web/dom.js";
|
|
10
|
+
import { TrueSheetBackground } from "./TrueSheetBackground.js";
|
|
11
|
+
import { getDetentBackgroundIndex, normalizeDetentBackground } from "./detentBackgrounds.js";
|
|
12
|
+
import { revealFocusedElement, useKeyboardViewport } from "./web/keyboard.js";
|
|
13
|
+
import { observeSheetLayout } from "./web/layout.js";
|
|
14
|
+
import { resolveScrollable } from "./scrollable.js";
|
|
10
15
|
import { Drawer } from "./web/vaul/index.js";
|
|
11
|
-
import { DEFAULT_PEEK_HEIGHT, TRANSITIONS } from "./web/vaul/constants.js";
|
|
16
|
+
import { DEFAULT_PEEK_HEIGHT, DRAG_CLASS, TRANSITIONS } from "./web/vaul/constants.js";
|
|
12
17
|
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
const {
|
|
20
|
-
overflowY
|
|
21
|
-
} = window.getComputedStyle(el);
|
|
22
|
-
if (overflowY === 'auto' || overflowY === 'scroll') return el;
|
|
23
|
-
}
|
|
24
|
-
return null;
|
|
18
|
+
// Resolves the user's `scrollableRef` to its scrollable DOM element. The
|
|
19
|
+
// node's first child is the content container.
|
|
20
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
21
|
+
const getScrollableElement = scrollable => {
|
|
22
|
+
const node = resolveScrollable(scrollable);
|
|
23
|
+
return node instanceof HTMLElement ? node : null;
|
|
25
24
|
};
|
|
26
25
|
const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
27
26
|
const {
|
|
@@ -29,18 +28,26 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
29
28
|
name,
|
|
30
29
|
dismissible = true,
|
|
31
30
|
draggable = true,
|
|
31
|
+
dismissThreshold = 'half',
|
|
32
32
|
cornerRadius,
|
|
33
33
|
style,
|
|
34
34
|
backgroundColor: backgroundColorProp,
|
|
35
|
+
detentBackgrounds,
|
|
36
|
+
background,
|
|
37
|
+
backgroundStyle,
|
|
35
38
|
maxContentHeight,
|
|
36
39
|
maxContentWidth,
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
placement = 'automatic',
|
|
41
|
+
placementOffset = DEFAULT_PLACEMENT_OFFSET,
|
|
42
|
+
scrollableRef,
|
|
43
|
+
scrollableOptions,
|
|
39
44
|
grabber = true,
|
|
40
45
|
grabberOptions,
|
|
41
46
|
accessibilityOptions,
|
|
42
47
|
detents = [0.5, 1],
|
|
43
48
|
dimmed = true,
|
|
49
|
+
dimColor,
|
|
50
|
+
dimOpacity,
|
|
44
51
|
dimmedDetentIndex = 0,
|
|
45
52
|
initialDetentIndex = -1,
|
|
46
53
|
header,
|
|
@@ -60,6 +67,7 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
60
67
|
onDidPresent,
|
|
61
68
|
onWillDismiss,
|
|
62
69
|
onDidDismiss,
|
|
70
|
+
onDismissAttempt,
|
|
63
71
|
onDetentChange,
|
|
64
72
|
onDragBegin,
|
|
65
73
|
onDragChange,
|
|
@@ -91,9 +99,26 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
91
99
|
// is explicitly set.
|
|
92
100
|
const effectiveDetached = isFormSheet || detached;
|
|
93
101
|
const colorScheme = useColorScheme();
|
|
94
|
-
const backgroundColor = backgroundColorProp ?? (colorScheme === 'dark' ? COLOR_SURFACE_CONTAINER_LOW_DARK : COLOR_SURFACE_CONTAINER_LOW_LIGHT);
|
|
95
102
|
const shouldAutoPresent = initialDetentIndex >= 0 && initialDetentIndex < validDetents.length;
|
|
103
|
+
const didInitiallyPresentRef = useRef(shouldAutoPresent);
|
|
96
104
|
const [isOpen, setIsOpen] = useState(shouldAutoPresent);
|
|
105
|
+
const isOpenRef = useRef(isOpen);
|
|
106
|
+
isOpenRef.current = isOpen;
|
|
107
|
+
const keyboardViewport = useKeyboardViewport(isOpen);
|
|
108
|
+
const isPresentingRef = useRef(shouldAutoPresent);
|
|
109
|
+
// Native's lifecycle state — updated synchronously so calls in the same tick see each other
|
|
110
|
+
const phaseRef = useRef(shouldAutoPresent ? 'presenting' : 'dismissed');
|
|
111
|
+
const resolvePresentRef = useRef(null);
|
|
112
|
+
const resolveDismissRef = useRef(null);
|
|
113
|
+
// Whether willPresent fired for the current open cycle — a present undone
|
|
114
|
+
// before it fires has nothing to pair a dismiss with.
|
|
115
|
+
const didEmitWillPresentRef = useRef(false);
|
|
116
|
+
// Whether the current open cycle came from initialDetentIndex. Native mounts those
|
|
117
|
+
// eagerly, so a dismiss before willPresent lands after the present instead of cancelling it.
|
|
118
|
+
const isAutoPresentRef = useRef(shouldAutoPresent);
|
|
119
|
+
const shouldDismissOnPresentRef = useRef(false);
|
|
120
|
+
const [displayedBackgroundIndex, setDisplayedBackgroundIndex] = useState(shouldAutoPresent ? initialDetentIndex : 0);
|
|
121
|
+
const backgroundColor = normalizeDetentBackground(detentBackgrounds?.[Math.min(displayedBackgroundIndex, validDetents.length - 1)])?.color ?? backgroundColorProp ?? (colorScheme === 'dark' ? COLOR_SURFACE_CONTAINER_LOW_DARK : COLOR_SURFACE_CONTAINER_LOW_LIGHT);
|
|
97
122
|
const [activeSnapPoint, setActiveSnapPoint] = useState(() => validDetents[shouldAutoPresent ? initialDetentIndex : 0] ?? null);
|
|
98
123
|
|
|
99
124
|
// Keep activeSnapPoint valid if detents change (e.g., prop updates).
|
|
@@ -103,15 +128,32 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
103
128
|
}, [validDetents]);
|
|
104
129
|
const validDetentsRef = useRef(validDetents);
|
|
105
130
|
validDetentsRef.current = validDetents;
|
|
106
|
-
const hasAutoDetent = validDetents.includes('auto');
|
|
107
131
|
const handleSetActiveSnapPoint = useCallback(snapPoint => {
|
|
108
132
|
setActiveSnapPoint(snapPoint == null ? null : typeof snapPoint === 'number' || snapPoint === 'auto' || snapPoint === 'peek' ? snapPoint : null);
|
|
109
133
|
}, []);
|
|
110
|
-
|
|
111
|
-
|
|
134
|
+
|
|
135
|
+
// Returns null when there's nothing to dismiss. Resolves on didDismiss.
|
|
136
|
+
const dismissSheet = useCallback(() => {
|
|
137
|
+
if (phaseRef.current === 'dismissed' || phaseRef.current === 'dismissing') return null;
|
|
138
|
+
settle(resolvePresentRef);
|
|
139
|
+
if (didEmitWillPresentRef.current) {
|
|
140
|
+
setIsOpen(false);
|
|
141
|
+
} else if (isAutoPresentRef.current) {
|
|
142
|
+
shouldDismissOnPresentRef.current = true;
|
|
143
|
+
} else {
|
|
144
|
+
// Mirror native's lazy-mount cancel: nothing shown yet, nothing to animate out
|
|
112
145
|
setIsOpen(false);
|
|
146
|
+
phaseRef.current = 'dismissed';
|
|
147
|
+
return Promise.resolve();
|
|
113
148
|
}
|
|
114
|
-
|
|
149
|
+
phaseRef.current = 'dismissing';
|
|
150
|
+
return new Promise(resolve => {
|
|
151
|
+
resolveDismissRef.current = resolve;
|
|
152
|
+
});
|
|
153
|
+
}, []);
|
|
154
|
+
const handleOpenChange = useCallback(open => {
|
|
155
|
+
if (!open) dismissSheet();
|
|
156
|
+
}, [dismissSheet]);
|
|
115
157
|
const portalContainer = usePortalContainer();
|
|
116
158
|
const handlePointerDownOutside = e => {
|
|
117
159
|
const target = e.target;
|
|
@@ -122,29 +164,61 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
122
164
|
e.preventDefault();
|
|
123
165
|
return;
|
|
124
166
|
}
|
|
167
|
+
if (!(target instanceof Element)) return;
|
|
168
|
+
const wrapper = drawerContentRef.current?.closest('[data-vaul-detached-wrapper]');
|
|
169
|
+
if (!wrapper) return;
|
|
125
170
|
// The footer is rendered via vaul's `detachedSiblings` as a sibling of
|
|
126
171
|
// Drawer.Content inside [data-vaul-detached-wrapper], so Radix treats
|
|
127
172
|
// clicks on it as "outside" the content. Don't dismiss for clicks that
|
|
128
173
|
// landed inside the wrapper.
|
|
129
|
-
if (target
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
e.preventDefault();
|
|
133
|
-
}
|
|
174
|
+
if (wrapper.contains(target)) {
|
|
175
|
+
e.preventDefault();
|
|
176
|
+
return;
|
|
134
177
|
}
|
|
178
|
+
// Every sheet runs Radix non-modal and portals its overlay + wrapper as
|
|
179
|
+
// siblings into the shared container, so a press on a sheet stacked above
|
|
180
|
+
// this one (its content or its dim overlay) is also delivered here as
|
|
181
|
+
// "outside". Sheets present in document order, so anything after our
|
|
182
|
+
// wrapper belongs to a descendant. Radix may dispatch on the deferred
|
|
183
|
+
// click (touch), after a dismissing descendant has left the DOM — treat
|
|
184
|
+
// disconnected as above too.
|
|
185
|
+
/* eslint-disable no-bitwise */
|
|
186
|
+
const above = Node.DOCUMENT_POSITION_FOLLOWING | Node.DOCUMENT_POSITION_DISCONNECTED;
|
|
187
|
+
if (wrapper.compareDocumentPosition(target) & above) {
|
|
188
|
+
e.preventDefault();
|
|
189
|
+
}
|
|
190
|
+
/* eslint-enable no-bitwise */
|
|
135
191
|
};
|
|
136
192
|
const dismissAboveRef = useRef(async () => {});
|
|
193
|
+
const dismissDescendantsRef = useRef(() => {});
|
|
194
|
+
const presentSheet = useCallback(async (index, auto) => {
|
|
195
|
+
const detent = validDetentsRef.current[index];
|
|
196
|
+
if (detent === undefined) {
|
|
197
|
+
throw new Error(`TrueSheet: present index (${index}) is out of bounds. detents array has ${validDetentsRef.current.length} item(s)`);
|
|
198
|
+
}
|
|
199
|
+
if (phaseRef.current !== 'dismissed') {
|
|
200
|
+
console.warn(ALREADY_PRESENTED_WARNING);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
phaseRef.current = 'presenting';
|
|
204
|
+
isAutoPresentRef.current = auto;
|
|
205
|
+
isPresentingRef.current = true;
|
|
206
|
+
setDisplayedBackgroundIndex(index);
|
|
207
|
+
setActiveSnapPoint(detent);
|
|
208
|
+
setIsOpen(true);
|
|
209
|
+
await new Promise(resolve => {
|
|
210
|
+
resolvePresentRef.current = resolve;
|
|
211
|
+
});
|
|
212
|
+
}, []);
|
|
137
213
|
const methods = useMemo(() => ({
|
|
138
|
-
present:
|
|
139
|
-
const detent = validDetentsRef.current[index];
|
|
140
|
-
if (detent === undefined) {
|
|
141
|
-
throw new Error(`TrueSheet: present index (${index}) is out of bounds. detents array has ${validDetentsRef.current.length} item(s)`);
|
|
142
|
-
}
|
|
143
|
-
setActiveSnapPoint(detent);
|
|
144
|
-
setIsOpen(true);
|
|
145
|
-
},
|
|
214
|
+
present: (index = 0) => presentSheet(index, false),
|
|
146
215
|
dismiss: async () => {
|
|
147
|
-
|
|
216
|
+
const dismissal = dismissSheet();
|
|
217
|
+
if (!dismissal) {
|
|
218
|
+
console.warn(ALREADY_DISMISSED_WARNING);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
await dismissal;
|
|
148
222
|
},
|
|
149
223
|
resize: async index => {
|
|
150
224
|
const detent = validDetentsRef.current[index];
|
|
@@ -154,13 +228,40 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
154
228
|
setActiveSnapPoint(detent);
|
|
155
229
|
},
|
|
156
230
|
dismissStack: async animated => {
|
|
231
|
+
if (phaseRef.current === 'dismissed' || phaseRef.current === 'dismissing') {
|
|
232
|
+
console.warn(ALREADY_DISMISSED_WARNING);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
157
235
|
await dismissAboveRef.current(animated);
|
|
158
236
|
}
|
|
159
|
-
}), []);
|
|
237
|
+
}), [presentSheet, dismissSheet]);
|
|
160
238
|
useImperativeHandle(ref, () => methods, [methods]);
|
|
161
|
-
|
|
162
|
-
|
|
239
|
+
useEffect(() => {
|
|
240
|
+
if (!shouldAutoPresent || didInitiallyPresentRef.current) return;
|
|
241
|
+
didInitiallyPresentRef.current = true;
|
|
242
|
+
if (phaseRef.current === 'dismissed') {
|
|
243
|
+
presentSheet(initialDetentIndex, true);
|
|
244
|
+
}
|
|
245
|
+
}, [initialDetentIndex, shouldAutoPresent, presentSheet]);
|
|
246
|
+
|
|
247
|
+
// Unmounted mid-transition: don't leave callers awaiting forever
|
|
248
|
+
useEffect(() => () => {
|
|
249
|
+
settle(resolvePresentRef);
|
|
250
|
+
settle(resolveDismissRef);
|
|
251
|
+
}, []);
|
|
252
|
+
const handleRef = useRef({
|
|
253
|
+
methods,
|
|
254
|
+
dismissQuietly: async () => {
|
|
255
|
+
await dismissSheet();
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
useRegisterSheet(name, handleRef);
|
|
163
259
|
const drawerContentRef = useRef(null);
|
|
260
|
+
const [drawerContent, setDrawerContent] = useState(null);
|
|
261
|
+
const handleDrawerRef = useCallback(node => {
|
|
262
|
+
drawerContentRef.current = node;
|
|
263
|
+
setDrawerContent(node);
|
|
264
|
+
}, []);
|
|
164
265
|
|
|
165
266
|
// Measured header/footer heights drive the 'peek' snap point — mirrors
|
|
166
267
|
// native, where the controller tracks headerHeight/footerHeight.
|
|
@@ -185,17 +286,40 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
185
286
|
const [peekContentHeight, setPeekContentHeight] = useState(0);
|
|
186
287
|
const contentRef = useRef(null);
|
|
187
288
|
const peekElRef = useRef(null);
|
|
289
|
+
const measurePeek = useCallback(() => {
|
|
290
|
+
const peekEl = getDOMElement(peekElRef.current);
|
|
291
|
+
const contentEl = getDOMElement(contentRef.current);
|
|
292
|
+
if (peekEl && contentEl) {
|
|
293
|
+
setPeekContentHeight(measurePeekContentHeight(peekEl, contentEl));
|
|
294
|
+
}
|
|
295
|
+
}, []);
|
|
296
|
+
|
|
297
|
+
// Last-attached wins — during a screen swap the incoming peek mounts before
|
|
298
|
+
// the outgoing one unmounts, so the outgoing detach must not clear it.
|
|
188
299
|
const peekContext = useMemo(() => ({
|
|
189
300
|
contentRef,
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
301
|
+
attachPeek: view => {
|
|
302
|
+
peekElRef.current = view;
|
|
303
|
+
measurePeek();
|
|
304
|
+
},
|
|
305
|
+
detachPeek: view => {
|
|
306
|
+
if (peekElRef.current !== view) return;
|
|
307
|
+
peekElRef.current = null;
|
|
308
|
+
setPeekContentHeight(0);
|
|
309
|
+
},
|
|
310
|
+
measurePeek: view => {
|
|
311
|
+
if (peekElRef.current === view) measurePeek();
|
|
312
|
+
}
|
|
313
|
+
}), [measurePeek]);
|
|
314
|
+
|
|
315
|
+
// The peek's own onLayout only fires when *it* resizes. Content added above
|
|
316
|
+
// it moves it without resizing it, so re-measure its offset whenever the
|
|
317
|
+
// content view's layout changes.
|
|
318
|
+
const handleContentLayout = measurePeek;
|
|
193
319
|
|
|
194
320
|
// An absolute (floating) header overlaps the content, so it contributes no
|
|
195
321
|
// height to the 'auto' detent measurement.
|
|
196
322
|
const absoluteHeader = headerOptions?.position === 'absolute';
|
|
197
|
-
const absoluteHeaderRef = useRef(absoluteHeader);
|
|
198
|
-
absoluteHeaderRef.current = absoluteHeader;
|
|
199
323
|
const resolvedHeaderStyle = absoluteHeader ? [absoluteHeaderStyle, headerStyle] : headerStyle;
|
|
200
324
|
|
|
201
325
|
// Same for an absolute (floating) footer — rendered via vaul's
|
|
@@ -222,115 +346,56 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
222
346
|
// A relative footer is laid out below the content, so it's pushed off-screen
|
|
223
347
|
// at the peek detent and contributes no height.
|
|
224
348
|
const peekHeight = (header ? headerHeight : 0) + (footer && absoluteFooter ? footerHeight : 0) + peekContentHeight || DEFAULT_PEEK_HEIGHT;
|
|
349
|
+
const [measuredContentHeight, setMeasuredContentHeight] = useState(0);
|
|
350
|
+
const naturalHeightRef = useRef(0);
|
|
351
|
+
const insetBehavior = scrollableOptions?.contentInsetAdjustment ?? 'automatic';
|
|
352
|
+
const footerInsetAdjustment = Boolean(footer) && absoluteFooter && (insetBehavior === 'automatic' || insetBehavior === 'footer');
|
|
353
|
+
const measureContent = validDetents.includes('auto') || isFormSheet;
|
|
354
|
+
useLayoutEffect(() => {
|
|
355
|
+
const layoutEl = sizedLayoutRef.current;
|
|
356
|
+
if (!isOpen || !drawerContent || !layoutEl?.isConnected) return undefined;
|
|
357
|
+
setHeaderHeight(getDOMElement(headerElRef.current)?.offsetHeight ?? 0);
|
|
358
|
+
setFooterHeight(getDOMElement(footerElRef.current)?.offsetHeight ?? 0);
|
|
359
|
+
measurePeek();
|
|
360
|
+
const observer = observeSheetLayout({
|
|
361
|
+
drawer: drawerContent,
|
|
362
|
+
layout: layoutEl,
|
|
363
|
+
getContent: () => getDOMElement(contentRef.current),
|
|
364
|
+
getHeader: () => getDOMElement(headerElRef.current),
|
|
365
|
+
getFooter: () => getDOMElement(footerElRef.current),
|
|
366
|
+
getScrollable: () => getScrollableElement(scrollableRef?.current),
|
|
367
|
+
absoluteHeader,
|
|
368
|
+
absoluteFooter,
|
|
369
|
+
footerInsetAdjustment,
|
|
370
|
+
measureContent,
|
|
371
|
+
onHeightChange: height => {
|
|
372
|
+
naturalHeightRef.current = height;
|
|
373
|
+
setMeasuredContentHeight(height);
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
return () => observer.disconnect();
|
|
377
|
+
}, [isOpen, drawerContent, measurePeek, absoluteHeader, absoluteFooter, footerInsetAdjustment, measureContent, scrollableRef]);
|
|
225
378
|
|
|
226
|
-
//
|
|
227
|
-
// scrollable keeps the sized (bounded) layout so its viewport is capped to
|
|
228
|
-
// the visible sheet and can scroll, while the 'auto' height is measured with
|
|
229
|
-
// the viewport replaced by the scrollable's content size — mirrors native
|
|
230
|
-
// `naturalHeight`.
|
|
231
|
-
const [hasBoundedScrollable, setHasBoundedScrollable] = useState(false);
|
|
232
|
-
const [scrollableAutoHeight, setScrollableAutoHeight] = useState(0);
|
|
233
|
-
const pinnedScrollerRef = useRef(null);
|
|
234
|
-
|
|
235
|
-
// Natural content height (header + content + footer, with a pinned
|
|
236
|
-
// scrollable's viewport replaced by its content size) — the height the
|
|
237
|
-
// content wants regardless of the sheet's bounds.
|
|
238
|
-
const measureNaturalHeight = useCallback(() => {
|
|
239
|
-
const contentEl = getDOMElement(contentRef.current);
|
|
240
|
-
if (!contentEl || !contentEl.isConnected) return 0;
|
|
241
|
-
const headerEl = absoluteHeaderRef.current ? null : getDOMElement(headerElRef.current);
|
|
242
|
-
const footerEl = absoluteFooterRef.current ? null : getDOMElement(footerElRef.current);
|
|
243
|
-
let height = (headerEl?.offsetHeight ?? 0) + (footerEl?.offsetHeight ?? 0) + contentEl.offsetHeight;
|
|
244
|
-
const scroller = pinnedScrollerRef.current;
|
|
245
|
-
const scrollContent = scroller?.firstElementChild;
|
|
246
|
-
if (scroller?.isConnected && scrollContent instanceof HTMLElement) {
|
|
247
|
-
height += scrollContent.offsetHeight - scroller.clientHeight;
|
|
248
|
-
}
|
|
249
|
-
return Math.max(0, height);
|
|
250
|
-
}, []);
|
|
379
|
+
// Re-reveal the focused field once the sheet settles above the keyboard
|
|
251
380
|
useEffect(() => {
|
|
252
|
-
|
|
381
|
+
const drawer = drawerContentRef.current;
|
|
382
|
+
if (!keyboardViewport || !drawer) return undefined;
|
|
253
383
|
let canceled = false;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
if (canceled)
|
|
259
|
-
|
|
260
|
-
if (!drawerEl || !drawerEl.isConnected) {
|
|
261
|
-
// Radix Presence defers the portal mount; poll until the drawer is live.
|
|
262
|
-
rafId = window.requestAnimationFrame(attach);
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
const measure = () => setScrollableAutoHeight(measureNaturalHeight());
|
|
266
|
-
let observed = null;
|
|
267
|
-
|
|
268
|
-
// (Re)pin the first vertical scrollable and observe the nodes whose size
|
|
269
|
-
// feeds the natural height — content growth inside a bounded scroller is
|
|
270
|
-
// invisible to the sheet's own layout (mirrors native's contentSize
|
|
271
|
-
// observation). Resolves the content node live: the layout-branch swap
|
|
272
|
-
// (natural flow ↔ sized) remounts it.
|
|
273
|
-
const observe = () => {
|
|
274
|
-
const contentEl = getDOMElement(contentRef.current);
|
|
275
|
-
const headerEl = getDOMElement(headerElRef.current);
|
|
276
|
-
|
|
277
|
-
// Skip mutations that don't change the observed topology — e.g. a
|
|
278
|
-
// virtualized list mounting rows inside the pinned scroller, which
|
|
279
|
-
// would otherwise trigger a full-subtree rescan (findVerticalScroller
|
|
280
|
-
// resolves computed styles) on every scroll frame. Size changes are
|
|
281
|
-
// already covered by the ResizeObserver.
|
|
282
|
-
if (observed && observed.contentEl === contentEl && contentEl?.isConnected && observed.headerEl === headerEl && observed.scroller.isConnected && observed.scrollContent === observed.scroller.firstElementChild) {
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
const scroller = contentEl && contentEl.isConnected ? findVerticalScroller(contentEl) : null;
|
|
286
|
-
pinnedScrollerRef.current = scroller;
|
|
287
|
-
setHasBoundedScrollable(scroller != null);
|
|
288
|
-
observed = scroller ? {
|
|
289
|
-
contentEl,
|
|
290
|
-
headerEl,
|
|
291
|
-
scroller,
|
|
292
|
-
scrollContent: scroller.firstElementChild
|
|
293
|
-
} : null;
|
|
294
|
-
resizeObserver?.disconnect();
|
|
295
|
-
resizeObserver = new ResizeObserver(measure);
|
|
296
|
-
if (contentEl?.isConnected) resizeObserver.observe(contentEl);
|
|
297
|
-
if (headerEl) resizeObserver.observe(headerEl);
|
|
298
|
-
if (scroller) {
|
|
299
|
-
resizeObserver.observe(scroller);
|
|
300
|
-
if (scroller.firstElementChild) resizeObserver.observe(scroller.firstElementChild);
|
|
301
|
-
}
|
|
302
|
-
measure();
|
|
303
|
-
};
|
|
304
|
-
observe();
|
|
305
|
-
// Watch the whole drawer subtree — catches the scrollable mounting/
|
|
306
|
-
// unmounting AND the layout-branch swap, which remounts the content node
|
|
307
|
-
// (a content-scoped observer would go stale after the swap).
|
|
308
|
-
mutationObserver = new MutationObserver(observe);
|
|
309
|
-
mutationObserver.observe(drawerEl, {
|
|
310
|
-
childList: true,
|
|
311
|
-
subtree: true
|
|
312
|
-
});
|
|
313
|
-
};
|
|
314
|
-
rafId = window.requestAnimationFrame(attach);
|
|
384
|
+
// Flush styles so this commit's snap transition shows up in getAnimations()
|
|
385
|
+
// eslint-disable-next-line no-void
|
|
386
|
+
void drawer.offsetHeight;
|
|
387
|
+
Promise.allSettled(drawer.getAnimations().map(a => a.finished)).then(() => {
|
|
388
|
+
if (!canceled) revealFocusedElement(drawer);
|
|
389
|
+
});
|
|
315
390
|
return () => {
|
|
316
391
|
canceled = true;
|
|
317
|
-
window.cancelAnimationFrame(rafId);
|
|
318
|
-
mutationObserver?.disconnect();
|
|
319
|
-
resizeObserver?.disconnect();
|
|
320
392
|
};
|
|
321
|
-
}, [
|
|
393
|
+
}, [keyboardViewport]);
|
|
322
394
|
|
|
323
395
|
// Below the last detent a vertical touch pan moves the sheet, not the
|
|
324
396
|
// content — `[data-vaul-scroll-locked]` disables vertical touch panning on
|
|
325
397
|
// the scroll container and everything inside it (see vaul/style.css).
|
|
326
398
|
const isScrollLocked = validDetents.length > 0 && activeSnapPoint !== validDetents[validDetents.length - 1];
|
|
327
|
-
|
|
328
|
-
// Vaul measures the auto-size wrapper's offsetHeight (always, post fork).
|
|
329
|
-
// Track it here so the form sheet can size its card to fit content,
|
|
330
|
-
// clamped between a minimum ratio of the viewport and a maximum derived
|
|
331
|
-
// from `detachedOffset` (the breathing room left at top + bottom of the
|
|
332
|
-
// floating card).
|
|
333
|
-
const [measuredContentHeight, setMeasuredContentHeight] = useState(0);
|
|
334
399
|
const effectiveMaxContentHeight = useMemo(() => {
|
|
335
400
|
if (maxContentHeight !== undefined) return maxContentHeight;
|
|
336
401
|
if (!isFormSheet) return undefined;
|
|
@@ -394,7 +459,7 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
394
459
|
effectiveMaxContentHeight,
|
|
395
460
|
peekHeight,
|
|
396
461
|
peekContentHeight,
|
|
397
|
-
|
|
462
|
+
keyboardViewport
|
|
398
463
|
});
|
|
399
464
|
geometryInputsRef.current = {
|
|
400
465
|
effectiveDetached,
|
|
@@ -402,20 +467,20 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
402
467
|
effectiveMaxContentHeight,
|
|
403
468
|
peekHeight,
|
|
404
469
|
peekContentHeight,
|
|
405
|
-
|
|
470
|
+
keyboardViewport
|
|
406
471
|
};
|
|
407
472
|
const computeDetentGeometry = useCallback(() => {
|
|
408
473
|
const inputs = geometryInputsRef.current;
|
|
409
|
-
const windowH = window.innerHeight;
|
|
410
|
-
const
|
|
474
|
+
const windowH = inputs.keyboardViewport?.height ?? window.innerHeight;
|
|
475
|
+
const bottomGap = inputs.effectiveDetached ? inputs.effectiveDetachedOffset : 0;
|
|
476
|
+
const effectiveH = windowH - bottomGap;
|
|
477
|
+
// Above a keyboard, fractions keep their full-height size (see vaul).
|
|
478
|
+
const fractionH = inputs.keyboardViewport ? inputs.keyboardViewport.layoutHeight - bottomGap : effectiveH;
|
|
411
479
|
// Matches vaul's height ceiling: min(effectiveH, maxContentHeight).
|
|
412
480
|
const ceiling = inputs.effectiveMaxContentHeight !== undefined ? Math.min(effectiveH, inputs.effectiveMaxContentHeight) : effectiveH;
|
|
413
|
-
//
|
|
414
|
-
//
|
|
415
|
-
|
|
416
|
-
// so it reads 0 until the ResizeObserver fires). Falls back to the state
|
|
417
|
-
// value, then to vaul's pre-measure fallback (effectiveHeight / 2).
|
|
418
|
-
const measuredHeight = measureNaturalHeight() || inputs.measuredContentHeight;
|
|
481
|
+
// Reuse the intrinsic measurement during position events so animation
|
|
482
|
+
// frames don't clone the content tree again.
|
|
483
|
+
const measuredHeight = naturalHeightRef.current;
|
|
419
484
|
const autoHeight = Math.min(measuredHeight > 0 ? measuredHeight : effectiveH / 2, ceiling);
|
|
420
485
|
|
|
421
486
|
// 'peek' is measured live from the DOM (offsetHeight is layout-based, so
|
|
@@ -435,7 +500,7 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
435
500
|
const positions = [];
|
|
436
501
|
const values = [];
|
|
437
502
|
for (const d of validDetentsRef.current) {
|
|
438
|
-
const h = typeof d === 'number' ? Math.min(d *
|
|
503
|
+
const h = typeof d === 'number' ? Math.min(d * fractionH, ceiling) : d === 'peek' ? Math.min(livePeekHeight, ceiling) : autoHeight;
|
|
439
504
|
positions.push(effectiveH - h);
|
|
440
505
|
values.push(effectiveH > 0 ? h / effectiveH : 0);
|
|
441
506
|
}
|
|
@@ -446,7 +511,7 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
446
511
|
positions,
|
|
447
512
|
values
|
|
448
513
|
};
|
|
449
|
-
}, [
|
|
514
|
+
}, []);
|
|
450
515
|
|
|
451
516
|
// Detent info for lifecycle events. Position/detent come from the active
|
|
452
517
|
// detent's target geometry — not the live DOM rect — so willPresent (drawer
|
|
@@ -533,50 +598,20 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
533
598
|
detent: values[count - 1]
|
|
534
599
|
};
|
|
535
600
|
}, [computeDetentGeometry]);
|
|
536
|
-
|
|
537
|
-
// Mirror native synchronous layout: while dragging (and through the post-
|
|
538
|
-
// release settle) the sized layout tracks the sheet's visible height in
|
|
539
|
-
// realtime, clamped to the smallest detent — dragging below it slides the
|
|
540
|
-
// sheet out without resizing. Once settled, the inline height is cleared so
|
|
541
|
-
// the CSS calc() owns sizing again (adapts to viewport resizes at rest).
|
|
542
601
|
const sizedLayoutRef = useRef(null);
|
|
543
602
|
const isDraggingRef = useRef(false);
|
|
544
|
-
const isSettlingAfterDragRef = useRef(false);
|
|
545
|
-
const updateSizedLayoutHeight = useCallback(position => {
|
|
546
|
-
const node = sizedLayoutRef.current;
|
|
547
|
-
if (!node) return;
|
|
548
|
-
const {
|
|
549
|
-
effectiveH,
|
|
550
|
-
ceiling,
|
|
551
|
-
positions
|
|
552
|
-
} = computeDetentGeometry();
|
|
553
|
-
if (positions.length === 0) return;
|
|
554
|
-
if (isSettlingAfterDragRef.current) {
|
|
555
|
-
const snap = activeSnapPointRef.current;
|
|
556
|
-
const index = snap != null ? validDetentsRef.current.indexOf(snap) : -1;
|
|
557
|
-
const target = index >= 0 ? positions[index] : null;
|
|
558
|
-
if (target != null && Math.abs(position - target) < 1) {
|
|
559
|
-
isSettlingAfterDragRef.current = false;
|
|
560
|
-
// Restore the calc — height is an inline style, so clearing it would
|
|
561
|
-
// leave the div unsized (React won't re-apply it without a render)
|
|
562
|
-
node.style.height = SIZED_LAYOUT_HEIGHT;
|
|
563
|
-
return;
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
// positions[0] is the smallest detent's top-Y — its height is the clamp floor.
|
|
568
|
-
const minHeight = effectiveH - positions[0];
|
|
569
|
-
const height = Math.min(Math.max(effectiveH - position, minHeight), ceiling);
|
|
570
|
-
node.style.height = `${height}px`;
|
|
571
|
-
}, [computeDetentGeometry]);
|
|
572
603
|
const handlePositionChange = useCallback(position => {
|
|
573
|
-
if (isDraggingRef.current || isSettlingAfterDragRef.current) {
|
|
574
|
-
updateSizedLayoutHeight(position);
|
|
575
|
-
}
|
|
576
604
|
const {
|
|
577
605
|
index,
|
|
578
606
|
detent
|
|
579
607
|
} = interpolateFromPosition(position);
|
|
608
|
+
if (isOpenRef.current && !isPresentingRef.current) {
|
|
609
|
+
const {
|
|
610
|
+
effectiveH,
|
|
611
|
+
positions
|
|
612
|
+
} = computeDetentGeometry();
|
|
613
|
+
setDisplayedBackgroundIndex(current => getDetentBackgroundIndex(effectiveH - position, positions.map(top => effectiveH - top), current));
|
|
614
|
+
}
|
|
580
615
|
onPositionChangeRef.current?.({
|
|
581
616
|
nativeEvent: {
|
|
582
617
|
index,
|
|
@@ -585,7 +620,7 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
585
620
|
realtime: true
|
|
586
621
|
}
|
|
587
622
|
});
|
|
588
|
-
}, [interpolateFromPosition,
|
|
623
|
+
}, [interpolateFromPosition, computeDetentGeometry]);
|
|
589
624
|
|
|
590
625
|
// Fire onMount once after first render. React-mount is the earliest point
|
|
591
626
|
// the component is ready for imperative calls, matching the native
|
|
@@ -611,6 +646,12 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
611
646
|
if (isOpen === wasOpen) return undefined;
|
|
612
647
|
const present = isOpen;
|
|
613
648
|
if (!present) {
|
|
649
|
+
// Mirror native: dismissing a sheet takes every sheet stacked above it
|
|
650
|
+
// (iOS dismisses from the presenter; Android runs dismissStack first).
|
|
651
|
+
dismissDescendantsRef.current();
|
|
652
|
+
// Mirror native's lazy-mount cancel: dismissed before willPresent, no events
|
|
653
|
+
if (!didEmitWillPresentRef.current) return undefined;
|
|
654
|
+
didEmitWillPresentRef.current = false;
|
|
614
655
|
// Pair willBlur with willDismiss on dismiss — mirrors native iOS
|
|
615
656
|
// emitWillDismissEvents (blur fires before dismiss). willPresent is
|
|
616
657
|
// deferred to `start()` below (needs the mounted drawer's geometry).
|
|
@@ -623,19 +664,29 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
623
664
|
}
|
|
624
665
|
const fireDone = () => {
|
|
625
666
|
if (present) {
|
|
667
|
+
isPresentingRef.current = false;
|
|
668
|
+
// A dismiss may already be underway (it lands on the next commit)
|
|
669
|
+
if (phaseRef.current === 'presenting') phaseRef.current = 'presented';
|
|
626
670
|
onDidPresentRef.current?.({
|
|
627
671
|
nativeEvent: computeDetentInfo()
|
|
628
672
|
});
|
|
629
673
|
onDidFocusRef.current?.({
|
|
630
674
|
nativeEvent: null
|
|
631
675
|
});
|
|
676
|
+
settle(resolvePresentRef);
|
|
677
|
+
if (shouldDismissOnPresentRef.current) {
|
|
678
|
+
shouldDismissOnPresentRef.current = false;
|
|
679
|
+
setIsOpen(false);
|
|
680
|
+
}
|
|
632
681
|
} else {
|
|
682
|
+
phaseRef.current = 'dismissed';
|
|
633
683
|
onDidBlurRef.current?.({
|
|
634
684
|
nativeEvent: null
|
|
635
685
|
});
|
|
636
686
|
onDidDismissRef.current?.({
|
|
637
687
|
nativeEvent: null
|
|
638
688
|
});
|
|
689
|
+
settle(resolveDismissRef);
|
|
639
690
|
}
|
|
640
691
|
};
|
|
641
692
|
let canceled = false;
|
|
@@ -659,6 +710,7 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
659
710
|
// willPresent mirrors native iOS where viewWillAppear dispatches
|
|
660
711
|
// both; the descendant-stack focus effect handles subsequent
|
|
661
712
|
// transitions.
|
|
713
|
+
didEmitWillPresentRef.current = true;
|
|
662
714
|
onWillPresentRef.current?.({
|
|
663
715
|
nativeEvent: computeDetentInfo()
|
|
664
716
|
});
|
|
@@ -728,12 +780,9 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
728
780
|
nativeEvent: computeDetentInfo(true)
|
|
729
781
|
});
|
|
730
782
|
}, [computeDetentInfo]);
|
|
731
|
-
const handleRelease = useCallback((
|
|
783
|
+
const handleRelease = useCallback(() => {
|
|
732
784
|
if (!isDraggingRef.current) return;
|
|
733
785
|
isDraggingRef.current = false;
|
|
734
|
-
// Track the sized layout through the settle animation only when the sheet
|
|
735
|
-
// stays open — a dismissal slides out at the min-clamped height.
|
|
736
|
-
isSettlingAfterDragRef.current = open;
|
|
737
786
|
onDragEndRef.current?.({
|
|
738
787
|
nativeEvent: computeDetentInfo(true)
|
|
739
788
|
});
|
|
@@ -742,12 +791,20 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
742
791
|
isNested,
|
|
743
792
|
dismissAbove,
|
|
744
793
|
descendants
|
|
745
|
-
} = useSheetStack(
|
|
794
|
+
} = useSheetStack(handleRef, drawerContentRef, isOpen, isFormSheet);
|
|
746
795
|
dismissAboveRef.current = dismissAbove;
|
|
796
|
+
// Captured per render: by the time the dismiss effect runs, this sheet has
|
|
797
|
+
// already been popped from the stack, so `dismissAbove` can't find it.
|
|
798
|
+
dismissDescendantsRef.current = () => {
|
|
799
|
+
for (let i = descendants.length - 1; i >= 0; i--) {
|
|
800
|
+
descendants[i].ref.current?.dismissQuietly();
|
|
801
|
+
}
|
|
802
|
+
};
|
|
747
803
|
|
|
748
804
|
// Mirror Android: translate this sheet down to match the deepest descendant's
|
|
749
805
|
// top so the whole stack visually aligns. Cascades because every ancestor
|
|
750
806
|
// re-runs whenever the stack (and thus its descendants) changes.
|
|
807
|
+
const wasCascadedRef = useRef(false);
|
|
751
808
|
useEffect(() => {
|
|
752
809
|
const parent = drawerContentRef.current;
|
|
753
810
|
if (!parent) return;
|
|
@@ -758,7 +815,7 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
758
815
|
// out — nothing to align with anymore.
|
|
759
816
|
if (!isOpen) return;
|
|
760
817
|
const parentWrapper = parent.closest('[data-vaul-detached-wrapper]');
|
|
761
|
-
const transition = `transform ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(',')})`;
|
|
818
|
+
const transition = `transform ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(',')}), --snap-point-height ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(',')})`;
|
|
762
819
|
const wrapperTransition = `clip-path ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(',')})`;
|
|
763
820
|
const CLIP_NONE = 'inset(0px round 0px)';
|
|
764
821
|
|
|
@@ -781,22 +838,29 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
781
838
|
parentWrapper.style.clipPath = next;
|
|
782
839
|
};
|
|
783
840
|
if (descendants.length === 0) {
|
|
841
|
+
if (!wasCascadedRef.current) return;
|
|
842
|
+
wasCascadedRef.current = false;
|
|
784
843
|
parent.style.transition = transition;
|
|
785
844
|
parent.style.transform = '';
|
|
786
845
|
setClip(CLIP_NONE);
|
|
787
846
|
return;
|
|
788
847
|
}
|
|
848
|
+
wasCascadedRef.current = true;
|
|
789
849
|
|
|
790
|
-
// Track
|
|
791
|
-
//
|
|
792
|
-
//
|
|
793
|
-
// grandchild
|
|
850
|
+
// Track the immediate child's *actual* top: its cascade transform when it
|
|
851
|
+
// was itself pushed behind a grandchild, else its own snap point. Mirrors
|
|
852
|
+
// Android's addTranslation propagating extra translation up the stack —
|
|
853
|
+
// a grandchild opening pushes this sheet only as far as our child moved.
|
|
854
|
+
const readTranslateY = el => {
|
|
855
|
+
const match = el.style.transform.match(/translate3d\([^,]*,\s*(-?\d*\.?\d+)px/);
|
|
856
|
+
return match ? Number.parseFloat(match[1]) : null;
|
|
857
|
+
};
|
|
794
858
|
const computeTargetY = () => {
|
|
795
859
|
const parentSnap = Number.parseFloat(parent.style.getPropertyValue('--snap-point-height')) || 0;
|
|
796
860
|
const node = descendants[0]?.nodeRef.current;
|
|
797
861
|
if (!node) return parentSnap;
|
|
798
|
-
const
|
|
799
|
-
return Math.max(parentSnap,
|
|
862
|
+
const childTop = readTranslateY(node) ?? (Number.parseFloat(node.style.getPropertyValue('--snap-point-height')) || 0);
|
|
863
|
+
return Math.max(parentSnap, childTop);
|
|
800
864
|
};
|
|
801
865
|
|
|
802
866
|
// When a form-sheet parent has a form-sheet descendant, clip the parent to
|
|
@@ -830,18 +894,26 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
830
894
|
};
|
|
831
895
|
const apply = () => {
|
|
832
896
|
applyFormClip();
|
|
833
|
-
// Mirror
|
|
834
|
-
//
|
|
835
|
-
//
|
|
836
|
-
|
|
837
|
-
|
|
897
|
+
// Mirror iPad/Android (`isExpanded`): a parent at full height (detent 1)
|
|
898
|
+
// stays put and the child simply overlays it. Full height is the sheet's
|
|
899
|
+
// own ceiling — the viewport for a page sheet, the capped content-fit
|
|
900
|
+
// height for a form sheet — so compare against where detent 1 lands.
|
|
901
|
+
const parentSnap = Number.parseFloat(parent.style.getPropertyValue('--snap-point-height')) || 0;
|
|
902
|
+
const {
|
|
903
|
+
effectiveH,
|
|
904
|
+
ceiling
|
|
905
|
+
} = computeDetentGeometry();
|
|
906
|
+
if (parentSnap <= effectiveH - ceiling + 0.5) {
|
|
838
907
|
parent.style.transition = transition;
|
|
839
908
|
parent.style.transform = '';
|
|
840
909
|
return;
|
|
841
910
|
}
|
|
911
|
+
// Mirror Android: stay put while the child is being dragged (its live
|
|
912
|
+
// transform would drag us along during drag-to-dismiss). The release
|
|
913
|
+
// snap rewrites the child's style and re-applies.
|
|
914
|
+
if (descendants[0]?.nodeRef.current?.classList.contains(DRAG_CLASS)) return;
|
|
842
915
|
const targetY = computeTargetY();
|
|
843
|
-
const
|
|
844
|
-
const currentY = match ? Number.parseFloat(match[1]) : 0;
|
|
916
|
+
const currentY = readTranslateY(parent) ?? 0;
|
|
845
917
|
if (Math.abs(currentY - targetY) < 0.5) return;
|
|
846
918
|
parent.style.transition = transition;
|
|
847
919
|
parent.style.transform = `translate3d(0, ${targetY}px, 0)`;
|
|
@@ -855,11 +927,17 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
855
927
|
attributes: true,
|
|
856
928
|
attributeFilter: ['style']
|
|
857
929
|
});
|
|
930
|
+
// The child's own cascade lands in a later frame — follow it when it moves.
|
|
931
|
+
const childNode = descendants[0]?.nodeRef.current;
|
|
932
|
+
if (childNode) observer.observe(childNode, {
|
|
933
|
+
attributes: true,
|
|
934
|
+
attributeFilter: ['style']
|
|
935
|
+
});
|
|
858
936
|
return () => {
|
|
859
937
|
cancelAnimationFrame(raf);
|
|
860
938
|
observer.disconnect();
|
|
861
939
|
};
|
|
862
|
-
}, [descendants, activeSnapPoint, cornerRadius, isFormSheet, isOpen]);
|
|
940
|
+
}, [descendants, activeSnapPoint, cornerRadius, isFormSheet, isOpen, computeDetentGeometry]);
|
|
863
941
|
|
|
864
942
|
// Focus/blur events fire when a descendant sheet appears on top of this one
|
|
865
943
|
// (blur) or when all descendants are dismissed (focus). will-events fire
|
|
@@ -870,12 +948,19 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
870
948
|
const onDidBlurRef = useRef(onDidBlur);
|
|
871
949
|
const onWillFocusRef = useRef(onWillFocus);
|
|
872
950
|
const onDidFocusRef = useRef(onDidFocus);
|
|
951
|
+
const onDismissAttemptRef = useRef(onDismissAttempt);
|
|
873
952
|
useEffect(() => {
|
|
874
953
|
onWillBlurRef.current = onWillBlur;
|
|
875
954
|
onDidBlurRef.current = onDidBlur;
|
|
876
955
|
onWillFocusRef.current = onWillFocus;
|
|
877
956
|
onDidFocusRef.current = onDidFocus;
|
|
957
|
+
onDismissAttemptRef.current = onDismissAttempt;
|
|
878
958
|
});
|
|
959
|
+
const handleDismissAttempt = useCallback(() => {
|
|
960
|
+
onDismissAttemptRef.current?.({
|
|
961
|
+
nativeEvent: null
|
|
962
|
+
});
|
|
963
|
+
}, []);
|
|
879
964
|
const prevDescendantCountRef = useRef(0);
|
|
880
965
|
useEffect(() => {
|
|
881
966
|
const prevCount = prevDescendantCountRef.current;
|
|
@@ -924,11 +1009,6 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
924
1009
|
window.cancelAnimationFrame(rafId);
|
|
925
1010
|
};
|
|
926
1011
|
}, [isOpen, descendants.length]);
|
|
927
|
-
|
|
928
|
-
// Definite-height flex layout (per-detent sizing) unless content must be
|
|
929
|
-
// measured in natural flow: 'auto' detents (without a plugged scrollable —
|
|
930
|
-
// those measure via `measureNaturalHeight`) and form-sheet content-fit sizing.
|
|
931
|
-
const useSizedLayout = (!hasAutoDetent || hasBoundedScrollable) && !isFormSheet;
|
|
932
1012
|
const effectiveCornerRadius = cornerRadius ?? DEFAULT_CORNER_RADIUS;
|
|
933
1013
|
|
|
934
1014
|
// Shadow cast upward from the sheet's top edge toward the background. Matches
|
|
@@ -942,6 +1022,23 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
942
1022
|
// wrapper edges). drop-shadow on the wrapper follows the post-clip silhouette
|
|
943
1023
|
// and isn't clipped by the wrapper itself.
|
|
944
1024
|
const dropShadow = elevation > 0 ? `drop-shadow(0 ${-elevation}px ${elevation * 3}px rgba(0, 0, 0, 0.15))` : undefined;
|
|
1025
|
+
const backgroundLayerStyle = {
|
|
1026
|
+
position: 'absolute',
|
|
1027
|
+
inset: 0,
|
|
1028
|
+
pointerEvents: 'none',
|
|
1029
|
+
backgroundColor: backgroundColor,
|
|
1030
|
+
transition: isPresentingRef.current ? 'none' : 'background-color 200ms'
|
|
1031
|
+
};
|
|
1032
|
+
|
|
1033
|
+
// Vaul fades the overlay through its opacity, so the dim opacity goes into the color
|
|
1034
|
+
const resolvedOverlayStyle = useMemo(() => {
|
|
1035
|
+
if (dimColor == null && dimOpacity == null) return overlayStyle;
|
|
1036
|
+
const percentage = Math.min(1, Math.max(0, dimOpacity ?? DEFAULT_DIM_OPACITY)) * 100;
|
|
1037
|
+
return {
|
|
1038
|
+
...overlayStyle,
|
|
1039
|
+
backgroundColor: `color-mix(in srgb, ${String(dimColor ?? 'black')} ${percentage}%, transparent)`
|
|
1040
|
+
};
|
|
1041
|
+
}, [dimColor, dimOpacity]);
|
|
945
1042
|
const mergedContentStyle = useMemo(() => ({
|
|
946
1043
|
position: 'fixed',
|
|
947
1044
|
top: 0,
|
|
@@ -952,7 +1049,6 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
952
1049
|
flexDirection: 'column',
|
|
953
1050
|
borderTopLeftRadius: effectiveCornerRadius,
|
|
954
1051
|
borderTopRightRadius: effectiveCornerRadius,
|
|
955
|
-
backgroundColor: backgroundColor,
|
|
956
1052
|
// Clip children to the rounded top so headers/content with their own
|
|
957
1053
|
// background don't bleed past the corners. `clip` (not `hidden`): a
|
|
958
1054
|
// hidden box is still a scroll container, so browser focus-reveal (e.g.
|
|
@@ -962,8 +1058,11 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
962
1058
|
// Lift content above iOS home indicator / bottom safe area when enabled.
|
|
963
1059
|
// A relative footer owns the inset instead (see resolvedFooterStyle); an
|
|
964
1060
|
// absolute footer floats over the content, so the content keeps its lift.
|
|
965
|
-
|
|
966
|
-
|
|
1061
|
+
// A plugged scrollable keeps the lift too (contentInsetAdjustment
|
|
1062
|
+
// 'automatic' | 'safe-area') — 'footer' | 'never' scroll edge-to-edge
|
|
1063
|
+
// behind the indicator with user-applied content padding.
|
|
1064
|
+
paddingBottom: insetAdjustment === 'automatic' && !(footerOwnsInset && !absoluteFooter) && (!scrollableRef || insetBehavior === 'automatic' || insetBehavior === 'safe-area') ? 'env(safe-area-inset-bottom, 0px)' : 0
|
|
1065
|
+
}), [effectiveCornerRadius, insetAdjustment, footerOwnsInset, absoluteFooter, scrollableRef, insetBehavior]);
|
|
967
1066
|
const defaultGrabberColor = colorScheme === 'dark' ? DEFAULT_GRABBER_COLOR_DARK : DEFAULT_GRABBER_COLOR_LIGHT;
|
|
968
1067
|
const grabberHeight = grabberOptions?.height ?? DEFAULT_GRABBER_HEIGHT;
|
|
969
1068
|
|
|
@@ -994,7 +1093,7 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
994
1093
|
// `maxContentWidth` is ignored ('form' is absolute).
|
|
995
1094
|
// - presentation='page' → `maxContentWidth` (any viewport) or
|
|
996
1095
|
// DEFAULT_MAX_WIDTH (tablet/landscape readability cap).
|
|
997
|
-
// Detached without a width constraint applies
|
|
1096
|
+
// Detached without a width constraint applies placementOffset on both edges so
|
|
998
1097
|
// the floating card breathes from the viewport sides.
|
|
999
1098
|
const wrapperStyle = useMemo(() => {
|
|
1000
1099
|
// Mobile portrait ignores width sizing entirely (matches iOS/Android:
|
|
@@ -1006,23 +1105,23 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
1006
1105
|
const next = {};
|
|
1007
1106
|
if (dropShadow) next.filter = dropShadow;
|
|
1008
1107
|
if (!needsMargins) return next;
|
|
1009
|
-
let
|
|
1010
|
-
let
|
|
1108
|
+
let marginInlineStart;
|
|
1109
|
+
let marginInlineEnd;
|
|
1011
1110
|
if (isFormSheet) {
|
|
1012
|
-
|
|
1013
|
-
|
|
1111
|
+
marginInlineStart = 'auto';
|
|
1112
|
+
marginInlineEnd = 'auto';
|
|
1014
1113
|
} else if (maxWidth == null) {
|
|
1015
|
-
|
|
1016
|
-
|
|
1114
|
+
marginInlineStart = placementOffset;
|
|
1115
|
+
marginInlineEnd = placementOffset;
|
|
1017
1116
|
} else {
|
|
1018
|
-
|
|
1019
|
-
|
|
1117
|
+
marginInlineStart = placement === 'leading' ? placementOffset : 'auto';
|
|
1118
|
+
marginInlineEnd = placement === 'trailing' ? placementOffset : 'auto';
|
|
1020
1119
|
}
|
|
1021
1120
|
if (maxWidth != null) next.maxWidth = maxWidth;
|
|
1022
|
-
next.
|
|
1023
|
-
next.
|
|
1121
|
+
next.marginInlineStart = marginInlineStart;
|
|
1122
|
+
next.marginInlineEnd = marginInlineEnd;
|
|
1024
1123
|
return next;
|
|
1025
|
-
}, [isLandscapeOrTablet, isFormSheet, maxContentWidth, presentation,
|
|
1124
|
+
}, [isLandscapeOrTablet, isFormSheet, maxContentWidth, presentation, placement, placementOffset, effectiveDetached, dropShadow]);
|
|
1026
1125
|
|
|
1027
1126
|
// Absolute-position the grabber so it overlays the content top-edge
|
|
1028
1127
|
// instead of consuming flow height — mirrors native iOS/Android, where
|
|
@@ -1052,6 +1151,8 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
1052
1151
|
onDrag: handleDrag,
|
|
1053
1152
|
onRelease: handleRelease,
|
|
1054
1153
|
dismissible: dismissible,
|
|
1154
|
+
dismissThreshold: dismissThreshold,
|
|
1155
|
+
onDismissAttempt: handleDismissAttempt,
|
|
1055
1156
|
draggable: draggable,
|
|
1056
1157
|
repositionInputs: false,
|
|
1057
1158
|
modal: dimmed,
|
|
@@ -1061,22 +1162,19 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
1061
1162
|
detachedRadius: effectiveCornerRadius,
|
|
1062
1163
|
maxContentHeight: effectiveMaxContentHeight,
|
|
1063
1164
|
peekHeight: peekHeight,
|
|
1064
|
-
initialAnimated: initialDetentAnimated,
|
|
1165
|
+
initialAnimated: shouldAutoPresent ? initialDetentAnimated : true,
|
|
1065
1166
|
detachedWrapperStyle: wrapperStyle,
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
// Bounded scrollable → the auto-size wrapper measures 0 (sized
|
|
1069
|
-
// layout is absolute), so feed vaul the natural height instead.
|
|
1070
|
-
hasAutoDetent && hasBoundedScrollable && scrollableAutoHeight > 0 ? scrollableAutoHeight : undefined,
|
|
1167
|
+
contentHeight: measuredContentHeight,
|
|
1168
|
+
keyboardViewport: keyboardViewport,
|
|
1071
1169
|
activeSnapPoint: activeSnapPoint,
|
|
1072
1170
|
setActiveSnapPoint: handleSetActiveSnapPoint,
|
|
1073
1171
|
...snapPointsProps,
|
|
1074
1172
|
children: /*#__PURE__*/_jsxs(Drawer.Portal, {
|
|
1075
1173
|
container: portalContainer ?? undefined,
|
|
1076
1174
|
children: [/*#__PURE__*/_jsx(Drawer.Overlay, {
|
|
1077
|
-
style:
|
|
1175
|
+
style: resolvedOverlayStyle
|
|
1078
1176
|
}), /*#__PURE__*/_jsxs(Drawer.Content, {
|
|
1079
|
-
ref:
|
|
1177
|
+
ref: handleDrawerRef,
|
|
1080
1178
|
style: mergedContentStyle,
|
|
1081
1179
|
onPointerDownOutside: handlePointerDownOutside,
|
|
1082
1180
|
detachedSiblings: footer && absoluteFooter ? /*#__PURE__*/_jsx("div", {
|
|
@@ -1088,31 +1186,30 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
1088
1186
|
children: /*#__PURE__*/isValidElement(footer) ? footer : /*#__PURE__*/createElement(footer)
|
|
1089
1187
|
})
|
|
1090
1188
|
}) : undefined,
|
|
1091
|
-
children: [/*#__PURE__*/_jsx(
|
|
1189
|
+
children: [/*#__PURE__*/_jsx("div", {
|
|
1190
|
+
"aria-hidden": true,
|
|
1191
|
+
style: backgroundLayerStyle
|
|
1192
|
+
}), /*#__PURE__*/_jsx(Drawer.Title, {
|
|
1092
1193
|
style: visuallyHiddenStyle,
|
|
1093
1194
|
children: accessibilityOptions?.paneTitle ?? 'Sheet'
|
|
1094
1195
|
}), grabber && /*#__PURE__*/_jsx(Drawer.Handle, {
|
|
1095
1196
|
style: handleStyle
|
|
1096
|
-
}),
|
|
1097
|
-
/*#__PURE__*/
|
|
1098
|
-
// vaul wraps children in `[data-vaul-auto-size-wrapper]` (display:
|
|
1099
|
-
// flow-root) which doesn't honor descendant flex layout. Use an
|
|
1100
|
-
// absolute fill sized to the visible portion (via vaul's
|
|
1101
|
-
// `--snap-point-height` var) so the inner flex column has a
|
|
1102
|
-
// definite height for flex layouts to fill — mirrors native, where
|
|
1103
|
-
// the container is sized to the sheet's visible height per detent.
|
|
1104
|
-
_jsxs("div", {
|
|
1197
|
+
}), /*#__PURE__*/_jsxs("div", {
|
|
1105
1198
|
ref: sizedLayoutRef,
|
|
1106
1199
|
style: sizedLayoutStyle,
|
|
1107
1200
|
"data-vaul-scroll-locked": isScrollLocked ? '' : undefined,
|
|
1108
|
-
children: [
|
|
1201
|
+
children: [/*#__PURE__*/_jsx(TrueSheetBackground, {
|
|
1202
|
+
background: background,
|
|
1203
|
+
backgroundStyle: backgroundStyle
|
|
1204
|
+
}), header && /*#__PURE__*/_jsx(View, {
|
|
1109
1205
|
ref: headerElRef,
|
|
1110
1206
|
style: resolvedHeaderStyle,
|
|
1111
1207
|
onLayout: handleHeaderLayout,
|
|
1112
1208
|
children: /*#__PURE__*/isValidElement(header) ? header : /*#__PURE__*/createElement(header)
|
|
1113
1209
|
}), /*#__PURE__*/_jsx(View, {
|
|
1114
1210
|
ref: contentRef,
|
|
1115
|
-
style:
|
|
1211
|
+
style: [contentStyle, style],
|
|
1212
|
+
onLayout: handleContentLayout,
|
|
1116
1213
|
children: children
|
|
1117
1214
|
}), footer && !absoluteFooter && /*#__PURE__*/_jsx(View, {
|
|
1118
1215
|
ref: footerElRef,
|
|
@@ -1120,32 +1217,13 @@ const TrueSheetComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
1120
1217
|
onLayout: handleFooterLayout,
|
|
1121
1218
|
children: /*#__PURE__*/isValidElement(footer) ? footer : /*#__PURE__*/createElement(footer)
|
|
1122
1219
|
})]
|
|
1123
|
-
}) :
|
|
1124
|
-
/*#__PURE__*/
|
|
1125
|
-
// Natural flow so vaul can measure content height — required for
|
|
1126
|
-
// 'auto' detents and form-sheet content-fit sizing.
|
|
1127
|
-
_jsxs(_Fragment, {
|
|
1128
|
-
children: [header && /*#__PURE__*/_jsx(View, {
|
|
1129
|
-
ref: headerElRef,
|
|
1130
|
-
style: resolvedHeaderStyle,
|
|
1131
|
-
onLayout: handleHeaderLayout,
|
|
1132
|
-
children: /*#__PURE__*/isValidElement(header) ? header : /*#__PURE__*/createElement(header)
|
|
1133
|
-
}), /*#__PURE__*/_jsx(View, {
|
|
1134
|
-
ref: contentRef,
|
|
1135
|
-
style: style,
|
|
1136
|
-
children: children
|
|
1137
|
-
}), footer && !absoluteFooter && /*#__PURE__*/_jsx(View, {
|
|
1138
|
-
ref: footerElRef,
|
|
1139
|
-
style: resolvedFooterStyle,
|
|
1140
|
-
onLayout: handleFooterLayout,
|
|
1141
|
-
children: /*#__PURE__*/isValidElement(footer) ? footer : /*#__PURE__*/createElement(footer)
|
|
1142
|
-
})]
|
|
1143
1220
|
})]
|
|
1144
1221
|
})]
|
|
1145
1222
|
})
|
|
1146
1223
|
})
|
|
1147
1224
|
});
|
|
1148
1225
|
});
|
|
1226
|
+
const DEFAULT_DIM_OPACITY = 0.5;
|
|
1149
1227
|
const overlayStyle = {
|
|
1150
1228
|
position: 'fixed',
|
|
1151
1229
|
inset: 0,
|
|
@@ -1173,10 +1251,12 @@ const sizedLayoutStyle = {
|
|
|
1173
1251
|
right: 0,
|
|
1174
1252
|
height: SIZED_LAYOUT_HEIGHT,
|
|
1175
1253
|
display: 'flex',
|
|
1176
|
-
flexDirection: 'column'
|
|
1254
|
+
flexDirection: 'column',
|
|
1255
|
+
boxSizing: 'border-box',
|
|
1256
|
+
paddingBottom: 'inherit'
|
|
1177
1257
|
};
|
|
1178
|
-
const
|
|
1179
|
-
|
|
1258
|
+
const contentStyle = {
|
|
1259
|
+
flexShrink: 1,
|
|
1180
1260
|
minHeight: 0
|
|
1181
1261
|
};
|
|
1182
1262
|
const visuallyHiddenStyle = {
|
|
@@ -1190,6 +1270,15 @@ const visuallyHiddenStyle = {
|
|
|
1190
1270
|
whiteSpace: 'nowrap',
|
|
1191
1271
|
border: 0
|
|
1192
1272
|
};
|
|
1273
|
+
const settle = resolveRef => {
|
|
1274
|
+
const resolve = resolveRef.current;
|
|
1275
|
+
resolveRef.current = null;
|
|
1276
|
+
resolve?.();
|
|
1277
|
+
};
|
|
1278
|
+
|
|
1279
|
+
// Same as native
|
|
1280
|
+
const ALREADY_PRESENTED_WARNING = 'TrueSheet: sheet is already presented. Use resize() to change detent.';
|
|
1281
|
+
const ALREADY_DISMISSED_WARNING = 'TrueSheet: sheet is already dismissed. No need to dismiss it again.';
|
|
1193
1282
|
const STATIC_METHOD_ERROR = 'Static methods are not supported on web. Use the useTrueSheet() hook instead.';
|
|
1194
1283
|
export const TrueSheet = TrueSheetComponent;
|
|
1195
1284
|
const rejectStatic = async () => {
|