Skip to content

Commit 6286039

Browse files
committed
refactor(CDropdown): rebuild the component, and improve the syntax
1 parent f5f92ef commit 6286039

File tree

3 files changed

+106
-113
lines changed

3 files changed

+106
-113
lines changed

packages/coreui-vue/src/components/dropdown/CDropdown.ts

Lines changed: 99 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,53 @@
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'
56
import { 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+
751
const 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'

packages/coreui-vue/src/components/dropdown/CDropdownMenu.ts

Lines changed: 2 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defineComponent, h, inject, onUnmounted, onUpdated, Ref } from 'vue'
1+
import { defineComponent, h, inject, Ref } from 'vue'
22

33
const CDropdownMenu = defineComponent({
44
name: 'CDropdownMenu',
@@ -14,13 +14,11 @@ const CDropdownMenu = defineComponent({
1414
},
1515
},
1616
setup(props, { slots }) {
17-
const dropdownToggleRef = inject('dropdownToggleRef') as Ref<HTMLElement>
1817
const dropdownMenuRef = inject('dropdownMenuRef') as Ref<HTMLElement>
1918
const config = inject('config') as any // eslint-disable-line @typescript-eslint/no-explicit-any
20-
const hideMenu = inject('hideMenu') as () => void
2119
const visible = inject('visible') as Ref<boolean>
2220

23-
const { autoClose, alignment, dark, popper } = config
21+
const { alignment, dark, popper } = config
2422

2523
// eslint-disable-next-line @typescript-eslint/ban-types, unicorn/consistent-function-scoping
2624
const alignmentClassNames = (alignment: object | string) => {
@@ -36,49 +34,6 @@ const CDropdownMenu = defineComponent({
3634
return classNames
3735
}
3836

39-
const handleKeyup = (event: KeyboardEvent) => {
40-
if (autoClose === false) {
41-
return
42-
}
43-
44-
if (event.key === 'Escape') {
45-
hideMenu()
46-
}
47-
}
48-
49-
const handleMouseUp = (event: Event) => {
50-
if (dropdownToggleRef.value?.contains(event.target as HTMLElement)) {
51-
return
52-
}
53-
54-
if (autoClose === true) {
55-
hideMenu()
56-
return
57-
}
58-
59-
if (autoClose === 'inside' && dropdownMenuRef.value?.contains(event.target as HTMLElement)) {
60-
hideMenu()
61-
return
62-
}
63-
64-
if (
65-
autoClose === 'outside' &&
66-
!dropdownMenuRef.value?.contains(event.target as HTMLElement)
67-
) {
68-
hideMenu()
69-
}
70-
}
71-
72-
onUpdated(() => {
73-
visible.value && window.addEventListener('mouseup', handleMouseUp)
74-
visible.value && window.addEventListener('keyup', handleKeyup)
75-
})
76-
77-
onUnmounted(() => {
78-
window.removeEventListener('mouseup', handleMouseUp)
79-
window.removeEventListener('keyup', handleKeyup)
80-
})
81-
8237
return () =>
8338
h(
8439
props.component,

packages/coreui-vue/src/components/dropdown/CDropdownToggle.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { cloneVNode, defineComponent, h, inject, onMounted, PropType, Ref, ref }
33
import { CButton } from '../button'
44

55
import { Color, Shape } from '../../props'
6-
import { Triggers } from '../../types'
6+
import type { Triggers } from '../../types'
77

88
const CDropdownToggle = defineComponent({
99
name: 'CDropdownToggle',
@@ -85,7 +85,7 @@ const CDropdownToggle = defineComponent({
8585
const dropdownToggleRef = inject('dropdownToggleRef') as Ref<HTMLElement>
8686
const dropdownVariant = inject('variant') as string
8787
const visible = inject('visible') as Ref<boolean>
88-
const toggleMenu = inject('toggleMenu') as (_visible?: boolean) => void
88+
const setVisible = inject('setVisible') as (_visible?: boolean) => void
8989

9090
const className = [
9191
{
@@ -103,7 +103,7 @@ const CDropdownToggle = defineComponent({
103103
return
104104
}
105105

106-
toggleMenu()
106+
setVisible()
107107
},
108108
}),
109109
...((props.trigger === 'focus' || props.trigger.includes('focus')) && {
@@ -112,13 +112,13 @@ const CDropdownToggle = defineComponent({
112112
return
113113
}
114114

115-
toggleMenu(true)
115+
setVisible(true)
116116
},
117117
onblur: () => {
118118
if (props.disabled) {
119119
return
120120
}
121-
toggleMenu(false)
121+
setVisible(false)
122122
},
123123
}),
124124
}

0 commit comments

Comments
 (0)