@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
+ [![skills.sh](https://skills.sh/b/AbhiVarde/svelte-drawer)](https://skills.sh/AbhiVarde/svelte-drawer)
5
6
  [![](https://img.shields.io/badge/npm-@abhivarde/svelte--drawer-000?style=flat&logo=npm&logoColor=white)](https://www.npmjs.com/package/@abhivarde/svelte-drawer) [![](https://img.shields.io/badge/npmx-@abhivarde/svelte--drawer-000?style=flat&logo=node.js&logoColor=white)](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 @@ declare const Drawer: import("svelte").Component<{
11
11
  persistState?: boolean;
12
12
  persistKey?: string;
13
13
  persistSnapPoint?: boolean;
14
+ closeThreshold?: number;
14
15
  children: any;
15
16
  }, {}, "open" | "activeSnapPoint">;
16
17
  type Drawer = ReturnType<typeof Drawer>;
@@ -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 | TouchEvent) {
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
- ? "clientY" in e
85
- ? e.clientY
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 | TouchEvent) {
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
- ? "clientY" in e
109
- ? e.clientY
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 + 30) {
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 > 30) {
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
@@ -11,6 +11,7 @@ export interface DrawerProps {
11
11
  persistState?: boolean;
12
12
  persistKey?: string;
13
13
  persistSnapPoint?: boolean;
14
+ closeThreshold?: number;
14
15
  }
15
16
  export interface DrawerContentProps {
16
17
  class?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abhivarde/svelte-drawer",
3
- "version": "1.0.5",
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.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
  },