Skip to content

Commit 61ee9b7

Browse files
committed
refactor(CDropdown): improve arrow keys handling
1 parent 67b6f9a commit 61ee9b7

File tree

2 files changed

+42
-25
lines changed

2 files changed

+42
-25
lines changed

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

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defineComponent, h, ref, provide, watch, PropType } from 'vue'
1+
import { defineComponent, h, ref, provide, watch, PropType, onUnmounted, nextTick } from 'vue'
22
import type { Placement } from '@popperjs/core'
33

44
import { usePopper } from '../../composables'
@@ -158,6 +158,7 @@ const CDropdown = defineComponent({
158158
setup(props, { slots, emit }) {
159159
const dropdownToggleRef = ref()
160160
const dropdownMenuRef = ref()
161+
const pendingKeyDownEventRef = ref<KeyboardEvent | null>(null)
161162
const popper = ref(typeof props.alignment === 'object' ? false : props.popper)
162163
const visible = ref(props.visible)
163164

@@ -176,35 +177,50 @@ const CDropdown = defineComponent({
176177
props.placement,
177178
props.direction,
178179
props.alignment,
179-
isRTL(dropdownMenuRef.value),
180+
isRTL(dropdownMenuRef.value)
180181
) as Placement,
181182
}
182183

183184
watch(
184185
() => props.visible,
185186
() => {
186187
visible.value = props.visible
187-
},
188+
}
188189
)
189190

190191
watch(visible, () => {
191192
if (visible.value && dropdownToggleRef.value && dropdownMenuRef.value) {
192-
popper.value && initPopper(dropdownToggleRef.value, dropdownMenuRef.value, popperConfig)
193+
if (popper.value) {
194+
initPopper(dropdownToggleRef.value, dropdownMenuRef.value, popperConfig)
195+
}
196+
193197
window.addEventListener('mouseup', handleMouseUp)
194198
window.addEventListener('keyup', handleKeyup)
195199
dropdownToggleRef.value.addEventListener('keydown', handleKeydown)
196200
dropdownMenuRef.value.addEventListener('keydown', handleKeydown)
201+
202+
if (pendingKeyDownEventRef.value) {
203+
nextTick(() => {
204+
handleKeydown(pendingKeyDownEventRef.value as KeyboardEvent)
205+
pendingKeyDownEventRef.value = null
206+
})
207+
}
208+
197209
emit('show')
198210
return
199211
}
200212

201213
popper.value && destroyPopper()
202214
window.removeEventListener('mouseup', handleMouseUp)
203215
window.removeEventListener('keyup', handleKeyup)
216+
dropdownMenuRef.value && dropdownMenuRef.value.removeEventListener('keydown', handleKeydown)
217+
emit('hide')
218+
})
219+
220+
onUnmounted(() => {
204221
dropdownToggleRef.value &&
205222
dropdownToggleRef.value.removeEventListener('keydown', handleKeydown)
206223
dropdownMenuRef.value && dropdownMenuRef.value.removeEventListener('keydown', handleKeydown)
207-
emit('hide')
208224
})
209225

210226
provide('config', {
@@ -219,18 +235,14 @@ const CDropdown = defineComponent({
219235
provide('visible', visible)
220236
provide('dropdownToggleRef', dropdownToggleRef)
221237
provide('dropdownMenuRef', dropdownMenuRef)
238+
provide('pendingKeyDownEventRef', pendingKeyDownEventRef)
222239

223240
const handleKeydown = (event: KeyboardEvent) => {
224-
if (
225-
visible.value &&
226-
dropdownMenuRef.value &&
227-
(event.key === 'ArrowDown' || event.key === 'ArrowUp')
228-
) {
241+
if (dropdownMenuRef.value && (event.key === 'ArrowDown' || event.key === 'ArrowUp')) {
229242
event.preventDefault()
230243
const target = event.target as HTMLElement
231-
// eslint-disable-next-line unicorn/prefer-spread
232244
const items: HTMLElement[] = Array.from(
233-
dropdownMenuRef.value.querySelectorAll('.dropdown-item:not(.disabled):not(:disabled)'),
245+
dropdownMenuRef.value.querySelectorAll('.dropdown-item:not(.disabled):not(:disabled)')
234246
)
235247
getNextActiveElement(items, target, event.key === 'ArrowDown', true).focus()
236248
}
@@ -243,6 +255,7 @@ const CDropdown = defineComponent({
243255

244256
if (event.key === 'Escape') {
245257
setVisible(false)
258+
dropdownToggleRef.value?.focus()
246259
}
247260
}
248261

@@ -267,22 +280,20 @@ const CDropdown = defineComponent({
267280
}
268281
}
269282

270-
const setVisible = (_visible?: boolean) => {
283+
const setVisible = (_visible?: boolean, event?: KeyboardEvent) => {
271284
if (props.disabled) {
272285
return
273286
}
274287

275-
if (typeof _visible == 'boolean') {
288+
if (typeof _visible === 'boolean') {
289+
if (event) {
290+
pendingKeyDownEventRef.value = event || null
291+
}
292+
276293
visible.value = _visible
277-
return
278-
}
279294

280-
if (visible.value === true) {
281-
visible.value = false
282295
return
283296
}
284-
285-
visible.value = true
286297
}
287298

288299
provide('setVisible', setVisible)
@@ -298,11 +309,11 @@ const CDropdown = defineComponent({
298309
props.direction === 'center'
299310
? 'dropdown-center'
300311
: props.direction === 'dropup-center'
301-
? 'dropup dropup-center'
302-
: props.direction,
312+
? 'dropup dropup-center'
313+
: props.direction,
303314
],
304315
},
305-
slots.default && slots.default(),
316+
slots.default && slots.default()
306317
)
307318
},
308319
})

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ const CDropdownToggle = defineComponent({
100100
const dropdownToggleRef = inject('dropdownToggleRef') as Ref<HTMLElement>
101101
const dropdownVariant = inject('variant') as string
102102
const visible = inject('visible') as Ref<boolean>
103-
const setVisible = inject('setVisible') as (_visible?: boolean) => void
103+
const setVisible = inject('setVisible') as (_visible?: boolean, event?: KeyboardEvent) => void
104104

105105
const triggers = {
106106
...((props.trigger === 'click' || props.trigger.includes('click')) && {
@@ -110,7 +110,7 @@ const CDropdownToggle = defineComponent({
110110
return
111111
}
112112

113-
setVisible()
113+
setVisible(!visible.value)
114114
},
115115
}),
116116
...((props.trigger === 'focus' || props.trigger.includes('focus')) && {
@@ -128,6 +128,12 @@ const CDropdownToggle = defineComponent({
128128
setVisible(false)
129129
},
130130
}),
131+
onkeydown: (event: KeyboardEvent) => {
132+
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
133+
event.preventDefault()
134+
setVisible(true, event)
135+
}
136+
}
131137
}
132138

133139
const togglerProps = computed(() => {

0 commit comments

Comments
 (0)