@abhivarde/svelte-drawer 1.0.5 → 1.0.7
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/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A drawer component for Svelte 5, inspired by [Vaul](https://github.com/emilkowalski/vaul).
|
|
4
4
|
|
|
5
|
+
[](https://skills.sh/AbhiVarde/svelte-drawer)
|
|
5
6
|
[](https://www.npmjs.com/package/@abhivarde/svelte-drawer) [](https://npmx.dev/package/@abhivarde/svelte-drawer)
|
|
6
7
|
|
|
7
8
|
## Features
|
|
@@ -14,6 +15,8 @@ A drawer component for Svelte 5, inspired by [Vaul](https://github.com/emilkowal
|
|
|
14
15
|
- ✅ **Snap points** for iOS-like multi-height drawers
|
|
15
16
|
- ✅ **Portal rendering** to escape z-index conflicts
|
|
16
17
|
- ✅ **Optional header & footer** components for quick setup
|
|
18
|
+
- ✅ **Auto height** for dynamic content (AI streaming, forms, dynamic lists)
|
|
19
|
+
- ✅ **Configurable dismiss threshold** via `closeThreshold` prop
|
|
17
20
|
- ✅ Nested drawer support
|
|
18
21
|
- ✅ Scrollable content areas
|
|
19
22
|
- ✅ Keyboard shortcuts (**Escape to close**, Tab navigation)
|
|
@@ -21,7 +24,6 @@ A drawer component for Svelte 5, inspired by [Vaul](https://github.com/emilkowal
|
|
|
21
24
|
- ✅ Fully accessible with keyboard navigation
|
|
22
25
|
- ✅ Full **TypeScript** support
|
|
23
26
|
- ✅ Customizable styling with **Tailwind CSS**
|
|
24
|
-
- ✅ **Auto height** for dynamic content (AI streaming, forms, dynamic lists)
|
|
25
27
|
|
|
26
28
|
## Installation
|
|
27
29
|
|
|
@@ -160,6 +162,38 @@ Add a premium blur effect to the overlay background:
|
|
|
160
162
|
</Drawer>
|
|
161
163
|
```
|
|
162
164
|
|
|
165
|
+
### Close Threshold
|
|
166
|
+
|
|
167
|
+
Control how far the user needs to drag before the drawer dismisses.
|
|
168
|
+
|
|
169
|
+
```svelte
|
|
170
|
+
<script>
|
|
171
|
+
import { Drawer, DrawerOverlay, DrawerContent, DrawerHandle } from '@abhivarde/svelte-drawer';
|
|
172
|
+
|
|
173
|
+
let open = $state(false);
|
|
174
|
+
</script>
|
|
175
|
+
|
|
176
|
+
<!-- easier to dismiss (short drag) -->
|
|
177
|
+
<Drawer bind:open closeThreshold={0.15}>
|
|
178
|
+
<DrawerOverlay class="fixed inset-0 bg-black/40" />
|
|
179
|
+
<DrawerContent class="fixed bottom-0 left-0 right-0 bg-white rounded-t-lg p-4">
|
|
180
|
+
<DrawerHandle class="mb-8" />
|
|
181
|
+
<p>Short drag closes this drawer.</p>
|
|
182
|
+
</DrawerContent>
|
|
183
|
+
</Drawer>
|
|
184
|
+
|
|
185
|
+
<!-- harder to dismiss (long drag required) -->
|
|
186
|
+
<Drawer bind:open closeThreshold={0.5}>
|
|
187
|
+
<DrawerOverlay class="fixed inset-0 bg-black/40" />
|
|
188
|
+
<DrawerContent class="fixed bottom-0 left-0 right-0 bg-white rounded-t-lg p-4">
|
|
189
|
+
<DrawerHandle class="mb-8" />
|
|
190
|
+
<p>Requires a longer drag to close.</p>
|
|
191
|
+
</DrawerContent>
|
|
192
|
+
</Drawer>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Values range from `0` to `1`. Default is `0.3` (30% of the viewport).
|
|
196
|
+
|
|
163
197
|
### Using Variants
|
|
164
198
|
|
|
165
199
|
```svelte
|
|
@@ -558,6 +592,7 @@ Main wrapper component that manages drawer state and animations.
|
|
|
558
592
|
- `onOpenChange` (function, optional) - Callback when open state changes
|
|
559
593
|
- `direction` ('bottom' | 'top' | 'left' | 'right', default: 'bottom') - Direction from which drawer slides
|
|
560
594
|
- `closeOnEscape` (boolean, optional, default: true) - Whether Escape key closes the drawer
|
|
595
|
+
- `closeThreshold` (number, optional, default: 0.3) - How far the user must drag to dismiss (0–1). Lower = easier to close, higher = requires a longer drag.
|
|
561
596
|
- `snapPoints` (number[], optional) - Array of snap positions between 0-1
|
|
562
597
|
- `activeSnapPoint` (number, bindable, optional) - Current active snap point value
|
|
563
598
|
- `onSnapPointChange` (function, optional) - Callback fired when snap changes
|
|
@@ -15,9 +15,10 @@
|
|
|
15
15
|
onSnapPointChange = undefined,
|
|
16
16
|
portal = false,
|
|
17
17
|
portalContainer = undefined,
|
|
18
|
-
persistState = false,
|
|
19
|
-
persistKey = "default",
|
|
20
|
-
persistSnapPoint = false,
|
|
18
|
+
persistState = false,
|
|
19
|
+
persistKey = "default",
|
|
20
|
+
persistSnapPoint = false,
|
|
21
|
+
closeThreshold = 0.3,
|
|
21
22
|
children,
|
|
22
23
|
} = $props();
|
|
23
24
|
|
|
@@ -34,7 +35,7 @@
|
|
|
34
35
|
let previouslyFocusedElement: HTMLElement | null = null;
|
|
35
36
|
let visible = false;
|
|
36
37
|
let previousSnapPoint: number | undefined = undefined;
|
|
37
|
-
let stateLoaded = false;
|
|
38
|
+
let stateLoaded = false;
|
|
38
39
|
|
|
39
40
|
onMount(() => {
|
|
40
41
|
if (persistState && !stateLoaded) {
|
|
@@ -67,7 +68,7 @@
|
|
|
67
68
|
saveDrawerState(
|
|
68
69
|
persistKey,
|
|
69
70
|
open,
|
|
70
|
-
persistSnapPoint ? activeSnapPoint : undefined
|
|
71
|
+
persistSnapPoint ? activeSnapPoint : undefined,
|
|
71
72
|
);
|
|
72
73
|
}
|
|
73
74
|
});
|
|
@@ -171,6 +172,9 @@
|
|
|
171
172
|
get activeSnapPoint() {
|
|
172
173
|
return activeSnapPoint;
|
|
173
174
|
},
|
|
175
|
+
get closeThreshold() {
|
|
176
|
+
return closeThreshold;
|
|
177
|
+
},
|
|
174
178
|
setActiveSnapPoint(point: number) {
|
|
175
179
|
activeSnapPoint = point;
|
|
176
180
|
onSnapPointChange?.(point);
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
snapPoints?: number[];
|
|
12
12
|
activeSnapPoint?: number;
|
|
13
13
|
setActiveSnapPoint?: (point: number) => void;
|
|
14
|
+
closeThreshold: number;
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
let {
|
|
@@ -67,7 +68,7 @@
|
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
function onPointerDown(e: PointerEvent
|
|
71
|
+
function onPointerDown(e: PointerEvent) {
|
|
71
72
|
const target = e.target as HTMLElement;
|
|
72
73
|
|
|
73
74
|
if (
|
|
@@ -81,36 +82,24 @@
|
|
|
81
82
|
|
|
82
83
|
startPos =
|
|
83
84
|
drawer.direction === "bottom" || drawer.direction === "top"
|
|
84
|
-
?
|
|
85
|
-
|
|
86
|
-
: (e.touches[0]?.clientY ?? 0)
|
|
87
|
-
: "clientX" in e
|
|
88
|
-
? e.clientX
|
|
89
|
-
: (e.touches[0]?.clientX ?? 0);
|
|
85
|
+
? e.clientY
|
|
86
|
+
: e.clientX;
|
|
90
87
|
|
|
91
88
|
startDragPos = drawer.drawerPosition.current;
|
|
92
89
|
|
|
93
90
|
window.addEventListener("pointermove", onPointerMove, { passive: false });
|
|
94
91
|
window.addEventListener("pointerup", onPointerUp);
|
|
95
|
-
window.addEventListener("touchmove", onPointerMove, { passive: false });
|
|
96
|
-
window.addEventListener("touchend", onPointerUp);
|
|
97
|
-
|
|
98
|
-
e.preventDefault();
|
|
99
92
|
}
|
|
100
93
|
|
|
101
|
-
function onPointerMove(e: PointerEvent
|
|
94
|
+
function onPointerMove(e: PointerEvent) {
|
|
102
95
|
if (!dragging) return;
|
|
103
96
|
|
|
104
97
|
e.preventDefault();
|
|
105
98
|
|
|
106
99
|
const current =
|
|
107
100
|
drawer.direction === "bottom" || drawer.direction === "top"
|
|
108
|
-
?
|
|
109
|
-
|
|
110
|
-
: (e.touches[0]?.clientY ?? 0)
|
|
111
|
-
: "clientX" in e
|
|
112
|
-
? e.clientX
|
|
113
|
-
: (e.touches[0]?.clientX ?? 0);
|
|
101
|
+
? e.clientY
|
|
102
|
+
: e.clientX;
|
|
114
103
|
|
|
115
104
|
const delta = current - startPos;
|
|
116
105
|
let newPos = startDragPos;
|
|
@@ -134,6 +123,7 @@
|
|
|
134
123
|
dragging = false;
|
|
135
124
|
|
|
136
125
|
const pos = drawer.drawerPosition.current;
|
|
126
|
+
const threshold = drawer.closeThreshold * 100;
|
|
137
127
|
|
|
138
128
|
if (drawer.snapPoints && drawer.snapPoints.length > 0) {
|
|
139
129
|
const nearestSnapPoint = findNearestSnapPoint(pos);
|
|
@@ -142,14 +132,14 @@
|
|
|
142
132
|
const lowestSnapPoint = Math.min(...drawer.snapPoints);
|
|
143
133
|
const lowestSnapPos = snapPointToPosition(lowestSnapPoint);
|
|
144
134
|
|
|
145
|
-
if (pos > lowestSnapPos +
|
|
135
|
+
if (pos > lowestSnapPos + threshold) {
|
|
146
136
|
drawer.closeDrawer();
|
|
147
137
|
} else {
|
|
148
138
|
drawer.drawerPosition.set(snapPos);
|
|
149
139
|
drawer.setActiveSnapPoint?.(nearestSnapPoint);
|
|
150
140
|
}
|
|
151
141
|
} else {
|
|
152
|
-
if (pos >
|
|
142
|
+
if (pos > threshold) {
|
|
153
143
|
drawer.closeDrawer();
|
|
154
144
|
} else {
|
|
155
145
|
drawer.drawerPosition.set(0);
|
|
@@ -158,8 +148,6 @@
|
|
|
158
148
|
|
|
159
149
|
window.removeEventListener("pointermove", onPointerMove);
|
|
160
150
|
window.removeEventListener("pointerup", onPointerUp);
|
|
161
|
-
window.removeEventListener("touchmove", onPointerMove);
|
|
162
|
-
window.removeEventListener("touchend", onPointerUp);
|
|
163
151
|
}
|
|
164
152
|
|
|
165
153
|
function getFocusableElements(): HTMLElement[] {
|
|
@@ -221,7 +209,6 @@
|
|
|
221
209
|
role="dialog"
|
|
222
210
|
aria-modal="true"
|
|
223
211
|
onpointerdown={onPointerDown}
|
|
224
|
-
ontouchstart={onPointerDown}
|
|
225
212
|
{...restProps}
|
|
226
213
|
>
|
|
227
214
|
{@render children()}
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abhivarde/svelte-drawer",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "A drawer component for Svelte 5, inspired by Vaul",
|
|
5
5
|
"author": "Abhi Varde",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
"@sveltejs/adapter-auto": "^7.0.0",
|
|
36
36
|
"@sveltejs/kit": "^2.53.0",
|
|
37
37
|
"@sveltejs/package": "^2.5.7",
|
|
38
|
-
"@sveltejs/vite-plugin-svelte": "^7.
|
|
38
|
+
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
|
39
39
|
"publint": "^0.2.12",
|
|
40
|
-
"svelte": "^5.
|
|
41
|
-
"svelte-check": "^4.
|
|
40
|
+
"svelte": "^5.56.0",
|
|
41
|
+
"svelte-check": "^4.4.8",
|
|
42
42
|
"typescript": "^5.9.3",
|
|
43
43
|
"vite": "^8.0.0"
|
|
44
44
|
},
|