base-vaul 0.0.2 → 1.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/dist/index.mjs CHANGED
@@ -1,1635 +1,1265 @@
1
- 'use client';
2
- function __insertCSS(code) {
3
- if (!code || typeof document == 'undefined') return
4
- let head = document.head || document.getElementsByTagName('head')[0]
5
- let style = document.createElement('style')
6
- style.type = 'text/css'
7
- head.appendChild(style)
8
- ;style.styleSheet ? (style.styleSheet.cssText = code) : style.appendChild(document.createTextNode(code))
9
- }
1
+ "use client";
10
2
 
11
- import { jsx } from 'react/jsx-runtime';
12
- import { Dialog } from '@base-ui/react';
13
- import * as React from 'react';
14
- import React__default, { useLayoutEffect, useEffect, useMemo } from 'react';
3
+ import { Dialog } from "@base-ui/react";
4
+ import * as React$1 from "react";
5
+ import React, { useEffect, useLayoutEffect, useMemo } from "react";
6
+ import { jsx } from "react/jsx-runtime";
15
7
 
16
- // @ts-nocheck [FIXME] See after monorepo migration
17
- const DrawerContext = React__default.createContext({
18
- drawerRef: {
19
- current: null
20
- },
21
- overlayRef: {
22
- current: null
23
- },
24
- onPress: ()=>{},
25
- onRelease: ()=>{},
26
- onDrag: ()=>{},
27
- onNestedDrag: ()=>{},
28
- onNestedOpenChange: ()=>{},
29
- onNestedRelease: ()=>{},
30
- openProp: undefined,
31
- dismissible: false,
32
- isOpen: false,
33
- isDragging: false,
34
- keyboardIsOpen: {
35
- current: false
36
- },
37
- snapPointsOffset: null,
38
- snapPoints: null,
39
- handleOnly: false,
40
- modal: false,
41
- shouldFade: false,
42
- activeSnapPoint: null,
43
- onOpenChange: ()=>{},
44
- setActiveSnapPoint: ()=>{},
45
- closeDrawer: ()=>{},
46
- direction: "bottom",
47
- shouldAnimate: {
48
- current: true
49
- },
50
- shouldScaleBackground: false,
51
- setBackgroundColorOnScale: true,
52
- noBodyStyles: false,
53
- container: null,
54
- autoFocus: false
8
+ import './index.css';
9
+ //#region src/context.ts
10
+ const DrawerContext = React.createContext({
11
+ drawerRef: { current: null },
12
+ overlayRef: { current: null },
13
+ onPress: () => {},
14
+ onRelease: () => {},
15
+ onDrag: () => {},
16
+ onNestedDrag: () => {},
17
+ onNestedOpenChange: () => {},
18
+ onNestedRelease: () => {},
19
+ openProp: void 0,
20
+ dismissible: false,
21
+ isOpen: false,
22
+ isDragging: false,
23
+ keyboardIsOpen: { current: false },
24
+ snapPointsOffset: null,
25
+ snapPoints: null,
26
+ handleOnly: false,
27
+ modal: false,
28
+ shouldFade: false,
29
+ activeSnapPoint: null,
30
+ onOpenChange: () => {},
31
+ setActiveSnapPoint: () => {},
32
+ closeDrawer: () => {},
33
+ direction: "bottom",
34
+ shouldAnimate: { current: true },
35
+ shouldScaleBackground: false,
36
+ setBackgroundColorOnScale: true,
37
+ noBodyStyles: false,
38
+ container: null,
39
+ autoFocus: false
55
40
  });
56
- const useDrawerContext = ()=>{
57
- const context = React__default.useContext(DrawerContext);
58
- if (!context) {
59
- throw new Error("useDrawerContext must be used within a Drawer.Root");
60
- }
61
- return context;
41
+ const useDrawerContext = () => {
42
+ const context = React.useContext(DrawerContext);
43
+ if (!context) throw new Error("useDrawerContext must be used within a Drawer.Root");
44
+ return context;
62
45
  };
63
46
 
64
- __insertCSS("[data-vaul-drawer]{touch-action:none;will-change:transform;transition:transform .5s cubic-bezier(.32, .72, 0, 1);animation-duration:.5s;animation-timing-function:cubic-bezier(0.32,0.72,0,1)}[data-vaul-drawer][data-vaul-snap-points=false][data-vaul-drawer-direction=bottom][data-open]{animation-name:slideFromBottom}[data-vaul-drawer][data-vaul-snap-points=false][data-vaul-drawer-direction=bottom][data-closed]{animation-name:slideToBottom}[data-vaul-drawer][data-vaul-snap-points=false][data-vaul-drawer-direction=top][data-open]{animation-name:slideFromTop}[data-vaul-drawer][data-vaul-snap-points=false][data-vaul-drawer-direction=top][data-closed]{animation-name:slideToTop}[data-vaul-drawer][data-vaul-snap-points=false][data-vaul-drawer-direction=left][data-open]{animation-name:slideFromLeft}[data-vaul-drawer][data-vaul-snap-points=false][data-vaul-drawer-direction=left][data-closed]{animation-name:slideToLeft}[data-vaul-drawer][data-vaul-snap-points=false][data-vaul-drawer-direction=right][data-open]{animation-name:slideFromRight}[data-vaul-drawer][data-vaul-snap-points=false][data-vaul-drawer-direction=right][data-closed]{animation-name:slideToRight}[data-vaul-drawer][data-vaul-snap-points=true][data-vaul-drawer-direction=bottom]{transform:translate3d(0,var(--initial-transform,100%),0)}[data-vaul-drawer][data-vaul-snap-points=true][data-vaul-drawer-direction=top]{transform:translate3d(0,calc(var(--initial-transform,100%) * -1),0)}[data-vaul-drawer][data-vaul-snap-points=true][data-vaul-drawer-direction=left]{transform:translate3d(calc(var(--initial-transform,100%) * -1),0,0)}[data-vaul-drawer][data-vaul-snap-points=true][data-vaul-drawer-direction=right]{transform:translate3d(var(--initial-transform,100%),0,0)}[data-vaul-drawer][data-vaul-delayed-snap-points=true][data-vaul-drawer-direction=top]{transform:translate3d(0,var(--snap-point-height,0),0)}[data-vaul-drawer][data-vaul-delayed-snap-points=true][data-vaul-drawer-direction=bottom]{transform:translate3d(0,var(--snap-point-height,0),0)}[data-vaul-drawer][data-vaul-delayed-snap-points=true][data-vaul-drawer-direction=left]{transform:translate3d(var(--snap-point-height,0),0,0)}[data-vaul-drawer][data-vaul-delayed-snap-points=true][data-vaul-drawer-direction=right]{transform:translate3d(var(--snap-point-height,0),0,0)}[data-vaul-overlay][data-vaul-snap-points=false]{animation-duration:.5s;animation-timing-function:cubic-bezier(0.32,0.72,0,1)}[data-vaul-overlay][data-vaul-snap-points=false][data-open]{animation-name:fadeIn}[data-vaul-overlay][data-closed]{animation-name:fadeOut}[data-vaul-animate=false]{animation:none!important}[data-vaul-overlay][data-vaul-snap-points=true]{opacity:0;transition:opacity .5s cubic-bezier(.32, .72, 0, 1)}[data-vaul-overlay][data-vaul-snap-points=true]{opacity:1}[data-vaul-drawer]:not([data-vaul-custom-container=true])::after{content:'';position:absolute;background:inherit;background-color:inherit}[data-vaul-drawer][data-vaul-drawer-direction=top]::after{top:initial;bottom:100%;left:0;right:0;height:200%}[data-vaul-drawer][data-vaul-drawer-direction=bottom]::after{top:100%;bottom:initial;left:0;right:0;height:200%}[data-vaul-drawer][data-vaul-drawer-direction=left]::after{left:initial;right:100%;top:0;bottom:0;width:200%}[data-vaul-drawer][data-vaul-drawer-direction=right]::after{left:100%;right:initial;top:0;bottom:0;width:200%}[data-vaul-overlay][data-vaul-snap-points=true]:not([data-vaul-snap-points-overlay=true]):not([data-closed]){opacity:0}[data-vaul-overlay][data-vaul-snap-points-overlay=true]{opacity:1}[data-vaul-handle]{display:block;position:relative;opacity:.7;background:#e2e2e4;margin-left:auto;margin-right:auto;height:5px;width:32px;border-radius:1rem;touch-action:pan-y}[data-vaul-handle]:active,[data-vaul-handle]:hover{opacity:1}[data-vaul-handle-hitarea]{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:max(100%,2.75rem);height:max(100%,2.75rem);touch-action:inherit}@media (hover:hover) and (pointer:fine){[data-vaul-drawer]{user-select:none}}@media (pointer:fine){[data-vaul-handle-hitarea]:{width:100%;height:100%}}@keyframes fadeIn{from{opacity:0}to{opacity:1}}@keyframes fadeOut{to{opacity:0}}@keyframes slideFromBottom{from{transform:translate3d(0,var(--initial-transform,100%),0)}to{transform:translate3d(0,0,0)}}@keyframes slideToBottom{to{transform:translate3d(0,var(--initial-transform,100%),0)}}@keyframes slideFromTop{from{transform:translate3d(0,calc(var(--initial-transform,100%) * -1),0)}to{transform:translate3d(0,0,0)}}@keyframes slideToTop{to{transform:translate3d(0,calc(var(--initial-transform,100%) * -1),0)}}@keyframes slideFromLeft{from{transform:translate3d(calc(var(--initial-transform,100%) * -1),0,0)}to{transform:translate3d(0,0,0)}}@keyframes slideToLeft{to{transform:translate3d(calc(var(--initial-transform,100%) * -1),0,0)}}@keyframes slideFromRight{from{transform:translate3d(var(--initial-transform,100%),0,0)}to{transform:translate3d(0,0,0)}}@keyframes slideToRight{to{transform:translate3d(var(--initial-transform,100%),0,0)}}");
65
-
47
+ //#endregion
48
+ //#region src/browser.ts
66
49
  function isMobileFirefox() {
67
- const userAgent = navigator.userAgent;
68
- return typeof window !== 'undefined' && (/Firefox/.test(userAgent) && /Mobile/.test(userAgent) || // Android Firefox
69
- /FxiOS/.test(userAgent) // iOS Firefox
70
- );
50
+ const userAgent = navigator.userAgent;
51
+ return typeof window !== "undefined" && (/Firefox/.test(userAgent) && /Mobile/.test(userAgent) || /FxiOS/.test(userAgent));
71
52
  }
72
53
  function isMac() {
73
- return testPlatform(/^Mac/);
54
+ return testPlatform(/^Mac/);
74
55
  }
75
56
  function isIPhone() {
76
- return testPlatform(/^iPhone/);
57
+ return testPlatform(/^iPhone/);
77
58
  }
78
59
  function isSafari() {
79
- return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
60
+ return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
80
61
  }
81
62
  function isIPad() {
82
- return testPlatform(/^iPad/) || // iPadOS 13 lies and says it's a Mac, but we can distinguish by detecting touch support.
83
- isMac() && navigator.maxTouchPoints > 1;
63
+ return testPlatform(/^iPad/) || isMac() && navigator.maxTouchPoints > 1;
84
64
  }
85
65
  function isIOS() {
86
- return isIPhone() || isIPad();
66
+ return isIPhone() || isIPad();
87
67
  }
88
68
  function testPlatform(re) {
89
- return typeof window !== 'undefined' && window.navigator != null ? re.test(window.navigator.platform) : undefined;
69
+ return typeof window !== "undefined" && window.navigator != null ? re.test(window.navigator.platform) : void 0;
90
70
  }
91
71
 
92
- // @ts-nocheck [FIXME] See after monorepo migration
93
- // This code comes from https://github.com/adobe/react-spectrum/blob/main/packages/%40react-aria/overlays/src/usePreventScroll.ts
72
+ //#endregion
73
+ //#region src/use-prevent-scroll.ts
94
74
  const KEYBOARD_BUFFER = 24;
95
75
  const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
96
76
  function chain$1(...callbacks) {
97
- return (...args)=>{
98
- for (const callback of callbacks){
99
- if (typeof callback === "function") {
100
- callback(...args);
101
- }
102
- }
103
- };
77
+ return (...args) => {
78
+ for (const callback of callbacks) if (typeof callback === "function") callback(...args);
79
+ };
104
80
  }
105
- // @ts-ignore
106
81
  const visualViewport = typeof document !== "undefined" && window.visualViewport;
107
82
  function isScrollable(node) {
108
- const style = window.getComputedStyle(node);
109
- return /(auto|scroll)/.test(style.overflow + style.overflowX + style.overflowY);
83
+ const style = window.getComputedStyle(node);
84
+ return /(auto|scroll)/.test(style.overflow + style.overflowX + style.overflowY);
110
85
  }
111
86
  function getScrollParent(node) {
112
- if (isScrollable(node)) {
113
- node = node.parentElement;
114
- }
115
- while(node && !isScrollable(node)){
116
- node = node.parentElement;
117
- }
118
- return node || document.scrollingElement || document.documentElement;
87
+ if (isScrollable(node)) node = node.parentElement;
88
+ while (node && !isScrollable(node)) node = node.parentElement;
89
+ return node || document.scrollingElement || document.documentElement;
119
90
  }
120
- // HTML input types that do not cause the software keyboard to appear.
121
91
  const nonTextInputTypes = new Set([
122
- "checkbox",
123
- "radio",
124
- "range",
125
- "color",
126
- "file",
127
- "image",
128
- "button",
129
- "submit",
130
- "reset"
92
+ "checkbox",
93
+ "radio",
94
+ "range",
95
+ "color",
96
+ "file",
97
+ "image",
98
+ "button",
99
+ "submit",
100
+ "reset"
131
101
  ]);
132
- // The number of active usePreventScroll calls. Used to determine whether to revert back to the original page style/scroll position
133
102
  let preventScrollCount = 0;
134
103
  let restore;
135
104
  /**
136
- * Prevents scrolling on the document body on mount, and
137
- * restores it on unmount. Also ensures that content does not
138
- * shift due to the scrollbars disappearing.
139
- */ function usePreventScroll(options = {}) {
140
- const { isDisabled } = options;
141
- useIsomorphicLayoutEffect(()=>{
142
- if (isDisabled) {
143
- return;
144
- }
145
- preventScrollCount++;
146
- if (preventScrollCount === 1) {
147
- if (isIOS()) {
148
- restore = preventScrollMobileSafari();
149
- }
150
- }
151
- return ()=>{
152
- preventScrollCount--;
153
- if (preventScrollCount === 0) {
154
- restore?.();
155
- }
156
- };
157
- }, [
158
- isDisabled
159
- ]);
105
+ * Prevents scrolling on the document body on mount, and
106
+ * restores it on unmount. Also ensures that content does not
107
+ * shift due to the scrollbars disappearing.
108
+ */
109
+ function usePreventScroll(options = {}) {
110
+ const { isDisabled } = options;
111
+ useIsomorphicLayoutEffect(() => {
112
+ if (isDisabled) return;
113
+ preventScrollCount++;
114
+ if (preventScrollCount === 1) {
115
+ if (isIOS()) restore = preventScrollMobileSafari();
116
+ }
117
+ return () => {
118
+ preventScrollCount--;
119
+ if (preventScrollCount === 0) restore?.();
120
+ };
121
+ }, [isDisabled]);
160
122
  }
161
- // Mobile Safari is a whole different beast. Even with overflow: hidden,
162
- // it still scrolls the page in many situations:
163
- //
164
- // 1. When the bottom toolbar and address bar are collapsed, page scrolling is always allowed.
165
- // 2. When the keyboard is visible, the viewport does not resize. Instead, the keyboard covers part of
166
- // it, so it becomes scrollable.
167
- // 3. When tapping on an input, the page always scrolls so that the input is centered in the visual viewport.
168
- // This may cause even fixed position elements to scroll off the screen.
169
- // 4. When using the next/previous buttons in the keyboard to navigate between inputs, the whole page always
170
- // scrolls, even if the input is inside a nested scrollable element that could be scrolled instead.
171
- //
172
- // In order to work around these cases, and prevent scrolling without jankiness, we do a few things:
173
- //
174
- // 1. Prevent default on `touchmove` events that are not in a scrollable element. This prevents touch scrolling
175
- // on the window.
176
- // 2. Prevent default on `touchmove` events inside a scrollable element when the scroll position is at the
177
- // top or bottom. This avoids the whole page scrolling instead, but does prevent overscrolling.
178
- // 3. Prevent default on `touchend` events on input elements and handle focusing the element ourselves.
179
- // 4. When focusing an input, apply a transform to trick Safari into thinking the input is at the top
180
- // of the page, which prevents it from scrolling the page. After the input is focused, scroll the element
181
- // into view ourselves, without scrolling the whole page.
182
- // 5. Offset the body by the scroll position using a negative margin and scroll to the top. This should appear the
183
- // same visually, but makes the actual scroll position always zero. This is required to make all of the
184
- // above work or Safari will still try to scroll the page when focusing an input.
185
- // 6. As a last resort, handle window scroll events, and scroll back to the top. This can happen when attempting
186
- // to navigate to an input with the next/previous buttons that's outside a modal.
187
123
  function preventScrollMobileSafari() {
188
- let scrollable;
189
- let lastY = 0;
190
- const onTouchStart = (e)=>{
191
- // Store the nearest scrollable parent element from the element that the user touched.
192
- scrollable = getScrollParent(e.target);
193
- if (scrollable === document.documentElement && scrollable === document.body) {
194
- return;
195
- }
196
- lastY = e.changedTouches[0].pageY;
197
- };
198
- const onTouchMove = (e)=>{
199
- // Prevent scrolling the window.
200
- if (!scrollable || scrollable === document.documentElement || scrollable === document.body) {
201
- e.preventDefault();
202
- return;
203
- }
204
- // Prevent scrolling up when at the top and scrolling down when at the bottom
205
- // of a nested scrollable area, otherwise mobile Safari will start scrolling
206
- // the window instead. Unfortunately, this disables bounce scrolling when at
207
- // the top but it's the best we can do.
208
- const y = e.changedTouches[0].pageY;
209
- const scrollTop = scrollable.scrollTop;
210
- const bottom = scrollable.scrollHeight - scrollable.clientHeight;
211
- if (bottom === 0) {
212
- return;
213
- }
214
- if (scrollTop <= 0 && y > lastY || scrollTop >= bottom && y < lastY) {
215
- e.preventDefault();
216
- }
217
- lastY = y;
218
- };
219
- const onTouchEnd = (e)=>{
220
- const target = e.target;
221
- // Apply this change if we're not already focused on the target element
222
- if (isInput(target) && target !== document.activeElement) {
223
- e.preventDefault();
224
- // Apply a transform to trick Safari into thinking the input is at the top of the page
225
- // so it doesn't try to scroll it into view. When tapping on an input, this needs to
226
- // be done before the "focus" event, so we have to focus the element ourselves.
227
- target.style.transform = "translateY(-2000px)";
228
- target.focus();
229
- requestAnimationFrame(()=>{
230
- target.style.transform = "";
231
- });
232
- }
233
- };
234
- const onFocus = (e)=>{
235
- const target = e.target;
236
- if (isInput(target)) {
237
- // Transform also needs to be applied in the focus event in cases where focus moves
238
- // other than tapping on an input directly, e.g. the next/previous buttons in the
239
- // software keyboard. In these cases, it seems applying the transform in the focus event
240
- // is good enough, whereas when tapping an input, it must be done before the focus event. 🤷‍♂️
241
- target.style.transform = "translateY(-2000px)";
242
- requestAnimationFrame(()=>{
243
- target.style.transform = "";
244
- // This will have prevented the browser from scrolling the focused element into view,
245
- // so we need to do this ourselves in a way that doesn't cause the whole page to scroll.
246
- if (visualViewport) {
247
- if (visualViewport.height < window.innerHeight) {
248
- // If the keyboard is already visible, do this after one additional frame
249
- // to wait for the transform to be removed.
250
- requestAnimationFrame(()=>{
251
- scrollIntoView(target);
252
- });
253
- } else {
254
- // Otherwise, wait for the visual viewport to resize before scrolling so we can
255
- // measure the correct position to scroll to.
256
- visualViewport.addEventListener("resize", ()=>scrollIntoView(target), {
257
- once: true
258
- });
259
- }
260
- }
261
- });
262
- }
263
- };
264
- const onWindowScroll = ()=>{
265
- // Last resort. If the window scrolled, scroll it back to the top.
266
- // It should always be at the top because the body will have a negative margin (see below).
267
- window.scrollTo(0, 0);
268
- };
269
- // Record the original scroll position so we can restore it.
270
- // Then apply a negative margin to the body to offset it by the scroll position. This will
271
- // enable us to scroll the window to the top, which is required for the rest of this to work.
272
- const scrollX = window.pageXOffset;
273
- const scrollY = window.pageYOffset;
274
- const restoreStyles = chain$1(setStyle(document.documentElement, "paddingRight", `${window.innerWidth - document.documentElement.clientWidth}px`));
275
- // Scroll to the top. The negative margin on the body will make this appear the same.
276
- window.scrollTo(0, 0);
277
- const removeEvents = chain$1(addEvent(document, "touchstart", onTouchStart, {
278
- passive: false,
279
- capture: true
280
- }), addEvent(document, "touchmove", onTouchMove, {
281
- passive: false,
282
- capture: true
283
- }), addEvent(document, "touchend", onTouchEnd, {
284
- passive: false,
285
- capture: true
286
- }), addEvent(document, "focus", onFocus, true), addEvent(window, "scroll", onWindowScroll));
287
- return ()=>{
288
- // Restore styles and scroll the page back to where it was.
289
- restoreStyles();
290
- removeEvents();
291
- window.scrollTo(scrollX, scrollY);
292
- };
124
+ let scrollable;
125
+ let lastY = 0;
126
+ const onTouchStart = (e) => {
127
+ scrollable = getScrollParent(e.target);
128
+ if (scrollable === document.documentElement && scrollable === document.body) return;
129
+ lastY = e.changedTouches[0].pageY;
130
+ };
131
+ const onTouchMove = (e) => {
132
+ if (!scrollable || scrollable === document.documentElement || scrollable === document.body) {
133
+ e.preventDefault();
134
+ return;
135
+ }
136
+ const y = e.changedTouches[0].pageY;
137
+ const scrollTop = scrollable.scrollTop;
138
+ const bottom = scrollable.scrollHeight - scrollable.clientHeight;
139
+ if (bottom === 0) return;
140
+ if (scrollTop <= 0 && y > lastY || scrollTop >= bottom && y < lastY) e.preventDefault();
141
+ lastY = y;
142
+ };
143
+ const onTouchEnd = (e) => {
144
+ const target = e.target;
145
+ if (isInput(target) && target !== document.activeElement) {
146
+ e.preventDefault();
147
+ target.style.transform = "translateY(-2000px)";
148
+ target.focus();
149
+ requestAnimationFrame(() => {
150
+ target.style.transform = "";
151
+ });
152
+ }
153
+ };
154
+ const onFocus = (e) => {
155
+ const target = e.target;
156
+ if (isInput(target)) {
157
+ target.style.transform = "translateY(-2000px)";
158
+ requestAnimationFrame(() => {
159
+ target.style.transform = "";
160
+ if (visualViewport) if (visualViewport.height < window.innerHeight) requestAnimationFrame(() => {
161
+ scrollIntoView(target);
162
+ });
163
+ else visualViewport.addEventListener("resize", () => scrollIntoView(target), { once: true });
164
+ });
165
+ }
166
+ };
167
+ const onWindowScroll = () => {
168
+ window.scrollTo(0, 0);
169
+ };
170
+ const scrollX = window.pageXOffset;
171
+ const scrollY = window.pageYOffset;
172
+ const restoreStyles = chain$1(setStyle(document.documentElement, "paddingRight", `${window.innerWidth - document.documentElement.clientWidth}px`));
173
+ window.scrollTo(0, 0);
174
+ const removeEvents = chain$1(addEvent(document, "touchstart", onTouchStart, {
175
+ passive: false,
176
+ capture: true
177
+ }), addEvent(document, "touchmove", onTouchMove, {
178
+ passive: false,
179
+ capture: true
180
+ }), addEvent(document, "touchend", onTouchEnd, {
181
+ passive: false,
182
+ capture: true
183
+ }), addEvent(document, "focus", onFocus, true), addEvent(window, "scroll", onWindowScroll));
184
+ return () => {
185
+ restoreStyles();
186
+ removeEvents();
187
+ window.scrollTo(scrollX, scrollY);
188
+ };
293
189
  }
294
- // Sets a CSS property on an element, and returns a function to revert it to the previous value.
295
190
  function setStyle(element, style, value) {
296
- // https://github.com/microsoft/TypeScript/issues/17827#issuecomment-391663310
297
- // @ts-ignore
298
- const cur = element.style[style];
299
- // @ts-ignore
300
- element.style[style] = value;
301
- return ()=>{
302
- // @ts-ignore
303
- element.style[style] = cur;
304
- };
191
+ const cur = element.style[style];
192
+ element.style[style] = value;
193
+ return () => {
194
+ element.style[style] = cur;
195
+ };
305
196
  }
306
- // Adds an event listener to an element, and returns a function to remove it.
307
197
  function addEvent(target, event, handler, options) {
308
- // @ts-ignore
309
- target.addEventListener(event, handler, options);
310
- return ()=>{
311
- // @ts-ignore
312
- target.removeEventListener(event, handler, options);
313
- };
198
+ target.addEventListener(event, handler, options);
199
+ return () => {
200
+ target.removeEventListener(event, handler, options);
201
+ };
314
202
  }
315
203
  function scrollIntoView(target) {
316
- const root = document.scrollingElement || document.documentElement;
317
- while(target && target !== root){
318
- // Find the parent scrollable element and adjust the scroll position if the target is not already in view.
319
- const scrollable = getScrollParent(target);
320
- if (scrollable !== document.documentElement && scrollable !== document.body && scrollable !== target) {
321
- const scrollableTop = scrollable.getBoundingClientRect().top;
322
- const targetTop = target.getBoundingClientRect().top;
323
- const targetBottom = target.getBoundingClientRect().bottom;
324
- // Buffer is needed for some edge cases
325
- const keyboardHeight = scrollable.getBoundingClientRect().bottom + KEYBOARD_BUFFER;
326
- if (targetBottom > keyboardHeight) {
327
- scrollable.scrollTop += targetTop - scrollableTop;
328
- }
329
- }
330
- // @ts-ignore
331
- target = scrollable.parentElement;
332
- }
204
+ const root = document.scrollingElement || document.documentElement;
205
+ while (target && target !== root) {
206
+ const scrollable = getScrollParent(target);
207
+ if (scrollable !== document.documentElement && scrollable !== document.body && scrollable !== target) {
208
+ const scrollableTop = scrollable.getBoundingClientRect().top;
209
+ const targetTop = target.getBoundingClientRect().top;
210
+ if (target.getBoundingClientRect().bottom > scrollable.getBoundingClientRect().bottom + KEYBOARD_BUFFER) scrollable.scrollTop += targetTop - scrollableTop;
211
+ }
212
+ target = scrollable.parentElement;
213
+ }
333
214
  }
334
215
  function isInput(target) {
335
- return target instanceof HTMLInputElement && !nonTextInputTypes.has(target.type) || target instanceof HTMLTextAreaElement || target instanceof HTMLElement && target.isContentEditable;
216
+ return target instanceof HTMLInputElement && !nonTextInputTypes.has(target.type) || target instanceof HTMLTextAreaElement || target instanceof HTMLElement && target.isContentEditable;
336
217
  }
337
218
 
338
- // This code comes from https://github.com/radix-ui/primitives/tree/main/packages/react/compose-refs
219
+ //#endregion
220
+ //#region src/use-composed-refs.ts
339
221
  /**
340
- * Set a given ref to a given value
341
- * This utility takes care of different types of refs: callback refs and RefObject(s)
342
- */ function setRef(ref, value) {
343
- if (typeof ref === 'function') {
344
- ref(value);
345
- } else if (ref !== null && ref !== undefined) {
346
- ref.current = value;
347
- }
222
+ * Set a given ref to a given value
223
+ * This utility takes care of different types of refs: callback refs and RefObject(s)
224
+ */
225
+ function setRef(ref, value) {
226
+ if (typeof ref === "function") ref(value);
227
+ else if (ref !== null && ref !== void 0) ref.current = value;
348
228
  }
349
229
  /**
350
- * A utility to compose multiple refs together
351
- * Accepts callback refs and RefObject(s)
352
- */ function composeRefs(...refs) {
353
- return (node)=>refs.forEach((ref)=>setRef(ref, node));
230
+ * A utility to compose multiple refs together
231
+ * Accepts callback refs and RefObject(s)
232
+ */
233
+ function composeRefs(...refs) {
234
+ return (node) => refs.forEach((ref) => setRef(ref, node));
354
235
  }
355
236
  /**
356
- * A custom hook that composes multiple refs
357
- * Accepts callback refs and RefObject(s)
358
- */ function useComposedRefs(...refs) {
359
- // eslint-disable-next-line react-hooks/exhaustive-deps
360
- return React.useCallback(composeRefs(...refs), refs);
237
+ * A custom hook that composes multiple refs
238
+ * Accepts callback refs and RefObject(s)
239
+ */
240
+ function useComposedRefs(...refs) {
241
+ return React$1.useCallback(composeRefs(...refs), refs);
361
242
  }
362
243
 
363
- const cache = new WeakMap();
244
+ //#endregion
245
+ //#region src/helpers.ts
246
+ const cache = /* @__PURE__ */ new WeakMap();
364
247
  function set(el, styles, ignoreCache = false) {
365
- if (!el || !(el instanceof HTMLElement)) return;
366
- const originalStyles = {};
367
- Object.entries(styles).forEach(([key, value])=>{
368
- if (key.startsWith('--')) {
369
- el.style.setProperty(key, value);
370
- return;
371
- }
372
- originalStyles[key] = el.style[key];
373
- el.style[key] = value;
374
- });
375
- if (ignoreCache) return;
376
- cache.set(el, originalStyles);
248
+ if (!el || !(el instanceof HTMLElement)) return;
249
+ const originalStyles = {};
250
+ Object.entries(styles).forEach(([key, value]) => {
251
+ if (key.startsWith("--")) {
252
+ el.style.setProperty(key, value);
253
+ return;
254
+ }
255
+ originalStyles[key] = el.style[key];
256
+ el.style[key] = value;
257
+ });
258
+ if (ignoreCache) return;
259
+ cache.set(el, originalStyles);
377
260
  }
378
261
  function reset(el, prop) {
379
- if (!el || !(el instanceof HTMLElement)) return;
380
- const originalStyles = cache.get(el);
381
- if (!originalStyles) {
382
- return;
383
- }
384
- {
385
- el.style[prop] = originalStyles[prop];
386
- }
262
+ if (!el || !(el instanceof HTMLElement)) return;
263
+ const originalStyles = cache.get(el);
264
+ if (!originalStyles) return;
265
+ if (prop) el.style[prop] = originalStyles[prop];
266
+ else Object.entries(originalStyles).forEach(([key, value]) => {
267
+ el.style[key] = value;
268
+ });
387
269
  }
388
- const isVertical = (direction)=>{
389
- switch(direction){
390
- case 'top':
391
- case 'bottom':
392
- return true;
393
- case 'left':
394
- case 'right':
395
- return false;
396
- default:
397
- return direction;
398
- }
270
+ const isVertical = (direction) => {
271
+ switch (direction) {
272
+ case "top":
273
+ case "bottom": return true;
274
+ case "left":
275
+ case "right": return false;
276
+ default: return direction;
277
+ }
399
278
  };
400
279
  function getTranslate(element, direction) {
401
- if (!element) {
402
- return null;
403
- }
404
- const style = window.getComputedStyle(element);
405
- const transform = // @ts-ignore
406
- style.transform || style.webkitTransform || style.mozTransform;
407
- let mat = transform.match(/^matrix3d\((.+)\)$/);
408
- if (mat) {
409
- // https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/matrix3d
410
- return parseFloat(mat[1].split(', ')[isVertical(direction) ? 13 : 12]);
411
- }
412
- // https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/matrix
413
- mat = transform.match(/^matrix\((.+)\)$/);
414
- return mat ? parseFloat(mat[1].split(', ')[isVertical(direction) ? 5 : 4]) : null;
280
+ if (!element) return null;
281
+ const style = window.getComputedStyle(element);
282
+ const transform = style.transform || style.webkitTransform || style.mozTransform;
283
+ let mat = transform.match(/^matrix3d\((.+)\)$/);
284
+ if (mat) return parseFloat(mat[1].split(", ")[isVertical(direction) ? 13 : 12]);
285
+ mat = transform.match(/^matrix\((.+)\)$/);
286
+ return mat ? parseFloat(mat[1].split(", ")[isVertical(direction) ? 5 : 4]) : null;
415
287
  }
416
288
  function dampenValue(v) {
417
- return 8 * (Math.log(v + 1) - 2);
289
+ return 8 * (Math.log(v + 1) - 2);
418
290
  }
419
291
  function assignStyle(element, style) {
420
- if (!element) return ()=>{};
421
- const prevStyle = element.style.cssText;
422
- Object.assign(element.style, style);
423
- return ()=>{
424
- element.style.cssText = prevStyle;
425
- };
292
+ if (!element) return () => {};
293
+ const prevStyle = element.style.cssText;
294
+ Object.assign(element.style, style);
295
+ return () => {
296
+ element.style.cssText = prevStyle;
297
+ };
426
298
  }
427
299
  /**
428
- * Receives functions as arguments and returns a new function that calls all.
429
- */ function chain(...fns) {
430
- return (...args)=>{
431
- for (const fn of fns){
432
- if (typeof fn === 'function') {
433
- // @ts-ignore
434
- fn(...args);
435
- }
436
- }
437
- };
300
+ * Receives functions as arguments and returns a new function that calls all.
301
+ */
302
+ function chain(...fns) {
303
+ return (...args) => {
304
+ for (const fn of fns) if (typeof fn === "function") fn(...args);
305
+ };
438
306
  }
439
307
 
308
+ //#endregion
309
+ //#region src/constants.ts
440
310
  const TRANSITIONS = {
441
- DURATION: 0.5,
442
- EASE: [
443
- 0.32,
444
- 0.72,
445
- 0,
446
- 1
447
- ]
311
+ DURATION: .5,
312
+ EASE: [
313
+ .32,
314
+ .72,
315
+ 0,
316
+ 1
317
+ ]
448
318
  };
449
- const VELOCITY_THRESHOLD = 0.4;
450
- const CLOSE_THRESHOLD = 0.25;
319
+ const VELOCITY_THRESHOLD = .4;
320
+ const CLOSE_THRESHOLD = .25;
451
321
  const SCROLL_LOCK_TIMEOUT = 100;
452
322
  const BORDER_RADIUS = 8;
453
323
  const NESTED_DISPLACEMENT = 16;
454
324
  const WINDOW_TOP_OFFSET = 26;
455
- const DRAG_CLASS = 'vaul-dragging';
325
+ const DRAG_CLASS = "vaul-dragging";
456
326
 
457
- // This code comes from https://github.com/radix-ui/primitives/blob/main/packages/react/use-controllable-state/src/useControllableState.tsx
327
+ //#endregion
328
+ //#region src/use-controllable-state.ts
458
329
  function useCallbackRef(callback) {
459
- const callbackRef = React__default.useRef(callback);
460
- React__default.useEffect(()=>{
461
- callbackRef.current = callback;
462
- });
463
- // https://github.com/facebook/react/issues/19240
464
- return React__default.useMemo(()=>(...args)=>callbackRef.current?.(...args), []);
330
+ const callbackRef = React.useRef(callback);
331
+ React.useEffect(() => {
332
+ callbackRef.current = callback;
333
+ });
334
+ return React.useMemo(() => ((...args) => callbackRef.current?.(...args)), []);
465
335
  }
466
336
  function useUncontrolledState({ defaultProp, onChange }) {
467
- const uncontrolledState = React__default.useState(defaultProp);
468
- const [value] = uncontrolledState;
469
- const prevValueRef = React__default.useRef(value);
470
- const handleChange = useCallbackRef(onChange);
471
- React__default.useEffect(()=>{
472
- if (prevValueRef.current !== value) {
473
- handleChange(value);
474
- prevValueRef.current = value;
475
- }
476
- }, [
477
- value,
478
- prevValueRef,
479
- handleChange
480
- ]);
481
- return uncontrolledState;
337
+ const uncontrolledState = React.useState(defaultProp);
338
+ const [value] = uncontrolledState;
339
+ const prevValueRef = React.useRef(value);
340
+ const handleChange = useCallbackRef(onChange);
341
+ React.useEffect(() => {
342
+ if (prevValueRef.current !== value) {
343
+ handleChange(value);
344
+ prevValueRef.current = value;
345
+ }
346
+ }, [
347
+ value,
348
+ prevValueRef,
349
+ handleChange
350
+ ]);
351
+ return uncontrolledState;
482
352
  }
483
- function useControllableState({ prop, defaultProp, onChange = ()=>{} }) {
484
- const [uncontrolledProp, setUncontrolledProp] = useUncontrolledState({
485
- defaultProp,
486
- onChange
487
- });
488
- const isControlled = prop !== undefined;
489
- const value = isControlled ? prop : uncontrolledProp;
490
- const handleChange = useCallbackRef(onChange);
491
- const setValue = React__default.useCallback((nextValue)=>{
492
- if (isControlled) {
493
- const setter = nextValue;
494
- const value = typeof nextValue === 'function' ? setter(prop) : nextValue;
495
- if (value !== prop) handleChange(value);
496
- } else {
497
- setUncontrolledProp(nextValue);
498
- }
499
- }, [
500
- isControlled,
501
- prop,
502
- setUncontrolledProp,
503
- handleChange
504
- ]);
505
- return [
506
- value,
507
- setValue
508
- ];
353
+ function useControllableState({ prop, defaultProp, onChange = () => {} }) {
354
+ const [uncontrolledProp, setUncontrolledProp] = useUncontrolledState({
355
+ defaultProp,
356
+ onChange
357
+ });
358
+ const isControlled = prop !== void 0;
359
+ const value = isControlled ? prop : uncontrolledProp;
360
+ const handleChange = useCallbackRef(onChange);
361
+ return [value, React.useCallback((nextValue) => {
362
+ if (isControlled) {
363
+ const value$1 = typeof nextValue === "function" ? nextValue(prop) : nextValue;
364
+ if (value$1 !== prop) handleChange(value$1);
365
+ } else setUncontrolledProp(nextValue);
366
+ }, [
367
+ isControlled,
368
+ prop,
369
+ setUncontrolledProp,
370
+ handleChange
371
+ ])];
509
372
  }
510
373
 
511
- // @ts-nocheck [FIXME] See after monorepo migration
374
+ //#endregion
375
+ //#region src/use-snap-points.ts
512
376
  function useSnapPoints({ activeSnapPointProp, setActiveSnapPointProp, snapPoints, drawerRef, overlayRef, fadeFromIndex, onSnapPointChange, direction = "bottom", container, snapToSequentialPoint }) {
513
- const [activeSnapPoint, setActiveSnapPoint] = useControllableState({
514
- prop: activeSnapPointProp,
515
- defaultProp: snapPoints?.[0],
516
- onChange: setActiveSnapPointProp
517
- });
518
- const [windowDimensions, setWindowDimensions] = React__default.useState(typeof window !== "undefined" ? {
519
- innerWidth: window.innerWidth,
520
- innerHeight: window.innerHeight
521
- } : undefined);
522
- React__default.useEffect(()=>{
523
- function onResize() {
524
- setWindowDimensions({
525
- innerWidth: window.innerWidth,
526
- innerHeight: window.innerHeight
527
- });
528
- }
529
- window.addEventListener("resize", onResize);
530
- return ()=>window.removeEventListener("resize", onResize);
531
- }, []);
532
- const isLastSnapPoint = React__default.useMemo(()=>activeSnapPoint === snapPoints?.[snapPoints.length - 1] || null, [
533
- snapPoints,
534
- activeSnapPoint
535
- ]);
536
- const activeSnapPointIndex = React__default.useMemo(()=>snapPoints?.findIndex((snapPoint)=>snapPoint === activeSnapPoint) ?? null, [
537
- snapPoints,
538
- activeSnapPoint
539
- ]);
540
- const shouldFade = snapPoints && snapPoints.length > 0 && (fadeFromIndex || fadeFromIndex === 0) && !Number.isNaN(fadeFromIndex) && snapPoints[fadeFromIndex] === activeSnapPoint || !snapPoints;
541
- const snapPointsOffset = React__default.useMemo(()=>{
542
- const containerSize = container ? {
543
- width: container.getBoundingClientRect().width,
544
- height: container.getBoundingClientRect().height
545
- } : typeof window !== "undefined" ? {
546
- width: window.innerWidth,
547
- height: window.innerHeight
548
- } : {
549
- width: 0,
550
- height: 0
551
- };
552
- return snapPoints?.map((snapPoint)=>{
553
- const isPx = typeof snapPoint === "string";
554
- let snapPointAsNumber = 0;
555
- if (isPx) {
556
- snapPointAsNumber = parseInt(snapPoint, 10);
557
- }
558
- if (isVertical(direction)) {
559
- const height = isPx ? snapPointAsNumber : windowDimensions ? snapPoint * containerSize.height : 0;
560
- if (windowDimensions) {
561
- return direction === "bottom" ? containerSize.height - height : -containerSize.height + height;
562
- }
563
- return height;
564
- }
565
- const width = isPx ? snapPointAsNumber : windowDimensions ? snapPoint * containerSize.width : 0;
566
- if (windowDimensions) {
567
- return direction === "right" ? containerSize.width - width : -containerSize.width + width;
568
- }
569
- return width;
570
- }) ?? [];
571
- }, [
572
- snapPoints,
573
- windowDimensions,
574
- container
575
- ]);
576
- const activeSnapPointOffset = React__default.useMemo(()=>activeSnapPointIndex !== null ? snapPointsOffset?.[activeSnapPointIndex] : null, [
577
- snapPointsOffset,
578
- activeSnapPointIndex
579
- ]);
580
- const snapToPoint = React__default.useCallback((dimension)=>{
581
- const newSnapPointIndex = snapPointsOffset?.findIndex((snapPointDim)=>snapPointDim === dimension) ?? null;
582
- onSnapPointChange(newSnapPointIndex);
583
- set(drawerRef.current, {
584
- transition: `transform ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`,
585
- transform: isVertical(direction) ? `translate3d(0, ${dimension}px, 0)` : `translate3d(${dimension}px, 0, 0)`
586
- });
587
- if (snapPointsOffset && newSnapPointIndex !== snapPointsOffset.length - 1 && fadeFromIndex !== undefined && newSnapPointIndex !== fadeFromIndex && newSnapPointIndex < fadeFromIndex) {
588
- set(overlayRef.current, {
589
- transition: `opacity ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`,
590
- opacity: "0"
591
- });
592
- } else {
593
- set(overlayRef.current, {
594
- transition: `opacity ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`,
595
- opacity: "1"
596
- });
597
- }
598
- setActiveSnapPoint(snapPoints?.[Math.max(newSnapPointIndex, 0)]);
599
- }, [
600
- drawerRef.current,
601
- snapPoints,
602
- snapPointsOffset,
603
- fadeFromIndex,
604
- overlayRef,
605
- setActiveSnapPoint
606
- ]);
607
- React__default.useEffect(()=>{
608
- if (activeSnapPoint || activeSnapPointProp) {
609
- const newIndex = snapPoints?.findIndex((snapPoint)=>snapPoint === activeSnapPointProp || snapPoint === activeSnapPoint) ?? -1;
610
- if (snapPointsOffset && newIndex !== -1 && typeof snapPointsOffset[newIndex] === "number") {
611
- snapToPoint(snapPointsOffset[newIndex]);
612
- }
613
- }
614
- }, [
615
- activeSnapPoint,
616
- activeSnapPointProp,
617
- snapPoints,
618
- snapPointsOffset,
619
- snapToPoint
620
- ]);
621
- function onRelease({ draggedDistance, closeDrawer, velocity, dismissible }) {
622
- if (fadeFromIndex === undefined) return;
623
- const currentPosition = direction === "bottom" || direction === "right" ? (activeSnapPointOffset ?? 0) - draggedDistance : (activeSnapPointOffset ?? 0) + draggedDistance;
624
- const isOverlaySnapPoint = activeSnapPointIndex === fadeFromIndex - 1;
625
- const isFirst = activeSnapPointIndex === 0;
626
- const hasDraggedUp = draggedDistance > 0;
627
- if (isOverlaySnapPoint) {
628
- set(overlayRef.current, {
629
- transition: `opacity ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`
630
- });
631
- }
632
- if (!snapToSequentialPoint && velocity > 2 && !hasDraggedUp) {
633
- if (dismissible) closeDrawer();
634
- else snapToPoint(snapPointsOffset[0]); // snap to initial point
635
- return;
636
- }
637
- if (!snapToSequentialPoint && velocity > 2 && hasDraggedUp && snapPointsOffset && snapPoints) {
638
- snapToPoint(snapPointsOffset[snapPoints.length - 1]);
639
- return;
640
- }
641
- // Find the closest snap point to the current position
642
- const closestSnapPoint = snapPointsOffset?.reduce((prev, curr)=>{
643
- if (typeof prev !== "number" || typeof curr !== "number") return prev;
644
- return Math.abs(curr - currentPosition) < Math.abs(prev - currentPosition) ? curr : prev;
645
- });
646
- const dim = isVertical(direction) ? window.innerHeight : window.innerWidth;
647
- if (velocity > VELOCITY_THRESHOLD && Math.abs(draggedDistance) < dim * 0.4) {
648
- const dragDirection = hasDraggedUp ? 1 : -1; // 1 = up, -1 = down
649
- // Don't do anything if we swipe upwards while being on the last snap point
650
- if (dragDirection > 0 && isLastSnapPoint && snapPoints) {
651
- snapToPoint(snapPointsOffset[snapPoints.length - 1]);
652
- return;
653
- }
654
- if (isFirst && dragDirection < 0 && dismissible) {
655
- closeDrawer();
656
- }
657
- if (activeSnapPointIndex === null) return;
658
- snapToPoint(snapPointsOffset[activeSnapPointIndex + dragDirection]);
659
- return;
660
- }
661
- snapToPoint(closestSnapPoint);
662
- }
663
- function onDrag({ draggedDistance }) {
664
- if (activeSnapPointOffset === null) return;
665
- const newValue = direction === "bottom" || direction === "right" ? activeSnapPointOffset - draggedDistance : activeSnapPointOffset + draggedDistance;
666
- // Don't do anything if we exceed the last(biggest) snap point
667
- if ((direction === "bottom" || direction === "right") && newValue < snapPointsOffset[snapPointsOffset.length - 1]) {
668
- return;
669
- }
670
- if ((direction === "top" || direction === "left") && newValue > snapPointsOffset[snapPointsOffset.length - 1]) {
671
- return;
672
- }
673
- set(drawerRef.current, {
674
- transform: isVertical(direction) ? `translate3d(0, ${newValue}px, 0)` : `translate3d(${newValue}px, 0, 0)`
675
- });
676
- }
677
- function getPercentageDragged(absDraggedDistance, isDraggingDown) {
678
- if (!snapPoints || typeof activeSnapPointIndex !== "number" || !snapPointsOffset || fadeFromIndex === undefined) return null;
679
- // If this is true we are dragging to a snap point that is supposed to have an overlay
680
- const isOverlaySnapPoint = activeSnapPointIndex === fadeFromIndex - 1;
681
- const isOverlaySnapPointOrHigher = activeSnapPointIndex >= fadeFromIndex;
682
- if (isOverlaySnapPointOrHigher && isDraggingDown) {
683
- return 0;
684
- }
685
- // Don't animate, but still use this one if we are dragging away from the overlaySnapPoint
686
- if (isOverlaySnapPoint && !isDraggingDown) return 1;
687
- if (!shouldFade && !isOverlaySnapPoint) return null;
688
- // Either fadeFrom index or the one before
689
- const targetSnapPointIndex = isOverlaySnapPoint ? activeSnapPointIndex + 1 : activeSnapPointIndex - 1;
690
- // Get the distance from overlaySnapPoint to the one before or vice-versa to calculate the opacity percentage accordingly
691
- const snapPointDistance = isOverlaySnapPoint ? snapPointsOffset[targetSnapPointIndex] - snapPointsOffset[targetSnapPointIndex - 1] : snapPointsOffset[targetSnapPointIndex + 1] - snapPointsOffset[targetSnapPointIndex];
692
- const percentageDragged = absDraggedDistance / Math.abs(snapPointDistance);
693
- if (isOverlaySnapPoint) {
694
- return 1 - percentageDragged;
695
- } else {
696
- return percentageDragged;
697
- }
698
- }
699
- return {
700
- isLastSnapPoint,
701
- activeSnapPoint,
702
- shouldFade,
703
- getPercentageDragged,
704
- setActiveSnapPoint,
705
- activeSnapPointIndex,
706
- onRelease,
707
- onDrag,
708
- snapPointsOffset
709
- };
377
+ const [activeSnapPoint, setActiveSnapPoint] = useControllableState({
378
+ prop: activeSnapPointProp,
379
+ defaultProp: snapPoints?.[0],
380
+ onChange: setActiveSnapPointProp
381
+ });
382
+ const [windowDimensions, setWindowDimensions] = React.useState(typeof window !== "undefined" ? {
383
+ innerWidth: window.innerWidth,
384
+ innerHeight: window.innerHeight
385
+ } : void 0);
386
+ React.useEffect(() => {
387
+ function onResize() {
388
+ setWindowDimensions({
389
+ innerWidth: window.innerWidth,
390
+ innerHeight: window.innerHeight
391
+ });
392
+ }
393
+ window.addEventListener("resize", onResize);
394
+ return () => window.removeEventListener("resize", onResize);
395
+ }, []);
396
+ const isLastSnapPoint = React.useMemo(() => activeSnapPoint === snapPoints?.[snapPoints.length - 1] || null, [snapPoints, activeSnapPoint]);
397
+ const activeSnapPointIndex = React.useMemo(() => snapPoints?.findIndex((snapPoint) => snapPoint === activeSnapPoint) ?? null, [snapPoints, activeSnapPoint]);
398
+ const shouldFade = snapPoints && snapPoints.length > 0 && (fadeFromIndex || fadeFromIndex === 0) && !Number.isNaN(fadeFromIndex) && snapPoints[fadeFromIndex] === activeSnapPoint || !snapPoints;
399
+ const snapPointsOffset = React.useMemo(() => {
400
+ const containerSize = container ? {
401
+ width: container.getBoundingClientRect().width,
402
+ height: container.getBoundingClientRect().height
403
+ } : typeof window !== "undefined" ? {
404
+ width: window.innerWidth,
405
+ height: window.innerHeight
406
+ } : {
407
+ width: 0,
408
+ height: 0
409
+ };
410
+ return snapPoints?.map((snapPoint) => {
411
+ const isPx = typeof snapPoint === "string";
412
+ let snapPointAsNumber = 0;
413
+ if (isPx) snapPointAsNumber = parseInt(snapPoint, 10);
414
+ if (isVertical(direction)) {
415
+ const height = isPx ? snapPointAsNumber : windowDimensions ? snapPoint * containerSize.height : 0;
416
+ if (windowDimensions) return direction === "bottom" ? containerSize.height - height : -containerSize.height + height;
417
+ return height;
418
+ }
419
+ const width = isPx ? snapPointAsNumber : windowDimensions ? snapPoint * containerSize.width : 0;
420
+ if (windowDimensions) return direction === "right" ? containerSize.width - width : -containerSize.width + width;
421
+ return width;
422
+ }) ?? [];
423
+ }, [
424
+ snapPoints,
425
+ windowDimensions,
426
+ container
427
+ ]);
428
+ const activeSnapPointOffset = React.useMemo(() => activeSnapPointIndex !== null ? snapPointsOffset?.[activeSnapPointIndex] : null, [snapPointsOffset, activeSnapPointIndex]);
429
+ const snapToPoint = React.useCallback((dimension) => {
430
+ const newSnapPointIndex = snapPointsOffset?.findIndex((snapPointDim) => snapPointDim === dimension) ?? null;
431
+ onSnapPointChange(newSnapPointIndex);
432
+ set(drawerRef.current, {
433
+ transition: `transform ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`,
434
+ transform: isVertical(direction) ? `translate3d(0, ${dimension}px, 0)` : `translate3d(${dimension}px, 0, 0)`
435
+ });
436
+ if (snapPointsOffset && newSnapPointIndex !== snapPointsOffset.length - 1 && fadeFromIndex !== void 0 && newSnapPointIndex !== fadeFromIndex && newSnapPointIndex < fadeFromIndex) set(overlayRef.current, {
437
+ transition: `opacity ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`,
438
+ opacity: "0"
439
+ });
440
+ else set(overlayRef.current, {
441
+ transition: `opacity ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`,
442
+ opacity: "1"
443
+ });
444
+ setActiveSnapPoint(snapPoints?.[Math.max(newSnapPointIndex, 0)]);
445
+ }, [
446
+ drawerRef.current,
447
+ snapPoints,
448
+ snapPointsOffset,
449
+ fadeFromIndex,
450
+ overlayRef,
451
+ setActiveSnapPoint
452
+ ]);
453
+ React.useEffect(() => {
454
+ if (activeSnapPoint || activeSnapPointProp) {
455
+ const newIndex = snapPoints?.findIndex((snapPoint) => snapPoint === activeSnapPointProp || snapPoint === activeSnapPoint) ?? -1;
456
+ if (snapPointsOffset && newIndex !== -1 && typeof snapPointsOffset[newIndex] === "number") snapToPoint(snapPointsOffset[newIndex]);
457
+ }
458
+ }, [
459
+ activeSnapPoint,
460
+ activeSnapPointProp,
461
+ snapPoints,
462
+ snapPointsOffset,
463
+ snapToPoint
464
+ ]);
465
+ function onRelease({ draggedDistance, closeDrawer, velocity, dismissible }) {
466
+ if (fadeFromIndex === void 0) return;
467
+ const currentPosition = direction === "bottom" || direction === "right" ? (activeSnapPointOffset ?? 0) - draggedDistance : (activeSnapPointOffset ?? 0) + draggedDistance;
468
+ const isOverlaySnapPoint = activeSnapPointIndex === fadeFromIndex - 1;
469
+ const isFirst = activeSnapPointIndex === 0;
470
+ const hasDraggedUp = draggedDistance > 0;
471
+ if (isOverlaySnapPoint) set(overlayRef.current, { transition: `opacity ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})` });
472
+ if (!snapToSequentialPoint && velocity > 2 && !hasDraggedUp) {
473
+ if (dismissible) closeDrawer();
474
+ else snapToPoint(snapPointsOffset[0]);
475
+ return;
476
+ }
477
+ if (!snapToSequentialPoint && velocity > 2 && hasDraggedUp && snapPointsOffset && snapPoints) {
478
+ snapToPoint(snapPointsOffset[snapPoints.length - 1]);
479
+ return;
480
+ }
481
+ const closestSnapPoint = snapPointsOffset?.reduce((prev, curr) => {
482
+ if (typeof prev !== "number" || typeof curr !== "number") return prev;
483
+ return Math.abs(curr - currentPosition) < Math.abs(prev - currentPosition) ? curr : prev;
484
+ });
485
+ const dim = isVertical(direction) ? window.innerHeight : window.innerWidth;
486
+ if (velocity > VELOCITY_THRESHOLD && Math.abs(draggedDistance) < dim * .4) {
487
+ const dragDirection = hasDraggedUp ? 1 : -1;
488
+ if (dragDirection > 0 && isLastSnapPoint && snapPoints) {
489
+ snapToPoint(snapPointsOffset[snapPoints.length - 1]);
490
+ return;
491
+ }
492
+ if (isFirst && dragDirection < 0 && dismissible) closeDrawer();
493
+ if (activeSnapPointIndex === null) return;
494
+ snapToPoint(snapPointsOffset[activeSnapPointIndex + dragDirection]);
495
+ return;
496
+ }
497
+ snapToPoint(closestSnapPoint);
498
+ }
499
+ function onDrag({ draggedDistance }) {
500
+ if (activeSnapPointOffset === null) return;
501
+ const newValue = direction === "bottom" || direction === "right" ? activeSnapPointOffset - draggedDistance : activeSnapPointOffset + draggedDistance;
502
+ if ((direction === "bottom" || direction === "right") && newValue < snapPointsOffset[snapPointsOffset.length - 1]) return;
503
+ if ((direction === "top" || direction === "left") && newValue > snapPointsOffset[snapPointsOffset.length - 1]) return;
504
+ set(drawerRef.current, { transform: isVertical(direction) ? `translate3d(0, ${newValue}px, 0)` : `translate3d(${newValue}px, 0, 0)` });
505
+ }
506
+ function getPercentageDragged(absDraggedDistance, isDraggingDown) {
507
+ if (!snapPoints || typeof activeSnapPointIndex !== "number" || !snapPointsOffset || fadeFromIndex === void 0) return null;
508
+ const isOverlaySnapPoint = activeSnapPointIndex === fadeFromIndex - 1;
509
+ if (activeSnapPointIndex >= fadeFromIndex && isDraggingDown) return 0;
510
+ if (isOverlaySnapPoint && !isDraggingDown) return 1;
511
+ if (!shouldFade && !isOverlaySnapPoint) return null;
512
+ const targetSnapPointIndex = isOverlaySnapPoint ? activeSnapPointIndex + 1 : activeSnapPointIndex - 1;
513
+ const snapPointDistance = isOverlaySnapPoint ? snapPointsOffset[targetSnapPointIndex] - snapPointsOffset[targetSnapPointIndex - 1] : snapPointsOffset[targetSnapPointIndex + 1] - snapPointsOffset[targetSnapPointIndex];
514
+ const percentageDragged = absDraggedDistance / Math.abs(snapPointDistance);
515
+ if (isOverlaySnapPoint) return 1 - percentageDragged;
516
+ else return percentageDragged;
517
+ }
518
+ return {
519
+ isLastSnapPoint,
520
+ activeSnapPoint,
521
+ shouldFade,
522
+ getPercentageDragged,
523
+ setActiveSnapPoint,
524
+ activeSnapPointIndex,
525
+ onRelease,
526
+ onDrag,
527
+ snapPointsOffset
528
+ };
710
529
  }
711
530
 
712
- const noop = ()=>()=>{};
531
+ //#endregion
532
+ //#region src/use-scale-background.ts
533
+ const noop = () => () => {};
713
534
  function useScaleBackground() {
714
- const { direction, isOpen, shouldScaleBackground, setBackgroundColorOnScale, noBodyStyles } = useDrawerContext();
715
- const timeoutIdRef = React__default.useRef(null);
716
- const initialBackgroundColor = useMemo(()=>document.body.style.backgroundColor, []);
717
- function getScale() {
718
- return (window.innerWidth - WINDOW_TOP_OFFSET) / window.innerWidth;
719
- }
720
- React__default.useEffect(()=>{
721
- if (isOpen && shouldScaleBackground) {
722
- if (timeoutIdRef.current) clearTimeout(timeoutIdRef.current);
723
- const wrapper = document.querySelector('[data-vaul-drawer-wrapper]') || document.querySelector('[vaul-drawer-wrapper]');
724
- if (!wrapper) return;
725
- chain(setBackgroundColorOnScale && !noBodyStyles ? assignStyle(document.body, {
726
- background: 'black'
727
- }) : noop, assignStyle(wrapper, {
728
- transformOrigin: isVertical(direction) ? 'top' : 'left',
729
- transitionProperty: 'transform, border-radius',
730
- transitionDuration: `${TRANSITIONS.DURATION}s`,
731
- transitionTimingFunction: `cubic-bezier(${TRANSITIONS.EASE.join(',')})`
732
- }));
733
- const wrapperStylesCleanup = assignStyle(wrapper, {
734
- borderRadius: `${BORDER_RADIUS}px`,
735
- overflow: 'hidden',
736
- ...isVertical(direction) ? {
737
- transform: `scale(${getScale()}) translate3d(0, calc(env(safe-area-inset-top) + 14px), 0)`
738
- } : {
739
- transform: `scale(${getScale()}) translate3d(calc(env(safe-area-inset-top) + 14px), 0, 0)`
740
- }
741
- });
742
- return ()=>{
743
- wrapperStylesCleanup();
744
- timeoutIdRef.current = window.setTimeout(()=>{
745
- if (initialBackgroundColor) {
746
- document.body.style.background = initialBackgroundColor;
747
- } else {
748
- document.body.style.removeProperty('background');
749
- }
750
- }, TRANSITIONS.DURATION * 1000);
751
- };
752
- }
753
- }, [
754
- isOpen,
755
- shouldScaleBackground,
756
- initialBackgroundColor
757
- ]);
535
+ const { direction, isOpen, shouldScaleBackground, setBackgroundColorOnScale, noBodyStyles } = useDrawerContext();
536
+ const timeoutIdRef = React.useRef(null);
537
+ const initialBackgroundColor = useMemo(() => document.body.style.backgroundColor, []);
538
+ function getScale() {
539
+ return (window.innerWidth - WINDOW_TOP_OFFSET) / window.innerWidth;
540
+ }
541
+ React.useEffect(() => {
542
+ if (isOpen && shouldScaleBackground) {
543
+ if (timeoutIdRef.current) clearTimeout(timeoutIdRef.current);
544
+ const wrapper = document.querySelector("[data-vaul-drawer-wrapper]") || document.querySelector("[vaul-drawer-wrapper]");
545
+ if (!wrapper) return;
546
+ chain(setBackgroundColorOnScale && !noBodyStyles ? assignStyle(document.body, { background: "black" }) : noop, assignStyle(wrapper, {
547
+ transformOrigin: isVertical(direction) ? "top" : "left",
548
+ transitionProperty: "transform, border-radius",
549
+ transitionDuration: `${TRANSITIONS.DURATION}s`,
550
+ transitionTimingFunction: `cubic-bezier(${TRANSITIONS.EASE.join(",")})`
551
+ }));
552
+ const wrapperStylesCleanup = assignStyle(wrapper, {
553
+ borderRadius: `${BORDER_RADIUS}px`,
554
+ overflow: "hidden",
555
+ ...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)` }
556
+ });
557
+ return () => {
558
+ wrapperStylesCleanup();
559
+ timeoutIdRef.current = window.setTimeout(() => {
560
+ if (initialBackgroundColor) document.body.style.background = initialBackgroundColor;
561
+ else document.body.style.removeProperty("background");
562
+ }, TRANSITIONS.DURATION * 1e3);
563
+ };
564
+ }
565
+ }, [
566
+ isOpen,
567
+ shouldScaleBackground,
568
+ initialBackgroundColor
569
+ ]);
758
570
  }
759
571
 
572
+ //#endregion
573
+ //#region src/use-position-fixed.ts
760
574
  let previousBodyPosition = null;
761
575
  /**
762
- * This hook is necessary to prevent buggy behavior on iOS devices (need to test on Android).
763
- * 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.
764
- * Issues that this hook solves:
765
- * https://github.com/emilkowalski/vaul/issues/435
766
- * https://github.com/emilkowalski/vaul/issues/433
767
- * And more that I discovered, but were just not reported.
768
- */ function usePositionFixed({ isOpen, modal, nested, hasBeenOpened, preventScrollRestoration, noBodyStyles }) {
769
- const [activeUrl, setActiveUrl] = React__default.useState(()=>typeof window !== 'undefined' ? window.location.href : '');
770
- const scrollPos = React__default.useRef(0);
771
- const setPositionFixed = React__default.useCallback(()=>{
772
- // All browsers on iOS will return true here.
773
- if (!isSafari()) return;
774
- // If previousBodyPosition is already set, don't set it again.
775
- if (previousBodyPosition === null && isOpen && !noBodyStyles) {
776
- previousBodyPosition = {
777
- position: document.body.style.position,
778
- top: document.body.style.top,
779
- left: document.body.style.left,
780
- height: document.body.style.height,
781
- right: 'unset'
782
- };
783
- // Update the dom inside an animation frame
784
- const { scrollX, innerHeight } = window;
785
- document.body.style.setProperty('position', 'fixed', 'important');
786
- Object.assign(document.body.style, {
787
- top: `${-scrollPos.current}px`,
788
- left: `${-scrollX}px`,
789
- right: '0px',
790
- height: 'auto'
791
- });
792
- window.setTimeout(()=>window.requestAnimationFrame(()=>{
793
- // Attempt to check if the bottom bar appeared due to the position change
794
- const bottomBarHeight = innerHeight - window.innerHeight;
795
- if (bottomBarHeight && scrollPos.current >= innerHeight) {
796
- // Move the content further up so that the bottom bar doesn't hide it
797
- document.body.style.top = `${-(scrollPos.current + bottomBarHeight)}px`;
798
- }
799
- }), 300);
800
- }
801
- }, [
802
- isOpen
803
- ]);
804
- const restorePositionSetting = React__default.useCallback(()=>{
805
- // All browsers on iOS will return true here.
806
- if (!isSafari()) return;
807
- if (previousBodyPosition !== null && !noBodyStyles) {
808
- // Convert the position from "px" to Int
809
- const y = -parseInt(document.body.style.top, 10);
810
- const x = -parseInt(document.body.style.left, 10);
811
- // Restore styles
812
- Object.assign(document.body.style, previousBodyPosition);
813
- window.requestAnimationFrame(()=>{
814
- if (preventScrollRestoration && activeUrl !== window.location.href) {
815
- setActiveUrl(window.location.href);
816
- return;
817
- }
818
- window.scrollTo(x, y);
819
- });
820
- previousBodyPosition = null;
821
- }
822
- }, [
823
- activeUrl
824
- ]);
825
- React__default.useEffect(()=>{
826
- function onScroll() {
827
- scrollPos.current = window.scrollY;
828
- }
829
- onScroll();
830
- window.addEventListener('scroll', onScroll);
831
- return ()=>{
832
- window.removeEventListener('scroll', onScroll);
833
- };
834
- }, []);
835
- React__default.useEffect(()=>{
836
- if (!modal) return;
837
- return ()=>{
838
- if (typeof document === 'undefined') return;
839
- // Another drawer is opened, safe to ignore the execution
840
- const hasDrawerOpened = !!document.querySelector('[data-vaul-drawer]');
841
- if (hasDrawerOpened) return;
842
- restorePositionSetting();
843
- };
844
- }, [
845
- modal,
846
- restorePositionSetting
847
- ]);
848
- React__default.useEffect(()=>{
849
- if (nested || !hasBeenOpened) return;
850
- // This is needed to force Safari toolbar to show **before** the drawer starts animating to prevent a gnarly shift from happening
851
- if (isOpen) {
852
- // avoid for standalone mode (PWA)
853
- const isStandalone = window.matchMedia('(display-mode: standalone)').matches;
854
- !isStandalone && setPositionFixed();
855
- if (!modal) {
856
- window.setTimeout(()=>{
857
- restorePositionSetting();
858
- }, 500);
859
- }
860
- } else {
861
- restorePositionSetting();
862
- }
863
- }, [
864
- isOpen,
865
- hasBeenOpened,
866
- activeUrl,
867
- modal,
868
- nested,
869
- setPositionFixed,
870
- restorePositionSetting
871
- ]);
872
- return {
873
- restorePositionSetting
874
- };
576
+ * This hook is necessary to prevent buggy behavior on iOS devices (need to test on Android).
577
+ * 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.
578
+ * Issues that this hook solves:
579
+ * https://github.com/emilkowalski/vaul/issues/435
580
+ * https://github.com/emilkowalski/vaul/issues/433
581
+ * And more that I discovered, but were just not reported.
582
+ */
583
+ function usePositionFixed({ isOpen, modal, nested, hasBeenOpened, preventScrollRestoration, noBodyStyles }) {
584
+ const [activeUrl, setActiveUrl] = React.useState(() => typeof window !== "undefined" ? window.location.href : "");
585
+ const scrollPos = React.useRef(0);
586
+ const setPositionFixed = React.useCallback(() => {
587
+ if (!isSafari()) return;
588
+ if (previousBodyPosition === null && isOpen && !noBodyStyles) {
589
+ previousBodyPosition = {
590
+ position: document.body.style.position,
591
+ top: document.body.style.top,
592
+ left: document.body.style.left,
593
+ height: document.body.style.height,
594
+ right: "unset"
595
+ };
596
+ const { scrollX, innerHeight } = window;
597
+ document.body.style.setProperty("position", "fixed", "important");
598
+ Object.assign(document.body.style, {
599
+ top: `${-scrollPos.current}px`,
600
+ left: `${-scrollX}px`,
601
+ right: "0px",
602
+ height: "auto"
603
+ });
604
+ window.setTimeout(() => window.requestAnimationFrame(() => {
605
+ const bottomBarHeight = innerHeight - window.innerHeight;
606
+ if (bottomBarHeight && scrollPos.current >= innerHeight) document.body.style.top = `${-(scrollPos.current + bottomBarHeight)}px`;
607
+ }), 300);
608
+ }
609
+ }, [isOpen]);
610
+ const restorePositionSetting = React.useCallback(() => {
611
+ if (!isSafari()) return;
612
+ if (previousBodyPosition !== null && !noBodyStyles) {
613
+ const y = -parseInt(document.body.style.top, 10);
614
+ const x = -parseInt(document.body.style.left, 10);
615
+ Object.assign(document.body.style, previousBodyPosition);
616
+ window.requestAnimationFrame(() => {
617
+ if (preventScrollRestoration && activeUrl !== window.location.href) {
618
+ setActiveUrl(window.location.href);
619
+ return;
620
+ }
621
+ window.scrollTo(x, y);
622
+ });
623
+ previousBodyPosition = null;
624
+ }
625
+ }, [activeUrl]);
626
+ React.useEffect(() => {
627
+ function onScroll() {
628
+ scrollPos.current = window.scrollY;
629
+ }
630
+ onScroll();
631
+ window.addEventListener("scroll", onScroll);
632
+ return () => {
633
+ window.removeEventListener("scroll", onScroll);
634
+ };
635
+ }, []);
636
+ React.useEffect(() => {
637
+ if (!modal) return;
638
+ return () => {
639
+ if (typeof document === "undefined") return;
640
+ if (!!document.querySelector("[data-vaul-drawer]")) return;
641
+ restorePositionSetting();
642
+ };
643
+ }, [modal, restorePositionSetting]);
644
+ React.useEffect(() => {
645
+ if (nested || !hasBeenOpened) return;
646
+ if (isOpen) {
647
+ !window.matchMedia("(display-mode: standalone)").matches && setPositionFixed();
648
+ if (!modal) window.setTimeout(() => {
649
+ restorePositionSetting();
650
+ }, 500);
651
+ } else restorePositionSetting();
652
+ }, [
653
+ isOpen,
654
+ hasBeenOpened,
655
+ activeUrl,
656
+ modal,
657
+ nested,
658
+ setPositionFixed,
659
+ restorePositionSetting
660
+ ]);
661
+ return { restorePositionSetting };
875
662
  }
876
663
 
664
+ //#endregion
665
+ //#region src/index.tsx
877
666
  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 }) {
878
- const [isOpen = false, setIsOpen] = useControllableState({
879
- defaultProp: defaultOpen,
880
- prop: openProp,
881
- onChange: (o)=>{
882
- onOpenChange?.(o);
883
- if (!o && !nested) {
884
- restorePositionSetting();
885
- }
886
- setTimeout(()=>{
887
- onAnimationEnd?.(o);
888
- }, TRANSITIONS.DURATION * 1000);
889
- if (o && !modal) {
890
- if (typeof window !== "undefined") {
891
- window.requestAnimationFrame(()=>{
892
- document.body.style.pointerEvents = "auto";
893
- });
894
- }
895
- }
896
- if (!o) {
897
- // This will be removed when the exit animation ends (`500ms`)
898
- document.body.style.pointerEvents = "auto";
899
- }
900
- }
901
- });
902
- const [hasBeenOpened, setHasBeenOpened] = React__default.useState(false);
903
- const [isDragging, setIsDragging] = React__default.useState(false);
904
- const [justReleased, setJustReleased] = React__default.useState(false);
905
- const overlayRef = React__default.useRef(null);
906
- const openTime = React__default.useRef(null);
907
- const dragStartTime = React__default.useRef(null);
908
- const dragEndTime = React__default.useRef(null);
909
- const lastTimeDragPrevented = React__default.useRef(null);
910
- const isAllowedToDrag = React__default.useRef(false);
911
- const nestedOpenChangeTimer = React__default.useRef(null);
912
- const pointerStart = React__default.useRef(0);
913
- const keyboardIsOpen = React__default.useRef(false);
914
- const shouldAnimate = React__default.useRef(!defaultOpen);
915
- const previousDiffFromInitial = React__default.useRef(0);
916
- const drawerRef = React__default.useRef(null);
917
- const drawerHeightRef = React__default.useRef(drawerRef.current?.getBoundingClientRect().height || 0);
918
- const drawerWidthRef = React__default.useRef(drawerRef.current?.getBoundingClientRect().width || 0);
919
- const initialDrawerHeight = React__default.useRef(0);
920
- const onSnapPointChange = React__default.useCallback((activeSnapPointIndex)=>{
921
- // Change openTime ref when we reach the last snap point to prevent dragging for 500ms incase it's scrollable.
922
- if (snapPoints && activeSnapPointIndex === snapPointsOffset.length - 1) openTime.current = new Date();
923
- }, []);
924
- const { activeSnapPoint, activeSnapPointIndex, setActiveSnapPoint, onRelease: onReleaseSnapPoints, snapPointsOffset, onDrag: onDragSnapPoints, shouldFade, getPercentageDragged: getSnapPointsPercentageDragged } = useSnapPoints({
925
- snapPoints,
926
- activeSnapPointProp,
927
- setActiveSnapPointProp,
928
- drawerRef,
929
- fadeFromIndex,
930
- overlayRef,
931
- onSnapPointChange,
932
- direction,
933
- container,
934
- snapToSequentialPoint
935
- });
936
- usePreventScroll({
937
- isDisabled: !isOpen || isDragging || !modal || justReleased || !hasBeenOpened || !repositionInputs || !disablePreventScroll
938
- });
939
- const { restorePositionSetting } = usePositionFixed({
940
- isOpen,
941
- modal,
942
- nested: nested ?? false,
943
- hasBeenOpened,
944
- preventScrollRestoration,
945
- noBodyStyles
946
- });
947
- function getScale() {
948
- return (window.innerWidth - WINDOW_TOP_OFFSET) / window.innerWidth;
949
- }
950
- function onPress(event) {
951
- if (!dismissible && !snapPoints) return;
952
- if (drawerRef.current && !drawerRef.current.contains(event.target)) return;
953
- drawerHeightRef.current = drawerRef.current?.getBoundingClientRect().height || 0;
954
- drawerWidthRef.current = drawerRef.current?.getBoundingClientRect().width || 0;
955
- setIsDragging(true);
956
- dragStartTime.current = new Date();
957
- // iOS doesn't trigger mouseUp after scrolling so we need to listen to touched in order to disallow dragging
958
- if (isIOS()) {
959
- window.addEventListener("touchend", ()=>isAllowedToDrag.current = false, {
960
- once: true
961
- });
962
- }
963
- // Ensure we maintain correct pointer capture even when going outside of the drawer
964
- event.target.setPointerCapture(event.pointerId);
965
- pointerStart.current = isVertical(direction) ? event.pageY : event.pageX;
966
- }
967
- function shouldDrag(el, isDraggingInDirection) {
968
- let element = el;
969
- const highlightedText = window.getSelection()?.toString();
970
- const swipeAmount = drawerRef.current ? getTranslate(drawerRef.current, direction) : null;
971
- const date = new Date();
972
- // Fixes https://github.com/emilkowalski/vaul/issues/483
973
- if (element.tagName === "SELECT") {
974
- return false;
975
- }
976
- if (element.hasAttribute("data-vaul-no-drag") || element.closest("[data-vaul-no-drag]")) {
977
- return false;
978
- }
979
- if (direction === "right" || direction === "left") {
980
- return true;
981
- }
982
- // Allow scrolling when animating
983
- if (openTime.current && date.getTime() - openTime.current.getTime() < 500) {
984
- return false;
985
- }
986
- if (swipeAmount !== null) {
987
- if (direction === "bottom" ? swipeAmount > 0 : swipeAmount < 0) {
988
- return true;
989
- }
990
- }
991
- // Don't drag if there's highlighted text
992
- if (highlightedText && highlightedText.length > 0) {
993
- return false;
994
- }
995
- // Disallow dragging if drawer was scrolled within `scrollLockTimeout`
996
- if (lastTimeDragPrevented.current && date.getTime() - lastTimeDragPrevented.current.getTime() < scrollLockTimeout && swipeAmount === 0) {
997
- lastTimeDragPrevented.current = date;
998
- return false;
999
- }
1000
- if (isDraggingInDirection) {
1001
- lastTimeDragPrevented.current = date;
1002
- // We are dragging down so we should allow scrolling
1003
- return false;
1004
- }
1005
- // Keep climbing up the DOM tree as long as there's a parent
1006
- while(element){
1007
- // Check if the element is scrollable
1008
- if (element.scrollHeight > element.clientHeight) {
1009
- if (element.scrollTop !== 0) {
1010
- lastTimeDragPrevented.current = new Date();
1011
- // The element is scrollable and not scrolled to the top, so don't drag
1012
- return false;
1013
- }
1014
- if (element.getAttribute("role") === "dialog") {
1015
- return true;
1016
- }
1017
- }
1018
- // Move up to the parent element
1019
- element = element.parentNode;
1020
- }
1021
- // No scrollable parents not scrolled to the top found, so drag
1022
- return true;
1023
- }
1024
- function onDrag(event) {
1025
- if (!drawerRef.current) {
1026
- return;
1027
- }
1028
- // We need to know how much of the drawer has been dragged in percentages so that we can transform background accordingly
1029
- if (isDragging) {
1030
- const directionMultiplier = direction === "bottom" || direction === "right" ? 1 : -1;
1031
- const draggedDistance = (pointerStart.current - (isVertical(direction) ? event.pageY : event.pageX)) * directionMultiplier;
1032
- const isDraggingInDirection = draggedDistance > 0;
1033
- // Pre condition for disallowing dragging in the close direction.
1034
- const noCloseSnapPointsPreCondition = snapPoints && !dismissible && !isDraggingInDirection;
1035
- // Disallow dragging down to close when first snap point is the active one and dismissible prop is set to false.
1036
- if (noCloseSnapPointsPreCondition && activeSnapPointIndex === 0) return;
1037
- // We need to capture last time when drag with scroll was triggered and have a timeout between
1038
- const absDraggedDistance = Math.abs(draggedDistance);
1039
- const wrapper = document.querySelector("[data-vaul-drawer-wrapper]");
1040
- const drawerDimension = direction === "bottom" || direction === "top" ? drawerHeightRef.current : drawerWidthRef.current;
1041
- // Calculate the percentage dragged, where 1 is the closed position
1042
- let percentageDragged = absDraggedDistance / drawerDimension;
1043
- const snapPointPercentageDragged = getSnapPointsPercentageDragged(absDraggedDistance, isDraggingInDirection);
1044
- if (snapPointPercentageDragged !== null) {
1045
- percentageDragged = snapPointPercentageDragged;
1046
- }
1047
- // Disallow close dragging beyond the smallest snap point.
1048
- if (noCloseSnapPointsPreCondition && percentageDragged >= 1) {
1049
- return;
1050
- }
1051
- if (!isAllowedToDrag.current && !shouldDrag(event.target, isDraggingInDirection)) return;
1052
- drawerRef.current.classList.add(DRAG_CLASS);
1053
- // If shouldDrag gave true once after pressing down on the drawer, we set isAllowedToDrag to true and it will remain true until we let go, there's no reason to disable dragging mid way, ever, and that's the solution to it
1054
- isAllowedToDrag.current = true;
1055
- set(drawerRef.current, {
1056
- transition: "none"
1057
- });
1058
- set(overlayRef.current, {
1059
- transition: "none"
1060
- });
1061
- if (snapPoints) {
1062
- onDragSnapPoints({
1063
- draggedDistance
1064
- });
1065
- }
1066
- // Run this only if snapPoints are not defined or if we are at the last snap point (highest one)
1067
- if (isDraggingInDirection && !snapPoints) {
1068
- const dampenedDraggedDistance = dampenValue(draggedDistance);
1069
- const translateValue = Math.min(dampenedDraggedDistance * -1, 0) * directionMultiplier;
1070
- set(drawerRef.current, {
1071
- transform: isVertical(direction) ? `translate3d(0, ${translateValue}px, 0)` : `translate3d(${translateValue}px, 0, 0)`
1072
- });
1073
- return;
1074
- }
1075
- const opacityValue = 1 - percentageDragged;
1076
- if (shouldFade || fadeFromIndex && activeSnapPointIndex === fadeFromIndex - 1) {
1077
- onDragProp?.(event, percentageDragged);
1078
- set(overlayRef.current, {
1079
- opacity: `${opacityValue}`,
1080
- transition: "none"
1081
- }, true);
1082
- }
1083
- if (wrapper && overlayRef.current && shouldScaleBackground) {
1084
- // Calculate percentageDragged as a fraction (0 to 1)
1085
- const scaleValue = Math.min(getScale() + percentageDragged * (1 - getScale()), 1);
1086
- const borderRadiusValue = 8 - percentageDragged * 8;
1087
- const translateValue = Math.max(0, 14 - percentageDragged * 14);
1088
- set(wrapper, {
1089
- borderRadius: `${borderRadiusValue}px`,
1090
- transform: isVertical(direction) ? `scale(${scaleValue}) translate3d(0, ${translateValue}px, 0)` : `scale(${scaleValue}) translate3d(${translateValue}px, 0, 0)`,
1091
- transition: "none"
1092
- }, true);
1093
- }
1094
- if (!snapPoints) {
1095
- const translateValue = absDraggedDistance * directionMultiplier;
1096
- set(drawerRef.current, {
1097
- transform: isVertical(direction) ? `translate3d(0, ${translateValue}px, 0)` : `translate3d(${translateValue}px, 0, 0)`
1098
- });
1099
- }
1100
- }
1101
- }
1102
- React__default.useEffect(()=>{
1103
- window.requestAnimationFrame(()=>{
1104
- shouldAnimate.current = true;
1105
- });
1106
- }, []);
1107
- React__default.useEffect(()=>{
1108
- function onVisualViewportChange() {
1109
- if (!drawerRef.current || !repositionInputs) return;
1110
- const focusedElement = document.activeElement;
1111
- if (isInput(focusedElement) || keyboardIsOpen.current) {
1112
- const visualViewportHeight = window.visualViewport?.height || 0;
1113
- const totalHeight = window.innerHeight;
1114
- // This is the height of the keyboard
1115
- let diffFromInitial = totalHeight - visualViewportHeight;
1116
- const drawerHeight = drawerRef.current.getBoundingClientRect().height || 0;
1117
- // Adjust drawer height only if it's tall enough
1118
- const isTallEnough = drawerHeight > totalHeight * 0.8;
1119
- if (!initialDrawerHeight.current) {
1120
- initialDrawerHeight.current = drawerHeight;
1121
- }
1122
- const offsetFromTop = drawerRef.current.getBoundingClientRect().top;
1123
- // visualViewport height may change due to somq e subtle changes to the keyboard. Checking if the height changed by 60 or more will make sure that they keyboard really changed its open state.
1124
- if (Math.abs(previousDiffFromInitial.current - diffFromInitial) > 60) {
1125
- keyboardIsOpen.current = !keyboardIsOpen.current;
1126
- }
1127
- if (snapPoints && snapPoints.length > 0 && snapPointsOffset && activeSnapPointIndex) {
1128
- const activeSnapPointHeight = snapPointsOffset[activeSnapPointIndex] || 0;
1129
- diffFromInitial += activeSnapPointHeight;
1130
- }
1131
- previousDiffFromInitial.current = diffFromInitial;
1132
- // We don't have to change the height if the input is in view, when we are here we are in the opened keyboard state so we can correctly check if the input is in view
1133
- if (drawerHeight > visualViewportHeight || keyboardIsOpen.current) {
1134
- const height = drawerRef.current.getBoundingClientRect().height;
1135
- let newDrawerHeight = height;
1136
- if (height > visualViewportHeight) {
1137
- newDrawerHeight = visualViewportHeight - (isTallEnough ? offsetFromTop : WINDOW_TOP_OFFSET);
1138
- }
1139
- // When fixed, don't move the drawer upwards if there's space, but rather only change it's height so it's fully scrollable when the keyboard is open
1140
- if (fixed) {
1141
- drawerRef.current.style.height = `${height - Math.max(diffFromInitial, 0)}px`;
1142
- } else {
1143
- drawerRef.current.style.height = `${Math.max(newDrawerHeight, visualViewportHeight - offsetFromTop)}px`;
1144
- }
1145
- } else if (!isMobileFirefox()) {
1146
- drawerRef.current.style.height = `${initialDrawerHeight.current}px`;
1147
- }
1148
- if (snapPoints && snapPoints.length > 0 && !keyboardIsOpen.current) {
1149
- drawerRef.current.style.bottom = `0px`;
1150
- } else {
1151
- // Negative bottom value would never make sense
1152
- drawerRef.current.style.bottom = `${Math.max(diffFromInitial, 0)}px`;
1153
- }
1154
- }
1155
- }
1156
- window.visualViewport?.addEventListener("resize", onVisualViewportChange);
1157
- return ()=>window.visualViewport?.removeEventListener("resize", onVisualViewportChange);
1158
- }, [
1159
- activeSnapPointIndex,
1160
- snapPoints,
1161
- snapPointsOffset
1162
- ]);
1163
- function closeDrawer(fromWithin) {
1164
- cancelDrag();
1165
- onClose?.();
1166
- if (!fromWithin) {
1167
- setIsOpen(false);
1168
- }
1169
- setTimeout(()=>{
1170
- if (snapPoints) {
1171
- setActiveSnapPoint(snapPoints[0]);
1172
- }
1173
- }, TRANSITIONS.DURATION * 1000); // seconds to ms
1174
- }
1175
- function resetDrawer() {
1176
- if (!drawerRef.current) return;
1177
- const wrapper = document.querySelector("[data-vaul-drawer-wrapper]");
1178
- const currentSwipeAmount = getTranslate(drawerRef.current, direction);
1179
- set(drawerRef.current, {
1180
- transform: "translate3d(0, 0, 0)",
1181
- transition: `transform ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`
1182
- });
1183
- set(overlayRef.current, {
1184
- transition: `opacity ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`,
1185
- opacity: "1"
1186
- });
1187
- // Don't reset background if swiped upwards
1188
- if (shouldScaleBackground && currentSwipeAmount && currentSwipeAmount > 0 && isOpen) {
1189
- set(wrapper, {
1190
- borderRadius: `${BORDER_RADIUS}px`,
1191
- overflow: "hidden",
1192
- ...isVertical(direction) ? {
1193
- transform: `scale(${getScale()}) translate3d(0, calc(env(safe-area-inset-top) + 14px), 0)`,
1194
- transformOrigin: "top"
1195
- } : {
1196
- transform: `scale(${getScale()}) translate3d(calc(env(safe-area-inset-top) + 14px), 0, 0)`,
1197
- transformOrigin: "left"
1198
- },
1199
- transitionProperty: "transform, border-radius",
1200
- transitionDuration: `${TRANSITIONS.DURATION}s`,
1201
- transitionTimingFunction: `cubic-bezier(${TRANSITIONS.EASE.join(",")})`
1202
- }, true);
1203
- }
1204
- }
1205
- function cancelDrag() {
1206
- if (!isDragging || !drawerRef.current) return;
1207
- drawerRef.current.classList.remove(DRAG_CLASS);
1208
- isAllowedToDrag.current = false;
1209
- setIsDragging(false);
1210
- dragEndTime.current = new Date();
1211
- }
1212
- function onRelease(event) {
1213
- if (!isDragging || !drawerRef.current) return;
1214
- drawerRef.current.classList.remove(DRAG_CLASS);
1215
- isAllowedToDrag.current = false;
1216
- setIsDragging(false);
1217
- dragEndTime.current = new Date();
1218
- const swipeAmount = getTranslate(drawerRef.current, direction);
1219
- if (!event || !shouldDrag(event.target, false) || !swipeAmount || Number.isNaN(swipeAmount)) return;
1220
- if (dragStartTime.current === null) return;
1221
- const timeTaken = dragEndTime.current.getTime() - dragStartTime.current.getTime();
1222
- const distMoved = pointerStart.current - (isVertical(direction) ? event.pageY : event.pageX);
1223
- const velocity = Math.abs(distMoved) / timeTaken;
1224
- if (velocity > 0.05) {
1225
- // `justReleased` is needed to prevent the drawer from focusing on an input when the drag ends, as it's not the intent most of the time.
1226
- setJustReleased(true);
1227
- setTimeout(()=>{
1228
- setJustReleased(false);
1229
- }, 200);
1230
- }
1231
- if (snapPoints) {
1232
- const directionMultiplier = direction === "bottom" || direction === "right" ? 1 : -1;
1233
- onReleaseSnapPoints({
1234
- draggedDistance: distMoved * directionMultiplier,
1235
- closeDrawer,
1236
- velocity,
1237
- dismissible
1238
- });
1239
- onReleaseProp?.(event, true);
1240
- return;
1241
- }
1242
- // Moved upwards, don't do anything
1243
- if (direction === "bottom" || direction === "right" ? distMoved > 0 : distMoved < 0) {
1244
- resetDrawer();
1245
- onReleaseProp?.(event, true);
1246
- return;
1247
- }
1248
- if (velocity > VELOCITY_THRESHOLD) {
1249
- closeDrawer();
1250
- onReleaseProp?.(event, false);
1251
- return;
1252
- }
1253
- const visibleDrawerHeight = Math.min(drawerRef.current.getBoundingClientRect().height ?? 0, window.innerHeight);
1254
- const visibleDrawerWidth = Math.min(drawerRef.current.getBoundingClientRect().width ?? 0, window.innerWidth);
1255
- const isHorizontalSwipe = direction === "left" || direction === "right";
1256
- if (Math.abs(swipeAmount) >= (isHorizontalSwipe ? visibleDrawerWidth : visibleDrawerHeight) * closeThreshold) {
1257
- closeDrawer();
1258
- onReleaseProp?.(event, false);
1259
- return;
1260
- }
1261
- onReleaseProp?.(event, true);
1262
- resetDrawer();
1263
- }
1264
- React__default.useEffect(()=>{
1265
- // Trigger enter animation without using CSS animation
1266
- if (isOpen) {
1267
- set(document.documentElement, {
1268
- scrollBehavior: "auto"
1269
- });
1270
- openTime.current = new Date();
1271
- }
1272
- return ()=>{
1273
- reset(document.documentElement, "scrollBehavior");
1274
- };
1275
- }, [
1276
- isOpen
1277
- ]);
1278
- function onNestedOpenChange(o) {
1279
- const scale = o ? (window.innerWidth - NESTED_DISPLACEMENT) / window.innerWidth : 1;
1280
- const initialTranslate = o ? -NESTED_DISPLACEMENT : 0;
1281
- if (nestedOpenChangeTimer.current) {
1282
- window.clearTimeout(nestedOpenChangeTimer.current);
1283
- }
1284
- set(drawerRef.current, {
1285
- transition: `transform ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`,
1286
- transform: isVertical(direction) ? `scale(${scale}) translate3d(0, ${initialTranslate}px, 0)` : `scale(${scale}) translate3d(${initialTranslate}px, 0, 0)`
1287
- });
1288
- if (!o && drawerRef.current) {
1289
- nestedOpenChangeTimer.current = setTimeout(()=>{
1290
- const translateValue = getTranslate(drawerRef.current, direction);
1291
- set(drawerRef.current, {
1292
- transition: "none",
1293
- transform: isVertical(direction) ? `translate3d(0, ${translateValue}px, 0)` : `translate3d(${translateValue}px, 0, 0)`
1294
- });
1295
- }, 500);
1296
- }
1297
- }
1298
- function onNestedDrag(_event, percentageDragged) {
1299
- if (percentageDragged < 0) return;
1300
- const initialScale = (window.innerWidth - NESTED_DISPLACEMENT) / window.innerWidth;
1301
- const newScale = initialScale + percentageDragged * (1 - initialScale);
1302
- const newTranslate = -NESTED_DISPLACEMENT + percentageDragged * NESTED_DISPLACEMENT;
1303
- set(drawerRef.current, {
1304
- transform: isVertical(direction) ? `scale(${newScale}) translate3d(0, ${newTranslate}px, 0)` : `scale(${newScale}) translate3d(${newTranslate}px, 0, 0)`,
1305
- transition: "none"
1306
- });
1307
- }
1308
- function onNestedRelease(_event, o) {
1309
- const dim = isVertical(direction) ? window.innerHeight : window.innerWidth;
1310
- const scale = o ? (dim - NESTED_DISPLACEMENT) / dim : 1;
1311
- const translate = o ? -NESTED_DISPLACEMENT : 0;
1312
- if (o) {
1313
- set(drawerRef.current, {
1314
- transition: `transform ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`,
1315
- transform: isVertical(direction) ? `scale(${scale}) translate3d(0, ${translate}px, 0)` : `scale(${scale}) translate3d(${translate}px, 0, 0)`
1316
- });
1317
- }
1318
- }
1319
- React__default.useEffect(()=>{
1320
- if (!modal) {
1321
- // Need to do this manually unfortunately
1322
- window.requestAnimationFrame(()=>{
1323
- document.body.style.pointerEvents = "auto";
1324
- });
1325
- }
1326
- }, [
1327
- modal
1328
- ]);
1329
- return /*#__PURE__*/ jsx(Dialog.Root, {
1330
- defaultOpen: defaultOpen,
1331
- onOpenChange: (open)=>{
1332
- if (!dismissible && !open) return;
1333
- if (open) {
1334
- setHasBeenOpened(true);
1335
- } else {
1336
- closeDrawer(true);
1337
- }
1338
- setIsOpen(open);
1339
- },
1340
- open: isOpen,
1341
- modal: modal,
1342
- children: /*#__PURE__*/ jsx(DrawerContext.Provider, {
1343
- value: {
1344
- activeSnapPoint,
1345
- snapPoints,
1346
- setActiveSnapPoint,
1347
- drawerRef,
1348
- overlayRef,
1349
- onOpenChange,
1350
- onPress,
1351
- onRelease,
1352
- onDrag,
1353
- dismissible,
1354
- shouldAnimate,
1355
- handleOnly,
1356
- isOpen,
1357
- isDragging,
1358
- shouldFade,
1359
- closeDrawer,
1360
- onNestedDrag,
1361
- onNestedOpenChange,
1362
- onNestedRelease,
1363
- keyboardIsOpen,
1364
- modal,
1365
- snapPointsOffset,
1366
- activeSnapPointIndex,
1367
- direction,
1368
- shouldScaleBackground,
1369
- setBackgroundColorOnScale,
1370
- noBodyStyles,
1371
- container,
1372
- autoFocus
1373
- },
1374
- children: children
1375
- })
1376
- });
667
+ const [isOpen = false, setIsOpen] = useControllableState({
668
+ defaultProp: defaultOpen,
669
+ prop: openProp,
670
+ onChange: (o) => {
671
+ onOpenChange?.(o);
672
+ if (!o && !nested) restorePositionSetting();
673
+ setTimeout(() => {
674
+ onAnimationEnd?.(o);
675
+ }, TRANSITIONS.DURATION * 1e3);
676
+ if (o && !modal) {
677
+ if (typeof window !== "undefined") window.requestAnimationFrame(() => {
678
+ document.body.style.pointerEvents = "auto";
679
+ });
680
+ }
681
+ if (!o) document.body.style.pointerEvents = "auto";
682
+ }
683
+ });
684
+ const [hasBeenOpened, setHasBeenOpened] = React.useState(false);
685
+ const [isDragging, setIsDragging] = React.useState(false);
686
+ const [justReleased, setJustReleased] = React.useState(false);
687
+ const overlayRef = React.useRef(null);
688
+ const openTime = React.useRef(null);
689
+ const dragStartTime = React.useRef(null);
690
+ const dragEndTime = React.useRef(null);
691
+ const lastTimeDragPrevented = React.useRef(null);
692
+ const isAllowedToDrag = React.useRef(false);
693
+ const nestedOpenChangeTimer = React.useRef(null);
694
+ const pointerStart = React.useRef(0);
695
+ const keyboardIsOpen = React.useRef(false);
696
+ const shouldAnimate = React.useRef(!defaultOpen);
697
+ const previousDiffFromInitial = React.useRef(0);
698
+ const drawerRef = React.useRef(null);
699
+ const drawerHeightRef = React.useRef(drawerRef.current?.getBoundingClientRect().height || 0);
700
+ const drawerWidthRef = React.useRef(drawerRef.current?.getBoundingClientRect().width || 0);
701
+ const initialDrawerHeight = React.useRef(0);
702
+ const { activeSnapPoint, activeSnapPointIndex, setActiveSnapPoint, onRelease: onReleaseSnapPoints, snapPointsOffset, onDrag: onDragSnapPoints, shouldFade, getPercentageDragged: getSnapPointsPercentageDragged } = useSnapPoints({
703
+ snapPoints,
704
+ activeSnapPointProp,
705
+ setActiveSnapPointProp,
706
+ drawerRef,
707
+ fadeFromIndex,
708
+ overlayRef,
709
+ onSnapPointChange: React.useCallback((activeSnapPointIndex$1) => {
710
+ if (snapPoints && activeSnapPointIndex$1 === snapPointsOffset.length - 1) openTime.current = /* @__PURE__ */ new Date();
711
+ }, []),
712
+ direction,
713
+ container,
714
+ snapToSequentialPoint
715
+ });
716
+ usePreventScroll({ isDisabled: !isOpen || isDragging || !modal || justReleased || !hasBeenOpened || !repositionInputs || !disablePreventScroll });
717
+ const { restorePositionSetting } = usePositionFixed({
718
+ isOpen,
719
+ modal,
720
+ nested: nested ?? false,
721
+ hasBeenOpened,
722
+ preventScrollRestoration,
723
+ noBodyStyles
724
+ });
725
+ function getScale() {
726
+ return (window.innerWidth - WINDOW_TOP_OFFSET) / window.innerWidth;
727
+ }
728
+ function onPress(event) {
729
+ if (!dismissible && !snapPoints) return;
730
+ if (drawerRef.current && !drawerRef.current.contains(event.target)) return;
731
+ drawerHeightRef.current = drawerRef.current?.getBoundingClientRect().height || 0;
732
+ drawerWidthRef.current = drawerRef.current?.getBoundingClientRect().width || 0;
733
+ setIsDragging(true);
734
+ dragStartTime.current = /* @__PURE__ */ new Date();
735
+ if (isIOS()) window.addEventListener("touchend", () => isAllowedToDrag.current = false, { once: true });
736
+ event.target.setPointerCapture(event.pointerId);
737
+ pointerStart.current = isVertical(direction) ? event.pageY : event.pageX;
738
+ }
739
+ function shouldDrag(el, isDraggingInDirection) {
740
+ let element = el;
741
+ const highlightedText = window.getSelection()?.toString();
742
+ const swipeAmount = drawerRef.current ? getTranslate(drawerRef.current, direction) : null;
743
+ const date = /* @__PURE__ */ new Date();
744
+ if (element.tagName === "SELECT") return false;
745
+ if (element.hasAttribute("data-vaul-no-drag") || element.closest("[data-vaul-no-drag]")) return false;
746
+ if (direction === "right" || direction === "left") return true;
747
+ if (openTime.current && date.getTime() - openTime.current.getTime() < 500) return false;
748
+ if (swipeAmount !== null) {
749
+ if (direction === "bottom" ? swipeAmount > 0 : swipeAmount < 0) return true;
750
+ }
751
+ if (highlightedText && highlightedText.length > 0) return false;
752
+ if (lastTimeDragPrevented.current && date.getTime() - lastTimeDragPrevented.current.getTime() < scrollLockTimeout && swipeAmount === 0) {
753
+ lastTimeDragPrevented.current = date;
754
+ return false;
755
+ }
756
+ if (isDraggingInDirection) {
757
+ lastTimeDragPrevented.current = date;
758
+ return false;
759
+ }
760
+ while (element) {
761
+ if (element.scrollHeight > element.clientHeight) {
762
+ if (element.scrollTop !== 0) {
763
+ lastTimeDragPrevented.current = /* @__PURE__ */ new Date();
764
+ return false;
765
+ }
766
+ if (element.getAttribute("role") === "dialog") return true;
767
+ }
768
+ element = element.parentNode;
769
+ }
770
+ return true;
771
+ }
772
+ function onDrag(event) {
773
+ if (!drawerRef.current) return;
774
+ if (isDragging) {
775
+ const directionMultiplier = direction === "bottom" || direction === "right" ? 1 : -1;
776
+ const draggedDistance = (pointerStart.current - (isVertical(direction) ? event.pageY : event.pageX)) * directionMultiplier;
777
+ const isDraggingInDirection = draggedDistance > 0;
778
+ const noCloseSnapPointsPreCondition = snapPoints && !dismissible && !isDraggingInDirection;
779
+ if (noCloseSnapPointsPreCondition && activeSnapPointIndex === 0) return;
780
+ const absDraggedDistance = Math.abs(draggedDistance);
781
+ const wrapper = document.querySelector("[data-vaul-drawer-wrapper]");
782
+ let percentageDragged = absDraggedDistance / (direction === "bottom" || direction === "top" ? drawerHeightRef.current : drawerWidthRef.current);
783
+ const snapPointPercentageDragged = getSnapPointsPercentageDragged(absDraggedDistance, isDraggingInDirection);
784
+ if (snapPointPercentageDragged !== null) percentageDragged = snapPointPercentageDragged;
785
+ if (noCloseSnapPointsPreCondition && percentageDragged >= 1) return;
786
+ if (!isAllowedToDrag.current && !shouldDrag(event.target, isDraggingInDirection)) return;
787
+ drawerRef.current.classList.add(DRAG_CLASS);
788
+ isAllowedToDrag.current = true;
789
+ set(drawerRef.current, { transition: "none" });
790
+ set(overlayRef.current, { transition: "none" });
791
+ if (snapPoints) onDragSnapPoints({ draggedDistance });
792
+ if (isDraggingInDirection && !snapPoints) {
793
+ const dampenedDraggedDistance = dampenValue(draggedDistance);
794
+ const translateValue = Math.min(dampenedDraggedDistance * -1, 0) * directionMultiplier;
795
+ set(drawerRef.current, { transform: isVertical(direction) ? `translate3d(0, ${translateValue}px, 0)` : `translate3d(${translateValue}px, 0, 0)` });
796
+ return;
797
+ }
798
+ const opacityValue = 1 - percentageDragged;
799
+ if (shouldFade || fadeFromIndex && activeSnapPointIndex === fadeFromIndex - 1) {
800
+ onDragProp?.(event, percentageDragged);
801
+ set(overlayRef.current, {
802
+ opacity: `${opacityValue}`,
803
+ transition: "none"
804
+ }, true);
805
+ }
806
+ if (wrapper && overlayRef.current && shouldScaleBackground) {
807
+ const scaleValue = Math.min(getScale() + percentageDragged * (1 - getScale()), 1);
808
+ const borderRadiusValue = 8 - percentageDragged * 8;
809
+ const translateValue = Math.max(0, 14 - percentageDragged * 14);
810
+ set(wrapper, {
811
+ borderRadius: `${borderRadiusValue}px`,
812
+ transform: isVertical(direction) ? `scale(${scaleValue}) translate3d(0, ${translateValue}px, 0)` : `scale(${scaleValue}) translate3d(${translateValue}px, 0, 0)`,
813
+ transition: "none"
814
+ }, true);
815
+ }
816
+ if (!snapPoints) {
817
+ const translateValue = absDraggedDistance * directionMultiplier;
818
+ set(drawerRef.current, { transform: isVertical(direction) ? `translate3d(0, ${translateValue}px, 0)` : `translate3d(${translateValue}px, 0, 0)` });
819
+ }
820
+ }
821
+ }
822
+ React.useEffect(() => {
823
+ window.requestAnimationFrame(() => {
824
+ shouldAnimate.current = true;
825
+ });
826
+ }, []);
827
+ React.useEffect(() => {
828
+ function onVisualViewportChange() {
829
+ if (!drawerRef.current || !repositionInputs) return;
830
+ const focusedElement = document.activeElement;
831
+ if (isInput(focusedElement) || keyboardIsOpen.current) {
832
+ const visualViewportHeight = window.visualViewport?.height || 0;
833
+ const totalHeight = window.innerHeight;
834
+ let diffFromInitial = totalHeight - visualViewportHeight;
835
+ const drawerHeight = drawerRef.current.getBoundingClientRect().height || 0;
836
+ const isTallEnough = drawerHeight > totalHeight * .8;
837
+ if (!initialDrawerHeight.current) initialDrawerHeight.current = drawerHeight;
838
+ const offsetFromTop = drawerRef.current.getBoundingClientRect().top;
839
+ if (Math.abs(previousDiffFromInitial.current - diffFromInitial) > 60) keyboardIsOpen.current = !keyboardIsOpen.current;
840
+ if (snapPoints && snapPoints.length > 0 && snapPointsOffset && activeSnapPointIndex) {
841
+ const activeSnapPointHeight = snapPointsOffset[activeSnapPointIndex] || 0;
842
+ diffFromInitial += activeSnapPointHeight;
843
+ }
844
+ previousDiffFromInitial.current = diffFromInitial;
845
+ if (drawerHeight > visualViewportHeight || keyboardIsOpen.current) {
846
+ const height = drawerRef.current.getBoundingClientRect().height;
847
+ let newDrawerHeight = height;
848
+ if (height > visualViewportHeight) newDrawerHeight = visualViewportHeight - (isTallEnough ? offsetFromTop : WINDOW_TOP_OFFSET);
849
+ if (fixed) drawerRef.current.style.height = `${height - Math.max(diffFromInitial, 0)}px`;
850
+ else drawerRef.current.style.height = `${Math.max(newDrawerHeight, visualViewportHeight - offsetFromTop)}px`;
851
+ } else if (!isMobileFirefox()) drawerRef.current.style.height = `${initialDrawerHeight.current}px`;
852
+ if (snapPoints && snapPoints.length > 0 && !keyboardIsOpen.current) drawerRef.current.style.bottom = `0px`;
853
+ else drawerRef.current.style.bottom = `${Math.max(diffFromInitial, 0)}px`;
854
+ }
855
+ }
856
+ window.visualViewport?.addEventListener("resize", onVisualViewportChange);
857
+ return () => window.visualViewport?.removeEventListener("resize", onVisualViewportChange);
858
+ }, [
859
+ activeSnapPointIndex,
860
+ snapPoints,
861
+ snapPointsOffset
862
+ ]);
863
+ function closeDrawer(fromWithin) {
864
+ cancelDrag();
865
+ onClose?.();
866
+ if (!fromWithin) setIsOpen(false);
867
+ setTimeout(() => {
868
+ if (snapPoints) setActiveSnapPoint(snapPoints[0]);
869
+ }, TRANSITIONS.DURATION * 1e3);
870
+ }
871
+ function resetDrawer() {
872
+ if (!drawerRef.current) return;
873
+ const wrapper = document.querySelector("[data-vaul-drawer-wrapper]");
874
+ const currentSwipeAmount = getTranslate(drawerRef.current, direction);
875
+ set(drawerRef.current, {
876
+ transform: "translate3d(0, 0, 0)",
877
+ transition: `transform ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`
878
+ });
879
+ set(overlayRef.current, {
880
+ transition: `opacity ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`,
881
+ opacity: "1"
882
+ });
883
+ if (shouldScaleBackground && currentSwipeAmount && currentSwipeAmount > 0 && isOpen) set(wrapper, {
884
+ borderRadius: `${BORDER_RADIUS}px`,
885
+ overflow: "hidden",
886
+ ...isVertical(direction) ? {
887
+ transform: `scale(${getScale()}) translate3d(0, calc(env(safe-area-inset-top) + 14px), 0)`,
888
+ transformOrigin: "top"
889
+ } : {
890
+ transform: `scale(${getScale()}) translate3d(calc(env(safe-area-inset-top) + 14px), 0, 0)`,
891
+ transformOrigin: "left"
892
+ },
893
+ transitionProperty: "transform, border-radius",
894
+ transitionDuration: `${TRANSITIONS.DURATION}s`,
895
+ transitionTimingFunction: `cubic-bezier(${TRANSITIONS.EASE.join(",")})`
896
+ }, true);
897
+ }
898
+ function cancelDrag() {
899
+ if (!isDragging || !drawerRef.current) return;
900
+ drawerRef.current.classList.remove(DRAG_CLASS);
901
+ isAllowedToDrag.current = false;
902
+ setIsDragging(false);
903
+ dragEndTime.current = /* @__PURE__ */ new Date();
904
+ }
905
+ function onRelease(event) {
906
+ if (!isDragging || !drawerRef.current) return;
907
+ drawerRef.current.classList.remove(DRAG_CLASS);
908
+ isAllowedToDrag.current = false;
909
+ setIsDragging(false);
910
+ dragEndTime.current = /* @__PURE__ */ new Date();
911
+ const swipeAmount = getTranslate(drawerRef.current, direction);
912
+ if (!event || !shouldDrag(event.target, false) || !swipeAmount || Number.isNaN(swipeAmount)) return;
913
+ if (dragStartTime.current === null) return;
914
+ const timeTaken = dragEndTime.current.getTime() - dragStartTime.current.getTime();
915
+ const distMoved = pointerStart.current - (isVertical(direction) ? event.pageY : event.pageX);
916
+ const velocity = Math.abs(distMoved) / timeTaken;
917
+ if (velocity > .05) {
918
+ setJustReleased(true);
919
+ setTimeout(() => {
920
+ setJustReleased(false);
921
+ }, 200);
922
+ }
923
+ if (snapPoints) {
924
+ onReleaseSnapPoints({
925
+ draggedDistance: distMoved * (direction === "bottom" || direction === "right" ? 1 : -1),
926
+ closeDrawer,
927
+ velocity,
928
+ dismissible
929
+ });
930
+ onReleaseProp?.(event, true);
931
+ return;
932
+ }
933
+ if (direction === "bottom" || direction === "right" ? distMoved > 0 : distMoved < 0) {
934
+ resetDrawer();
935
+ onReleaseProp?.(event, true);
936
+ return;
937
+ }
938
+ if (velocity > VELOCITY_THRESHOLD) {
939
+ closeDrawer();
940
+ onReleaseProp?.(event, false);
941
+ return;
942
+ }
943
+ const visibleDrawerHeight = Math.min(drawerRef.current.getBoundingClientRect().height ?? 0, window.innerHeight);
944
+ const visibleDrawerWidth = Math.min(drawerRef.current.getBoundingClientRect().width ?? 0, window.innerWidth);
945
+ const isHorizontalSwipe = direction === "left" || direction === "right";
946
+ if (Math.abs(swipeAmount) >= (isHorizontalSwipe ? visibleDrawerWidth : visibleDrawerHeight) * closeThreshold) {
947
+ closeDrawer();
948
+ onReleaseProp?.(event, false);
949
+ return;
950
+ }
951
+ onReleaseProp?.(event, true);
952
+ resetDrawer();
953
+ }
954
+ React.useEffect(() => {
955
+ if (isOpen) {
956
+ set(document.documentElement, { scrollBehavior: "auto" });
957
+ openTime.current = /* @__PURE__ */ new Date();
958
+ }
959
+ return () => {
960
+ reset(document.documentElement, "scrollBehavior");
961
+ };
962
+ }, [isOpen]);
963
+ function onNestedOpenChange(o) {
964
+ const scale = o ? (window.innerWidth - NESTED_DISPLACEMENT) / window.innerWidth : 1;
965
+ const initialTranslate = o ? -NESTED_DISPLACEMENT : 0;
966
+ if (nestedOpenChangeTimer.current) window.clearTimeout(nestedOpenChangeTimer.current);
967
+ set(drawerRef.current, {
968
+ transition: `transform ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`,
969
+ transform: isVertical(direction) ? `scale(${scale}) translate3d(0, ${initialTranslate}px, 0)` : `scale(${scale}) translate3d(${initialTranslate}px, 0, 0)`
970
+ });
971
+ if (!o && drawerRef.current) nestedOpenChangeTimer.current = setTimeout(() => {
972
+ const translateValue = getTranslate(drawerRef.current, direction);
973
+ set(drawerRef.current, {
974
+ transition: "none",
975
+ transform: isVertical(direction) ? `translate3d(0, ${translateValue}px, 0)` : `translate3d(${translateValue}px, 0, 0)`
976
+ });
977
+ }, 500);
978
+ }
979
+ function onNestedDrag(_event, percentageDragged) {
980
+ if (percentageDragged < 0) return;
981
+ const initialScale = (window.innerWidth - NESTED_DISPLACEMENT) / window.innerWidth;
982
+ const newScale = initialScale + percentageDragged * (1 - initialScale);
983
+ const newTranslate = -NESTED_DISPLACEMENT + percentageDragged * NESTED_DISPLACEMENT;
984
+ set(drawerRef.current, {
985
+ transform: isVertical(direction) ? `scale(${newScale}) translate3d(0, ${newTranslate}px, 0)` : `scale(${newScale}) translate3d(${newTranslate}px, 0, 0)`,
986
+ transition: "none"
987
+ });
988
+ }
989
+ function onNestedRelease(_event, o) {
990
+ const dim = isVertical(direction) ? window.innerHeight : window.innerWidth;
991
+ const scale = o ? (dim - NESTED_DISPLACEMENT) / dim : 1;
992
+ const translate = o ? -NESTED_DISPLACEMENT : 0;
993
+ if (o) set(drawerRef.current, {
994
+ transition: `transform ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`,
995
+ transform: isVertical(direction) ? `scale(${scale}) translate3d(0, ${translate}px, 0)` : `scale(${scale}) translate3d(${translate}px, 0, 0)`
996
+ });
997
+ }
998
+ React.useEffect(() => {
999
+ if (!modal) window.requestAnimationFrame(() => {
1000
+ document.body.style.pointerEvents = "auto";
1001
+ });
1002
+ }, [modal]);
1003
+ return /* @__PURE__ */ jsx(Dialog.Root, {
1004
+ defaultOpen,
1005
+ onOpenChange: (open) => {
1006
+ if (!dismissible && !open) return;
1007
+ if (open) setHasBeenOpened(true);
1008
+ else closeDrawer(true);
1009
+ setIsOpen(open);
1010
+ },
1011
+ open: isOpen,
1012
+ modal,
1013
+ children: /* @__PURE__ */ jsx(DrawerContext.Provider, {
1014
+ value: {
1015
+ activeSnapPoint,
1016
+ snapPoints,
1017
+ setActiveSnapPoint,
1018
+ drawerRef,
1019
+ overlayRef,
1020
+ onOpenChange,
1021
+ onPress,
1022
+ onRelease,
1023
+ onDrag,
1024
+ dismissible,
1025
+ shouldAnimate,
1026
+ handleOnly,
1027
+ isOpen,
1028
+ isDragging,
1029
+ shouldFade,
1030
+ closeDrawer,
1031
+ onNestedDrag,
1032
+ onNestedOpenChange,
1033
+ onNestedRelease,
1034
+ keyboardIsOpen,
1035
+ modal,
1036
+ snapPointsOffset,
1037
+ activeSnapPointIndex,
1038
+ direction,
1039
+ shouldScaleBackground,
1040
+ setBackgroundColorOnScale,
1041
+ noBodyStyles,
1042
+ container,
1043
+ autoFocus
1044
+ },
1045
+ children
1046
+ })
1047
+ });
1377
1048
  }
1378
- const Overlay = /*#__PURE__*/ React__default.forwardRef(function({ ...rest }, ref) {
1379
- const { overlayRef, snapPoints, onRelease, shouldFade, isOpen, modal, shouldAnimate } = useDrawerContext();
1380
- const composedRef = useComposedRefs(ref, overlayRef);
1381
- const hasSnapPoints = snapPoints && snapPoints.length > 0;
1382
- const onMouseUp = React__default.useCallback((event)=>onRelease(event), [
1383
- onRelease
1384
- ]);
1385
- // Overlay is the component that is locking scroll, removing it will unlock the scroll without having to dig into Radix's Dialog library
1386
- if (!modal) {
1387
- return null;
1388
- }
1389
- return /*#__PURE__*/ jsx(Dialog.Backdrop, {
1390
- onMouseUp: onMouseUp,
1391
- ref: composedRef,
1392
- "data-vaul-overlay": "",
1393
- "data-vaul-snap-points": isOpen && hasSnapPoints ? "true" : "false",
1394
- "data-vaul-snap-points-overlay": isOpen && shouldFade ? "true" : "false",
1395
- "data-vaul-animate": shouldAnimate?.current ? "true" : "false",
1396
- ...rest
1397
- });
1049
+ const Overlay = React.forwardRef(function({ ...rest }, ref) {
1050
+ const { overlayRef, snapPoints, onRelease, shouldFade, isOpen, modal, shouldAnimate } = useDrawerContext();
1051
+ const composedRef = useComposedRefs(ref, overlayRef);
1052
+ const hasSnapPoints = snapPoints && snapPoints.length > 0;
1053
+ const onMouseUp = React.useCallback((event) => onRelease(event), [onRelease]);
1054
+ if (!modal) return null;
1055
+ return /* @__PURE__ */ jsx(Dialog.Backdrop, {
1056
+ onMouseUp,
1057
+ ref: composedRef,
1058
+ "data-vaul-overlay": "",
1059
+ "data-vaul-snap-points": isOpen && hasSnapPoints ? "true" : "false",
1060
+ "data-vaul-snap-points-overlay": isOpen && shouldFade ? "true" : "false",
1061
+ "data-vaul-animate": shouldAnimate?.current ? "true" : "false",
1062
+ ...rest
1063
+ });
1398
1064
  });
1399
1065
  Overlay.displayName = "Drawer.Overlay";
1400
- const Content = /*#__PURE__*/ React__default.forwardRef(function({ style, ...rest }, ref) {
1401
- const { drawerRef, onPress, onRelease, onDrag, keyboardIsOpen, snapPointsOffset, activeSnapPointIndex, modal, isOpen, direction, snapPoints, container, handleOnly, shouldAnimate, autoFocus } = useDrawerContext();
1402
- // Needed to use transition instead of animations
1403
- const [delayedSnapPoints, setDelayedSnapPoints] = React__default.useState(false);
1404
- const composedRef = useComposedRefs(ref, drawerRef);
1405
- const pointerStartRef = React__default.useRef(null);
1406
- const lastKnownPointerEventRef = React__default.useRef(null);
1407
- const wasBeyondThePointRef = React__default.useRef(false);
1408
- const hasSnapPoints = snapPoints && snapPoints.length > 0;
1409
- useScaleBackground();
1410
- const isDeltaInDirection = (delta, direction, threshold = 0)=>{
1411
- if (wasBeyondThePointRef.current) return true;
1412
- const deltaY = Math.abs(delta.y);
1413
- const deltaX = Math.abs(delta.x);
1414
- const isDeltaX = deltaX > deltaY;
1415
- const dFactor = [
1416
- "bottom",
1417
- "right"
1418
- ].includes(direction) ? 1 : -1;
1419
- if (direction === "left" || direction === "right") {
1420
- const isReverseDirection = delta.x * dFactor < 0;
1421
- if (!isReverseDirection && deltaX >= 0 && deltaX <= threshold) {
1422
- return isDeltaX;
1423
- }
1424
- } else {
1425
- const isReverseDirection = delta.y * dFactor < 0;
1426
- if (!isReverseDirection && deltaY >= 0 && deltaY <= threshold) {
1427
- return !isDeltaX;
1428
- }
1429
- }
1430
- wasBeyondThePointRef.current = true;
1431
- return true;
1432
- };
1433
- React__default.useEffect(()=>{
1434
- if (hasSnapPoints) {
1435
- window.requestAnimationFrame(()=>{
1436
- setDelayedSnapPoints(true);
1437
- });
1438
- }
1439
- }, []);
1440
- function handleOnPointerUp(event) {
1441
- pointerStartRef.current = null;
1442
- wasBeyondThePointRef.current = false;
1443
- onRelease(event);
1444
- }
1445
- return /*#__PURE__*/ jsx(Dialog.Viewport, {
1446
- children: /*#__PURE__*/ jsx(Dialog.Popup, {
1447
- "data-vaul-drawer-direction": direction,
1448
- "data-vaul-drawer": "",
1449
- "data-vaul-delayed-snap-points": delayedSnapPoints ? "true" : "false",
1450
- "data-vaul-snap-points": isOpen && hasSnapPoints ? "true" : "false",
1451
- "data-vaul-custom-container": container ? "true" : "false",
1452
- "data-vaul-animate": shouldAnimate?.current ? "true" : "false",
1453
- ...rest,
1454
- ref: composedRef,
1455
- style: snapPointsOffset && snapPointsOffset.length > 0 ? {
1456
- // @ts-ignore This should not be an error
1457
- "--snap-point-height": `${snapPointsOffset[activeSnapPointIndex ?? 0]}px`,
1458
- ...style
1459
- } : style,
1460
- onPointerDown: (event)=>{
1461
- if (handleOnly) return;
1462
- rest.onPointerDown?.(event);
1463
- pointerStartRef.current = {
1464
- x: event.pageX,
1465
- y: event.pageY
1466
- };
1467
- onPress(event);
1468
- },
1469
- onPointerMove: (event)=>{
1470
- lastKnownPointerEventRef.current = event;
1471
- if (handleOnly) return;
1472
- rest.onPointerMove?.(event);
1473
- if (!pointerStartRef.current) return;
1474
- const yPosition = event.pageY - pointerStartRef.current.y;
1475
- const xPosition = event.pageX - pointerStartRef.current.x;
1476
- const swipeStartThreshold = event.pointerType === "touch" ? 10 : 2;
1477
- const delta = {
1478
- x: xPosition,
1479
- y: yPosition
1480
- };
1481
- const isAllowedToSwipe = isDeltaInDirection(delta, direction, swipeStartThreshold);
1482
- if (isAllowedToSwipe) onDrag(event);
1483
- else if (Math.abs(xPosition) > swipeStartThreshold || Math.abs(yPosition) > swipeStartThreshold) {
1484
- pointerStartRef.current = null;
1485
- }
1486
- },
1487
- onPointerUp: (event)=>{
1488
- rest.onPointerUp?.(event);
1489
- pointerStartRef.current = null;
1490
- wasBeyondThePointRef.current = false;
1491
- onRelease(event);
1492
- },
1493
- onPointerOut: (event)=>{
1494
- rest.onPointerOut?.(event);
1495
- handleOnPointerUp(lastKnownPointerEventRef.current);
1496
- },
1497
- onContextMenu: (event)=>{
1498
- rest.onContextMenu?.(event);
1499
- if (lastKnownPointerEventRef.current) {
1500
- handleOnPointerUp(lastKnownPointerEventRef.current);
1501
- }
1502
- }
1503
- })
1504
- });
1066
+ const Content = React.forwardRef(function({ style, ...rest }, ref) {
1067
+ const { drawerRef, onPress, onRelease, onDrag, keyboardIsOpen, snapPointsOffset, activeSnapPointIndex, modal, isOpen, direction, snapPoints, container, handleOnly, shouldAnimate, autoFocus } = useDrawerContext();
1068
+ const [delayedSnapPoints, setDelayedSnapPoints] = React.useState(false);
1069
+ const composedRef = useComposedRefs(ref, drawerRef);
1070
+ const pointerStartRef = React.useRef(null);
1071
+ const lastKnownPointerEventRef = React.useRef(null);
1072
+ const wasBeyondThePointRef = React.useRef(false);
1073
+ const hasSnapPoints = snapPoints && snapPoints.length > 0;
1074
+ useScaleBackground();
1075
+ const isDeltaInDirection = (delta, direction$1, threshold = 0) => {
1076
+ if (wasBeyondThePointRef.current) return true;
1077
+ const deltaY = Math.abs(delta.y);
1078
+ const deltaX = Math.abs(delta.x);
1079
+ const isDeltaX = deltaX > deltaY;
1080
+ const dFactor = ["bottom", "right"].includes(direction$1) ? 1 : -1;
1081
+ if (direction$1 === "left" || direction$1 === "right") {
1082
+ if (!(delta.x * dFactor < 0) && deltaX >= 0 && deltaX <= threshold) return isDeltaX;
1083
+ } else if (!(delta.y * dFactor < 0) && deltaY >= 0 && deltaY <= threshold) return !isDeltaX;
1084
+ wasBeyondThePointRef.current = true;
1085
+ return true;
1086
+ };
1087
+ React.useEffect(() => {
1088
+ if (hasSnapPoints) window.requestAnimationFrame(() => {
1089
+ setDelayedSnapPoints(true);
1090
+ });
1091
+ }, []);
1092
+ function handleOnPointerUp(event) {
1093
+ pointerStartRef.current = null;
1094
+ wasBeyondThePointRef.current = false;
1095
+ onRelease(event);
1096
+ }
1097
+ return /* @__PURE__ */ jsx(Dialog.Viewport, { children: /* @__PURE__ */ jsx(Dialog.Popup, {
1098
+ "data-vaul-drawer-direction": direction,
1099
+ "data-vaul-drawer": "",
1100
+ "data-vaul-delayed-snap-points": delayedSnapPoints ? "true" : "false",
1101
+ "data-vaul-snap-points": isOpen && hasSnapPoints ? "true" : "false",
1102
+ "data-vaul-custom-container": container ? "true" : "false",
1103
+ "data-vaul-animate": shouldAnimate?.current ? "true" : "false",
1104
+ ...rest,
1105
+ ref: composedRef,
1106
+ style: snapPointsOffset && snapPointsOffset.length > 0 ? {
1107
+ "--snap-point-height": `${snapPointsOffset[activeSnapPointIndex ?? 0]}px`,
1108
+ ...style
1109
+ } : style,
1110
+ onPointerDown: (event) => {
1111
+ if (handleOnly) return;
1112
+ rest.onPointerDown?.(event);
1113
+ pointerStartRef.current = {
1114
+ x: event.pageX,
1115
+ y: event.pageY
1116
+ };
1117
+ onPress(event);
1118
+ },
1119
+ onPointerMove: (event) => {
1120
+ lastKnownPointerEventRef.current = event;
1121
+ if (handleOnly) return;
1122
+ rest.onPointerMove?.(event);
1123
+ if (!pointerStartRef.current) return;
1124
+ const yPosition = event.pageY - pointerStartRef.current.y;
1125
+ const xPosition = event.pageX - pointerStartRef.current.x;
1126
+ const swipeStartThreshold = event.pointerType === "touch" ? 10 : 2;
1127
+ if (isDeltaInDirection({
1128
+ x: xPosition,
1129
+ y: yPosition
1130
+ }, direction, swipeStartThreshold)) onDrag(event);
1131
+ else if (Math.abs(xPosition) > swipeStartThreshold || Math.abs(yPosition) > swipeStartThreshold) pointerStartRef.current = null;
1132
+ },
1133
+ onPointerUp: (event) => {
1134
+ rest.onPointerUp?.(event);
1135
+ pointerStartRef.current = null;
1136
+ wasBeyondThePointRef.current = false;
1137
+ onRelease(event);
1138
+ },
1139
+ onPointerOut: (event) => {
1140
+ rest.onPointerOut?.(event);
1141
+ handleOnPointerUp(lastKnownPointerEventRef.current);
1142
+ },
1143
+ onContextMenu: (event) => {
1144
+ rest.onContextMenu?.(event);
1145
+ if (lastKnownPointerEventRef.current) handleOnPointerUp(lastKnownPointerEventRef.current);
1146
+ }
1147
+ }) });
1505
1148
  });
1506
1149
  Content.displayName = "Drawer.Content";
1507
1150
  const LONG_HANDLE_PRESS_TIMEOUT = 250;
1508
1151
  const DOUBLE_TAP_TIMEOUT = 120;
1509
- const Handle = /*#__PURE__*/ React__default.forwardRef(function({ preventCycle = false, children, ...rest }, ref) {
1510
- const { closeDrawer, isDragging, snapPoints, activeSnapPoint, setActiveSnapPoint, dismissible, handleOnly, isOpen, onPress, onDrag } = useDrawerContext();
1511
- const closeTimeoutIdRef = React__default.useRef(null);
1512
- const shouldCancelInteractionRef = React__default.useRef(false);
1513
- function handleStartCycle() {
1514
- // Stop if this is the second click of a double click
1515
- if (shouldCancelInteractionRef.current) {
1516
- handleCancelInteraction();
1517
- return;
1518
- }
1519
- window.setTimeout(()=>{
1520
- handleCycleSnapPoints();
1521
- }, DOUBLE_TAP_TIMEOUT);
1522
- }
1523
- function handleCycleSnapPoints() {
1524
- // Prevent accidental taps while resizing drawer
1525
- if (isDragging || preventCycle || shouldCancelInteractionRef.current) {
1526
- handleCancelInteraction();
1527
- return;
1528
- }
1529
- // Make sure to clear the timeout id if the user releases the handle before the cancel timeout
1530
- handleCancelInteraction();
1531
- if (!snapPoints || snapPoints.length === 0) {
1532
- if (!dismissible) {
1533
- closeDrawer();
1534
- }
1535
- return;
1536
- }
1537
- const isLastSnapPoint = activeSnapPoint === snapPoints[snapPoints.length - 1];
1538
- if (isLastSnapPoint && dismissible) {
1539
- closeDrawer();
1540
- return;
1541
- }
1542
- const currentSnapIndex = snapPoints.findIndex((point)=>point === activeSnapPoint);
1543
- if (currentSnapIndex === -1) return; // activeSnapPoint not found in snapPoints
1544
- const nextSnapPoint = snapPoints[currentSnapIndex + 1];
1545
- setActiveSnapPoint(nextSnapPoint);
1546
- }
1547
- function handleStartInteraction() {
1548
- closeTimeoutIdRef.current = window.setTimeout(()=>{
1549
- // Cancel click interaction on a long press
1550
- shouldCancelInteractionRef.current = true;
1551
- }, LONG_HANDLE_PRESS_TIMEOUT);
1552
- }
1553
- function handleCancelInteraction() {
1554
- if (closeTimeoutIdRef.current) {
1555
- window.clearTimeout(closeTimeoutIdRef.current);
1556
- }
1557
- shouldCancelInteractionRef.current = false;
1558
- }
1559
- return /*#__PURE__*/ jsx("div", {
1560
- onClick: handleStartCycle,
1561
- onPointerCancel: handleCancelInteraction,
1562
- onPointerDown: (e)=>{
1563
- if (handleOnly) onPress({
1564
- ...e,
1565
- preventBaseUIHandler: ()=>{}
1566
- });
1567
- handleStartInteraction();
1568
- },
1569
- onPointerMove: (e)=>{
1570
- if (handleOnly) onDrag({
1571
- ...e,
1572
- preventBaseUIHandler: ()=>{}
1573
- });
1574
- },
1575
- // onPointerUp is already handled by the content component
1576
- ref: ref,
1577
- "data-vaul-drawer-visible": isOpen ? "true" : "false",
1578
- "data-vaul-handle": "",
1579
- "aria-hidden": "true",
1580
- ...rest,
1581
- children: /*#__PURE__*/ jsx("span", {
1582
- "data-vaul-handle-hitarea": "",
1583
- "aria-hidden": "true",
1584
- children: children
1585
- })
1586
- });
1152
+ const Handle = React.forwardRef(function({ preventCycle = false, children, ...rest }, ref) {
1153
+ const { closeDrawer, isDragging, snapPoints, activeSnapPoint, setActiveSnapPoint, dismissible, handleOnly, isOpen, onPress, onDrag } = useDrawerContext();
1154
+ const closeTimeoutIdRef = React.useRef(null);
1155
+ const shouldCancelInteractionRef = React.useRef(false);
1156
+ function handleStartCycle() {
1157
+ if (shouldCancelInteractionRef.current) {
1158
+ handleCancelInteraction();
1159
+ return;
1160
+ }
1161
+ window.setTimeout(() => {
1162
+ handleCycleSnapPoints();
1163
+ }, DOUBLE_TAP_TIMEOUT);
1164
+ }
1165
+ function handleCycleSnapPoints() {
1166
+ if (isDragging || preventCycle || shouldCancelInteractionRef.current) {
1167
+ handleCancelInteraction();
1168
+ return;
1169
+ }
1170
+ handleCancelInteraction();
1171
+ if (!snapPoints || snapPoints.length === 0) {
1172
+ if (!dismissible) closeDrawer();
1173
+ return;
1174
+ }
1175
+ if (activeSnapPoint === snapPoints[snapPoints.length - 1] && dismissible) {
1176
+ closeDrawer();
1177
+ return;
1178
+ }
1179
+ const currentSnapIndex = snapPoints.findIndex((point) => point === activeSnapPoint);
1180
+ if (currentSnapIndex === -1) return;
1181
+ const nextSnapPoint = snapPoints[currentSnapIndex + 1];
1182
+ setActiveSnapPoint(nextSnapPoint);
1183
+ }
1184
+ function handleStartInteraction() {
1185
+ closeTimeoutIdRef.current = window.setTimeout(() => {
1186
+ shouldCancelInteractionRef.current = true;
1187
+ }, LONG_HANDLE_PRESS_TIMEOUT);
1188
+ }
1189
+ function handleCancelInteraction() {
1190
+ if (closeTimeoutIdRef.current) window.clearTimeout(closeTimeoutIdRef.current);
1191
+ shouldCancelInteractionRef.current = false;
1192
+ }
1193
+ return /* @__PURE__ */ jsx("div", {
1194
+ onClick: handleStartCycle,
1195
+ onPointerCancel: handleCancelInteraction,
1196
+ onPointerDown: (e) => {
1197
+ if (handleOnly) onPress({
1198
+ ...e,
1199
+ preventBaseUIHandler: () => {}
1200
+ });
1201
+ handleStartInteraction();
1202
+ },
1203
+ onPointerMove: (e) => {
1204
+ if (handleOnly) onDrag({
1205
+ ...e,
1206
+ preventBaseUIHandler: () => {}
1207
+ });
1208
+ },
1209
+ ref,
1210
+ "data-vaul-drawer-visible": isOpen ? "true" : "false",
1211
+ "data-vaul-handle": "",
1212
+ "aria-hidden": "true",
1213
+ ...rest,
1214
+ children: /* @__PURE__ */ jsx("span", {
1215
+ "data-vaul-handle-hitarea": "",
1216
+ "aria-hidden": "true",
1217
+ children
1218
+ })
1219
+ });
1587
1220
  });
1588
1221
  Handle.displayName = "Drawer.Handle";
1589
1222
  function NestedRoot({ onDrag, onOpenChange, open: nestedIsOpen, ...rest }) {
1590
- const { onNestedDrag, onNestedOpenChange, onNestedRelease } = useDrawerContext();
1591
- if (!onNestedDrag) {
1592
- throw new Error("Drawer.NestedRoot must be placed in another drawer");
1593
- }
1594
- return /*#__PURE__*/ jsx(Root, {
1595
- nested: true,
1596
- open: nestedIsOpen,
1597
- onClose: ()=>{
1598
- onNestedOpenChange(false);
1599
- },
1600
- onDrag: (e, p)=>{
1601
- onNestedDrag(e, p);
1602
- onDrag?.(e, p);
1603
- },
1604
- onOpenChange: (o)=>{
1605
- if (o) {
1606
- onNestedOpenChange(o);
1607
- }
1608
- onOpenChange?.(o);
1609
- },
1610
- onRelease: onNestedRelease,
1611
- ...rest
1612
- });
1223
+ const { onNestedDrag, onNestedOpenChange, onNestedRelease } = useDrawerContext();
1224
+ if (!onNestedDrag) throw new Error("Drawer.NestedRoot must be placed in another drawer");
1225
+ return /* @__PURE__ */ jsx(Root, {
1226
+ nested: true,
1227
+ open: nestedIsOpen,
1228
+ onClose: () => {
1229
+ onNestedOpenChange(false);
1230
+ },
1231
+ onDrag: (e, p) => {
1232
+ onNestedDrag(e, p);
1233
+ onDrag?.(e, p);
1234
+ },
1235
+ onOpenChange: (o) => {
1236
+ if (o) onNestedOpenChange(o);
1237
+ onOpenChange?.(o);
1238
+ },
1239
+ onRelease: onNestedRelease,
1240
+ ...rest
1241
+ });
1613
1242
  }
1614
1243
  function Portal(props) {
1615
- const context = useDrawerContext();
1616
- const { container = context.container, ...portalProps } = props;
1617
- return /*#__PURE__*/ jsx(Dialog.Portal, {
1618
- container: container,
1619
- ...portalProps
1620
- });
1244
+ const context = useDrawerContext();
1245
+ const { container = context.container, ...portalProps } = props;
1246
+ return /* @__PURE__ */ jsx(Dialog.Portal, {
1247
+ container,
1248
+ ...portalProps
1249
+ });
1621
1250
  }
1622
1251
  const Drawer = {
1623
- Root,
1624
- NestedRoot,
1625
- Content,
1626
- Overlay,
1627
- Trigger: Dialog.Trigger,
1628
- Portal,
1629
- Handle,
1630
- Close: Dialog.Close,
1631
- Title: Dialog.Title,
1632
- Description: Dialog.Description
1252
+ Root,
1253
+ NestedRoot,
1254
+ Content,
1255
+ Overlay,
1256
+ Trigger: Dialog.Trigger,
1257
+ Portal,
1258
+ Handle,
1259
+ Close: Dialog.Close,
1260
+ Title: Dialog.Title,
1261
+ Description: Dialog.Description
1633
1262
  };
1634
1263
 
1635
- export { Content, Drawer, Handle, NestedRoot, Overlay, Portal, Root };
1264
+ //#endregion
1265
+ export { Content, Drawer, Handle, NestedRoot, Overlay, Portal, Root };