@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 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 | TouchEvent) {
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
- ? "clientY" in e
86
- ? e.clientY
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 | TouchEvent) {
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
- ? "clientY" in e
110
- ? e.clientY
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
- const threshold = drawer.closeThreshold * 100;
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 + threshold) {
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 > threshold) {
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
- style="transform: {getTransform()}; z-index: 50; touch-action: none;{autoHeight
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.6",
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.0.0",
38
+ "@sveltejs/vite-plugin-svelte": "^7.1.2",
39
39
  "publint": "^0.2.12",
40
- "svelte": "^5.46.4",
41
- "svelte-check": "^4.3.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
  },