Skip to content

Commit da8343c

Browse files
authored
fix: scrollable content drag on touch devices (#38)
1 parent 9263b5b commit da8343c

File tree

6 files changed

+61
-34
lines changed

6 files changed

+61
-34
lines changed

.changeset/young-readers-scream.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"vaul-svelte": patch
3+
---
4+
5+
fix: dragging scrollable content on touch devices

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,6 @@
7171
"types": "./dist/index.d.ts",
7272
"type": "module",
7373
"dependencies": {
74-
"bits-ui": "^0.13.3"
74+
"bits-ui": "^0.16.0"
7575
}
7676
}

pnpm-lock.yaml

+8-14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/lib/internal/helpers/style.ts

-4
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@ const cache = new WeakMap();
77
export function set(el?: Element | HTMLElement | null, styles?: Style, ignoreCache = false) {
88
if (!el || !(el instanceof HTMLElement) || !styles) return;
99

10-
// if (el.hasAttribute('data-vaul-drawer')) {
11-
// console.log(styles);
12-
// }
13-
1410
const originalStyles: Style = {};
1511

1612
Object.entries(styles).forEach(([key, value]: [string, string]) => {

src/lib/internal/vaul.ts

+31-12
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,14 @@ export type CreateVaulProps = {
5757
scrollLockTimeout?: number;
5858
fixed?: boolean;
5959
dismissible?: boolean;
60-
onDrag?: (event: SvelteEvent<PointerEvent, HTMLElement>, percentageDragged: number) => void;
61-
onRelease?: (event: SvelteEvent<PointerEvent | MouseEvent, HTMLElement>, open: boolean) => void;
60+
onDrag?: (
61+
event: SvelteEvent<PointerEvent | TouchEvent, HTMLElement>,
62+
percentageDragged: number
63+
) => void;
64+
onRelease?: (
65+
event: SvelteEvent<PointerEvent | MouseEvent | TouchEvent, HTMLElement>,
66+
open: boolean
67+
) => void;
6268
modal?: boolean;
6369
nested?: boolean;
6470
onClose?: () => void;
@@ -342,21 +348,19 @@ export function createVaul(props: CreateVaulProps) {
342348
return true;
343349
}
344350

345-
function onDrag(event: SvelteEvent<PointerEvent, HTMLElement>) {
351+
function onDrag(event: SvelteEvent<PointerEvent | TouchEvent, HTMLElement>) {
346352
if (!isDragging) return;
347353
// We need to know how much of the drawer has been dragged in percentages so that we can transform background accordingly
348-
const draggedDistance = pointerStartY - event.screenY;
354+
const draggedDistance = getDistanceMoved(pointerStartY, event);
349355
const isDraggingDown = draggedDistance > 0;
350356

351357
const $activeSnapPointIndex = get(activeSnapPointIndex);
352358
const $snapPoints = get(snapPoints);
353359

354360
// Disallow dragging down to close when first snap point is the active one and dismissible prop is set to false.
355361
if ($snapPoints && $activeSnapPointIndex === 0 && !get(dismissible)) return;
362+
if (!isAllowedToDrag && !shouldDrag(event.target as HTMLElement, isDraggingDown)) return;
356363

357-
if (!isAllowedToDrag && !shouldDrag(event.target as HTMLElement, isDraggingDown)) {
358-
return;
359-
}
360364
const $drawerRef = get(drawerRef);
361365
if (!$drawerRef) return;
362366

@@ -390,7 +394,7 @@ export function createVaul(props: CreateVaulProps) {
390394

391395
// We need to capture last time when drag with scroll was triggered and have a timeout between
392396
const absDraggedDistance = Math.abs(draggedDistance);
393-
const wrapper = document.querySelector("[data-vaul-drawer-wrapper]");
397+
394398
let percentageDragged = absDraggedDistance / drawerHeightRef;
395399
const snapPointPercentageDragged = getSnapPointsPercentageDragged(
396400
absDraggedDistance,
@@ -418,6 +422,7 @@ export function createVaul(props: CreateVaulProps) {
418422
true
419423
);
420424
}
425+
const wrapper = document.querySelector("[data-vaul-drawer-wrapper]");
421426

422427
if (wrapper && $overlayRef && get(shouldScaleBackground)) {
423428
// Calculate percentageDragged as a fraction (0 to 1)
@@ -555,6 +560,7 @@ export function createVaul(props: CreateVaulProps) {
555560

556561
function closeDrawer(withKeyboard: boolean = false) {
557562
if (isClosing) return;
563+
558564
const $drawerRef = get(drawerRef);
559565
if (!$drawerRef) return;
560566

@@ -638,7 +644,7 @@ export function createVaul(props: CreateVaulProps) {
638644
}
639645
}
640646

641-
function onRelease(event: SvelteEvent<PointerEvent | MouseEvent, HTMLElement>) {
647+
function onRelease(event: SvelteEvent<PointerEvent | MouseEvent | TouchEvent, HTMLElement>) {
642648
const $drawerRef = get(drawerRef);
643649
if (!isDragging || !$drawerRef) return;
644650

@@ -664,7 +670,7 @@ export function createVaul(props: CreateVaulProps) {
664670
if (dragStartTime === null) return;
665671

666672
const timeTaken = dragEndTime.getTime() - dragStartTime.getTime();
667-
const distMoved = pointerStartY - event.screenY;
673+
const distMoved = getDistanceMoved(pointerStartY, event);
668674
const velocity = Math.abs(distMoved) / timeTaken;
669675

670676
if (velocity > 0.05) {
@@ -766,7 +772,7 @@ export function createVaul(props: CreateVaulProps) {
766772
}
767773

768774
function onNestedDrag(
769-
_: SvelteEvent<PointerEvent | MouseEvent, HTMLElement>,
775+
_: SvelteEvent<PointerEvent | MouseEvent | TouchEvent, HTMLElement>,
770776
percentageDragged: number
771777
) {
772778
if (percentageDragged < 0) return;
@@ -780,7 +786,10 @@ export function createVaul(props: CreateVaulProps) {
780786
});
781787
}
782788

783-
function onNestedRelease(_: SvelteEvent<PointerEvent | MouseEvent, HTMLElement>, o: boolean) {
789+
function onNestedRelease(
790+
_: SvelteEvent<PointerEvent | MouseEvent | TouchEvent, HTMLElement>,
791+
o: boolean
792+
) {
784793
const scale = o ? (window.innerWidth - NESTED_DISPLACEMENT) / window.innerWidth : 1;
785794
const y = o ? -NESTED_DISPLACEMENT : 0;
786795

@@ -839,3 +848,13 @@ export function dampenValue(v: number) {
839848
function getScale() {
840849
return (window.innerWidth - WINDOW_TOP_OFFSET) / window.innerWidth;
841850
}
851+
852+
function getDistanceMoved(
853+
pointerStartY: number,
854+
event: SvelteEvent<PointerEvent | MouseEvent | TouchEvent, HTMLElement>
855+
) {
856+
if (event instanceof TouchEvent) {
857+
return pointerStartY - event.changedTouches[0].screenY;
858+
}
859+
return pointerStartY - event.screenY;
860+
}

src/lib/vaul/components/content.svelte

+16-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import Visible from "./visible.svelte";
66
77
type $$Props = ContentProps;
8+
type $$Events = DialogPrimitive.ContentEvents;
89
910
const {
1011
refs: { drawerRef },
@@ -19,9 +20,21 @@
1920
<DialogPrimitive.Content
2021
bind:el={$drawerRef}
2122
style={$getContentStyle(style)}
22-
on:pointermove={onDrag}
23-
on:pointerdown={onPress}
24-
on:pointerup={onRelease}
23+
on:pointerdown={(e) => {
24+
onPress(e);
25+
}}
26+
on:pointerup={(e) => {
27+
onRelease(e);
28+
}}
29+
on:pointermove={(e) => {
30+
onDrag(e);
31+
}}
32+
on:touchend={(e) => {
33+
onRelease(e);
34+
}}
35+
on:touchmove={(e) => {
36+
onDrag(e);
37+
}}
2538
data-vaul-drawer=""
2639
data-vaul-drawer-visible={$visible ? "true" : "false"}
2740
{...$$restProps}

0 commit comments

Comments
 (0)