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'
33
4- import { Triggers } from '../../types'
4+ import { usePopper } from '../../composables'
5+ import type { Placements , Triggers } from '../../types'
56import { isRTL } from '../../utils'
67
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+
751const CDropdown = defineComponent ( {
852 name : 'CDropdown' ,
953 props : {
@@ -13,7 +57,7 @@ const CDropdown = defineComponent({
1357 * @values { 'start' | 'end' | { xs: 'start' | 'end' } | { sm: 'start' | 'end' } | { md: 'start' | 'end' } | { lg: 'start' | 'end' } | { xl: 'start' | 'end'} | { xxl: 'start' | 'end'} }
1458 */
1559 alignment : {
16- type : [ String , Object ] ,
60+ type : [ String , Object ] as PropType < string | Alignments > ,
1761 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1862 validator : ( value : string | any ) => {
1963 if ( value === 'start' || value === 'end' ) {
@@ -127,19 +171,43 @@ const CDropdown = defineComponent({
127171 setup ( props , { slots, emit } ) {
128172 const dropdownToggleRef = ref ( )
129173 const dropdownMenuRef = ref ( )
130- const placement = ref ( props . placement )
131- const popper = ref ( )
174+ const popper = ref ( typeof props . alignment === 'object' ? false : props . popper )
132175 const visible = ref ( props . visible )
133176
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+
134188 watch (
135189 ( ) => props . visible ,
136190 ( ) => {
137191 visible . value = props . visible
138192 } ,
139193 )
140194
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+
141210 provide ( 'config' , {
142- autoClose : props . autoClose ,
143211 alignment : props . alignment ,
144212 dark : props . dark ,
145213 popper : props . popper ,
@@ -150,27 +218,38 @@ const CDropdown = defineComponent({
150218 provide ( 'dropdownToggleRef' , dropdownToggleRef )
151219 provide ( 'dropdownMenuRef' , dropdownMenuRef )
152220
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 ) {
156223 return
157224 }
158225
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 )
163228 }
164229 }
165230
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
169249 }
170- popper . value = undefined
171250 }
172251
173- const toggleMenu = ( _visible ?: boolean ) => {
252+ const setVisible = ( _visible ?: boolean ) => {
174253 if ( props . disabled ) {
175254 return
176255 }
@@ -188,48 +267,7 @@ const CDropdown = defineComponent({
188267 visible . value = true
189268 }
190269
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 )
233271
234272 return ( ) =>
235273 props . variant === 'input-group'
0 commit comments