fix(components): [input-tag] paste multiple delimiters (#21256)

* fix(components): [input-tag] paste multiple delimiters

* chore: refact

* refactor: easier to read & import types

* chore: remove useless convert regex

Co-authored-by: btea <2356281422@qq.com>

* chore: fix

* chore: fix

* chore: same logic on paste & better tests

* chore: fix edge case

* chore: fix

* chore: add

---------

Co-authored-by: btea <2356281422@qq.com>
This commit is contained in:
Noblet Ouways
2025-07-08 11:02:13 +02:00
committed by GitHub
parent 6c8e4f4feb
commit 8a243036fe
4 changed files with 74 additions and 31 deletions

View File

@@ -137,15 +137,15 @@ input-tag/prefix-suffix
### Events
| Name | Description | Type |
| ---------- | --------------------------------------- | ---------------------------------------- |
| change | triggers when the modelValue change | ^[Function]`(value: string[]) => void` |
| input | triggers when the input value change | ^[Function]`(value: string) => void` |
| add-tag | triggers when a tag is added | ^[Function]`(value: string) => void` |
| remove-tag | triggers when a tag is removed | ^[Function]`(value: string) => void` |
| focus | triggers when InputTag focuses | ^[Function]`(event: FocusEvent) => void` |
| blur | triggers when InputTag blurs | ^[Function]`(event: FocusEvent) => void` |
| clear | triggers when the clear icon is clicked | ^[Function]`() => void` |
| Name | Description | Type |
| ---------- | --------------------------------------- | ------------------------------------------------- |
| change | triggers when the modelValue change | ^[Function]`(value: string[]) => void` |
| input | triggers when the input value change | ^[Function]`(value: string) => void` |
| add-tag | triggers when a tag is added | ^[Function]`(value: string \| string []) => void` |
| remove-tag | triggers when a tag is removed | ^[Function]`(value: string) => void` |
| focus | triggers when InputTag focuses | ^[Function]`(event: FocusEvent) => void` |
| blur | triggers when InputTag blurs | ^[Function]`(event: FocusEvent) => void` |
| clear | triggers when the clear icon is clicked | ^[Function]`() => void` |
### Slots

View File

@@ -233,26 +233,57 @@ describe('InputTag.vue', () => {
describe('delimiter', () => {
test('with string', async () => {
const inputValue = ref<string[]>()
const addTag = vi.fn()
const wrapper = mount(() => (
<InputTag v-model={inputValue.value} delimiter="," />
<InputTag v-model={inputValue.value} delimiter="," onAdd-tag={addTag} />
))
await wrapper.find('input').setValue(`${AXIOM},`)
expect(addTag).toBeCalledWith(AXIOM)
expect(wrapper.findAll('.el-tag').length).toBe(1)
expect(wrapper.find('.el-tag').text()).toBe(AXIOM)
expect(inputValue.value).toEqual([AXIOM])
})
test('with RegExp', async () => {
const inputValue = ref<string[]>()
const addTag = vi.fn()
const wrapper = mount(() => (
<InputTag v-model={inputValue.value} delimiter={/\./} />
<InputTag
v-model={inputValue.value}
delimiter={/\./}
onAdd-tag={addTag}
/>
))
await wrapper.find('input').setValue(`${AXIOM}.`)
expect(addTag).toBeCalledWith(AXIOM)
expect(wrapper.findAll('.el-tag').length).toBe(1)
expect(wrapper.find('.el-tag').text()).toBe(AXIOM)
expect(inputValue.value).toEqual([AXIOM])
})
test('paste multiple delimiter', async () => {
const inputValue = ref<string[]>()
const addTag = vi.fn()
const wrapper = mount(() => (
<InputTag
v-model={inputValue.value}
delimiter={/\./}
onAdd-tag={addTag}
/>
))
await wrapper
.find('input')
.setValue(`${AXIOM}.${AXIOM}.${AXIOM}.${AXIOM}.`)
const result = [AXIOM, AXIOM, AXIOM, AXIOM]
expect(wrapper.findAll('.el-tag').length).toBe(4)
expect(addTag).toBeCalledWith(result)
wrapper
.findAll('.el-tag')
.forEach((tag) => expect(tag.text()).toBe(AXIOM))
expect(inputValue.value).toEqual(result)
})
})
describe('events', () => {

View File

@@ -5,14 +5,12 @@ import {
INPUT_EVENT,
UPDATE_MODEL_EVENT,
} from '@element-plus/constants'
import { type EmitFn, debugWarn, isUndefined } from '@element-plus/utils'
import { debugWarn, ensureArray, isUndefined } from '@element-plus/utils'
import { useComposition, useFocusController } from '@element-plus/hooks'
import {
type FormItemContext,
useFormDisabled,
useFormSize,
} from '@element-plus/components/form'
import { useFormDisabled, useFormSize } from '@element-plus/components/form'
import type { EmitFn } from '@element-plus/utils'
import type { FormItemContext } from '@element-plus/components/form'
import type { InputTagEmits, InputTagProps } from '../input-tag'
interface UseInputTagOptions {
@@ -41,6 +39,26 @@ export function useInputTag({ props, emit, formItem }: UseInputTagOptions) {
: (props.modelValue?.length ?? 0) >= props.max
})
const addTagsEmit = (value: string | string[]) => {
const list = [...(props.modelValue ?? []), ...ensureArray(value)]
emit(UPDATE_MODEL_EVENT, list)
emit(CHANGE_EVENT, list)
emit('add-tag', value)
inputValue.value = undefined
}
const getDelimitedTags = (input: string) => {
const tags = input
.split(props.delimiter)
.filter((val) => val && val !== input)
if (props.max) {
const maxInsert = props.max - (props.modelValue?.length ?? 0)
tags.splice(maxInsert)
}
return tags.length === 1 ? tags[0] : tags
}
const handleInput = (event: Event) => {
if (inputLimit.value) {
inputValue.value = undefined
@@ -48,11 +66,10 @@ export function useInputTag({ props, emit, formItem }: UseInputTagOptions) {
}
if (isComposing.value) return
if (props.delimiter) {
const replacement = inputValue.value?.replace(props.delimiter, '')
if (replacement?.length !== inputValue.value?.length) {
inputValue.value = replacement
handleAddTag()
if (props.delimiter && inputValue.value) {
const tags = getDelimitedTags(inputValue.value)
if (tags.length) {
addTagsEmit(tags)
}
}
emit(INPUT_EVENT, (event.target as HTMLInputElement).value)
@@ -86,12 +103,7 @@ export function useInputTag({ props, emit, formItem }: UseInputTagOptions) {
const handleAddTag = () => {
const value = inputValue.value?.trim()
if (!value || inputLimit.value) return
const list = [...(props.modelValue ?? []), value]
emit(UPDATE_MODEL_EVENT, list)
emit(CHANGE_EVENT, list)
emit('add-tag', value)
inputValue.value = undefined
addTagsEmit(value)
}
const handleRemoveTag = (index: number) => {

View File

@@ -140,7 +140,7 @@ export const inputTagEmits = {
isArray(value) || isUndefined(value),
[CHANGE_EVENT]: (value?: string[]) => isArray(value) || isUndefined(value),
[INPUT_EVENT]: (value: string) => isString(value),
'add-tag': (value: string) => isString(value),
'add-tag': (value: string | string[]) => isString(value) || isArray(value),
'remove-tag': (value: string) => isString(value),
focus: (evt: FocusEvent) => evt instanceof FocusEvent,
blur: (evt: FocusEvent) => evt instanceof FocusEvent,