Skip to content

Commit

Permalink
Fix drag issue when element is scrollable (#167)
Browse files Browse the repository at this point in the history
* Fix scrolling issue

* Refactor so that scroll is only disabled on scrollable elements

* Fix scorlling down

* Polish
  • Loading branch information
emilkowalski authored Nov 10, 2023
1 parent db6f58a commit 740160c
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 16 deletions.
44 changes: 29 additions & 15 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@ 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;

const NESTED_DISPLACEMENT = 16;

const WINDOW_TOP_OFFSET = 26;

const DRAG_CLASS = 'vaul-dragging';

interface WithFadeFromProps {
snapPoints: (number | string)[];
fadeFromIndex: number;
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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
Expand All @@ -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, {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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]');

Expand Down
4 changes: 4 additions & 0 deletions src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion test/src/app/scrollable-with-inputs/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default function Page() {
<Drawer.Portal>
<Drawer.Overlay className="fixed inset-0 bg-black/40" />
<Drawer.Content className="bg-white flex flex-col fixed bottom-0 left-0 right-0 max-h-[82vh] rounded-t-[10px]">
<div className="max-w-md w-full mx-auto flex flex-col overflow-auto p-4 rounded-t-[10px]">
<div className="max-w-md w-full mx-auto overflow-auto p-4 rounded-t-[10px]">
<input className="border border-gray-400 my-8" placeholder="Input" />
<p>
But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born
Expand Down

0 comments on commit 740160c

Please sign in to comment.