fix(channels): allow tabbing out of an empty model input

This commit is contained in:
chihao.ou
2026-09-16 18:19:41 +08:00
parent 881f320269
commit f8e5a0d94f
2 changed files with 46 additions and 1 deletions
@@ -24,7 +24,7 @@
class="flex-1 min-w-[120px] border-none bg-transparent text-sm outline-none placeholder:text-gray-400 dark:text-white"
:placeholder="models.length === 0 ? placeholder : ''"
@keydown.enter.prevent="addModel"
@keydown.tab.prevent="addModel"
@keydown.tab="handleTab"
@keydown.delete="handleBackspace"
@paste="handlePaste"
@blur="addModel"
@@ -66,6 +66,12 @@ function addModel() {
inputValue.value = ''
}
function handleTab(event: KeyboardEvent) {
if (!inputValue.value.trim()) return
event.preventDefault()
addModel()
}
function removeModel(idx: number) {
const newModels = [...props.models]
newModels.splice(idx, 1)
@@ -0,0 +1,39 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { enableAutoUnmount, mount } from '@vue/test-utils'
import { nextTick } from 'vue'
import ModelTagInput from '../ModelTagInput.vue'
vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: (key: string) => key }) }))
enableAutoUnmount(afterEach)
function pressTab(input: Element, shiftKey = false) {
const event = new KeyboardEvent('keydown', { key: 'Tab', shiftKey, bubbles: true, cancelable: true })
input.dispatchEvent(event)
return event
}
describe('model tag keyboard navigation', () => {
it.each([false, true])('allows leaving an empty input with Tab (shift: %s)', (shift) => {
const wrapper = mount(ModelTagInput, { props: { models: ['gpt-4o'] } })
expect(pressTab(wrapper.get('input').element, shift).defaultPrevented).toBe(false)
expect(wrapper.emitted('update:models')).toBeUndefined()
})
it('allows leaving a whitespace-only input', async () => {
const wrapper = mount(ModelTagInput, { props: { models: [] } })
await wrapper.get('input').setValue(' ')
expect(pressTab(wrapper.get('input').element).defaultPrevented).toBe(false)
expect(wrapper.emitted('update:models')).toBeUndefined()
})
it('still commits a pending model with Tab, then allows the next Tab to leave', async () => {
const wrapper = mount(ModelTagInput, { props: { models: ['gpt-4o'] } })
const input = wrapper.get('input')
await input.setValue('gpt-4.1')
expect(pressTab(input.element).defaultPrevented).toBe(true)
await nextTick()
expect(wrapper.emitted('update:models')).toEqual([[['gpt-4o', 'gpt-4.1']]])
expect((input.element as HTMLInputElement).value).toBe('')
expect(pressTab(input.element).defaultPrevented).toBe(false)
})
})