@abhivarde/svelte-drawer 0.0.6 → 0.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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Tween } from "svelte/motion";
|
|
3
|
-
import {
|
|
3
|
+
import { expoOut } from "svelte/easing";
|
|
4
4
|
import { setContext, onMount } from "svelte";
|
|
5
5
|
|
|
6
6
|
let {
|
|
@@ -11,30 +11,48 @@
|
|
|
11
11
|
children,
|
|
12
12
|
} = $props();
|
|
13
13
|
|
|
14
|
-
let overlayOpacity = new Tween(0, {
|
|
15
|
-
|
|
14
|
+
let overlayOpacity = new Tween(0, {
|
|
15
|
+
duration: 150,
|
|
16
|
+
easing: expoOut,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
let drawerPosition = new Tween(100, {
|
|
20
|
+
duration: 220,
|
|
21
|
+
easing: expoOut,
|
|
22
|
+
});
|
|
23
|
+
|
|
16
24
|
let previouslyFocusedElement: HTMLElement | null = null;
|
|
17
25
|
|
|
26
|
+
let visible = false;
|
|
27
|
+
|
|
18
28
|
$effect(() => {
|
|
19
29
|
if (open) {
|
|
30
|
+
visible = true;
|
|
20
31
|
previouslyFocusedElement = document.activeElement as HTMLElement;
|
|
32
|
+
document.body.style.overflow = "hidden";
|
|
21
33
|
|
|
22
34
|
overlayOpacity.set(1);
|
|
23
35
|
drawerPosition.set(0);
|
|
24
|
-
} else {
|
|
25
|
-
overlayOpacity.set(0);
|
|
26
|
-
drawerPosition.set(100);
|
|
36
|
+
} else if (visible) {
|
|
37
|
+
overlayOpacity.set(0, { duration: 120 });
|
|
38
|
+
drawerPosition.set(100, { duration: 180 });
|
|
39
|
+
|
|
40
|
+
document.body.style.overflow = "";
|
|
27
41
|
|
|
28
42
|
if (previouslyFocusedElement) {
|
|
29
43
|
previouslyFocusedElement.focus();
|
|
30
44
|
previouslyFocusedElement = null;
|
|
31
45
|
}
|
|
46
|
+
|
|
47
|
+
setTimeout(() => {
|
|
48
|
+
visible = false;
|
|
49
|
+
}, 180);
|
|
32
50
|
}
|
|
33
51
|
});
|
|
34
52
|
|
|
35
53
|
function closeDrawer() {
|
|
36
54
|
open = false;
|
|
37
|
-
|
|
55
|
+
onOpenChange?.(false);
|
|
38
56
|
}
|
|
39
57
|
|
|
40
58
|
function handleKeydown(e: KeyboardEvent) {
|
|
@@ -48,6 +66,7 @@
|
|
|
48
66
|
window.addEventListener("keydown", handleKeydown);
|
|
49
67
|
return () => {
|
|
50
68
|
window.removeEventListener("keydown", handleKeydown);
|
|
69
|
+
document.body.style.overflow = "";
|
|
51
70
|
};
|
|
52
71
|
});
|
|
53
72
|
|
|
@@ -55,6 +74,9 @@
|
|
|
55
74
|
get open() {
|
|
56
75
|
return open;
|
|
57
76
|
},
|
|
77
|
+
get visible() {
|
|
78
|
+
return visible;
|
|
79
|
+
},
|
|
58
80
|
get overlayOpacity() {
|
|
59
81
|
return overlayOpacity;
|
|
60
82
|
},
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
type DrawerContext = {
|
|
5
5
|
open: boolean;
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
visible: boolean;
|
|
7
|
+
overlayOpacity: { current: number; set: (v: number, opts?: any) => void };
|
|
8
|
+
drawerPosition: { current: number; set: (v: number, opts?: any) => void };
|
|
8
9
|
direction: "bottom" | "top" | "left" | "right";
|
|
9
10
|
closeDrawer: () => void;
|
|
10
11
|
};
|
|
@@ -33,8 +34,6 @@
|
|
|
33
34
|
return `translateX(-${pos}%)`;
|
|
34
35
|
case "right":
|
|
35
36
|
return `translateX(${pos}%)`;
|
|
36
|
-
default:
|
|
37
|
-
return `translateY(${pos}%)`;
|
|
38
37
|
}
|
|
39
38
|
}
|
|
40
39
|
|
|
@@ -90,9 +89,8 @@
|
|
|
90
89
|
document.body.style.cursor = "default";
|
|
91
90
|
|
|
92
91
|
const pos = drawer.drawerPosition.current;
|
|
93
|
-
const deltaThreshold = 30;
|
|
94
92
|
|
|
95
|
-
if (pos >
|
|
93
|
+
if (pos > 30) {
|
|
96
94
|
drawer.closeDrawer();
|
|
97
95
|
} else {
|
|
98
96
|
drawer.drawerPosition.set(0);
|
|
@@ -104,53 +102,36 @@
|
|
|
104
102
|
|
|
105
103
|
function getFocusableElements(): HTMLElement[] {
|
|
106
104
|
if (!contentElement) return [];
|
|
107
|
-
|
|
108
|
-
const focusableSelectors = [
|
|
109
|
-
"a[href]",
|
|
110
|
-
"button:not([disabled])",
|
|
111
|
-
"textarea:not([disabled])",
|
|
112
|
-
"input:not([disabled])",
|
|
113
|
-
"select:not([disabled])",
|
|
114
|
-
'[tabindex]:not([tabindex="-1"])',
|
|
115
|
-
];
|
|
116
|
-
|
|
117
105
|
return Array.from(
|
|
118
|
-
contentElement.querySelectorAll(
|
|
106
|
+
contentElement.querySelectorAll(
|
|
107
|
+
'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'
|
|
108
|
+
)
|
|
119
109
|
) as HTMLElement[];
|
|
120
110
|
}
|
|
121
111
|
|
|
122
112
|
function handleFocusTrap(e: KeyboardEvent) {
|
|
123
|
-
if (!trapFocus || !drawer.open) return;
|
|
124
|
-
if (e.key !== "Tab") return;
|
|
113
|
+
if (!trapFocus || !drawer.open || e.key !== "Tab") return;
|
|
125
114
|
|
|
126
|
-
const
|
|
127
|
-
if (
|
|
115
|
+
const focusable = getFocusableElements();
|
|
116
|
+
if (!focusable.length) return;
|
|
128
117
|
|
|
129
|
-
const
|
|
130
|
-
const
|
|
118
|
+
const first = focusable[0];
|
|
119
|
+
const last = focusable[focusable.length - 1];
|
|
131
120
|
|
|
132
|
-
if (e.shiftKey) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (document.activeElement === lastElement) {
|
|
139
|
-
e.preventDefault();
|
|
140
|
-
firstElement.focus();
|
|
141
|
-
}
|
|
121
|
+
if (e.shiftKey && document.activeElement === first) {
|
|
122
|
+
e.preventDefault();
|
|
123
|
+
last.focus();
|
|
124
|
+
} else if (!e.shiftKey && document.activeElement === last) {
|
|
125
|
+
e.preventDefault();
|
|
126
|
+
first.focus();
|
|
142
127
|
}
|
|
143
128
|
}
|
|
144
129
|
|
|
145
130
|
$effect(() => {
|
|
146
131
|
if (drawer.open && trapFocus && contentElement) {
|
|
147
132
|
tick().then(() => {
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
focusableElements[0].focus();
|
|
151
|
-
} else {
|
|
152
|
-
contentElement?.focus();
|
|
153
|
-
}
|
|
133
|
+
const focusable = getFocusableElements();
|
|
134
|
+
(focusable[0] ?? contentElement)?.focus();
|
|
154
135
|
});
|
|
155
136
|
}
|
|
156
137
|
});
|
|
@@ -163,7 +144,7 @@
|
|
|
163
144
|
});
|
|
164
145
|
</script>
|
|
165
146
|
|
|
166
|
-
{#if drawer.open}
|
|
147
|
+
{#if drawer.open || drawer.visible}
|
|
167
148
|
<div
|
|
168
149
|
bind:this={contentElement}
|
|
169
150
|
class={className}
|