@seed-design/react-drawer 1.0.11 → 2.0.0
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/lib/{Drawer-12s-DxbsRb10.js → Drawer-12s-5syEwTtq.js} +270 -334
- package/lib/{Drawer-12s-Bkn6enz-.cjs → Drawer-12s-gd4zJm0S.cjs} +267 -350
- package/lib/index.cjs +1 -1
- package/lib/index.d.ts +4075 -56
- package/lib/index.js +2 -2
- package/package.json +9 -5
- package/src/Drawer.tsx +218 -191
- package/src/browser.ts +0 -4
- package/src/useDrawer.test.tsx +54 -40
- package/src/useDrawer.ts +77 -62
- package/src/use-position-fixed.ts +0 -149
|
@@ -1,34 +1,19 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
var jsxRuntime = require('react/jsx-runtime');
|
|
3
3
|
var reactComposeRefs = require('@radix-ui/react-compose-refs');
|
|
4
|
-
var
|
|
4
|
+
var reactFocusScope = require('@radix-ui/react-focus-scope');
|
|
5
5
|
var reactUseCallbackRef = require('@radix-ui/react-use-callback-ref');
|
|
6
|
+
var ariaHidden = require('aria-hidden');
|
|
7
|
+
var reactDismissibleLayer = require('@seed-design/react-dismissible-layer');
|
|
8
|
+
var reactPresence = require('@seed-design/react-presence');
|
|
6
9
|
var domUtils = require('@seed-design/dom-utils');
|
|
10
|
+
var reactPreventScroll = require('@seed-design/react-prevent-scroll');
|
|
7
11
|
var reactPrimitive = require('@seed-design/react-primitive');
|
|
8
12
|
var React = require('react');
|
|
9
13
|
var reactUseControllableState = require('@seed-design/react-use-controllable-state');
|
|
10
14
|
|
|
11
15
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
16
|
|
|
13
|
-
function _interopNamespace(e) {
|
|
14
|
-
if (e && e.__esModule) return e;
|
|
15
|
-
var n = Object.create(null);
|
|
16
|
-
if (e) {
|
|
17
|
-
Object.keys(e).forEach(function (k) {
|
|
18
|
-
if (k !== 'default') {
|
|
19
|
-
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
20
|
-
Object.defineProperty(n, k, d.get ? d : {
|
|
21
|
-
enumerable: true,
|
|
22
|
-
get: function () { return e[k]; }
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
n.default = e;
|
|
28
|
-
return n;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
var DialogPrimitive__namespace = /*#__PURE__*/_interopNamespace(DialogPrimitive);
|
|
32
17
|
var React__default = /*#__PURE__*/_interopDefault(React);
|
|
33
18
|
|
|
34
19
|
function isMobileFirefox() {
|
|
@@ -41,9 +26,6 @@ function isMac() {
|
|
|
41
26
|
function isIPhone() {
|
|
42
27
|
return testPlatform(/^iPhone/);
|
|
43
28
|
}
|
|
44
|
-
function isSafari() {
|
|
45
|
-
return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
|
46
|
-
}
|
|
47
29
|
function isIPad() {
|
|
48
30
|
return testPlatform(/^iPad/) || // iPadOS 13 lies and says it's a Mac, but we can distinguish by detecting touch support.
|
|
49
31
|
isMac() && navigator.maxTouchPoints > 1;
|
|
@@ -148,124 +130,6 @@ function dampenValue(v) {
|
|
|
148
130
|
return 8 * (Math.log(v + 1) - 2);
|
|
149
131
|
}
|
|
150
132
|
|
|
151
|
-
// This code comes from https://github.com/emilkowalski/vaul/blob/main/src/use-position-fixed.ts
|
|
152
|
-
let previousBodyPosition = null;
|
|
153
|
-
/**
|
|
154
|
-
* This hook is necessary to prevent buggy behavior on iOS devices (need to test on Android).
|
|
155
|
-
* I won't get into too much detail about what bugs it solves, but so far I've found that setting the body to `position: fixed` is the most reliable way to prevent those bugs.
|
|
156
|
-
* Issues that this hook solves:
|
|
157
|
-
* https://github.com/emilkowalski/vaul/issues/435
|
|
158
|
-
* https://github.com/emilkowalski/vaul/issues/433
|
|
159
|
-
* And more that I discovered, but were just not reported.
|
|
160
|
-
*/ function usePositionFixed({ isOpen, modal, nested, hasBeenOpened, preventScrollRestoration, noBodyStyles }) {
|
|
161
|
-
const [activeUrl, setActiveUrl] = React__default.default.useState(()=>typeof window !== "undefined" ? window.location.href : "");
|
|
162
|
-
const scrollPos = React__default.default.useRef(0);
|
|
163
|
-
const setPositionFixed = React__default.default.useCallback(()=>{
|
|
164
|
-
// All browsers on iOS will return true here.
|
|
165
|
-
if (!isSafari()) return;
|
|
166
|
-
// If previousBodyPosition is already set, don't set it again.
|
|
167
|
-
if (previousBodyPosition === null && isOpen && !noBodyStyles) {
|
|
168
|
-
previousBodyPosition = {
|
|
169
|
-
position: document.body.style.position,
|
|
170
|
-
top: document.body.style.top,
|
|
171
|
-
left: document.body.style.left,
|
|
172
|
-
height: document.body.style.height,
|
|
173
|
-
right: "unset"
|
|
174
|
-
};
|
|
175
|
-
// Update the dom inside an animation frame
|
|
176
|
-
const { scrollX, innerHeight } = window;
|
|
177
|
-
document.body.style.setProperty("position", "fixed", "important");
|
|
178
|
-
Object.assign(document.body.style, {
|
|
179
|
-
top: `${-scrollPos.current}px`,
|
|
180
|
-
left: `${-scrollX}px`,
|
|
181
|
-
right: "0px",
|
|
182
|
-
height: "auto"
|
|
183
|
-
});
|
|
184
|
-
window.setTimeout(()=>window.requestAnimationFrame(()=>{
|
|
185
|
-
// Attempt to check if the bottom bar appeared due to the position change
|
|
186
|
-
const bottomBarHeight = innerHeight - window.innerHeight;
|
|
187
|
-
if (bottomBarHeight && scrollPos.current >= innerHeight) {
|
|
188
|
-
// Move the content further up so that the bottom bar doesn't hide it
|
|
189
|
-
document.body.style.top = `${-(scrollPos.current + bottomBarHeight)}px`;
|
|
190
|
-
}
|
|
191
|
-
}), 300);
|
|
192
|
-
}
|
|
193
|
-
}, [
|
|
194
|
-
isOpen
|
|
195
|
-
]);
|
|
196
|
-
const restorePositionSetting = React__default.default.useCallback(()=>{
|
|
197
|
-
// All browsers on iOS will return true here.
|
|
198
|
-
if (!isSafari()) return;
|
|
199
|
-
if (previousBodyPosition !== null && !noBodyStyles) {
|
|
200
|
-
// Convert the position from "px" to Int
|
|
201
|
-
const y = -parseInt(document.body.style.top, 10);
|
|
202
|
-
const x = -parseInt(document.body.style.left, 10);
|
|
203
|
-
// Restore styles
|
|
204
|
-
Object.assign(document.body.style, previousBodyPosition);
|
|
205
|
-
window.requestAnimationFrame(()=>{
|
|
206
|
-
if (preventScrollRestoration && activeUrl !== window.location.href) {
|
|
207
|
-
setActiveUrl(window.location.href);
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
window.scrollTo(x, y);
|
|
211
|
-
});
|
|
212
|
-
previousBodyPosition = null;
|
|
213
|
-
}
|
|
214
|
-
}, [
|
|
215
|
-
activeUrl
|
|
216
|
-
]);
|
|
217
|
-
React__default.default.useEffect(()=>{
|
|
218
|
-
function onScroll() {
|
|
219
|
-
scrollPos.current = window.scrollY;
|
|
220
|
-
}
|
|
221
|
-
onScroll();
|
|
222
|
-
window.addEventListener("scroll", onScroll);
|
|
223
|
-
return ()=>{
|
|
224
|
-
window.removeEventListener("scroll", onScroll);
|
|
225
|
-
};
|
|
226
|
-
}, []);
|
|
227
|
-
React__default.default.useEffect(()=>{
|
|
228
|
-
if (!modal) return;
|
|
229
|
-
return ()=>{
|
|
230
|
-
if (typeof document === "undefined") return;
|
|
231
|
-
// Another drawer is opened, safe to ignore the execution
|
|
232
|
-
const hasDrawerOpened = !!document.querySelector("[data-drawer]");
|
|
233
|
-
if (hasDrawerOpened) return;
|
|
234
|
-
restorePositionSetting();
|
|
235
|
-
};
|
|
236
|
-
}, [
|
|
237
|
-
modal,
|
|
238
|
-
restorePositionSetting
|
|
239
|
-
]);
|
|
240
|
-
React__default.default.useEffect(()=>{
|
|
241
|
-
if (nested || !hasBeenOpened) return;
|
|
242
|
-
// This is needed to force Safari toolbar to show **before** the drawer starts animating to prevent a gnarly shift from happening
|
|
243
|
-
if (isOpen) {
|
|
244
|
-
// avoid for standalone mode (PWA)
|
|
245
|
-
const isStandalone = window.matchMedia("(display-mode: standalone)").matches;
|
|
246
|
-
!isStandalone && setPositionFixed();
|
|
247
|
-
if (!modal) {
|
|
248
|
-
window.setTimeout(()=>{
|
|
249
|
-
restorePositionSetting();
|
|
250
|
-
}, 500);
|
|
251
|
-
}
|
|
252
|
-
} else {
|
|
253
|
-
restorePositionSetting();
|
|
254
|
-
}
|
|
255
|
-
}, [
|
|
256
|
-
isOpen,
|
|
257
|
-
hasBeenOpened,
|
|
258
|
-
activeUrl,
|
|
259
|
-
modal,
|
|
260
|
-
nested,
|
|
261
|
-
setPositionFixed,
|
|
262
|
-
restorePositionSetting
|
|
263
|
-
]);
|
|
264
|
-
return {
|
|
265
|
-
restorePositionSetting
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
|
|
269
133
|
function useSnapPoints({ activeSnapPointProp, setActiveSnapPointProp, snapPoints, drawerRef, overlayRef, fadeFromIndex, onSnapPointChange, direction = "bottom", container, snapToSequentialPoint }) {
|
|
270
134
|
const [activeSnapPoint, setActiveSnapPoint] = reactUseControllableState.useControllableState({
|
|
271
135
|
prop: activeSnapPointProp,
|
|
@@ -491,38 +355,24 @@ function useSnapPoints({ activeSnapPointProp, setActiveSnapPointProp, snapPoints
|
|
|
491
355
|
}
|
|
492
356
|
|
|
493
357
|
function useDrawer(props) {
|
|
494
|
-
const { open: openProp, onOpenChange, onDrag: onDragProp, onRelease: onReleaseProp, snapPoints, closeThreshold = CLOSE_THRESHOLD, scrollLockTimeout = SCROLL_LOCK_TIMEOUT, dismissible = true, handleOnly = false, fadeFromIndex = snapPoints && snapPoints.length - 1, activeSnapPoint: activeSnapPointProp, setActiveSnapPoint: setActiveSnapPointProp, fixed, modal = true, onClose,
|
|
358
|
+
const { open: openProp, onOpenChange, onDrag: onDragProp, onRelease: onReleaseProp, snapPoints, closeThreshold = CLOSE_THRESHOLD, scrollLockTimeout = SCROLL_LOCK_TIMEOUT, dismissible = true, handleOnly = false, fadeFromIndex = snapPoints && snapPoints.length - 1, activeSnapPoint: activeSnapPointProp, setActiveSnapPoint: setActiveSnapPointProp, fixed, modal = true, onClose, direction = "bottom", defaultOpen = false, snapToSequentialPoint = false, repositionInputs = true, onAnimationEnd, container, autoFocus = true, closeOnInteractOutside = true, closeOnEscape = true, lazyMount: lazyMountProp = false, unmountOnExit: unmountOnExitProp = false } = props;
|
|
359
|
+
const drawerId = React.useId();
|
|
360
|
+
const titleId = `${drawerId}-title`;
|
|
361
|
+
const descriptionId = `${drawerId}-description`;
|
|
495
362
|
const [isOpen = false, setIsOpen] = reactUseControllableState.useControllableState({
|
|
496
363
|
defaultProp: defaultOpen,
|
|
497
364
|
prop: openProp,
|
|
498
365
|
onChange: (o, details)=>{
|
|
499
366
|
onOpenChange?.(o, details);
|
|
500
|
-
if (!o && !nested) {
|
|
501
|
-
restorePositionSetting();
|
|
502
|
-
}
|
|
503
367
|
setTimeout(()=>{
|
|
504
368
|
onAnimationEnd?.(o);
|
|
505
369
|
}, TRANSITIONS.EXIT_DURATION * 1000);
|
|
506
|
-
if (o && !modal) {
|
|
507
|
-
if (typeof window !== "undefined") {
|
|
508
|
-
window.requestAnimationFrame(()=>{
|
|
509
|
-
document.body.style.pointerEvents = "auto";
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
if (!o) {
|
|
514
|
-
document.body.style.pointerEvents = "auto";
|
|
515
|
-
}
|
|
516
370
|
}
|
|
517
371
|
});
|
|
518
372
|
const [hasBeenOpened, setHasBeenOpened] = React.useState(false);
|
|
519
373
|
const [hasAnimationDone, setHasAnimationDone] = React.useState(false);
|
|
520
374
|
const [isDragging, setIsDragging] = React.useState(false);
|
|
521
375
|
const [shouldOverlayAnimate, setShouldOverlayAnimate] = React.useState(false);
|
|
522
|
-
const [isCloseButtonRendered, setIsCloseButtonRendered] = React.useState(false);
|
|
523
|
-
const closeButtonRef = React.useCallback((node)=>{
|
|
524
|
-
setIsCloseButtonRendered(!!node);
|
|
525
|
-
}, []);
|
|
526
376
|
const overlayRef = React.useRef(null);
|
|
527
377
|
const openTime = React.useRef(null);
|
|
528
378
|
const dragStartTime = React.useRef(null);
|
|
@@ -554,14 +404,6 @@ function useDrawer(props) {
|
|
|
554
404
|
direction,
|
|
555
405
|
snapToSequentialPoint
|
|
556
406
|
});
|
|
557
|
-
const { restorePositionSetting } = usePositionFixed({
|
|
558
|
-
isOpen,
|
|
559
|
-
modal,
|
|
560
|
-
nested: nested ?? false,
|
|
561
|
-
hasBeenOpened,
|
|
562
|
-
preventScrollRestoration,
|
|
563
|
-
noBodyStyles
|
|
564
|
-
});
|
|
565
407
|
function onPress(event) {
|
|
566
408
|
if (!dismissible && !snapPoints) return;
|
|
567
409
|
if (drawerRef.current && !drawerRef.current.contains(event.target)) return;
|
|
@@ -843,15 +685,6 @@ function useDrawer(props) {
|
|
|
843
685
|
repositionInputs,
|
|
844
686
|
fixed
|
|
845
687
|
]);
|
|
846
|
-
React.useEffect(()=>{
|
|
847
|
-
if (!modal) {
|
|
848
|
-
window.requestAnimationFrame(()=>{
|
|
849
|
-
document.body.style.pointerEvents = "auto";
|
|
850
|
-
});
|
|
851
|
-
}
|
|
852
|
-
}, [
|
|
853
|
-
modal
|
|
854
|
-
]);
|
|
855
688
|
// Effect 1: Track drawer open state
|
|
856
689
|
React.useEffect(()=>{
|
|
857
690
|
if (isOpen) {
|
|
@@ -892,6 +725,11 @@ function useDrawer(props) {
|
|
|
892
725
|
snapPoints,
|
|
893
726
|
fadeFromIndex
|
|
894
727
|
]);
|
|
728
|
+
const stateProps = React.useMemo(()=>domUtils.elementProps({
|
|
729
|
+
"data-open": domUtils.dataAttr(isOpen)
|
|
730
|
+
}), [
|
|
731
|
+
isOpen
|
|
732
|
+
]);
|
|
895
733
|
return React.useMemo(()=>({
|
|
896
734
|
activeSnapPoint,
|
|
897
735
|
snapPoints,
|
|
@@ -914,16 +752,57 @@ function useDrawer(props) {
|
|
|
914
752
|
snapPointsOffset,
|
|
915
753
|
activeSnapPointIndex,
|
|
916
754
|
direction,
|
|
917
|
-
noBodyStyles,
|
|
918
755
|
container,
|
|
919
756
|
autoFocus,
|
|
920
757
|
setHasBeenOpened,
|
|
921
758
|
setIsOpen,
|
|
922
759
|
closeOnInteractOutside,
|
|
923
760
|
closeOnEscape,
|
|
761
|
+
titleId,
|
|
762
|
+
descriptionId,
|
|
763
|
+
lazyMount: lazyMountProp,
|
|
764
|
+
unmountOnExit: unmountOnExitProp,
|
|
924
765
|
hasAnimationDone,
|
|
925
|
-
|
|
926
|
-
|
|
766
|
+
triggerProps: domUtils.buttonProps({
|
|
767
|
+
...stateProps,
|
|
768
|
+
onClick: (e)=>{
|
|
769
|
+
if (e.defaultPrevented) return;
|
|
770
|
+
setIsOpen(true, {
|
|
771
|
+
reason: "trigger",
|
|
772
|
+
event: e.nativeEvent
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
}),
|
|
776
|
+
positionerProps: domUtils.elementProps({
|
|
777
|
+
...stateProps,
|
|
778
|
+
style: {
|
|
779
|
+
pointerEvents: isOpen && modal ? undefined : "none"
|
|
780
|
+
}
|
|
781
|
+
}),
|
|
782
|
+
backdropProps: domUtils.elementProps({
|
|
783
|
+
...stateProps
|
|
784
|
+
}),
|
|
785
|
+
titleProps: domUtils.elementProps({
|
|
786
|
+
id: titleId,
|
|
787
|
+
...stateProps
|
|
788
|
+
}),
|
|
789
|
+
descriptionProps: domUtils.elementProps({
|
|
790
|
+
id: descriptionId,
|
|
791
|
+
...stateProps
|
|
792
|
+
}),
|
|
793
|
+
headerProps: domUtils.elementProps({
|
|
794
|
+
...stateProps
|
|
795
|
+
}),
|
|
796
|
+
closeButtonProps: domUtils.buttonProps({
|
|
797
|
+
...stateProps,
|
|
798
|
+
onClick: (e)=>{
|
|
799
|
+
if (e.defaultPrevented) return;
|
|
800
|
+
setIsOpen(false, {
|
|
801
|
+
reason: "closeButton",
|
|
802
|
+
event: e.nativeEvent
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
})
|
|
927
806
|
}), [
|
|
928
807
|
activeSnapPoint,
|
|
929
808
|
snapPoints,
|
|
@@ -940,7 +819,6 @@ function useDrawer(props) {
|
|
|
940
819
|
snapPointsOffset,
|
|
941
820
|
activeSnapPointIndex,
|
|
942
821
|
direction,
|
|
943
|
-
noBodyStyles,
|
|
944
822
|
container,
|
|
945
823
|
autoFocus,
|
|
946
824
|
setIsOpen,
|
|
@@ -949,9 +827,12 @@ function useDrawer(props) {
|
|
|
949
827
|
onRelease,
|
|
950
828
|
onDrag,
|
|
951
829
|
onPress,
|
|
830
|
+
titleId,
|
|
831
|
+
descriptionId,
|
|
832
|
+
lazyMountProp,
|
|
833
|
+
unmountOnExitProp,
|
|
952
834
|
hasAnimationDone,
|
|
953
|
-
|
|
954
|
-
isCloseButtonRendered
|
|
835
|
+
stateProps
|
|
955
836
|
]);
|
|
956
837
|
}
|
|
957
838
|
|
|
@@ -966,66 +847,74 @@ function useDrawerContext() {
|
|
|
966
847
|
}
|
|
967
848
|
|
|
968
849
|
const DrawerRoot = (props)=>{
|
|
969
|
-
const { children, defaultOpen, dismissible, modal } = props;
|
|
970
850
|
const api = useDrawer(props);
|
|
971
|
-
return /*#__PURE__*/ jsxRuntime.jsx(
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
onOpenChange: (open)=>{
|
|
975
|
-
if (!dismissible && !open) return;
|
|
976
|
-
if (open) {
|
|
977
|
-
api.setHasBeenOpened(true);
|
|
978
|
-
} else {
|
|
979
|
-
api.closeDrawer(true);
|
|
980
|
-
}
|
|
981
|
-
api.setIsOpen(open);
|
|
982
|
-
},
|
|
983
|
-
modal: modal,
|
|
984
|
-
children: /*#__PURE__*/ jsxRuntime.jsx(DrawerProvider, {
|
|
985
|
-
value: api,
|
|
986
|
-
children: children
|
|
987
|
-
})
|
|
851
|
+
return /*#__PURE__*/ jsxRuntime.jsx(DrawerProvider, {
|
|
852
|
+
value: api,
|
|
853
|
+
children: props.children
|
|
988
854
|
});
|
|
989
855
|
};
|
|
990
|
-
const DrawerTrigger =
|
|
856
|
+
const DrawerTrigger = /*#__PURE__*/ React.forwardRef((props, ref)=>{
|
|
857
|
+
const api = useDrawerContext();
|
|
858
|
+
return /*#__PURE__*/ jsxRuntime.jsx(reactPrimitive.Primitive.button, {
|
|
859
|
+
ref: ref,
|
|
860
|
+
...domUtils.mergeProps(api.triggerProps, props)
|
|
861
|
+
});
|
|
862
|
+
});
|
|
863
|
+
DrawerTrigger.displayName = "DrawerTrigger";
|
|
991
864
|
const DrawerPositioner = /*#__PURE__*/ React.forwardRef((props, ref)=>{
|
|
992
865
|
const api = useDrawerContext();
|
|
993
866
|
return /*#__PURE__*/ jsxRuntime.jsx(reactPrimitive.Primitive.div, {
|
|
994
867
|
ref: ref,
|
|
995
|
-
...props
|
|
996
|
-
style: {
|
|
997
|
-
pointerEvents: api.isOpen ? undefined : "none",
|
|
998
|
-
...props.style
|
|
999
|
-
}
|
|
868
|
+
...domUtils.mergeProps(api.positionerProps, props)
|
|
1000
869
|
});
|
|
1001
870
|
});
|
|
1002
871
|
DrawerPositioner.displayName = "DrawerPositioner";
|
|
1003
872
|
const DrawerBackdrop = /*#__PURE__*/ React.forwardRef((props, ref)=>{
|
|
1004
|
-
const { overlayRef, onRelease, modal, snapPoints, isOpen, shouldFade, shouldOverlayAnimate, hasAnimationDone } = useDrawerContext();
|
|
1005
|
-
const composedRef = reactComposeRefs.
|
|
873
|
+
const { overlayRef, onRelease, modal, snapPoints, isOpen, shouldFade, shouldOverlayAnimate, hasAnimationDone, lazyMount, unmountOnExit } = useDrawerContext();
|
|
874
|
+
const composedRef = reactComposeRefs.composeRefs(ref, overlayRef);
|
|
1006
875
|
const hasSnapPoints = snapPoints && snapPoints.length > 0;
|
|
1007
876
|
const onMouseUp = reactUseCallbackRef.useCallbackRef((event)=>onRelease(event));
|
|
1008
877
|
if (!modal) {
|
|
1009
878
|
return null;
|
|
1010
879
|
}
|
|
1011
|
-
return /*#__PURE__*/ jsxRuntime.jsx(
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
880
|
+
return /*#__PURE__*/ jsxRuntime.jsx(reactPresence.Presence, {
|
|
881
|
+
present: isOpen,
|
|
882
|
+
unmountOnExit: unmountOnExit,
|
|
883
|
+
lazyMount: lazyMount,
|
|
884
|
+
children: /*#__PURE__*/ jsxRuntime.jsx(reactPrimitive.Primitive.div, {
|
|
885
|
+
ref: composedRef,
|
|
886
|
+
onMouseUp: onMouseUp,
|
|
887
|
+
"data-snap-points": isOpen && hasSnapPoints ? "true" : "false",
|
|
888
|
+
"data-snap-points-overlay": isOpen && shouldFade ? "true" : "false",
|
|
889
|
+
"data-should-overlay-animate": shouldOverlayAnimate ? "true" : "false",
|
|
890
|
+
"data-open": domUtils.dataAttr(isOpen),
|
|
891
|
+
"data-animation-done": hasAnimationDone ? "true" : "false",
|
|
892
|
+
...props
|
|
893
|
+
})
|
|
1020
894
|
});
|
|
1021
895
|
});
|
|
1022
896
|
DrawerBackdrop.displayName = "DrawerBackdrop";
|
|
1023
897
|
const DrawerContent = /*#__PURE__*/ React.forwardRef((props, ref)=>{
|
|
1024
|
-
const {
|
|
1025
|
-
const { drawerRef, onPress, onRelease, onDrag, keyboardIsOpen, snapPointsOffset, activeSnapPointIndex, modal, isOpen, direction, snapPoints, container, handleOnly, autoFocus, closeDrawer, closeOnInteractOutside, closeOnEscape, dismissible, hasAnimationDone } = useDrawerContext();
|
|
898
|
+
const { style, ...restProps } = props;
|
|
899
|
+
const { drawerRef, onPress, onRelease, onDrag, keyboardIsOpen, snapPointsOffset, activeSnapPointIndex, modal, isOpen, direction, snapPoints, container, handleOnly, autoFocus, closeDrawer, closeOnInteractOutside, closeOnEscape, dismissible, hasAnimationDone, titleId, descriptionId, lazyMount, unmountOnExit } = useDrawerContext();
|
|
900
|
+
// Lock body scroll while the modal drawer is open. Mirrors the `modal` contract: the lock
|
|
901
|
+
// releases the moment the drawer is non-modal or closed.
|
|
902
|
+
reactPreventScroll.usePreventScroll({
|
|
903
|
+
isDisabled: !(modal && isOpen)
|
|
904
|
+
});
|
|
905
|
+
const [contentNode, setContentNode] = React.useState(null);
|
|
906
|
+
const contentRef = React.useCallback((el)=>setContentNode(el), []);
|
|
907
|
+
// aria-hide everything except the content when modal
|
|
908
|
+
React.useEffect(()=>{
|
|
909
|
+
if (!isOpen || !modal || !contentNode) return;
|
|
910
|
+
return ariaHidden.hideOthers(contentNode);
|
|
911
|
+
}, [
|
|
912
|
+
isOpen,
|
|
913
|
+
modal,
|
|
914
|
+
contentNode
|
|
915
|
+
]);
|
|
1026
916
|
// Needed to use transition instead of animations
|
|
1027
917
|
const [delayedSnapPoints, setDelayedSnapPoints] = React.useState(false);
|
|
1028
|
-
const composedRef = reactComposeRefs.useComposedRefs(ref, drawerRef);
|
|
1029
918
|
const pointerStartRef = React.useRef(null);
|
|
1030
919
|
const lastKnownPointerEventRef = React.useRef(null);
|
|
1031
920
|
const wasBeyondThePointRef = React.useRef(false);
|
|
@@ -1034,7 +923,7 @@ const DrawerContent = /*#__PURE__*/ React.forwardRef((props, ref)=>{
|
|
|
1034
923
|
// element the gesture started on (iOS still synthesizes a click on release).
|
|
1035
924
|
const draggedRef = React.useRef(false);
|
|
1036
925
|
const hasSnapPoints = snapPoints && snapPoints.length > 0;
|
|
1037
|
-
const isDeltaInDirection = (delta,
|
|
926
|
+
const isDeltaInDirection = (delta, dir, threshold = 0)=>{
|
|
1038
927
|
if (wasBeyondThePointRef.current) return true;
|
|
1039
928
|
const deltaY = Math.abs(delta.y);
|
|
1040
929
|
const deltaX = Math.abs(delta.x);
|
|
@@ -1042,8 +931,8 @@ const DrawerContent = /*#__PURE__*/ React.forwardRef((props, ref)=>{
|
|
|
1042
931
|
const dFactor = [
|
|
1043
932
|
"bottom",
|
|
1044
933
|
"right"
|
|
1045
|
-
].includes(
|
|
1046
|
-
if (
|
|
934
|
+
].includes(dir) ? 1 : -1;
|
|
935
|
+
if (dir === "left" || dir === "right") {
|
|
1047
936
|
const isReverseDirection = delta.x * dFactor < 0;
|
|
1048
937
|
if (!isReverseDirection && deltaX >= 0 && deltaX <= threshold) {
|
|
1049
938
|
return isDeltaX;
|
|
@@ -1069,149 +958,177 @@ const DrawerContent = /*#__PURE__*/ React.forwardRef((props, ref)=>{
|
|
|
1069
958
|
wasBeyondThePointRef.current = false;
|
|
1070
959
|
onRelease(event);
|
|
1071
960
|
}
|
|
1072
|
-
return /*#__PURE__*/ jsxRuntime.jsx(
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
ref: composedRef,
|
|
1082
|
-
style: snapPointsOffset && snapPointsOffset.length > 0 ? {
|
|
1083
|
-
"--snap-point-height": `${snapPointsOffset[activeSnapPointIndex ?? 0]}px`,
|
|
1084
|
-
...style
|
|
1085
|
-
} : style ?? {},
|
|
1086
|
-
onPointerDown: (event)=>{
|
|
1087
|
-
if (handleOnly) return;
|
|
1088
|
-
restProps.onPointerDown?.(event);
|
|
1089
|
-
pointerStartRef.current = {
|
|
1090
|
-
x: event.pageX,
|
|
1091
|
-
y: event.pageY
|
|
1092
|
-
};
|
|
1093
|
-
draggedRef.current = false;
|
|
1094
|
-
onPress(event);
|
|
1095
|
-
},
|
|
1096
|
-
onOpenAutoFocus: (e)=>{
|
|
1097
|
-
onOpenAutoFocus?.(e);
|
|
1098
|
-
if (!autoFocus) {
|
|
1099
|
-
e.preventDefault();
|
|
1100
|
-
}
|
|
1101
|
-
},
|
|
1102
|
-
onPointerDownOutside: (e)=>{
|
|
1103
|
-
onPointerDownOutside?.(e);
|
|
1104
|
-
if (!modal || e.defaultPrevented) {
|
|
1105
|
-
e.preventDefault();
|
|
1106
|
-
return;
|
|
1107
|
-
}
|
|
1108
|
-
if (keyboardIsOpen.current) {
|
|
1109
|
-
keyboardIsOpen.current = false;
|
|
1110
|
-
}
|
|
1111
|
-
},
|
|
1112
|
-
onFocusOutside: (e)=>{
|
|
1113
|
-
props.onFocusOutside?.(e);
|
|
1114
|
-
// Always prevent focusOutside to avoid conflicts when focus moves between modals
|
|
1115
|
-
// (e.g., when Dialog closes and restores focus while BottomSheet is opening)
|
|
1116
|
-
e.preventDefault();
|
|
1117
|
-
},
|
|
1118
|
-
onPointerMove: (event)=>{
|
|
1119
|
-
lastKnownPointerEventRef.current = event;
|
|
1120
|
-
if (handleOnly) return;
|
|
1121
|
-
restProps.onPointerMove?.(event);
|
|
1122
|
-
if (!pointerStartRef.current) return;
|
|
1123
|
-
const yPosition = event.pageY - pointerStartRef.current.y;
|
|
1124
|
-
const xPosition = event.pageX - pointerStartRef.current.x;
|
|
1125
|
-
const swipeStartThreshold = event.pointerType === "touch" ? 10 : 2;
|
|
1126
|
-
const delta = {
|
|
1127
|
-
x: xPosition,
|
|
1128
|
-
y: yPosition
|
|
1129
|
-
};
|
|
1130
|
-
// Once the pointer travels past the swipe-start threshold the gesture
|
|
1131
|
-
// is a drag, not a tap — remember it so the trailing click can be
|
|
1132
|
-
// suppressed on release (iOS still synthesizes one on the origin).
|
|
1133
|
-
const isBeyondThreshold = Math.abs(xPosition) > swipeStartThreshold || Math.abs(yPosition) > swipeStartThreshold;
|
|
1134
|
-
if (isBeyondThreshold) draggedRef.current = true;
|
|
1135
|
-
const isAllowedToSwipe = isDeltaInDirection(delta, direction, swipeStartThreshold);
|
|
1136
|
-
if (isAllowedToSwipe) onDrag(event);
|
|
1137
|
-
else if (isBeyondThreshold) {
|
|
1138
|
-
pointerStartRef.current = null;
|
|
1139
|
-
}
|
|
1140
|
-
},
|
|
1141
|
-
onPointerUp: (event)=>{
|
|
1142
|
-
restProps.onPointerUp?.(event);
|
|
1143
|
-
pointerStartRef.current = null;
|
|
1144
|
-
wasBeyondThePointRef.current = false;
|
|
1145
|
-
onRelease(event);
|
|
1146
|
-
},
|
|
1147
|
-
onPointerOut: (event)=>{
|
|
1148
|
-
restProps.onPointerOut?.(event);
|
|
1149
|
-
handleOnPointerUp(lastKnownPointerEventRef.current);
|
|
1150
|
-
},
|
|
1151
|
-
onContextMenu: (event)=>{
|
|
1152
|
-
restProps.onContextMenu?.(event);
|
|
1153
|
-
if (lastKnownPointerEventRef.current) {
|
|
1154
|
-
handleOnPointerUp(lastKnownPointerEventRef.current);
|
|
1155
|
-
}
|
|
1156
|
-
},
|
|
1157
|
-
onClickCapture: (event)=>{
|
|
1158
|
-
restProps.onClickCapture?.(event);
|
|
1159
|
-
// Swallow the click that follows a drag so dragging the sheet (e.g. a
|
|
1160
|
-
// swipe-to-dismiss started on an item) doesn't also activate that item.
|
|
1161
|
-
// A plain tap leaves draggedRef false and clicks normally.
|
|
1162
|
-
if (draggedRef.current) {
|
|
1163
|
-
event.preventDefault();
|
|
1164
|
-
event.stopPropagation();
|
|
1165
|
-
draggedRef.current = false;
|
|
1166
|
-
}
|
|
1167
|
-
},
|
|
1168
|
-
onInteractOutside: (e)=>{
|
|
1169
|
-
// Only close if event is not prevented (e.g., by onFocusOutside or onPointerDownOutside)
|
|
1170
|
-
if (dismissible && closeOnInteractOutside && !e.defaultPrevented) {
|
|
961
|
+
return /*#__PURE__*/ jsxRuntime.jsx(reactPresence.Presence, {
|
|
962
|
+
present: isOpen,
|
|
963
|
+
unmountOnExit: unmountOnExit,
|
|
964
|
+
lazyMount: lazyMount,
|
|
965
|
+
children: /*#__PURE__*/ jsxRuntime.jsx(reactDismissibleLayer.DismissibleLayer, {
|
|
966
|
+
enabled: isOpen,
|
|
967
|
+
onEscapeKeyDown: (e)=>{
|
|
968
|
+
if (e.defaultPrevented) return;
|
|
969
|
+
if (!dismissible || !closeOnEscape) return;
|
|
1171
970
|
closeDrawer(false, {
|
|
1172
|
-
reason: "
|
|
1173
|
-
event: e
|
|
971
|
+
reason: "escapeKeyDown",
|
|
972
|
+
event: e
|
|
1174
973
|
});
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
974
|
+
},
|
|
975
|
+
onPressOutside: (e)=>{
|
|
976
|
+
if (e.defaultPrevented) return;
|
|
977
|
+
if (!modal) return;
|
|
978
|
+
if (keyboardIsOpen.current) keyboardIsOpen.current = false;
|
|
979
|
+
if (!dismissible || !closeOnInteractOutside) return;
|
|
1180
980
|
closeDrawer(false, {
|
|
1181
|
-
reason: "
|
|
981
|
+
reason: "interactOutside",
|
|
1182
982
|
event: e
|
|
1183
983
|
});
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
|
|
984
|
+
},
|
|
985
|
+
onFocusOutside: ()=>{
|
|
986
|
+
// Focus trapping is handled by FocusScope — nothing to do here.
|
|
987
|
+
// The old Radix architecture needed e.preventDefault() here (PR #1187)
|
|
988
|
+
// to prevent cascade closes, but the new DismissibleLayer handles
|
|
989
|
+
// that via onCascadeDismiss instead.
|
|
990
|
+
},
|
|
991
|
+
onCascadeDismiss: ({ dismissedParent })=>{
|
|
992
|
+
closeDrawer(false, {
|
|
993
|
+
reason: "cascadeDismiss",
|
|
994
|
+
dismissedParent
|
|
995
|
+
});
|
|
996
|
+
},
|
|
997
|
+
children: /*#__PURE__*/ jsxRuntime.jsx(reactFocusScope.FocusScope, {
|
|
998
|
+
asChild: true,
|
|
999
|
+
loop: modal,
|
|
1000
|
+
trapped: modal && isOpen,
|
|
1001
|
+
onMountAutoFocus: (e)=>{
|
|
1002
|
+
// prevent FocusScope's default autoFocus behavior
|
|
1003
|
+
e.preventDefault();
|
|
1004
|
+
// when autoFocus is true, FocusScope sets the focus to the first tabbable element; otherwise content;
|
|
1005
|
+
// the desired behavior is to set the focus to the content regardless of whether there are tabbable elements or not when true]
|
|
1006
|
+
if (autoFocus) {
|
|
1007
|
+
drawerRef.current?.focus();
|
|
1008
|
+
}
|
|
1009
|
+
// later, we can do something like:
|
|
1010
|
+
// when trigger has clicked using keyboard, focus the first tabbable element;
|
|
1011
|
+
// when trigger has clicked using mouse, focus the content
|
|
1012
|
+
// -> matches Menu behavior
|
|
1013
|
+
},
|
|
1014
|
+
children: /*#__PURE__*/ jsxRuntime.jsx(reactPrimitive.Primitive.div, {
|
|
1015
|
+
role: "dialog",
|
|
1016
|
+
"aria-modal": modal,
|
|
1017
|
+
"aria-labelledby": titleId,
|
|
1018
|
+
"aria-describedby": descriptionId,
|
|
1019
|
+
"data-delayed-snap-points": delayedSnapPoints ? "true" : "false",
|
|
1020
|
+
"data-drawer-direction": direction,
|
|
1021
|
+
"data-open": domUtils.dataAttr(isOpen),
|
|
1022
|
+
"data-animation-done": hasAnimationDone ? "true" : "false",
|
|
1023
|
+
"data-drawer": "",
|
|
1024
|
+
"data-snap-points": isOpen && hasSnapPoints ? "true" : "false",
|
|
1025
|
+
"data-custom-container": container ? "true" : "false",
|
|
1026
|
+
...restProps,
|
|
1027
|
+
ref: reactComposeRefs.composeRefs(ref, drawerRef, contentRef),
|
|
1028
|
+
style: {
|
|
1029
|
+
...snapPointsOffset && snapPointsOffset.length > 0 ? {
|
|
1030
|
+
"--snap-point-height": `${snapPointsOffset[activeSnapPointIndex ?? 0]}px`,
|
|
1031
|
+
...style
|
|
1032
|
+
} : style,
|
|
1033
|
+
...!modal && {
|
|
1034
|
+
pointerEvents: "auto"
|
|
1035
|
+
}
|
|
1036
|
+
},
|
|
1037
|
+
onPointerDown: (event)=>{
|
|
1038
|
+
if (handleOnly) return;
|
|
1039
|
+
restProps.onPointerDown?.(event);
|
|
1040
|
+
pointerStartRef.current = {
|
|
1041
|
+
x: event.pageX,
|
|
1042
|
+
y: event.pageY
|
|
1043
|
+
};
|
|
1044
|
+
draggedRef.current = false;
|
|
1045
|
+
onPress(event);
|
|
1046
|
+
},
|
|
1047
|
+
onPointerMove: (event)=>{
|
|
1048
|
+
lastKnownPointerEventRef.current = event;
|
|
1049
|
+
if (handleOnly) return;
|
|
1050
|
+
restProps.onPointerMove?.(event);
|
|
1051
|
+
if (!pointerStartRef.current) return;
|
|
1052
|
+
const yPosition = event.pageY - pointerStartRef.current.y;
|
|
1053
|
+
const xPosition = event.pageX - pointerStartRef.current.x;
|
|
1054
|
+
const swipeStartThreshold = event.pointerType === "touch" ? 10 : 2;
|
|
1055
|
+
const delta = {
|
|
1056
|
+
x: xPosition,
|
|
1057
|
+
y: yPosition
|
|
1058
|
+
};
|
|
1059
|
+
// Once the pointer travels past the swipe-start threshold the gesture
|
|
1060
|
+
// is a drag, not a tap — remember it so the trailing click can be
|
|
1061
|
+
// suppressed on release (iOS still synthesizes one on the origin).
|
|
1062
|
+
const isBeyondThreshold = Math.abs(xPosition) > swipeStartThreshold || Math.abs(yPosition) > swipeStartThreshold;
|
|
1063
|
+
if (isBeyondThreshold) draggedRef.current = true;
|
|
1064
|
+
const isAllowedToSwipe = isDeltaInDirection(delta, direction, swipeStartThreshold);
|
|
1065
|
+
if (isAllowedToSwipe) onDrag(event);
|
|
1066
|
+
else if (isBeyondThreshold) {
|
|
1067
|
+
pointerStartRef.current = null;
|
|
1068
|
+
}
|
|
1069
|
+
},
|
|
1070
|
+
onPointerUp: (event)=>{
|
|
1071
|
+
restProps.onPointerUp?.(event);
|
|
1072
|
+
pointerStartRef.current = null;
|
|
1073
|
+
wasBeyondThePointRef.current = false;
|
|
1074
|
+
onRelease(event);
|
|
1075
|
+
},
|
|
1076
|
+
onPointerOut: (event)=>{
|
|
1077
|
+
restProps.onPointerOut?.(event);
|
|
1078
|
+
handleOnPointerUp(lastKnownPointerEventRef.current);
|
|
1079
|
+
},
|
|
1080
|
+
onContextMenu: (event)=>{
|
|
1081
|
+
restProps.onContextMenu?.(event);
|
|
1082
|
+
if (lastKnownPointerEventRef.current) {
|
|
1083
|
+
handleOnPointerUp(lastKnownPointerEventRef.current);
|
|
1084
|
+
}
|
|
1085
|
+
},
|
|
1086
|
+
onClickCapture: (event)=>{
|
|
1087
|
+
restProps.onClickCapture?.(event);
|
|
1088
|
+
// Swallow the click that follows a drag so dragging the sheet (e.g. a
|
|
1089
|
+
// swipe-to-dismiss started on an item) doesn't also activate that item.
|
|
1090
|
+
// A plain tap leaves draggedRef false and clicks normally.
|
|
1091
|
+
if (draggedRef.current) {
|
|
1092
|
+
event.preventDefault();
|
|
1093
|
+
event.stopPropagation();
|
|
1094
|
+
draggedRef.current = false;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
})
|
|
1098
|
+
})
|
|
1099
|
+
})
|
|
1187
1100
|
});
|
|
1188
1101
|
});
|
|
1189
1102
|
DrawerContent.displayName = "DrawerContent";
|
|
1190
|
-
const DrawerTitle =
|
|
1191
|
-
const
|
|
1103
|
+
const DrawerTitle = /*#__PURE__*/ React.forwardRef((props, ref)=>{
|
|
1104
|
+
const api = useDrawerContext();
|
|
1105
|
+
return /*#__PURE__*/ jsxRuntime.jsx(reactPrimitive.Primitive.h2, {
|
|
1106
|
+
ref: ref,
|
|
1107
|
+
...domUtils.mergeProps(api.titleProps, props)
|
|
1108
|
+
});
|
|
1109
|
+
});
|
|
1110
|
+
DrawerTitle.displayName = "DrawerTitle";
|
|
1111
|
+
const DrawerDescription = /*#__PURE__*/ React.forwardRef((props, ref)=>{
|
|
1112
|
+
const api = useDrawerContext();
|
|
1113
|
+
return /*#__PURE__*/ jsxRuntime.jsx(reactPrimitive.Primitive.p, {
|
|
1114
|
+
ref: ref,
|
|
1115
|
+
...domUtils.mergeProps(api.descriptionProps, props)
|
|
1116
|
+
});
|
|
1117
|
+
});
|
|
1118
|
+
DrawerDescription.displayName = "DrawerDescription";
|
|
1192
1119
|
const DrawerHeader = /*#__PURE__*/ React.forwardRef((props, ref)=>{
|
|
1193
|
-
const
|
|
1120
|
+
const api = useDrawerContext();
|
|
1194
1121
|
return /*#__PURE__*/ jsxRuntime.jsx(reactPrimitive.Primitive.div, {
|
|
1195
1122
|
ref: ref,
|
|
1196
|
-
|
|
1197
|
-
...props
|
|
1123
|
+
...domUtils.mergeProps(api.headerProps, props)
|
|
1198
1124
|
});
|
|
1199
1125
|
});
|
|
1200
1126
|
DrawerHeader.displayName = "DrawerHeader";
|
|
1201
1127
|
const DrawerCloseButton = /*#__PURE__*/ React.forwardRef((props, ref)=>{
|
|
1202
|
-
const
|
|
1203
|
-
const composedRef = reactComposeRefs.useComposedRefs(ref, closeButtonRef);
|
|
1128
|
+
const api = useDrawerContext();
|
|
1204
1129
|
return /*#__PURE__*/ jsxRuntime.jsx(reactPrimitive.Primitive.button, {
|
|
1205
|
-
ref:
|
|
1206
|
-
...props
|
|
1207
|
-
onClick: (e)=>{
|
|
1208
|
-
props.onClick?.(e);
|
|
1209
|
-
if (e.defaultPrevented) return;
|
|
1210
|
-
setIsOpen(false, {
|
|
1211
|
-
reason: "closeButton",
|
|
1212
|
-
event: e.nativeEvent
|
|
1213
|
-
});
|
|
1214
|
-
}
|
|
1130
|
+
ref: ref,
|
|
1131
|
+
...domUtils.mergeProps(api.closeButtonProps, props)
|
|
1215
1132
|
});
|
|
1216
1133
|
});
|
|
1217
1134
|
const LONG_HANDLE_PRESS_TIMEOUT = 250;
|