1
1
<script lang="ts" setup>
2
- import { defineCustomElement , onMounted , onUnmounted } from ' vue'
2
+ import { onMounted , onUnmounted , ref , watch , nextTick } from ' vue'
3
+
4
+ const props = defineProps <{
5
+ modelValue: boolean
6
+ }>()
3
7
4
8
const emit = defineEmits <{
9
+ (event : ' update:modelValue' , value : boolean ): void
5
10
(event : ' close' , visible : boolean ): void
6
11
}>()
7
12
13
+ const dialogRef = ref <HTMLElement | null >(null )
14
+ const previousActiveElement = ref <HTMLElement | null >(null )
15
+
8
16
function handleClose() {
17
+ emit (' update:modelValue' , false )
9
18
emit (' close' , false )
10
19
}
11
20
@@ -15,42 +24,111 @@ function handleEscape(event: KeyboardEvent) {
15
24
}
16
25
}
17
26
27
+ function trapFocus(event : KeyboardEvent ) {
28
+ if (event .key !== ' Tab' ) return
29
+
30
+ const focusableElements = dialogRef .value ?.querySelectorAll (
31
+ ' button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
32
+ )
33
+
34
+ if (! focusableElements ?.length ) return
35
+
36
+ const firstFocusable = focusableElements [0 ] as HTMLElement
37
+ const lastFocusable = focusableElements [focusableElements .length - 1 ] as HTMLElement
38
+
39
+ if (event .shiftKey ) {
40
+ if (document .activeElement === firstFocusable ) {
41
+ lastFocusable .focus ()
42
+ }
43
+ } else {
44
+ if (document .activeElement === lastFocusable ) {
45
+ firstFocusable .focus ()
46
+ }
47
+ }
48
+ }
49
+
50
+ watch (() => props .modelValue , (newValue ) => {
51
+ if (newValue ) {
52
+ previousActiveElement .value = document .activeElement as HTMLElement
53
+ nextTick (() => {
54
+ dialogRef .value ?.focus ()
55
+ })
56
+ } else if (previousActiveElement .value ) {
57
+ previousActiveElement .value .focus ()
58
+ }
59
+ })
60
+
18
61
onMounted (() => {
19
62
window .addEventListener (' keydown' , handleEscape )
63
+ window .addEventListener (' keydown' , trapFocus )
20
64
})
21
65
22
66
onUnmounted (() => {
23
67
window .removeEventListener (' keydown' , handleEscape )
24
- })
25
-
26
- defineCustomElement ({
27
- shadow: true ,
68
+ window .removeEventListener (' keydown' , trapFocus )
28
69
})
29
70
</script >
30
71
31
72
<template >
32
- <div class =" fixed inset-0 z-50 overflow-y-auto" >
33
- <div
34
- class =" z-20 min-h-full flex items-end justify-center bg-gray-500 bg-opacity-75 p-4 text-center transition-opacity sm:items-center sm:p-0"
35
- @click.self =" emit('close', false)"
36
- >
37
- <slot />
73
+ <Transition name =" dialog-fade" >
74
+ <div v-if =" modelValue" class =" stacks-dialog-container" >
75
+ <div
76
+ class =" stacks-dialog-backdrop"
77
+ @click.self =" handleClose"
78
+ >
79
+ <div class =" stacks-dialog-content" >
80
+ <slot />
81
+ </div >
82
+ </div >
38
83
</div >
39
- </div >
84
+ </Transition >
40
85
</template >
41
86
42
87
<style scoped>
43
- button {
44
- cursor : pointer ;
88
+ .stacks-dialog-container {
89
+ position : fixed !important ;
90
+ inset : 0 !important ;
91
+ z-index : 50 !important ;
92
+ min-height : 100% !important ;
93
+ overflow-y : auto !important ;
45
94
}
46
95
47
- .fade-enter-active ,
48
- .fade-leave-active {
49
- transition : opacity 0.3s ease ;
96
+ .stacks-dialog-backdrop {
97
+ position : fixed !important ;
98
+ inset : 0 !important ;
99
+ z-index : 20 !important ;
100
+ display : flex !important ;
101
+ align-items : flex-end !important ;
102
+ justify-content : center !important ;
103
+ min-height : 100% !important ;
104
+ padding : 1rem !important ;
105
+ text-align : center !important ;
106
+ transition-property : opacity !important ;
107
+ background-color : rgb (107 114 128 / 0.75 ) !important ;
50
108
}
51
- .fade-enter-from ,
52
- .fade-leave-to {
53
- opacity : 0 ;
109
+
110
+ .stacks-dialog-content {
111
+ position : relative !important ;
112
+ width : 100% !important ;
113
+ display : flex !important ;
114
+ align-items : center !important ;
115
+ justify-content : center !important ;
116
+ }
117
+
118
+ @media (min-width : 640px ) {
119
+ .stacks-dialog-backdrop {
120
+ align-items : center !important ;
121
+ padding : 0 !important ;
122
+ }
123
+ }
124
+
125
+ .dialog-fade-enter-active ,
126
+ .dialog-fade-leave-active {
127
+ transition : opacity 0.3s ease !important ;
128
+ }
129
+
130
+ .dialog-fade-enter-from ,
131
+ .dialog-fade-leave-to {
132
+ opacity : 0 !important ;
54
133
}
55
- /* @unocss-placeholder */
56
134
</style >
0 commit comments