base-vaul 0.0.1 → 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,1314 @@
1
+ "use client";
2
+
3
+ require('./index.css');
4
+ //#region rolldown:runtime
5
+ var __create = Object.create;
6
+ var __defProp = Object.defineProperty;
7
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
8
+ var __getOwnPropNames = Object.getOwnPropertyNames;
9
+ var __getProtoOf = Object.getPrototypeOf;
10
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
14
+ key = keys[i];
15
+ if (!__hasOwnProp.call(to, key) && key !== except) {
16
+ __defProp(to, key, {
17
+ get: ((k) => from[k]).bind(null, key),
18
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
19
+ });
20
+ }
21
+ }
22
+ }
23
+ return to;
24
+ };
25
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
26
+ value: mod,
27
+ enumerable: true
28
+ }) : target, mod));
29
+
30
+ //#endregion
31
+ let _base_ui_react = require("@base-ui/react");
32
+ let react = require("react");
33
+ react = __toESM(react);
34
+ let react_jsx_runtime = require("react/jsx-runtime");
35
+
36
+ //#region src/context.ts
37
+ const DrawerContext = react.default.createContext({
38
+ drawerRef: { current: null },
39
+ overlayRef: { current: null },
40
+ onPress: () => {},
41
+ onRelease: () => {},
42
+ onDrag: () => {},
43
+ onNestedDrag: () => {},
44
+ onNestedOpenChange: () => {},
45
+ onNestedRelease: () => {},
46
+ openProp: void 0,
47
+ dismissible: false,
48
+ isOpen: false,
49
+ isDragging: false,
50
+ keyboardIsOpen: { current: false },
51
+ snapPointsOffset: null,
52
+ snapPoints: null,
53
+ handleOnly: false,
54
+ modal: false,
55
+ shouldFade: false,
56
+ activeSnapPoint: null,
57
+ onOpenChange: () => {},
58
+ setActiveSnapPoint: () => {},
59
+ closeDrawer: () => {},
60
+ direction: "bottom",
61
+ shouldAnimate: { current: true },
62
+ shouldScaleBackground: false,
63
+ setBackgroundColorOnScale: true,
64
+ noBodyStyles: false,
65
+ container: null,
66
+ autoFocus: false
67
+ });
68
+ const useDrawerContext = () => {
69
+ const context = react.default.useContext(DrawerContext);
70
+ if (!context) throw new Error("useDrawerContext must be used within a Drawer.Root");
71
+ return context;
72
+ };
73
+
74
+ //#endregion
75
+ //#region src/browser.ts
76
+ function isMobileFirefox() {
77
+ const userAgent = navigator.userAgent;
78
+ return typeof window !== "undefined" && (/Firefox/.test(userAgent) && /Mobile/.test(userAgent) || /FxiOS/.test(userAgent));
79
+ }
80
+ function isMac() {
81
+ return testPlatform(/^Mac/);
82
+ }
83
+ function isIPhone() {
84
+ return testPlatform(/^iPhone/);
85
+ }
86
+ function isSafari() {
87
+ return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
88
+ }
89
+ function isIPad() {
90
+ return testPlatform(/^iPad/) || isMac() && navigator.maxTouchPoints > 1;
91
+ }
92
+ function isIOS() {
93
+ return isIPhone() || isIPad();
94
+ }
95
+ function testPlatform(re) {
96
+ return typeof window !== "undefined" && window.navigator != null ? re.test(window.navigator.platform) : void 0;
97
+ }
98
+
99
+ //#endregion
100
+ //#region src/use-prevent-scroll.ts
101
+ const KEYBOARD_BUFFER = 24;
102
+ const useIsomorphicLayoutEffect = typeof window !== "undefined" ? react.useLayoutEffect : react.useEffect;
103
+ function chain$1(...callbacks) {
104
+ return (...args) => {
105
+ for (const callback of callbacks) if (typeof callback === "function") callback(...args);
106
+ };
107
+ }
108
+ const visualViewport = typeof document !== "undefined" && window.visualViewport;
109
+ function isScrollable(node) {
110
+ const style = window.getComputedStyle(node);
111
+ return /(auto|scroll)/.test(style.overflow + style.overflowX + style.overflowY);
112
+ }
113
+ function getScrollParent(node) {
114
+ if (isScrollable(node)) node = node.parentElement;
115
+ while (node && !isScrollable(node)) node = node.parentElement;
116
+ return node || document.scrollingElement || document.documentElement;
117
+ }
118
+ const nonTextInputTypes = new Set([
119
+ "checkbox",
120
+ "radio",
121
+ "range",
122
+ "color",
123
+ "file",
124
+ "image",
125
+ "button",
126
+ "submit",
127
+ "reset"
128
+ ]);
129
+ let preventScrollCount = 0;
130
+ let restore;
131
+ /**
132
+ * Prevents scrolling on the document body on mount, and
133
+ * restores it on unmount. Also ensures that content does not
134
+ * shift due to the scrollbars disappearing.
135
+ */
136
+ function usePreventScroll(options = {}) {
137
+ const { isDisabled } = options;
138
+ useIsomorphicLayoutEffect(() => {
139
+ if (isDisabled) return;
140
+ preventScrollCount++;
141
+ if (preventScrollCount === 1) {
142
+ if (isIOS()) restore = preventScrollMobileSafari();
143
+ }
144
+ return () => {
145
+ preventScrollCount--;
146
+ if (preventScrollCount === 0) restore === null || restore === void 0 || restore();
147
+ };
148
+ }, [isDisabled]);
149
+ }
150
+ function preventScrollMobileSafari() {
151
+ let scrollable;
152
+ let lastY = 0;
153
+ const onTouchStart = (e) => {
154
+ scrollable = getScrollParent(e.target);
155
+ if (scrollable === document.documentElement && scrollable === document.body) return;
156
+ lastY = e.changedTouches[0].pageY;
157
+ };
158
+ const onTouchMove = (e) => {
159
+ if (!scrollable || scrollable === document.documentElement || scrollable === document.body) {
160
+ e.preventDefault();
161
+ return;
162
+ }
163
+ const y = e.changedTouches[0].pageY;
164
+ const scrollTop = scrollable.scrollTop;
165
+ const bottom = scrollable.scrollHeight - scrollable.clientHeight;
166
+ if (bottom === 0) return;
167
+ if (scrollTop <= 0 && y > lastY || scrollTop >= bottom && y < lastY) e.preventDefault();
168
+ lastY = y;
169
+ };
170
+ const onTouchEnd = (e) => {
171
+ const target = e.target;
172
+ if (isInput(target) && target !== document.activeElement) {
173
+ e.preventDefault();
174
+ target.style.transform = "translateY(-2000px)";
175
+ target.focus();
176
+ requestAnimationFrame(() => {
177
+ target.style.transform = "";
178
+ });
179
+ }
180
+ };
181
+ const onFocus = (e) => {
182
+ const target = e.target;
183
+ if (isInput(target)) {
184
+ target.style.transform = "translateY(-2000px)";
185
+ requestAnimationFrame(() => {
186
+ target.style.transform = "";
187
+ if (visualViewport) if (visualViewport.height < window.innerHeight) requestAnimationFrame(() => {
188
+ scrollIntoView(target);
189
+ });
190
+ else visualViewport.addEventListener("resize", () => scrollIntoView(target), { once: true });
191
+ });
192
+ }
193
+ };
194
+ const onWindowScroll = () => {
195
+ window.scrollTo(0, 0);
196
+ };
197
+ const scrollX = window.pageXOffset;
198
+ const scrollY = window.pageYOffset;
199
+ const restoreStyles = chain$1(setStyle(document.documentElement, "paddingRight", `${window.innerWidth - document.documentElement.clientWidth}px`));
200
+ window.scrollTo(0, 0);
201
+ const removeEvents = chain$1(addEvent(document, "touchstart", onTouchStart, {
202
+ passive: false,
203
+ capture: true
204
+ }), addEvent(document, "touchmove", onTouchMove, {
205
+ passive: false,
206
+ capture: true
207
+ }), addEvent(document, "touchend", onTouchEnd, {
208
+ passive: false,
209
+ capture: true
210
+ }), addEvent(document, "focus", onFocus, true), addEvent(window, "scroll", onWindowScroll));
211
+ return () => {
212
+ restoreStyles();
213
+ removeEvents();
214
+ window.scrollTo(scrollX, scrollY);
215
+ };
216
+ }
217
+ function setStyle(element, style, value) {
218
+ const cur = element.style[style];
219
+ element.style[style] = value;
220
+ return () => {
221
+ element.style[style] = cur;
222
+ };
223
+ }
224
+ function addEvent(target, event, handler, options) {
225
+ target.addEventListener(event, handler, options);
226
+ return () => {
227
+ target.removeEventListener(event, handler, options);
228
+ };
229
+ }
230
+ function scrollIntoView(target) {
231
+ const root = document.scrollingElement || document.documentElement;
232
+ while (target && target !== root) {
233
+ const scrollable = getScrollParent(target);
234
+ if (scrollable !== document.documentElement && scrollable !== document.body && scrollable !== target) {
235
+ const scrollableTop = scrollable.getBoundingClientRect().top;
236
+ const targetTop = target.getBoundingClientRect().top;
237
+ if (target.getBoundingClientRect().bottom > scrollable.getBoundingClientRect().bottom + KEYBOARD_BUFFER) scrollable.scrollTop += targetTop - scrollableTop;
238
+ }
239
+ target = scrollable.parentElement;
240
+ }
241
+ }
242
+ function isInput(target) {
243
+ return target instanceof HTMLInputElement && !nonTextInputTypes.has(target.type) || target instanceof HTMLTextAreaElement || target instanceof HTMLElement && target.isContentEditable;
244
+ }
245
+
246
+ //#endregion
247
+ //#region src/use-composed-refs.ts
248
+ /**
249
+ * Set a given ref to a given value
250
+ * This utility takes care of different types of refs: callback refs and RefObject(s)
251
+ */
252
+ function setRef(ref, value) {
253
+ if (typeof ref === "function") ref(value);
254
+ else if (ref !== null && ref !== void 0) ref.current = value;
255
+ }
256
+ /**
257
+ * A utility to compose multiple refs together
258
+ * Accepts callback refs and RefObject(s)
259
+ */
260
+ function composeRefs(...refs) {
261
+ return (node) => refs.forEach((ref) => setRef(ref, node));
262
+ }
263
+ /**
264
+ * A custom hook that composes multiple refs
265
+ * Accepts callback refs and RefObject(s)
266
+ */
267
+ function useComposedRefs(...refs) {
268
+ return react.useCallback(composeRefs(...refs), refs);
269
+ }
270
+
271
+ //#endregion
272
+ //#region src/helpers.ts
273
+ const cache = /* @__PURE__ */ new WeakMap();
274
+ function set(el, styles, ignoreCache = false) {
275
+ if (!el || !(el instanceof HTMLElement)) return;
276
+ const originalStyles = {};
277
+ Object.entries(styles).forEach(([key, value]) => {
278
+ if (key.startsWith("--")) {
279
+ el.style.setProperty(key, value);
280
+ return;
281
+ }
282
+ originalStyles[key] = el.style[key];
283
+ el.style[key] = value;
284
+ });
285
+ if (ignoreCache) return;
286
+ cache.set(el, originalStyles);
287
+ }
288
+ function reset(el, prop) {
289
+ if (!el || !(el instanceof HTMLElement)) return;
290
+ const originalStyles = cache.get(el);
291
+ if (!originalStyles) return;
292
+ if (prop) el.style[prop] = originalStyles[prop];
293
+ else Object.entries(originalStyles).forEach(([key, value]) => {
294
+ el.style[key] = value;
295
+ });
296
+ }
297
+ const isVertical = (direction) => {
298
+ switch (direction) {
299
+ case "top":
300
+ case "bottom": return true;
301
+ case "left":
302
+ case "right": return false;
303
+ default: return direction;
304
+ }
305
+ };
306
+ function getTranslate(element, direction) {
307
+ if (!element) return null;
308
+ const style = window.getComputedStyle(element);
309
+ const transform = style.transform || style.webkitTransform || style.mozTransform;
310
+ let mat = transform.match(/^matrix3d\((.+)\)$/);
311
+ if (mat) return parseFloat(mat[1].split(", ")[isVertical(direction) ? 13 : 12]);
312
+ mat = transform.match(/^matrix\((.+)\)$/);
313
+ return mat ? parseFloat(mat[1].split(", ")[isVertical(direction) ? 5 : 4]) : null;
314
+ }
315
+ function dampenValue(v) {
316
+ return 8 * (Math.log(v + 1) - 2);
317
+ }
318
+ function assignStyle(element, style) {
319
+ if (!element) return () => {};
320
+ const prevStyle = element.style.cssText;
321
+ Object.assign(element.style, style);
322
+ return () => {
323
+ element.style.cssText = prevStyle;
324
+ };
325
+ }
326
+ /**
327
+ * Receives functions as arguments and returns a new function that calls all.
328
+ */
329
+ function chain(...fns) {
330
+ return (...args) => {
331
+ for (const fn of fns) if (typeof fn === "function") fn(...args);
332
+ };
333
+ }
334
+
335
+ //#endregion
336
+ //#region src/constants.ts
337
+ const TRANSITIONS = {
338
+ DURATION: .5,
339
+ EASE: [
340
+ .32,
341
+ .72,
342
+ 0,
343
+ 1
344
+ ]
345
+ };
346
+ const VELOCITY_THRESHOLD = .4;
347
+ const CLOSE_THRESHOLD = .25;
348
+ const SCROLL_LOCK_TIMEOUT = 100;
349
+ const BORDER_RADIUS = 8;
350
+ const NESTED_DISPLACEMENT = 16;
351
+ const WINDOW_TOP_OFFSET = 26;
352
+ const DRAG_CLASS = "vaul-dragging";
353
+
354
+ //#endregion
355
+ //#region src/use-controllable-state.ts
356
+ function useCallbackRef(callback) {
357
+ const callbackRef = react.default.useRef(callback);
358
+ react.default.useEffect(() => {
359
+ callbackRef.current = callback;
360
+ });
361
+ return react.default.useMemo(() => ((...args) => {
362
+ var _callbackRef$current;
363
+ return (_callbackRef$current = callbackRef.current) === null || _callbackRef$current === void 0 ? void 0 : _callbackRef$current.call(callbackRef, ...args);
364
+ }), []);
365
+ }
366
+ function useUncontrolledState({ defaultProp, onChange }) {
367
+ const uncontrolledState = react.default.useState(defaultProp);
368
+ const [value] = uncontrolledState;
369
+ const prevValueRef = react.default.useRef(value);
370
+ const handleChange = useCallbackRef(onChange);
371
+ react.default.useEffect(() => {
372
+ if (prevValueRef.current !== value) {
373
+ handleChange(value);
374
+ prevValueRef.current = value;
375
+ }
376
+ }, [
377
+ value,
378
+ prevValueRef,
379
+ handleChange
380
+ ]);
381
+ return uncontrolledState;
382
+ }
383
+ function useControllableState({ prop, defaultProp, onChange = () => {} }) {
384
+ const [uncontrolledProp, setUncontrolledProp] = useUncontrolledState({
385
+ defaultProp,
386
+ onChange
387
+ });
388
+ const isControlled = prop !== void 0;
389
+ const value = isControlled ? prop : uncontrolledProp;
390
+ const handleChange = useCallbackRef(onChange);
391
+ return [value, react.default.useCallback((nextValue) => {
392
+ if (isControlled) {
393
+ const value$1 = typeof nextValue === "function" ? nextValue(prop) : nextValue;
394
+ if (value$1 !== prop) handleChange(value$1);
395
+ } else setUncontrolledProp(nextValue);
396
+ }, [
397
+ isControlled,
398
+ prop,
399
+ setUncontrolledProp,
400
+ handleChange
401
+ ])];
402
+ }
403
+
404
+ //#endregion
405
+ //#region src/use-snap-points.ts
406
+ function useSnapPoints({ activeSnapPointProp, setActiveSnapPointProp, snapPoints, drawerRef, overlayRef, fadeFromIndex, onSnapPointChange, direction = "bottom", container, snapToSequentialPoint }) {
407
+ const [activeSnapPoint, setActiveSnapPoint] = useControllableState({
408
+ prop: activeSnapPointProp,
409
+ defaultProp: snapPoints === null || snapPoints === void 0 ? void 0 : snapPoints[0],
410
+ onChange: setActiveSnapPointProp
411
+ });
412
+ const [windowDimensions, setWindowDimensions] = react.default.useState(typeof window !== "undefined" ? {
413
+ innerWidth: window.innerWidth,
414
+ innerHeight: window.innerHeight
415
+ } : void 0);
416
+ react.default.useEffect(() => {
417
+ function onResize() {
418
+ setWindowDimensions({
419
+ innerWidth: window.innerWidth,
420
+ innerHeight: window.innerHeight
421
+ });
422
+ }
423
+ window.addEventListener("resize", onResize);
424
+ return () => window.removeEventListener("resize", onResize);
425
+ }, []);
426
+ const isLastSnapPoint = react.default.useMemo(() => activeSnapPoint === (snapPoints === null || snapPoints === void 0 ? void 0 : snapPoints[snapPoints.length - 1]) || null, [snapPoints, activeSnapPoint]);
427
+ const activeSnapPointIndex = react.default.useMemo(() => (snapPoints === null || snapPoints === void 0 ? void 0 : snapPoints.findIndex((snapPoint) => snapPoint === activeSnapPoint)) ?? null, [snapPoints, activeSnapPoint]);
428
+ const shouldFade = snapPoints && snapPoints.length > 0 && (fadeFromIndex || fadeFromIndex === 0) && !Number.isNaN(fadeFromIndex) && snapPoints[fadeFromIndex] === activeSnapPoint || !snapPoints;
429
+ const snapPointsOffset = react.default.useMemo(() => {
430
+ const containerSize = container ? {
431
+ width: container.getBoundingClientRect().width,
432
+ height: container.getBoundingClientRect().height
433
+ } : typeof window !== "undefined" ? {
434
+ width: window.innerWidth,
435
+ height: window.innerHeight
436
+ } : {
437
+ width: 0,
438
+ height: 0
439
+ };
440
+ return (snapPoints === null || snapPoints === void 0 ? void 0 : snapPoints.map((snapPoint) => {
441
+ const isPx = typeof snapPoint === "string";
442
+ let snapPointAsNumber = 0;
443
+ if (isPx) snapPointAsNumber = parseInt(snapPoint, 10);
444
+ if (isVertical(direction)) {
445
+ const height = isPx ? snapPointAsNumber : windowDimensions ? snapPoint * containerSize.height : 0;
446
+ if (windowDimensions) return direction === "bottom" ? containerSize.height - height : -containerSize.height + height;
447
+ return height;
448
+ }
449
+ const width = isPx ? snapPointAsNumber : windowDimensions ? snapPoint * containerSize.width : 0;
450
+ if (windowDimensions) return direction === "right" ? containerSize.width - width : -containerSize.width + width;
451
+ return width;
452
+ })) ?? [];
453
+ }, [
454
+ snapPoints,
455
+ windowDimensions,
456
+ container
457
+ ]);
458
+ const activeSnapPointOffset = react.default.useMemo(() => activeSnapPointIndex !== null ? snapPointsOffset === null || snapPointsOffset === void 0 ? void 0 : snapPointsOffset[activeSnapPointIndex] : null, [snapPointsOffset, activeSnapPointIndex]);
459
+ const snapToPoint = react.default.useCallback((dimension) => {
460
+ const newSnapPointIndex = (snapPointsOffset === null || snapPointsOffset === void 0 ? void 0 : snapPointsOffset.findIndex((snapPointDim) => snapPointDim === dimension)) ?? null;
461
+ onSnapPointChange(newSnapPointIndex);
462
+ set(drawerRef.current, {
463
+ transition: `transform ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`,
464
+ transform: isVertical(direction) ? `translate3d(0, ${dimension}px, 0)` : `translate3d(${dimension}px, 0, 0)`
465
+ });
466
+ if (snapPointsOffset && newSnapPointIndex !== snapPointsOffset.length - 1 && fadeFromIndex !== void 0 && newSnapPointIndex !== fadeFromIndex && newSnapPointIndex < fadeFromIndex) set(overlayRef.current, {
467
+ transition: `opacity ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`,
468
+ opacity: "0"
469
+ });
470
+ else set(overlayRef.current, {
471
+ transition: `opacity ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`,
472
+ opacity: "1"
473
+ });
474
+ setActiveSnapPoint(snapPoints === null || snapPoints === void 0 ? void 0 : snapPoints[Math.max(newSnapPointIndex, 0)]);
475
+ }, [
476
+ drawerRef.current,
477
+ snapPoints,
478
+ snapPointsOffset,
479
+ fadeFromIndex,
480
+ overlayRef,
481
+ setActiveSnapPoint
482
+ ]);
483
+ react.default.useEffect(() => {
484
+ if (activeSnapPoint || activeSnapPointProp) {
485
+ const newIndex = (snapPoints === null || snapPoints === void 0 ? void 0 : snapPoints.findIndex((snapPoint) => snapPoint === activeSnapPointProp || snapPoint === activeSnapPoint)) ?? -1;
486
+ if (snapPointsOffset && newIndex !== -1 && typeof snapPointsOffset[newIndex] === "number") snapToPoint(snapPointsOffset[newIndex]);
487
+ }
488
+ }, [
489
+ activeSnapPoint,
490
+ activeSnapPointProp,
491
+ snapPoints,
492
+ snapPointsOffset,
493
+ snapToPoint
494
+ ]);
495
+ function onRelease({ draggedDistance, closeDrawer, velocity, dismissible }) {
496
+ if (fadeFromIndex === void 0) return;
497
+ const currentPosition = direction === "bottom" || direction === "right" ? (activeSnapPointOffset ?? 0) - draggedDistance : (activeSnapPointOffset ?? 0) + draggedDistance;
498
+ const isOverlaySnapPoint = activeSnapPointIndex === fadeFromIndex - 1;
499
+ const isFirst = activeSnapPointIndex === 0;
500
+ const hasDraggedUp = draggedDistance > 0;
501
+ if (isOverlaySnapPoint) set(overlayRef.current, { transition: `opacity ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})` });
502
+ if (!snapToSequentialPoint && velocity > 2 && !hasDraggedUp) {
503
+ if (dismissible) closeDrawer();
504
+ else snapToPoint(snapPointsOffset[0]);
505
+ return;
506
+ }
507
+ if (!snapToSequentialPoint && velocity > 2 && hasDraggedUp && snapPointsOffset && snapPoints) {
508
+ snapToPoint(snapPointsOffset[snapPoints.length - 1]);
509
+ return;
510
+ }
511
+ const closestSnapPoint = snapPointsOffset === null || snapPointsOffset === void 0 ? void 0 : snapPointsOffset.reduce((prev, curr) => {
512
+ if (typeof prev !== "number" || typeof curr !== "number") return prev;
513
+ return Math.abs(curr - currentPosition) < Math.abs(prev - currentPosition) ? curr : prev;
514
+ });
515
+ const dim = isVertical(direction) ? window.innerHeight : window.innerWidth;
516
+ if (velocity > VELOCITY_THRESHOLD && Math.abs(draggedDistance) < dim * .4) {
517
+ const dragDirection = hasDraggedUp ? 1 : -1;
518
+ if (dragDirection > 0 && isLastSnapPoint && snapPoints) {
519
+ snapToPoint(snapPointsOffset[snapPoints.length - 1]);
520
+ return;
521
+ }
522
+ if (isFirst && dragDirection < 0 && dismissible) closeDrawer();
523
+ if (activeSnapPointIndex === null) return;
524
+ snapToPoint(snapPointsOffset[activeSnapPointIndex + dragDirection]);
525
+ return;
526
+ }
527
+ snapToPoint(closestSnapPoint);
528
+ }
529
+ function onDrag({ draggedDistance }) {
530
+ if (activeSnapPointOffset === null) return;
531
+ const newValue = direction === "bottom" || direction === "right" ? activeSnapPointOffset - draggedDistance : activeSnapPointOffset + draggedDistance;
532
+ if ((direction === "bottom" || direction === "right") && newValue < snapPointsOffset[snapPointsOffset.length - 1]) return;
533
+ if ((direction === "top" || direction === "left") && newValue > snapPointsOffset[snapPointsOffset.length - 1]) return;
534
+ set(drawerRef.current, { transform: isVertical(direction) ? `translate3d(0, ${newValue}px, 0)` : `translate3d(${newValue}px, 0, 0)` });
535
+ }
536
+ function getPercentageDragged(absDraggedDistance, isDraggingDown) {
537
+ if (!snapPoints || typeof activeSnapPointIndex !== "number" || !snapPointsOffset || fadeFromIndex === void 0) return null;
538
+ const isOverlaySnapPoint = activeSnapPointIndex === fadeFromIndex - 1;
539
+ if (activeSnapPointIndex >= fadeFromIndex && isDraggingDown) return 0;
540
+ if (isOverlaySnapPoint && !isDraggingDown) return 1;
541
+ if (!shouldFade && !isOverlaySnapPoint) return null;
542
+ const targetSnapPointIndex = isOverlaySnapPoint ? activeSnapPointIndex + 1 : activeSnapPointIndex - 1;
543
+ const snapPointDistance = isOverlaySnapPoint ? snapPointsOffset[targetSnapPointIndex] - snapPointsOffset[targetSnapPointIndex - 1] : snapPointsOffset[targetSnapPointIndex + 1] - snapPointsOffset[targetSnapPointIndex];
544
+ const percentageDragged = absDraggedDistance / Math.abs(snapPointDistance);
545
+ if (isOverlaySnapPoint) return 1 - percentageDragged;
546
+ else return percentageDragged;
547
+ }
548
+ return {
549
+ isLastSnapPoint,
550
+ activeSnapPoint,
551
+ shouldFade,
552
+ getPercentageDragged,
553
+ setActiveSnapPoint,
554
+ activeSnapPointIndex,
555
+ onRelease,
556
+ onDrag,
557
+ snapPointsOffset
558
+ };
559
+ }
560
+
561
+ //#endregion
562
+ //#region src/use-scale-background.ts
563
+ const noop = () => () => {};
564
+ function useScaleBackground() {
565
+ const { direction, isOpen, shouldScaleBackground, setBackgroundColorOnScale, noBodyStyles } = useDrawerContext();
566
+ const timeoutIdRef = react.default.useRef(null);
567
+ const initialBackgroundColor = (0, react.useMemo)(() => document.body.style.backgroundColor, []);
568
+ function getScale() {
569
+ return (window.innerWidth - WINDOW_TOP_OFFSET) / window.innerWidth;
570
+ }
571
+ react.default.useEffect(() => {
572
+ if (isOpen && shouldScaleBackground) {
573
+ if (timeoutIdRef.current) clearTimeout(timeoutIdRef.current);
574
+ const wrapper = document.querySelector("[data-vaul-drawer-wrapper]") || document.querySelector("[vaul-drawer-wrapper]");
575
+ if (!wrapper) return;
576
+ chain(setBackgroundColorOnScale && !noBodyStyles ? assignStyle(document.body, { background: "black" }) : noop, assignStyle(wrapper, {
577
+ transformOrigin: isVertical(direction) ? "top" : "left",
578
+ transitionProperty: "transform, border-radius",
579
+ transitionDuration: `${TRANSITIONS.DURATION}s`,
580
+ transitionTimingFunction: `cubic-bezier(${TRANSITIONS.EASE.join(",")})`
581
+ }));
582
+ const wrapperStylesCleanup = assignStyle(wrapper, {
583
+ borderRadius: `${BORDER_RADIUS}px`,
584
+ overflow: "hidden",
585
+ ...isVertical(direction) ? { transform: `scale(${getScale()}) translate3d(0, calc(env(safe-area-inset-top) + 14px), 0)` } : { transform: `scale(${getScale()}) translate3d(calc(env(safe-area-inset-top) + 14px), 0, 0)` }
586
+ });
587
+ return () => {
588
+ wrapperStylesCleanup();
589
+ timeoutIdRef.current = window.setTimeout(() => {
590
+ if (initialBackgroundColor) document.body.style.background = initialBackgroundColor;
591
+ else document.body.style.removeProperty("background");
592
+ }, TRANSITIONS.DURATION * 1e3);
593
+ };
594
+ }
595
+ }, [
596
+ isOpen,
597
+ shouldScaleBackground,
598
+ initialBackgroundColor
599
+ ]);
600
+ }
601
+
602
+ //#endregion
603
+ //#region src/use-position-fixed.ts
604
+ let previousBodyPosition = null;
605
+ /**
606
+ * This hook is necessary to prevent buggy behavior on iOS devices (need to test on Android).
607
+ * 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.
608
+ * Issues that this hook solves:
609
+ * https://github.com/emilkowalski/vaul/issues/435
610
+ * https://github.com/emilkowalski/vaul/issues/433
611
+ * And more that I discovered, but were just not reported.
612
+ */
613
+ function usePositionFixed({ isOpen, modal, nested, hasBeenOpened, preventScrollRestoration, noBodyStyles }) {
614
+ const [activeUrl, setActiveUrl] = react.default.useState(() => typeof window !== "undefined" ? window.location.href : "");
615
+ const scrollPos = react.default.useRef(0);
616
+ const setPositionFixed = react.default.useCallback(() => {
617
+ if (!isSafari()) return;
618
+ if (previousBodyPosition === null && isOpen && !noBodyStyles) {
619
+ previousBodyPosition = {
620
+ position: document.body.style.position,
621
+ top: document.body.style.top,
622
+ left: document.body.style.left,
623
+ height: document.body.style.height,
624
+ right: "unset"
625
+ };
626
+ const { scrollX, innerHeight } = window;
627
+ document.body.style.setProperty("position", "fixed", "important");
628
+ Object.assign(document.body.style, {
629
+ top: `${-scrollPos.current}px`,
630
+ left: `${-scrollX}px`,
631
+ right: "0px",
632
+ height: "auto"
633
+ });
634
+ window.setTimeout(() => window.requestAnimationFrame(() => {
635
+ const bottomBarHeight = innerHeight - window.innerHeight;
636
+ if (bottomBarHeight && scrollPos.current >= innerHeight) document.body.style.top = `${-(scrollPos.current + bottomBarHeight)}px`;
637
+ }), 300);
638
+ }
639
+ }, [isOpen]);
640
+ const restorePositionSetting = react.default.useCallback(() => {
641
+ if (!isSafari()) return;
642
+ if (previousBodyPosition !== null && !noBodyStyles) {
643
+ const y = -parseInt(document.body.style.top, 10);
644
+ const x = -parseInt(document.body.style.left, 10);
645
+ Object.assign(document.body.style, previousBodyPosition);
646
+ window.requestAnimationFrame(() => {
647
+ if (preventScrollRestoration && activeUrl !== window.location.href) {
648
+ setActiveUrl(window.location.href);
649
+ return;
650
+ }
651
+ window.scrollTo(x, y);
652
+ });
653
+ previousBodyPosition = null;
654
+ }
655
+ }, [activeUrl]);
656
+ react.default.useEffect(() => {
657
+ function onScroll() {
658
+ scrollPos.current = window.scrollY;
659
+ }
660
+ onScroll();
661
+ window.addEventListener("scroll", onScroll);
662
+ return () => {
663
+ window.removeEventListener("scroll", onScroll);
664
+ };
665
+ }, []);
666
+ react.default.useEffect(() => {
667
+ if (!modal) return;
668
+ return () => {
669
+ if (typeof document === "undefined") return;
670
+ if (!!document.querySelector("[data-vaul-drawer]")) return;
671
+ restorePositionSetting();
672
+ };
673
+ }, [modal, restorePositionSetting]);
674
+ react.default.useEffect(() => {
675
+ if (nested || !hasBeenOpened) return;
676
+ if (isOpen) {
677
+ !window.matchMedia("(display-mode: standalone)").matches && setPositionFixed();
678
+ if (!modal) window.setTimeout(() => {
679
+ restorePositionSetting();
680
+ }, 500);
681
+ } else restorePositionSetting();
682
+ }, [
683
+ isOpen,
684
+ hasBeenOpened,
685
+ activeUrl,
686
+ modal,
687
+ nested,
688
+ setPositionFixed,
689
+ restorePositionSetting
690
+ ]);
691
+ return { restorePositionSetting };
692
+ }
693
+
694
+ //#endregion
695
+ //#region src/index.tsx
696
+ function Root({ open: openProp, onOpenChange, children, onDrag: onDragProp, onRelease: onReleaseProp, snapPoints, shouldScaleBackground = false, setBackgroundColorOnScale = true, closeThreshold = CLOSE_THRESHOLD, scrollLockTimeout = SCROLL_LOCK_TIMEOUT, dismissible = true, handleOnly = false, fadeFromIndex = snapPoints && snapPoints.length - 1, activeSnapPoint: activeSnapPointProp, setActiveSnapPoint: setActiveSnapPointProp, fixed, modal = true, onClose, nested, noBodyStyles = false, direction = "bottom", defaultOpen = false, disablePreventScroll = true, snapToSequentialPoint = false, preventScrollRestoration = false, repositionInputs = true, onAnimationEnd, container, autoFocus = false }) {
697
+ var _drawerRef$current, _drawerRef$current2;
698
+ const [isOpen = false, setIsOpen] = useControllableState({
699
+ defaultProp: defaultOpen,
700
+ prop: openProp,
701
+ onChange: (o) => {
702
+ onOpenChange === null || onOpenChange === void 0 || onOpenChange(o);
703
+ if (!o && !nested) restorePositionSetting();
704
+ setTimeout(() => {
705
+ onAnimationEnd === null || onAnimationEnd === void 0 || onAnimationEnd(o);
706
+ }, TRANSITIONS.DURATION * 1e3);
707
+ if (o && !modal) {
708
+ if (typeof window !== "undefined") window.requestAnimationFrame(() => {
709
+ document.body.style.pointerEvents = "auto";
710
+ });
711
+ }
712
+ if (!o) document.body.style.pointerEvents = "auto";
713
+ }
714
+ });
715
+ const [hasBeenOpened, setHasBeenOpened] = react.default.useState(false);
716
+ const [isDragging, setIsDragging] = react.default.useState(false);
717
+ const [justReleased, setJustReleased] = react.default.useState(false);
718
+ const overlayRef = react.default.useRef(null);
719
+ const openTime = react.default.useRef(null);
720
+ const dragStartTime = react.default.useRef(null);
721
+ const dragEndTime = react.default.useRef(null);
722
+ const lastTimeDragPrevented = react.default.useRef(null);
723
+ const isAllowedToDrag = react.default.useRef(false);
724
+ const nestedOpenChangeTimer = react.default.useRef(null);
725
+ const pointerStart = react.default.useRef(0);
726
+ const keyboardIsOpen = react.default.useRef(false);
727
+ const shouldAnimate = react.default.useRef(!defaultOpen);
728
+ const previousDiffFromInitial = react.default.useRef(0);
729
+ const drawerRef = react.default.useRef(null);
730
+ const drawerHeightRef = react.default.useRef(((_drawerRef$current = drawerRef.current) === null || _drawerRef$current === void 0 ? void 0 : _drawerRef$current.getBoundingClientRect().height) || 0);
731
+ const drawerWidthRef = react.default.useRef(((_drawerRef$current2 = drawerRef.current) === null || _drawerRef$current2 === void 0 ? void 0 : _drawerRef$current2.getBoundingClientRect().width) || 0);
732
+ const initialDrawerHeight = react.default.useRef(0);
733
+ const { activeSnapPoint, activeSnapPointIndex, setActiveSnapPoint, onRelease: onReleaseSnapPoints, snapPointsOffset, onDrag: onDragSnapPoints, shouldFade, getPercentageDragged: getSnapPointsPercentageDragged } = useSnapPoints({
734
+ snapPoints,
735
+ activeSnapPointProp,
736
+ setActiveSnapPointProp,
737
+ drawerRef,
738
+ fadeFromIndex,
739
+ overlayRef,
740
+ onSnapPointChange: react.default.useCallback((activeSnapPointIndex$1) => {
741
+ if (snapPoints && activeSnapPointIndex$1 === snapPointsOffset.length - 1) openTime.current = /* @__PURE__ */ new Date();
742
+ }, []),
743
+ direction,
744
+ container,
745
+ snapToSequentialPoint
746
+ });
747
+ usePreventScroll({ isDisabled: !isOpen || isDragging || !modal || justReleased || !hasBeenOpened || !repositionInputs || !disablePreventScroll });
748
+ const { restorePositionSetting } = usePositionFixed({
749
+ isOpen,
750
+ modal,
751
+ nested: nested ?? false,
752
+ hasBeenOpened,
753
+ preventScrollRestoration,
754
+ noBodyStyles
755
+ });
756
+ function getScale() {
757
+ return (window.innerWidth - WINDOW_TOP_OFFSET) / window.innerWidth;
758
+ }
759
+ function onPress(event) {
760
+ var _drawerRef$current3, _drawerRef$current4;
761
+ if (!dismissible && !snapPoints) return;
762
+ if (drawerRef.current && !drawerRef.current.contains(event.target)) return;
763
+ drawerHeightRef.current = ((_drawerRef$current3 = drawerRef.current) === null || _drawerRef$current3 === void 0 ? void 0 : _drawerRef$current3.getBoundingClientRect().height) || 0;
764
+ drawerWidthRef.current = ((_drawerRef$current4 = drawerRef.current) === null || _drawerRef$current4 === void 0 ? void 0 : _drawerRef$current4.getBoundingClientRect().width) || 0;
765
+ setIsDragging(true);
766
+ dragStartTime.current = /* @__PURE__ */ new Date();
767
+ if (isIOS()) window.addEventListener("touchend", () => isAllowedToDrag.current = false, { once: true });
768
+ event.target.setPointerCapture(event.pointerId);
769
+ pointerStart.current = isVertical(direction) ? event.pageY : event.pageX;
770
+ }
771
+ function shouldDrag(el, isDraggingInDirection) {
772
+ var _window$getSelection;
773
+ let element = el;
774
+ const highlightedText = (_window$getSelection = window.getSelection()) === null || _window$getSelection === void 0 ? void 0 : _window$getSelection.toString();
775
+ const swipeAmount = drawerRef.current ? getTranslate(drawerRef.current, direction) : null;
776
+ const date = /* @__PURE__ */ new Date();
777
+ if (element.tagName === "SELECT") return false;
778
+ if (element.hasAttribute("data-vaul-no-drag") || element.closest("[data-vaul-no-drag]")) return false;
779
+ if (direction === "right" || direction === "left") return true;
780
+ if (openTime.current && date.getTime() - openTime.current.getTime() < 500) return false;
781
+ if (swipeAmount !== null) {
782
+ if (direction === "bottom" ? swipeAmount > 0 : swipeAmount < 0) return true;
783
+ }
784
+ if (highlightedText && highlightedText.length > 0) return false;
785
+ if (lastTimeDragPrevented.current && date.getTime() - lastTimeDragPrevented.current.getTime() < scrollLockTimeout && swipeAmount === 0) {
786
+ lastTimeDragPrevented.current = date;
787
+ return false;
788
+ }
789
+ if (isDraggingInDirection) {
790
+ lastTimeDragPrevented.current = date;
791
+ return false;
792
+ }
793
+ while (element) {
794
+ if (element.scrollHeight > element.clientHeight) {
795
+ if (element.scrollTop !== 0) {
796
+ lastTimeDragPrevented.current = /* @__PURE__ */ new Date();
797
+ return false;
798
+ }
799
+ if (element.getAttribute("role") === "dialog") return true;
800
+ }
801
+ element = element.parentNode;
802
+ }
803
+ return true;
804
+ }
805
+ function onDrag(event) {
806
+ if (!drawerRef.current) return;
807
+ if (isDragging) {
808
+ const directionMultiplier = direction === "bottom" || direction === "right" ? 1 : -1;
809
+ const draggedDistance = (pointerStart.current - (isVertical(direction) ? event.pageY : event.pageX)) * directionMultiplier;
810
+ const isDraggingInDirection = draggedDistance > 0;
811
+ const noCloseSnapPointsPreCondition = snapPoints && !dismissible && !isDraggingInDirection;
812
+ if (noCloseSnapPointsPreCondition && activeSnapPointIndex === 0) return;
813
+ const absDraggedDistance = Math.abs(draggedDistance);
814
+ const wrapper = document.querySelector("[data-vaul-drawer-wrapper]");
815
+ let percentageDragged = absDraggedDistance / (direction === "bottom" || direction === "top" ? drawerHeightRef.current : drawerWidthRef.current);
816
+ const snapPointPercentageDragged = getSnapPointsPercentageDragged(absDraggedDistance, isDraggingInDirection);
817
+ if (snapPointPercentageDragged !== null) percentageDragged = snapPointPercentageDragged;
818
+ if (noCloseSnapPointsPreCondition && percentageDragged >= 1) return;
819
+ if (!isAllowedToDrag.current && !shouldDrag(event.target, isDraggingInDirection)) return;
820
+ drawerRef.current.classList.add(DRAG_CLASS);
821
+ isAllowedToDrag.current = true;
822
+ set(drawerRef.current, { transition: "none" });
823
+ set(overlayRef.current, { transition: "none" });
824
+ if (snapPoints) onDragSnapPoints({ draggedDistance });
825
+ if (isDraggingInDirection && !snapPoints) {
826
+ const dampenedDraggedDistance = dampenValue(draggedDistance);
827
+ const translateValue = Math.min(dampenedDraggedDistance * -1, 0) * directionMultiplier;
828
+ set(drawerRef.current, { transform: isVertical(direction) ? `translate3d(0, ${translateValue}px, 0)` : `translate3d(${translateValue}px, 0, 0)` });
829
+ return;
830
+ }
831
+ const opacityValue = 1 - percentageDragged;
832
+ if (shouldFade || fadeFromIndex && activeSnapPointIndex === fadeFromIndex - 1) {
833
+ onDragProp === null || onDragProp === void 0 || onDragProp(event, percentageDragged);
834
+ set(overlayRef.current, {
835
+ opacity: `${opacityValue}`,
836
+ transition: "none"
837
+ }, true);
838
+ }
839
+ if (wrapper && overlayRef.current && shouldScaleBackground) {
840
+ const scaleValue = Math.min(getScale() + percentageDragged * (1 - getScale()), 1);
841
+ const borderRadiusValue = 8 - percentageDragged * 8;
842
+ const translateValue = Math.max(0, 14 - percentageDragged * 14);
843
+ set(wrapper, {
844
+ borderRadius: `${borderRadiusValue}px`,
845
+ transform: isVertical(direction) ? `scale(${scaleValue}) translate3d(0, ${translateValue}px, 0)` : `scale(${scaleValue}) translate3d(${translateValue}px, 0, 0)`,
846
+ transition: "none"
847
+ }, true);
848
+ }
849
+ if (!snapPoints) {
850
+ const translateValue = absDraggedDistance * directionMultiplier;
851
+ set(drawerRef.current, { transform: isVertical(direction) ? `translate3d(0, ${translateValue}px, 0)` : `translate3d(${translateValue}px, 0, 0)` });
852
+ }
853
+ }
854
+ }
855
+ react.default.useEffect(() => {
856
+ window.requestAnimationFrame(() => {
857
+ shouldAnimate.current = true;
858
+ });
859
+ }, []);
860
+ react.default.useEffect(() => {
861
+ var _window$visualViewpor2;
862
+ function onVisualViewportChange() {
863
+ if (!drawerRef.current || !repositionInputs) return;
864
+ const focusedElement = document.activeElement;
865
+ if (isInput(focusedElement) || keyboardIsOpen.current) {
866
+ var _window$visualViewpor;
867
+ const visualViewportHeight = ((_window$visualViewpor = window.visualViewport) === null || _window$visualViewpor === void 0 ? void 0 : _window$visualViewpor.height) || 0;
868
+ const totalHeight = window.innerHeight;
869
+ let diffFromInitial = totalHeight - visualViewportHeight;
870
+ const drawerHeight = drawerRef.current.getBoundingClientRect().height || 0;
871
+ const isTallEnough = drawerHeight > totalHeight * .8;
872
+ if (!initialDrawerHeight.current) initialDrawerHeight.current = drawerHeight;
873
+ const offsetFromTop = drawerRef.current.getBoundingClientRect().top;
874
+ if (Math.abs(previousDiffFromInitial.current - diffFromInitial) > 60) keyboardIsOpen.current = !keyboardIsOpen.current;
875
+ if (snapPoints && snapPoints.length > 0 && snapPointsOffset && activeSnapPointIndex) {
876
+ const activeSnapPointHeight = snapPointsOffset[activeSnapPointIndex] || 0;
877
+ diffFromInitial += activeSnapPointHeight;
878
+ }
879
+ previousDiffFromInitial.current = diffFromInitial;
880
+ if (drawerHeight > visualViewportHeight || keyboardIsOpen.current) {
881
+ const height = drawerRef.current.getBoundingClientRect().height;
882
+ let newDrawerHeight = height;
883
+ if (height > visualViewportHeight) newDrawerHeight = visualViewportHeight - (isTallEnough ? offsetFromTop : WINDOW_TOP_OFFSET);
884
+ if (fixed) drawerRef.current.style.height = `${height - Math.max(diffFromInitial, 0)}px`;
885
+ else drawerRef.current.style.height = `${Math.max(newDrawerHeight, visualViewportHeight - offsetFromTop)}px`;
886
+ } else if (!isMobileFirefox()) drawerRef.current.style.height = `${initialDrawerHeight.current}px`;
887
+ if (snapPoints && snapPoints.length > 0 && !keyboardIsOpen.current) drawerRef.current.style.bottom = `0px`;
888
+ else drawerRef.current.style.bottom = `${Math.max(diffFromInitial, 0)}px`;
889
+ }
890
+ }
891
+ (_window$visualViewpor2 = window.visualViewport) === null || _window$visualViewpor2 === void 0 || _window$visualViewpor2.addEventListener("resize", onVisualViewportChange);
892
+ return () => {
893
+ var _window$visualViewpor3;
894
+ return (_window$visualViewpor3 = window.visualViewport) === null || _window$visualViewpor3 === void 0 ? void 0 : _window$visualViewpor3.removeEventListener("resize", onVisualViewportChange);
895
+ };
896
+ }, [
897
+ activeSnapPointIndex,
898
+ snapPoints,
899
+ snapPointsOffset
900
+ ]);
901
+ function closeDrawer(fromWithin) {
902
+ cancelDrag();
903
+ onClose === null || onClose === void 0 || onClose();
904
+ if (!fromWithin) setIsOpen(false);
905
+ setTimeout(() => {
906
+ if (snapPoints) setActiveSnapPoint(snapPoints[0]);
907
+ }, TRANSITIONS.DURATION * 1e3);
908
+ }
909
+ function resetDrawer() {
910
+ if (!drawerRef.current) return;
911
+ const wrapper = document.querySelector("[data-vaul-drawer-wrapper]");
912
+ const currentSwipeAmount = getTranslate(drawerRef.current, direction);
913
+ set(drawerRef.current, {
914
+ transform: "translate3d(0, 0, 0)",
915
+ transition: `transform ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`
916
+ });
917
+ set(overlayRef.current, {
918
+ transition: `opacity ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`,
919
+ opacity: "1"
920
+ });
921
+ if (shouldScaleBackground && currentSwipeAmount && currentSwipeAmount > 0 && isOpen) set(wrapper, {
922
+ borderRadius: `${BORDER_RADIUS}px`,
923
+ overflow: "hidden",
924
+ ...isVertical(direction) ? {
925
+ transform: `scale(${getScale()}) translate3d(0, calc(env(safe-area-inset-top) + 14px), 0)`,
926
+ transformOrigin: "top"
927
+ } : {
928
+ transform: `scale(${getScale()}) translate3d(calc(env(safe-area-inset-top) + 14px), 0, 0)`,
929
+ transformOrigin: "left"
930
+ },
931
+ transitionProperty: "transform, border-radius",
932
+ transitionDuration: `${TRANSITIONS.DURATION}s`,
933
+ transitionTimingFunction: `cubic-bezier(${TRANSITIONS.EASE.join(",")})`
934
+ }, true);
935
+ }
936
+ function cancelDrag() {
937
+ if (!isDragging || !drawerRef.current) return;
938
+ drawerRef.current.classList.remove(DRAG_CLASS);
939
+ isAllowedToDrag.current = false;
940
+ setIsDragging(false);
941
+ dragEndTime.current = /* @__PURE__ */ new Date();
942
+ }
943
+ function onRelease(event) {
944
+ if (!isDragging || !drawerRef.current) return;
945
+ drawerRef.current.classList.remove(DRAG_CLASS);
946
+ isAllowedToDrag.current = false;
947
+ setIsDragging(false);
948
+ dragEndTime.current = /* @__PURE__ */ new Date();
949
+ const swipeAmount = getTranslate(drawerRef.current, direction);
950
+ if (!event || !shouldDrag(event.target, false) || !swipeAmount || Number.isNaN(swipeAmount)) return;
951
+ if (dragStartTime.current === null) return;
952
+ const timeTaken = dragEndTime.current.getTime() - dragStartTime.current.getTime();
953
+ const distMoved = pointerStart.current - (isVertical(direction) ? event.pageY : event.pageX);
954
+ const velocity = Math.abs(distMoved) / timeTaken;
955
+ if (velocity > .05) {
956
+ setJustReleased(true);
957
+ setTimeout(() => {
958
+ setJustReleased(false);
959
+ }, 200);
960
+ }
961
+ if (snapPoints) {
962
+ onReleaseSnapPoints({
963
+ draggedDistance: distMoved * (direction === "bottom" || direction === "right" ? 1 : -1),
964
+ closeDrawer,
965
+ velocity,
966
+ dismissible
967
+ });
968
+ onReleaseProp === null || onReleaseProp === void 0 || onReleaseProp(event, true);
969
+ return;
970
+ }
971
+ if (direction === "bottom" || direction === "right" ? distMoved > 0 : distMoved < 0) {
972
+ resetDrawer();
973
+ onReleaseProp === null || onReleaseProp === void 0 || onReleaseProp(event, true);
974
+ return;
975
+ }
976
+ if (velocity > VELOCITY_THRESHOLD) {
977
+ closeDrawer();
978
+ onReleaseProp === null || onReleaseProp === void 0 || onReleaseProp(event, false);
979
+ return;
980
+ }
981
+ const visibleDrawerHeight = Math.min(drawerRef.current.getBoundingClientRect().height ?? 0, window.innerHeight);
982
+ const visibleDrawerWidth = Math.min(drawerRef.current.getBoundingClientRect().width ?? 0, window.innerWidth);
983
+ const isHorizontalSwipe = direction === "left" || direction === "right";
984
+ if (Math.abs(swipeAmount) >= (isHorizontalSwipe ? visibleDrawerWidth : visibleDrawerHeight) * closeThreshold) {
985
+ closeDrawer();
986
+ onReleaseProp === null || onReleaseProp === void 0 || onReleaseProp(event, false);
987
+ return;
988
+ }
989
+ onReleaseProp === null || onReleaseProp === void 0 || onReleaseProp(event, true);
990
+ resetDrawer();
991
+ }
992
+ react.default.useEffect(() => {
993
+ if (isOpen) {
994
+ set(document.documentElement, { scrollBehavior: "auto" });
995
+ openTime.current = /* @__PURE__ */ new Date();
996
+ }
997
+ return () => {
998
+ reset(document.documentElement, "scrollBehavior");
999
+ };
1000
+ }, [isOpen]);
1001
+ function onNestedOpenChange(o) {
1002
+ const scale = o ? (window.innerWidth - NESTED_DISPLACEMENT) / window.innerWidth : 1;
1003
+ const initialTranslate = o ? -NESTED_DISPLACEMENT : 0;
1004
+ if (nestedOpenChangeTimer.current) window.clearTimeout(nestedOpenChangeTimer.current);
1005
+ set(drawerRef.current, {
1006
+ transition: `transform ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`,
1007
+ transform: isVertical(direction) ? `scale(${scale}) translate3d(0, ${initialTranslate}px, 0)` : `scale(${scale}) translate3d(${initialTranslate}px, 0, 0)`
1008
+ });
1009
+ if (!o && drawerRef.current) nestedOpenChangeTimer.current = setTimeout(() => {
1010
+ const translateValue = getTranslate(drawerRef.current, direction);
1011
+ set(drawerRef.current, {
1012
+ transition: "none",
1013
+ transform: isVertical(direction) ? `translate3d(0, ${translateValue}px, 0)` : `translate3d(${translateValue}px, 0, 0)`
1014
+ });
1015
+ }, 500);
1016
+ }
1017
+ function onNestedDrag(_event, percentageDragged) {
1018
+ if (percentageDragged < 0) return;
1019
+ const initialScale = (window.innerWidth - NESTED_DISPLACEMENT) / window.innerWidth;
1020
+ const newScale = initialScale + percentageDragged * (1 - initialScale);
1021
+ const newTranslate = -NESTED_DISPLACEMENT + percentageDragged * NESTED_DISPLACEMENT;
1022
+ set(drawerRef.current, {
1023
+ transform: isVertical(direction) ? `scale(${newScale}) translate3d(0, ${newTranslate}px, 0)` : `scale(${newScale}) translate3d(${newTranslate}px, 0, 0)`,
1024
+ transition: "none"
1025
+ });
1026
+ }
1027
+ function onNestedRelease(_event, o) {
1028
+ const dim = isVertical(direction) ? window.innerHeight : window.innerWidth;
1029
+ const scale = o ? (dim - NESTED_DISPLACEMENT) / dim : 1;
1030
+ const translate = o ? -NESTED_DISPLACEMENT : 0;
1031
+ if (o) set(drawerRef.current, {
1032
+ transition: `transform ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`,
1033
+ transform: isVertical(direction) ? `scale(${scale}) translate3d(0, ${translate}px, 0)` : `scale(${scale}) translate3d(${translate}px, 0, 0)`
1034
+ });
1035
+ }
1036
+ react.default.useEffect(() => {
1037
+ if (!modal) window.requestAnimationFrame(() => {
1038
+ document.body.style.pointerEvents = "auto";
1039
+ });
1040
+ }, [modal]);
1041
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_base_ui_react.Dialog.Root, {
1042
+ defaultOpen,
1043
+ onOpenChange: (open) => {
1044
+ if (!dismissible && !open) return;
1045
+ if (open) setHasBeenOpened(true);
1046
+ else closeDrawer(true);
1047
+ setIsOpen(open);
1048
+ },
1049
+ open: isOpen,
1050
+ modal,
1051
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DrawerContext.Provider, {
1052
+ value: {
1053
+ activeSnapPoint,
1054
+ snapPoints,
1055
+ setActiveSnapPoint,
1056
+ drawerRef,
1057
+ overlayRef,
1058
+ onOpenChange,
1059
+ onPress,
1060
+ onRelease,
1061
+ onDrag,
1062
+ dismissible,
1063
+ shouldAnimate,
1064
+ handleOnly,
1065
+ isOpen,
1066
+ isDragging,
1067
+ shouldFade,
1068
+ closeDrawer,
1069
+ onNestedDrag,
1070
+ onNestedOpenChange,
1071
+ onNestedRelease,
1072
+ keyboardIsOpen,
1073
+ modal,
1074
+ snapPointsOffset,
1075
+ activeSnapPointIndex,
1076
+ direction,
1077
+ shouldScaleBackground,
1078
+ setBackgroundColorOnScale,
1079
+ noBodyStyles,
1080
+ container,
1081
+ autoFocus
1082
+ },
1083
+ children
1084
+ })
1085
+ });
1086
+ }
1087
+ const Overlay = react.default.forwardRef(function({ ...rest }, ref) {
1088
+ const { overlayRef, snapPoints, onRelease, shouldFade, isOpen, modal, shouldAnimate } = useDrawerContext();
1089
+ const composedRef = useComposedRefs(ref, overlayRef);
1090
+ const hasSnapPoints = snapPoints && snapPoints.length > 0;
1091
+ const onMouseUp = react.default.useCallback((event) => onRelease(event), [onRelease]);
1092
+ if (!modal) return null;
1093
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_base_ui_react.Dialog.Backdrop, {
1094
+ onMouseUp,
1095
+ ref: composedRef,
1096
+ "data-vaul-overlay": "",
1097
+ "data-vaul-snap-points": isOpen && hasSnapPoints ? "true" : "false",
1098
+ "data-vaul-snap-points-overlay": isOpen && shouldFade ? "true" : "false",
1099
+ "data-vaul-animate": (shouldAnimate === null || shouldAnimate === void 0 ? void 0 : shouldAnimate.current) ? "true" : "false",
1100
+ ...rest
1101
+ });
1102
+ });
1103
+ Overlay.displayName = "Drawer.Overlay";
1104
+ const Content = react.default.forwardRef(function({ style, ...rest }, ref) {
1105
+ const { drawerRef, onPress, onRelease, onDrag, keyboardIsOpen, snapPointsOffset, activeSnapPointIndex, modal, isOpen, direction, snapPoints, container, handleOnly, shouldAnimate, autoFocus } = useDrawerContext();
1106
+ const [delayedSnapPoints, setDelayedSnapPoints] = react.default.useState(false);
1107
+ const composedRef = useComposedRefs(ref, drawerRef);
1108
+ const pointerStartRef = react.default.useRef(null);
1109
+ const lastKnownPointerEventRef = react.default.useRef(null);
1110
+ const wasBeyondThePointRef = react.default.useRef(false);
1111
+ const hasSnapPoints = snapPoints && snapPoints.length > 0;
1112
+ useScaleBackground();
1113
+ const isDeltaInDirection = (delta, direction$1, threshold = 0) => {
1114
+ if (wasBeyondThePointRef.current) return true;
1115
+ const deltaY = Math.abs(delta.y);
1116
+ const deltaX = Math.abs(delta.x);
1117
+ const isDeltaX = deltaX > deltaY;
1118
+ const dFactor = ["bottom", "right"].includes(direction$1) ? 1 : -1;
1119
+ if (direction$1 === "left" || direction$1 === "right") {
1120
+ if (!(delta.x * dFactor < 0) && deltaX >= 0 && deltaX <= threshold) return isDeltaX;
1121
+ } else if (!(delta.y * dFactor < 0) && deltaY >= 0 && deltaY <= threshold) return !isDeltaX;
1122
+ wasBeyondThePointRef.current = true;
1123
+ return true;
1124
+ };
1125
+ react.default.useEffect(() => {
1126
+ if (hasSnapPoints) window.requestAnimationFrame(() => {
1127
+ setDelayedSnapPoints(true);
1128
+ });
1129
+ }, []);
1130
+ function handleOnPointerUp(event) {
1131
+ pointerStartRef.current = null;
1132
+ wasBeyondThePointRef.current = false;
1133
+ onRelease(event);
1134
+ }
1135
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_base_ui_react.Dialog.Viewport, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_base_ui_react.Dialog.Popup, {
1136
+ "data-vaul-drawer-direction": direction,
1137
+ "data-vaul-drawer": "",
1138
+ "data-vaul-delayed-snap-points": delayedSnapPoints ? "true" : "false",
1139
+ "data-vaul-snap-points": isOpen && hasSnapPoints ? "true" : "false",
1140
+ "data-vaul-custom-container": container ? "true" : "false",
1141
+ "data-vaul-animate": (shouldAnimate === null || shouldAnimate === void 0 ? void 0 : shouldAnimate.current) ? "true" : "false",
1142
+ ...rest,
1143
+ ref: composedRef,
1144
+ style: snapPointsOffset && snapPointsOffset.length > 0 ? {
1145
+ "--snap-point-height": `${snapPointsOffset[activeSnapPointIndex ?? 0]}px`,
1146
+ ...style
1147
+ } : style,
1148
+ onPointerDown: (event) => {
1149
+ var _rest$onPointerDown;
1150
+ if (handleOnly) return;
1151
+ (_rest$onPointerDown = rest.onPointerDown) === null || _rest$onPointerDown === void 0 || _rest$onPointerDown.call(rest, event);
1152
+ pointerStartRef.current = {
1153
+ x: event.pageX,
1154
+ y: event.pageY
1155
+ };
1156
+ onPress(event);
1157
+ },
1158
+ onPointerMove: (event) => {
1159
+ var _rest$onPointerMove;
1160
+ lastKnownPointerEventRef.current = event;
1161
+ if (handleOnly) return;
1162
+ (_rest$onPointerMove = rest.onPointerMove) === null || _rest$onPointerMove === void 0 || _rest$onPointerMove.call(rest, event);
1163
+ if (!pointerStartRef.current) return;
1164
+ const yPosition = event.pageY - pointerStartRef.current.y;
1165
+ const xPosition = event.pageX - pointerStartRef.current.x;
1166
+ const swipeStartThreshold = event.pointerType === "touch" ? 10 : 2;
1167
+ if (isDeltaInDirection({
1168
+ x: xPosition,
1169
+ y: yPosition
1170
+ }, direction, swipeStartThreshold)) onDrag(event);
1171
+ else if (Math.abs(xPosition) > swipeStartThreshold || Math.abs(yPosition) > swipeStartThreshold) pointerStartRef.current = null;
1172
+ },
1173
+ onPointerUp: (event) => {
1174
+ var _rest$onPointerUp;
1175
+ (_rest$onPointerUp = rest.onPointerUp) === null || _rest$onPointerUp === void 0 || _rest$onPointerUp.call(rest, event);
1176
+ pointerStartRef.current = null;
1177
+ wasBeyondThePointRef.current = false;
1178
+ onRelease(event);
1179
+ },
1180
+ onPointerOut: (event) => {
1181
+ var _rest$onPointerOut;
1182
+ (_rest$onPointerOut = rest.onPointerOut) === null || _rest$onPointerOut === void 0 || _rest$onPointerOut.call(rest, event);
1183
+ handleOnPointerUp(lastKnownPointerEventRef.current);
1184
+ },
1185
+ onContextMenu: (event) => {
1186
+ var _rest$onContextMenu;
1187
+ (_rest$onContextMenu = rest.onContextMenu) === null || _rest$onContextMenu === void 0 || _rest$onContextMenu.call(rest, event);
1188
+ if (lastKnownPointerEventRef.current) handleOnPointerUp(lastKnownPointerEventRef.current);
1189
+ }
1190
+ }) });
1191
+ });
1192
+ Content.displayName = "Drawer.Content";
1193
+ const LONG_HANDLE_PRESS_TIMEOUT = 250;
1194
+ const DOUBLE_TAP_TIMEOUT = 120;
1195
+ const Handle = react.default.forwardRef(function({ preventCycle = false, children, ...rest }, ref) {
1196
+ const { closeDrawer, isDragging, snapPoints, activeSnapPoint, setActiveSnapPoint, dismissible, handleOnly, isOpen, onPress, onDrag } = useDrawerContext();
1197
+ const closeTimeoutIdRef = react.default.useRef(null);
1198
+ const shouldCancelInteractionRef = react.default.useRef(false);
1199
+ function handleStartCycle() {
1200
+ if (shouldCancelInteractionRef.current) {
1201
+ handleCancelInteraction();
1202
+ return;
1203
+ }
1204
+ window.setTimeout(() => {
1205
+ handleCycleSnapPoints();
1206
+ }, DOUBLE_TAP_TIMEOUT);
1207
+ }
1208
+ function handleCycleSnapPoints() {
1209
+ if (isDragging || preventCycle || shouldCancelInteractionRef.current) {
1210
+ handleCancelInteraction();
1211
+ return;
1212
+ }
1213
+ handleCancelInteraction();
1214
+ if (!snapPoints || snapPoints.length === 0) {
1215
+ if (!dismissible) closeDrawer();
1216
+ return;
1217
+ }
1218
+ if (activeSnapPoint === snapPoints[snapPoints.length - 1] && dismissible) {
1219
+ closeDrawer();
1220
+ return;
1221
+ }
1222
+ const currentSnapIndex = snapPoints.findIndex((point) => point === activeSnapPoint);
1223
+ if (currentSnapIndex === -1) return;
1224
+ const nextSnapPoint = snapPoints[currentSnapIndex + 1];
1225
+ setActiveSnapPoint(nextSnapPoint);
1226
+ }
1227
+ function handleStartInteraction() {
1228
+ closeTimeoutIdRef.current = window.setTimeout(() => {
1229
+ shouldCancelInteractionRef.current = true;
1230
+ }, LONG_HANDLE_PRESS_TIMEOUT);
1231
+ }
1232
+ function handleCancelInteraction() {
1233
+ if (closeTimeoutIdRef.current) window.clearTimeout(closeTimeoutIdRef.current);
1234
+ shouldCancelInteractionRef.current = false;
1235
+ }
1236
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1237
+ onClick: handleStartCycle,
1238
+ onPointerCancel: handleCancelInteraction,
1239
+ onPointerDown: (e) => {
1240
+ if (handleOnly) onPress({
1241
+ ...e,
1242
+ preventBaseUIHandler: () => {}
1243
+ });
1244
+ handleStartInteraction();
1245
+ },
1246
+ onPointerMove: (e) => {
1247
+ if (handleOnly) onDrag({
1248
+ ...e,
1249
+ preventBaseUIHandler: () => {}
1250
+ });
1251
+ },
1252
+ ref,
1253
+ "data-vaul-drawer-visible": isOpen ? "true" : "false",
1254
+ "data-vaul-handle": "",
1255
+ "aria-hidden": "true",
1256
+ ...rest,
1257
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1258
+ "data-vaul-handle-hitarea": "",
1259
+ "aria-hidden": "true",
1260
+ children
1261
+ })
1262
+ });
1263
+ });
1264
+ Handle.displayName = "Drawer.Handle";
1265
+ function NestedRoot({ onDrag, onOpenChange, open: nestedIsOpen, ...rest }) {
1266
+ const { onNestedDrag, onNestedOpenChange, onNestedRelease } = useDrawerContext();
1267
+ if (!onNestedDrag) throw new Error("Drawer.NestedRoot must be placed in another drawer");
1268
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Root, {
1269
+ nested: true,
1270
+ open: nestedIsOpen,
1271
+ onClose: () => {
1272
+ onNestedOpenChange(false);
1273
+ },
1274
+ onDrag: (e, p) => {
1275
+ onNestedDrag(e, p);
1276
+ onDrag === null || onDrag === void 0 || onDrag(e, p);
1277
+ },
1278
+ onOpenChange: (o) => {
1279
+ if (o) onNestedOpenChange(o);
1280
+ onOpenChange === null || onOpenChange === void 0 || onOpenChange(o);
1281
+ },
1282
+ onRelease: onNestedRelease,
1283
+ ...rest
1284
+ });
1285
+ }
1286
+ function Portal(props) {
1287
+ const context = useDrawerContext();
1288
+ const { container = context.container, ...portalProps } = props;
1289
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_base_ui_react.Dialog.Portal, {
1290
+ container,
1291
+ ...portalProps
1292
+ });
1293
+ }
1294
+ const Drawer = {
1295
+ Root,
1296
+ NestedRoot,
1297
+ Content,
1298
+ Overlay,
1299
+ Trigger: _base_ui_react.Dialog.Trigger,
1300
+ Portal,
1301
+ Handle,
1302
+ Close: _base_ui_react.Dialog.Close,
1303
+ Title: _base_ui_react.Dialog.Title,
1304
+ Description: _base_ui_react.Dialog.Description
1305
+ };
1306
+
1307
+ //#endregion
1308
+ exports.Content = Content;
1309
+ exports.Drawer = Drawer;
1310
+ exports.Handle = Handle;
1311
+ exports.NestedRoot = NestedRoot;
1312
+ exports.Overlay = Overlay;
1313
+ exports.Portal = Portal;
1314
+ exports.Root = Root;