Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 6286039

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

File tree

3 files changed

+106
-113
lines changed
Filter options

3 files changed

+106
-113
lines changed

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

Copy file name to clipboardExpand all lines: packages/coreui-vue/src/components/dropdown/CDropdown.ts
+99-61Lines 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

Copy file name to clipboardExpand all lines: packages/coreui-vue/src/components/dropdown/CDropdownMenu.ts
+2-47Lines 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

Copy file name to clipboardExpand all lines: packages/coreui-vue/src/components/dropdown/CDropdownToggle.ts
+5-5Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.