diff --git a/src/index.tsx b/src/index.tsx index e043d470..d30f76cf 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -13,7 +13,7 @@ import { TRANSITIONS, VELOCITY_THRESHOLD } from './constants'; const CLOSE_THRESHOLD = 0.25; -const SCROLL_LOCK_TIMEOUT = 500; +const SCROLL_LOCK_TIMEOUT = 100; const BORDER_RADIUS = 8; @@ -21,6 +21,8 @@ const NESTED_DISPLACEMENT = 16; const WINDOW_TOP_OFFSET = 26; +const DRAG_CLASS = 'vaul-dragging'; + interface WithFadeFromProps { snapPoints: (number | string)[]; fadeFromIndex: number; @@ -139,7 +141,6 @@ function Root({ if (isIOS()) { window.addEventListener('touchend', () => (isAllowedToDrag.current = false), { once: true }); } - // Ensure we maintain correct pointer capture even when going outside of the drawer (event.target as HTMLElement).setPointerCapture(event.pointerId); @@ -176,27 +177,27 @@ function Root({ return false; } + if (isDraggingDown) { + lastTimeDragPrevented.current = new Date(); + + // We are dragging down so we should allow scrolling + return false; + } + // Keep climbing up the DOM tree as long as there's a parent while (element) { // Check if the element is scrollable if (element.scrollHeight > element.clientHeight) { - if (element.getAttribute('role') === 'dialog') { - return true; - } - - if (isDraggingDown && element !== document.body && !swipeAmount && swipeAmount >= 0) { - lastTimeDragPrevented.current = new Date(); - - // Element is scrolled to the top, but we are dragging down so we should allow scrolling - return false; - } - if (element.scrollTop !== 0) { lastTimeDragPrevented.current = new Date(); // The element is scrollable and not scrolled to the top, so don't drag return false; } + + if (element.getAttribute('role') === 'dialog') { + return true; + } } // Move up to the parent element @@ -217,7 +218,7 @@ function Root({ if (snapPoints && activeSnapPointIndex === 0 && !dismissible) return; if (!isAllowedToDrag.current && !shouldDrag(event.target, isDraggingDown)) return; - + drawerRef.current.classList.add(DRAG_CLASS); // 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 isAllowedToDrag.current = true; set(drawerRef.current, { @@ -459,7 +460,7 @@ function Root({ // If we were just dragging, prevent focusing on inputs etc. on release (event.target as HTMLInputElement).blur(); } - + drawerRef.current.classList.remove(DRAG_CLASS); isAllowedToDrag.current = false; setIsDragging(false); dragEndTime.current = new Date(); @@ -526,6 +527,19 @@ function Root({ } }, [isOpen]); + React.useEffect(() => { + if (visible && visible) { + // Find all scrollable elements inside our drawer and assign a class to it so that we can disable overflow when dragging to prevent pointermove not being captured + const children = drawerRef.current.querySelectorAll('*'); + children.forEach((child: Element) => { + const htmlChild = child as HTMLElement; + if (htmlChild.scrollHeight > htmlChild.clientHeight || htmlChild.scrollWidth > htmlChild.clientWidth) { + htmlChild.classList.add('vaul-scrollable'); + } + }); + } + }, [visible]); + function scaleBackground(open: boolean) { const wrapper = document.querySelector('[vaul-drawer-wrapper]'); diff --git a/src/style.css b/src/style.css index 1adee655..336ed701 100644 --- a/src/style.css +++ b/src/style.css @@ -4,6 +4,10 @@ transition: transform 0.5s cubic-bezier(0.32, 0.72, 0, 1); } +.vaul-dragging .vaul-scrollable { + overflow-y: hidden !important; +} + [vaul-drawer][vaul-drawer-visible='true'] { transform: translate3d(0, var(--snap-point-height, 0), 0); } diff --git a/test/src/app/scrollable-with-inputs/page.tsx b/test/src/app/scrollable-with-inputs/page.tsx index a3704b93..a56faab4 100644 --- a/test/src/app/scrollable-with-inputs/page.tsx +++ b/test/src/app/scrollable-with-inputs/page.tsx @@ -12,7 +12,7 @@ export default function Page() { -
+

But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born