1
- import { defineComponent , h , ref , provide , watch , PropType , onMounted } from 'vue'
2
- import { createPopper , Placement } from '@popperjs/core'
1
+ import { defineComponent , h , ref , provide , watch , PropType } from 'vue'
2
+ import type { Placement } from '@popperjs/core'
3
3
4
- import { Triggers } from '../../types'
4
+ import { usePopper } from '../../composables'
5
+ import type { Placements , Triggers } from '../../types'
5
6
import { isRTL } from '../../utils'
6
7
8
+ export type Directions = 'start' | 'end'
9
+
10
+ export type Breakpoints =
11
+ | { xs : Directions }
12
+ | { sm : Directions }
13
+ | { md : Directions }
14
+ | { lg : Directions }
15
+ | { xl : Directions }
16
+ | { xxl : Directions }
17
+
18
+ export type Alignments = Directions | Breakpoints
19
+
20
+ const getPlacement = (
21
+ placement : Placement ,
22
+ direction : string | undefined ,
23
+ alignment : Alignments | string | undefined ,
24
+ isRTL : boolean ,
25
+ ) : Placements => {
26
+ let _placement = placement
27
+
28
+ if ( direction === 'dropup' ) {
29
+ _placement = isRTL ? 'top-end' : 'top-start'
30
+ }
31
+
32
+ if ( direction === 'dropup-center' ) {
33
+ _placement = 'top'
34
+ }
35
+
36
+ if ( direction === 'dropend' ) {
37
+ _placement = isRTL ? 'left-start' : 'right-start'
38
+ }
39
+
40
+ if ( direction === 'dropstart' ) {
41
+ _placement = isRTL ? 'right-start' : 'left-start'
42
+ }
43
+
44
+ if ( alignment === 'end' ) {
45
+ _placement = isRTL ? 'bottom-start' : 'bottom-end'
46
+ }
47
+
48
+ return _placement
49
+ }
50
+
7
51
const CDropdown = defineComponent ( {
8
52
name : 'CDropdown' ,
9
53
props : {
@@ -13,7 +57,7 @@ const CDropdown = defineComponent({
13
57
* @values { 'start' | 'end' | { xs: 'start' | 'end' } | { sm: 'start' | 'end' } | { md: 'start' | 'end' } | { lg: 'start' | 'end' } | { xl: 'start' | 'end'} | { xxl: 'start' | 'end'} }
14
58
*/
15
59
alignment : {
16
- type : [ String , Object ] ,
60
+ type : [ String , Object ] as PropType < string | Alignments > ,
17
61
// eslint-disable-next-line @typescript-eslint/no-explicit-any
18
62
validator : ( value : string | any ) => {
19
63
if ( value === 'start' || value === 'end' ) {
@@ -127,19 +171,43 @@ const CDropdown = defineComponent({
127
171
setup ( props , { slots, emit } ) {
128
172
const dropdownToggleRef = ref ( )
129
173
const dropdownMenuRef = ref ( )
130
- const placement = ref ( props . placement )
131
- const popper = ref ( )
174
+ const popper = ref ( typeof props . alignment === 'object' ? false : props . popper )
132
175
const visible = ref ( props . visible )
133
176
177
+ const { initPopper, destroyPopper } = usePopper ( )
178
+
179
+ const popperConfig = {
180
+ placement : getPlacement (
181
+ props . placement ,
182
+ props . direction ,
183
+ props . alignment ,
184
+ isRTL ( dropdownMenuRef . value ) ,
185
+ ) as Placement ,
186
+ }
187
+
134
188
watch (
135
189
( ) => props . visible ,
136
190
( ) => {
137
191
visible . value = props . visible
138
192
} ,
139
193
)
140
194
195
+ watch ( visible , ( ) => {
196
+ if ( visible . value && dropdownToggleRef . value && dropdownMenuRef . value ) {
197
+ popper . value && initPopper ( dropdownToggleRef . value , dropdownMenuRef . value , popperConfig )
198
+ window . addEventListener ( 'mouseup' , handleMouseUp )
199
+ window . addEventListener ( 'keyup' , handleKeyup )
200
+ emit ( 'show' )
201
+ return
202
+ }
203
+
204
+ popper . value && destroyPopper ( )
205
+ window . removeEventListener ( 'mouseup' , handleMouseUp )
206
+ window . removeEventListener ( 'keyup' , handleKeyup )
207
+ emit ( 'hide' )
208
+ } )
209
+
141
210
provide ( 'config' , {
142
- autoClose : props . autoClose ,
143
211
alignment : props . alignment ,
144
212
dark : props . dark ,
145
213
popper : props . popper ,
@@ -150,27 +218,38 @@ const CDropdown = defineComponent({
150
218
provide ( 'dropdownToggleRef' , dropdownToggleRef )
151
219
provide ( 'dropdownMenuRef' , dropdownMenuRef )
152
220
153
- const initPopper = ( ) => {
154
- // Disable popper if responsive aligment is set.
155
- if ( typeof props . alignment === 'object' ) {
221
+ const handleKeyup = ( event : KeyboardEvent ) => {
222
+ if ( props . autoClose === false ) {
156
223
return
157
224
}
158
225
159
- if ( dropdownToggleRef . value ) {
160
- popper . value = createPopper ( dropdownToggleRef . value , dropdownMenuRef . value , {
161
- placement : placement . value ,
162
- } )
226
+ if ( event . key === 'Escape' ) {
227
+ setVisible ( false )
163
228
}
164
229
}
165
230
166
- const destroyPopper = ( ) => {
167
- if ( popper . value ) {
168
- popper . value . destroy ( )
231
+ const handleMouseUp = ( event : Event ) => {
232
+ if ( ! dropdownToggleRef . value || ! dropdownMenuRef . value ) {
233
+ return
234
+ }
235
+
236
+ if ( dropdownToggleRef . value . contains ( event . target as HTMLElement ) ) {
237
+ return
238
+ }
239
+
240
+ if (
241
+ props . autoClose === true ||
242
+ ( props . autoClose === 'inside' &&
243
+ dropdownMenuRef . value . contains ( event . target as HTMLElement ) ) ||
244
+ ( props . autoClose === 'outside' &&
245
+ ! dropdownMenuRef . value . contains ( event . target as HTMLElement ) )
246
+ ) {
247
+ setVisible ( false )
248
+ return
169
249
}
170
- popper . value = undefined
171
250
}
172
251
173
- const toggleMenu = ( _visible ?: boolean ) => {
252
+ const setVisible = ( _visible ?: boolean ) => {
174
253
if ( props . disabled ) {
175
254
return
176
255
}
@@ -188,48 +267,7 @@ const CDropdown = defineComponent({
188
267
visible . value = true
189
268
}
190
269
191
- provide ( 'toggleMenu' , toggleMenu )
192
-
193
- const hideMenu = ( ) => {
194
- if ( props . disabled ) {
195
- return
196
- }
197
-
198
- visible . value = false
199
- }
200
-
201
- provide ( 'hideMenu' , hideMenu )
202
-
203
- watch ( visible , ( ) => {
204
- props . popper && ( visible . value ? initPopper ( ) : destroyPopper ( ) )
205
- visible . value ? emit ( 'show' ) : emit ( 'hide' )
206
- } )
207
-
208
- onMounted ( ( ) => {
209
- if ( props . direction === 'center' ) {
210
- placement . value = 'bottom'
211
- }
212
-
213
- if ( props . direction === 'dropup' ) {
214
- placement . value = isRTL ( dropdownMenuRef . value ) ? 'top-end' : 'top-start'
215
- }
216
-
217
- if ( props . direction === 'dropup-center' ) {
218
- placement . value = 'top'
219
- }
220
-
221
- if ( props . direction === 'dropend' ) {
222
- placement . value = isRTL ( dropdownMenuRef . value ) ? 'left-start' : 'right-start'
223
- }
224
-
225
- if ( props . direction === 'dropstart' ) {
226
- placement . value = isRTL ( dropdownMenuRef . value ) ? 'right-start' : 'left-start'
227
- }
228
-
229
- if ( props . alignment === 'end' ) {
230
- placement . value = isRTL ( dropdownMenuRef . value ) ? 'bottom-start' : 'bottom-end'
231
- }
232
- } )
270
+ provide ( 'setVisible' , setVisible )
233
271
234
272
return ( ) =>
235
273
props . variant === 'input-group'
0 commit comments