@abhivarde/svelte-drawer 1.0.6 → 1.0.8
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 +18 -3
- package/dist/components/DrawerContent.svelte +42 -25
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -15,6 +15,8 @@ A drawer component for Svelte 5, inspired by [Vaul](https://github.com/emilkowal
|
|
|
15
15
|
- ✅ **Snap points** for iOS-like multi-height drawers
|
|
16
16
|
- ✅ **Portal rendering** to escape z-index conflicts
|
|
17
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
|
|
18
20
|
- ✅ Nested drawer support
|
|
19
21
|
- ✅ Scrollable content areas
|
|
20
22
|
- ✅ Keyboard shortcuts (**Escape to close**, Tab navigation)
|
|
@@ -22,7 +24,6 @@ A drawer component for Svelte 5, inspired by [Vaul](https://github.com/emilkowal
|
|
|
22
24
|
- ✅ Fully accessible with keyboard navigation
|
|
23
25
|
- ✅ Full **TypeScript** support
|
|
24
26
|
- ✅ Customizable styling with **Tailwind CSS**
|
|
25
|
-
- ✅ **Auto height** for dynamic content (AI streaming, forms, dynamic lists)
|
|
26
27
|
|
|
27
28
|
## Installation
|
|
28
29
|
|
|
@@ -166,14 +167,28 @@ Add a premium blur effect to the overlay background:
|
|
|
166
167
|
Control how far the user needs to drag before the drawer dismisses.
|
|
167
168
|
|
|
168
169
|
```svelte
|
|
170
|
+
<script>
|
|
171
|
+
import { Drawer, DrawerOverlay, DrawerContent, DrawerHandle } from '@abhivarde/svelte-drawer';
|
|
172
|
+
|
|
173
|
+
let open = $state(false);
|
|
174
|
+
</script>
|
|
175
|
+
|
|
169
176
|
<!-- easier to dismiss (short drag) -->
|
|
170
177
|
<Drawer bind:open closeThreshold={0.15}>
|
|
171
|
-
|
|
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>
|
|
172
183
|
</Drawer>
|
|
173
184
|
|
|
174
185
|
<!-- harder to dismiss (long drag required) -->
|
|
175
186
|
<Drawer bind:open closeThreshold={0.5}>
|
|
176
|
-
|
|
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>
|
|
177
192
|
</Drawer>
|
|
178
193
|
```
|
|
179
194
|
|
|
@@ -28,6 +28,9 @@
|
|
|
28
28
|
let startPos = 0;
|
|
29
29
|
let startDragPos = 0;
|
|
30
30
|
let dragging = false;
|
|
31
|
+
let lastPointerPos = 0;
|
|
32
|
+
let lastPointerTime = 0;
|
|
33
|
+
let velocity = 0;
|
|
31
34
|
|
|
32
35
|
function snapPointToPosition(snapPoint: number): number {
|
|
33
36
|
return (1 - snapPoint) * 100;
|
|
@@ -68,7 +71,7 @@
|
|
|
68
71
|
}
|
|
69
72
|
}
|
|
70
73
|
|
|
71
|
-
function onPointerDown(e: PointerEvent
|
|
74
|
+
function onPointerDown(e: PointerEvent) {
|
|
72
75
|
const target = e.target as HTMLElement;
|
|
73
76
|
|
|
74
77
|
if (
|
|
@@ -82,36 +85,24 @@
|
|
|
82
85
|
|
|
83
86
|
startPos =
|
|
84
87
|
drawer.direction === "bottom" || drawer.direction === "top"
|
|
85
|
-
?
|
|
86
|
-
|
|
87
|
-
: (e.touches[0]?.clientY ?? 0)
|
|
88
|
-
: "clientX" in e
|
|
89
|
-
? e.clientX
|
|
90
|
-
: (e.touches[0]?.clientX ?? 0);
|
|
88
|
+
? e.clientY
|
|
89
|
+
: e.clientX;
|
|
91
90
|
|
|
92
91
|
startDragPos = drawer.drawerPosition.current;
|
|
93
92
|
|
|
94
93
|
window.addEventListener("pointermove", onPointerMove, { passive: false });
|
|
95
94
|
window.addEventListener("pointerup", onPointerUp);
|
|
96
|
-
window.addEventListener("touchmove", onPointerMove, { passive: false });
|
|
97
|
-
window.addEventListener("touchend", onPointerUp);
|
|
98
|
-
|
|
99
|
-
e.preventDefault();
|
|
100
95
|
}
|
|
101
96
|
|
|
102
|
-
function onPointerMove(e: PointerEvent
|
|
97
|
+
function onPointerMove(e: PointerEvent) {
|
|
103
98
|
if (!dragging) return;
|
|
104
99
|
|
|
105
100
|
e.preventDefault();
|
|
106
101
|
|
|
107
102
|
const current =
|
|
108
103
|
drawer.direction === "bottom" || drawer.direction === "top"
|
|
109
|
-
?
|
|
110
|
-
|
|
111
|
-
: (e.touches[0]?.clientY ?? 0)
|
|
112
|
-
: "clientX" in e
|
|
113
|
-
? e.clientX
|
|
114
|
-
: (e.touches[0]?.clientX ?? 0);
|
|
104
|
+
? e.clientY
|
|
105
|
+
: e.clientX;
|
|
115
106
|
|
|
116
107
|
const delta = current - startPos;
|
|
117
108
|
let newPos = startDragPos;
|
|
@@ -127,6 +118,13 @@
|
|
|
127
118
|
}
|
|
128
119
|
|
|
129
120
|
drawer.drawerPosition.set(newPos, { duration: 0 });
|
|
121
|
+
|
|
122
|
+
const now = Date.now();
|
|
123
|
+
if (lastPointerTime > 0) {
|
|
124
|
+
velocity = (current - lastPointerPos) / (now - lastPointerTime);
|
|
125
|
+
}
|
|
126
|
+
lastPointerPos = current;
|
|
127
|
+
lastPointerTime = now;
|
|
130
128
|
}
|
|
131
129
|
|
|
132
130
|
function onPointerUp() {
|
|
@@ -135,7 +133,12 @@
|
|
|
135
133
|
dragging = false;
|
|
136
134
|
|
|
137
135
|
const pos = drawer.drawerPosition.current;
|
|
138
|
-
|
|
136
|
+
|
|
137
|
+
const isFlick =
|
|
138
|
+
(drawer.direction === "bottom" && velocity > 0.5) ||
|
|
139
|
+
(drawer.direction === "top" && velocity < -0.5) ||
|
|
140
|
+
(drawer.direction === "left" && velocity < -0.5) ||
|
|
141
|
+
(drawer.direction === "right" && velocity > 0.5);
|
|
139
142
|
|
|
140
143
|
if (drawer.snapPoints && drawer.snapPoints.length > 0) {
|
|
141
144
|
const nearestSnapPoint = findNearestSnapPoint(pos);
|
|
@@ -144,24 +147,26 @@
|
|
|
144
147
|
const lowestSnapPoint = Math.min(...drawer.snapPoints);
|
|
145
148
|
const lowestSnapPos = snapPointToPosition(lowestSnapPoint);
|
|
146
149
|
|
|
147
|
-
if (pos > lowestSnapPos +
|
|
150
|
+
if (isFlick || pos > lowestSnapPos + 30) {
|
|
148
151
|
drawer.closeDrawer();
|
|
149
152
|
} else {
|
|
150
153
|
drawer.drawerPosition.set(snapPos);
|
|
151
154
|
drawer.setActiveSnapPoint?.(nearestSnapPoint);
|
|
152
155
|
}
|
|
153
156
|
} else {
|
|
154
|
-
if (pos >
|
|
157
|
+
if (isFlick || pos > 30) {
|
|
155
158
|
drawer.closeDrawer();
|
|
156
159
|
} else {
|
|
157
160
|
drawer.drawerPosition.set(0);
|
|
158
161
|
}
|
|
159
162
|
}
|
|
160
163
|
|
|
164
|
+
velocity = 0;
|
|
165
|
+
lastPointerPos = 0;
|
|
166
|
+
lastPointerTime = 0;
|
|
167
|
+
|
|
161
168
|
window.removeEventListener("pointermove", onPointerMove);
|
|
162
169
|
window.removeEventListener("pointerup", onPointerUp);
|
|
163
|
-
window.removeEventListener("touchmove", onPointerMove);
|
|
164
|
-
window.removeEventListener("touchend", onPointerUp);
|
|
165
170
|
}
|
|
166
171
|
|
|
167
172
|
function getFocusableElements(): HTMLElement[] {
|
|
@@ -216,16 +221,28 @@
|
|
|
216
221
|
<div
|
|
217
222
|
bind:this={contentElement}
|
|
218
223
|
class={className}
|
|
219
|
-
|
|
224
|
+
data-svelte-drawer
|
|
225
|
+
style="transform: {getTransform()}; z-index: 50; touch-action: none; will-change: transform;{autoHeight
|
|
220
226
|
? ' height: auto;'
|
|
221
227
|
: ''}"
|
|
222
228
|
tabindex="-1"
|
|
223
229
|
role="dialog"
|
|
224
230
|
aria-modal="true"
|
|
225
231
|
onpointerdown={onPointerDown}
|
|
226
|
-
ontouchstart={onPointerDown}
|
|
227
232
|
{...restProps}
|
|
228
233
|
>
|
|
229
234
|
{@render children()}
|
|
230
235
|
</div>
|
|
231
236
|
{/if}
|
|
237
|
+
|
|
238
|
+
<style>
|
|
239
|
+
:global([data-svelte-drawer]::after) {
|
|
240
|
+
content: "";
|
|
241
|
+
position: absolute;
|
|
242
|
+
background: inherit;
|
|
243
|
+
left: 0;
|
|
244
|
+
right: 0;
|
|
245
|
+
height: 200px;
|
|
246
|
+
top: 100%;
|
|
247
|
+
}
|
|
248
|
+
</style>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abhivarde/svelte-drawer",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
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
|
},
|