mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 12:02:27 +08:00
fix(ui): focus non-searchable select dropdowns
This commit is contained in:
@@ -55,6 +55,7 @@
|
||||
:class="[instanceId]"
|
||||
:style="dropdownStyle"
|
||||
role="listbox"
|
||||
tabindex="-1"
|
||||
@click.stop
|
||||
@mousedown.stop
|
||||
@keydown="onDropdownKeyDown"
|
||||
@@ -384,6 +385,8 @@ watch(isOpen, (open) => {
|
||||
|
||||
if (isSearchable.value) {
|
||||
nextTick(() => searchInputRef.value?.focus())
|
||||
} else {
|
||||
nextTick(() => dropdownRef.value?.focus())
|
||||
}
|
||||
// Add scroll listener to update position
|
||||
window.addEventListener('scroll', updateTriggerRect, { capture: true, passive: true })
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { nextTick } from 'vue'
|
||||
import Select from '../Select.vue'
|
||||
|
||||
vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: (key: string) => key }) }))
|
||||
let wrapper: ReturnType<typeof mount>
|
||||
afterEach(() => { wrapper?.unmount(); document.body.innerHTML = '' })
|
||||
|
||||
async function press(key: string) {
|
||||
document.activeElement!.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true, cancelable: true }))
|
||||
await nextTick()
|
||||
}
|
||||
|
||||
async function open(searchable: boolean | 'auto' = 'auto') {
|
||||
wrapper = mount(Select, {
|
||||
attachTo: document.body,
|
||||
props: { modelValue: 'alpha', searchable, options: [
|
||||
{ value: 'alpha', label: 'Alpha' },
|
||||
{ value: 'disabled', label: 'Disabled', disabled: true },
|
||||
{ value: 'beta', label: 'Beta' },
|
||||
] },
|
||||
})
|
||||
wrapper.get<HTMLButtonElement>('button').element.focus()
|
||||
await press('ArrowDown')
|
||||
await nextTick()
|
||||
}
|
||||
|
||||
describe('Select keyboard focus', () => {
|
||||
it.each([false, 'auto'] as const)('selects an enabled option without a search input (%s)', async (searchable) => {
|
||||
await open(searchable)
|
||||
expect(document.activeElement).toBe(document.querySelector('[role="listbox"]'))
|
||||
await press('ArrowDown')
|
||||
await press('Enter')
|
||||
expect(wrapper.emitted('update:modelValue')).toEqual([['beta']])
|
||||
expect(wrapper.get('button').attributes('aria-expanded')).toBe('false')
|
||||
expect(document.activeElement).toBe(wrapper.get('button').element)
|
||||
})
|
||||
|
||||
it('allows Escape to close a non-searchable dropdown and restore trigger focus', async () => {
|
||||
await open(false)
|
||||
await press('Escape')
|
||||
expect(wrapper.get('button').attributes('aria-expanded')).toBe('false')
|
||||
expect(document.activeElement).toBe(wrapper.get('button').element)
|
||||
expect(wrapper.emitted('update:modelValue')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('keeps focus in the search input when search is enabled', async () => {
|
||||
await open(true)
|
||||
expect(document.activeElement).toBe(document.querySelector('.select-search-input'))
|
||||
await press('ArrowDown')
|
||||
await press('Enter')
|
||||
expect(wrapper.emitted('update:modelValue')).toEqual([['beta']])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user