Skip to content

Commit c207e19

Browse files
authored
feat: Support multiple directions (#40)
1 parent 31f6b53 commit c207e19

23 files changed

+427
-137
lines changed

.changeset/blue-carrots-repeat.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"vaul-svelte": minor
3+
---
4+
5+
feat: add support for directions "top", "left", "bottom", and "right"

.changeset/happy-jokes-sort.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"vaul-svelte": patch
3+
---
4+
5+
Fix scroll top with smooth scroll

.changeset/popular-jobs-push.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"vaul-svelte": patch
3+
---
4+
5+
fix: snapPoints onRelease position

.changeset/selfish-chicken-speak.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"vaul-svelte": patch
3+
---
4+
5+
allow snapping to first snap point

.changeset/soft-flowers-wash.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"vaul-svelte": patch
3+
---
4+
5+
fix: prevent drawer from exceeding boundaries of last snap point while dragging

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ Additional props:
5858

5959
`fadeFromIndex`: Index of a `snapPoint` from which the overlay fade should be applied. Defaults to the last snap point.
6060

61+
`direction`: Direction of the drawer. Can be `top`, `bottom`, `left`, or `right`. Defaults to `bottom`.
62+
6163
`backgroundColor`: Background color of the body when the drawer is open and `shouldScaleBackground` is true. Defaults to black.
6264

6365
### Trigger

src/lib/internal/helpers/is.ts

+12
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { DrawerDirection } from "../types.js";
2+
13
// HTML input types that do not cause the software keyboard to appear.
24
const nonTextInputTypes = new Set([
35
"checkbox",
@@ -25,3 +27,13 @@ export function isInput(target: Element) {
2527
(target instanceof HTMLElement && target.isContentEditable)
2628
);
2729
}
30+
31+
export function isVertical(direction: DrawerDirection) {
32+
if (direction === "top" || direction === "bottom") return true;
33+
return false;
34+
}
35+
36+
export function isBottomOrRight(direction: DrawerDirection) {
37+
if (direction === "bottom" || direction === "right") return true;
38+
return false;
39+
}

src/lib/internal/helpers/style.ts

+11-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import type { DrawerDirection } from "../types.js";
2+
import { isVertical } from "./index.js";
3+
14
interface Style {
25
[key: string]: string;
36
}
@@ -45,15 +48,19 @@ export function reset(el: Element | HTMLElement | null, prop?: string) {
4548
}
4649
}
4750

48-
export function getTranslateY(element: HTMLElement): number | null {
51+
export function getTranslate(element: HTMLElement, direction: DrawerDirection): number | null {
4952
const style = window.getComputedStyle(element);
5053
const transform =
51-
// @ts-expect-error - mozTransform is not recognized
54+
// @ts-expect-error - vendor prefix
5255
style.transform || style.webkitTransform || style.mozTransform;
5356
let mat = transform.match(/^matrix3d\((.+)\)$/);
54-
if (mat) return parseFloat(mat[1].split(", ")[13]);
57+
if (mat) {
58+
// https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/matrix3d
59+
return parseFloat(mat[1].split(", ")[isVertical(direction) ? 13 : 12]);
60+
}
61+
// https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/matrix
5562
mat = transform.match(/^matrix\((.+)\)$/);
56-
return mat ? parseFloat(mat[1].split(", ")[5]) : null;
63+
return mat ? parseFloat(mat[1].split(", ")[isVertical(direction) ? 5 : 4]) : null;
5764
}
5865

5966
export function styleToString(style: Record<string, number | string | undefined>): string {

src/lib/internal/snap-points.ts

+58-19
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
11
import { tick } from "svelte";
22
import { derived, get, type Writable } from "svelte/store";
33
import { TRANSITIONS, VELOCITY_THRESHOLD } from "./constants.js";
4-
import { effect, set } from "./helpers/index.js";
4+
import { effect, set, isVertical, isBottomOrRight } from "./helpers/index.js";
5+
import type { DrawerDirection } from "./types.js";
56

67
export function handleSnapPoints({
78
activeSnapPoint,
89
snapPoints,
910
drawerRef,
1011
overlayRef,
1112
fadeFromIndex,
12-
openTime
13+
openTime,
14+
direction
1315
}: {
1416
activeSnapPoint: Writable<number | string | null>;
1517
snapPoints: Writable<(number | string)[] | undefined>;
1618
fadeFromIndex: Writable<number | undefined>;
1719
drawerRef: Writable<HTMLDivElement | undefined>;
1820
overlayRef: Writable<HTMLDivElement | undefined>;
1921
openTime: Writable<Date | null>;
22+
direction: Writable<DrawerDirection>;
2023
}) {
2124
const isLastSnapPoint = derived(
2225
[snapPoints, activeSnapPoint],
@@ -55,14 +58,27 @@ export function handleSnapPoints({
5558
if (isPx) {
5659
snapPointAsNumber = parseInt(snapPoint, 10);
5760
}
61+
const $direction = get(direction);
5862

59-
const height = isPx ? snapPointAsNumber : hasWindow ? snapPoint * window.innerHeight : 0;
63+
if (isVertical($direction)) {
64+
const height = isPx ? snapPointAsNumber : hasWindow ? snapPoint * window.innerHeight : 0;
65+
66+
if (hasWindow) {
67+
return $direction === "bottom"
68+
? window.innerHeight - height
69+
: window.innerHeight + height;
70+
}
71+
72+
return height;
73+
}
74+
75+
const width = isPx ? snapPointAsNumber : hasWindow ? snapPoint * window.innerWidth : 0;
6076

6177
if (hasWindow) {
62-
return window.innerHeight - height;
78+
return $direction === "right" ? window.innerWidth - width : window.innerWidth + width;
6379
}
6480

65-
return height;
81+
return width;
6682
});
6783
}
6884
return [];
@@ -78,29 +94,31 @@ export function handleSnapPoints({
7894
if ($activeSnapPoint && $drawerRef) {
7995
const $snapPoints = get(snapPoints);
8096
const $snapPointsOffset = get(snapPointsOffset);
81-
const newIndex =
82-
$snapPoints?.findIndex((snapPoint) => snapPoint === $activeSnapPoint) ?? null;
83-
if ($snapPointsOffset && newIndex && typeof $snapPointsOffset[newIndex] === "number") {
97+
const newIndex = $snapPoints?.findIndex((snapPoint) => snapPoint === $activeSnapPoint) ?? -1;
98+
if ($snapPointsOffset && newIndex !== -1 && typeof $snapPointsOffset[newIndex] === "number") {
8499
snapToPoint($snapPointsOffset[newIndex] as number);
85100
}
86101
}
87102
});
88103

89-
function snapToPoint(height: number) {
104+
function snapToPoint(dimension: number) {
90105
tick().then(() => {
91106
const $snapPointsOffset = get(snapPointsOffset);
92107
const newSnapPointIndex =
93-
$snapPointsOffset?.findIndex((snapPointHeight) => snapPointHeight === height) ?? null;
108+
$snapPointsOffset?.findIndex((snapPointDim) => snapPointDim === dimension) ?? null;
94109

95110
const $drawerRef = get(drawerRef);
111+
const $direction = get(direction);
96112

97113
onSnapPointChange(newSnapPointIndex);
98114

99115
set($drawerRef, {
100116
transition: `transform ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(
101117
","
102118
)})`,
103-
transform: `translate3d(0, ${height}px, 0)`
119+
transform: isVertical($direction)
120+
? `translate3d(0, ${dimension}px, 0)`
121+
: `translate3d(${dimension}px, 0, 0)`
104122
});
105123

106124
const $fadeFromIndex = get(fadeFromIndex);
@@ -151,13 +169,13 @@ export function handleSnapPoints({
151169
const $overlayRef = get(overlayRef);
152170
const $snapPointsOffset = get(snapPointsOffset);
153171
const $snapPoints = get(snapPoints);
172+
const $direction = get(direction);
154173

155-
function getCurrPosition() {
156-
const snapOffset = $activeSnapPointOffset ?? 0;
157-
return snapOffset - draggedDistance;
158-
}
174+
const currentPosition =
175+
$direction === "bottom" || $direction === "right"
176+
? ($activeSnapPointOffset ?? 0) - draggedDistance
177+
: ($activeSnapPointOffset ?? 0) + draggedDistance;
159178

160-
const currentPosition = getCurrPosition();
161179
const isOverlaySnapPoint = $activeSnapPointIndex === $fadeFromIndex - 1;
162180
const isFirst = $activeSnapPointIndex === 0;
163181
const hasDraggedUp = draggedDistance > 0;
@@ -186,7 +204,9 @@ export function handleSnapPoints({
186204
return Math.abs(curr - currentPosition) < Math.abs(prev - currentPosition) ? curr : prev;
187205
});
188206

189-
if (velocity > VELOCITY_THRESHOLD && Math.abs(draggedDistance) < window.innerHeight * 0.4) {
207+
const dim = isVertical($direction) ? window.innerHeight : window.innerWidth;
208+
209+
if (velocity > VELOCITY_THRESHOLD && Math.abs(draggedDistance) < dim * 0.4) {
190210
const dragDirection = hasDraggedUp ? 1 : -1; // 1 = up, -1 = down
191211

192212
// Don't do anything if we swipe upwards while being on the last snap point
@@ -212,10 +232,29 @@ export function handleSnapPoints({
212232
const $drawerRef = get(drawerRef);
213233
const $activeSnapPointOffset = get(activeSnapPointOffset);
214234
if ($activeSnapPointOffset === null) return;
215-
const newYValue = $activeSnapPointOffset - draggedDistance;
235+
const $snapPointsOffset = get(snapPointsOffset);
236+
const $direction = get(direction);
237+
238+
const newValue =
239+
$direction === "bottom" || $direction === "right"
240+
? $activeSnapPointOffset - draggedDistance
241+
: $activeSnapPointOffset + draggedDistance;
242+
243+
const lastSnapPoint = $snapPointsOffset[$snapPointsOffset.length - 1];
244+
245+
// Don't do anything if we exceed the last(biggest) snap point
246+
if (isBottomOrRight($direction) && newValue < lastSnapPoint) {
247+
return;
248+
}
249+
250+
if (!isBottomOrRight($direction) && newValue > lastSnapPoint) {
251+
return;
252+
}
216253

217254
set($drawerRef, {
218-
transform: `translate3d(0, ${newYValue}px, 0)`
255+
transform: isVertical($direction)
256+
? `translate3d(0, ${newValue}px, 0)`
257+
: `translate3d(${newValue}px, 0, 0)`
219258
});
220259
}
221260

src/lib/internal/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,5 @@ export type Builder<
2525
> = Record<string, any> & {
2626
action: Action<Element, Param, Attributes>;
2727
};
28+
29+
export type DrawerDirection = "left" | "right" | "top" | "bottom";

0 commit comments

Comments
 (0)