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 ( {
852name :'CDropdown' ,
953props :{
@@ -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 */
1559alignment :{
16- type :[ String , Object ] ,
60+ type :[ String , Object ] as PropType < string | Alignments > ,
1761// eslint-disable-next-line @typescript-eslint/no-explicit-any
1862validator :( value :string | any ) => {
1963if ( value === 'start' || value === 'end' ) {
@@ -127,19 +171,43 @@ const CDropdown = defineComponent({
127171setup ( props , { slots, emit} ) {
128172const dropdownToggleRef = ref ( )
129173const dropdownMenuRef = ref ( )
130- const placement = ref ( props . placement )
131- const popper = ref ( )
174+ const popper = ref ( typeof props . alignment === 'object' ?false :props . popper )
132175const 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+
134188watch (
135189( ) => props . visible ,
136190( ) => {
137191visible . 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+
141210provide ( 'config' , {
142- autoClose :props . autoClose ,
143211alignment :props . alignment ,
144212dark :props . dark ,
145213popper :props . popper ,
@@ -150,27 +218,38 @@ const CDropdown = defineComponent({
150218provide ( 'dropdownToggleRef' , dropdownToggleRef )
151219provide ( '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 ) {
156223return
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 ) => {
174253if ( props . disabled ) {
175254return
176255}
@@ -188,48 +267,7 @@ const CDropdown = defineComponent({
188267visible . 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
234272return ( ) =>
235273props . variant === 'input-group'