From 144999d52dd4500f7684b97b18f6e5f05b1f4cd2 Mon Sep 17 00:00:00 2001 From: zernonia <59365435+zernonia@users.noreply.github.com> Date: Tue, 5 Nov 2024 06:18:20 +0800 Subject: [PATCH] feat(TagsInput): support multiple delimiters via RegExp (#1414) * feat(TagsInput): support multiple delimiters via RegExp (#1400) * feat(TagsInput): use `replace` instead of `replaceAll` to avoid the need for RegExp global flag * feat(TagsInput): update description for delimiter prop * feat(TagsInput): update docs, add multiple delimiters example * feat(TagsInput): add test cases for delimiter prop * chore: fix test --------- Co-authored-by: Raman Paulau --- docs/content/docs/components/tags-input.md | 25 ++++++- packages/core/src/TagsInput/TagsInput.test.ts | 71 +++++++++++++++++++ .../core/src/TagsInput/TagsInputInput.vue | 8 ++- packages/core/src/TagsInput/TagsInputRoot.vue | 6 +- 4 files changed, 104 insertions(+), 6 deletions(-) diff --git a/docs/content/docs/components/tags-input.md b/docs/content/docs/components/tags-input.md index f75dbcc5e..c68f8b51f 100644 --- a/docs/content/docs/components/tags-input.md +++ b/docs/content/docs/components/tags-input.md @@ -165,7 +165,7 @@ You can compose Tags input together with [Combobox](../components/combobox.html) You can automatically add tags on paste by passing the `add-on-paste` prop. -```vue line=6 +```vue line=8 @@ -180,6 +180,29 @@ import { TagsInputInput, TagsInputItem, TagsInputItemDelete, TagsInputItemText, ``` +### Multiple delimiters + +You can pass `RegExp` as `delimiter` to allow multiple characters to trigger addition of a new tag. When `add-on-paste` is passed it will be also used to split tags for `@paste` event. + +```vue line=4-5,11 + + + +``` + ## Accessibility ### Keyboard Interactions diff --git a/packages/core/src/TagsInput/TagsInput.test.ts b/packages/core/src/TagsInput/TagsInput.test.ts index 6e877d525..083e1bb5a 100644 --- a/packages/core/src/TagsInput/TagsInput.test.ts +++ b/packages/core/src/TagsInput/TagsInput.test.ts @@ -5,6 +5,7 @@ import TagsInputObject from './story/_TagsInputObject.vue' import type { DOMWrapper, VueWrapper } from '@vue/test-utils' import { mount } from '@vue/test-utils' import { nextTick } from 'vue' +import userEvent from '@testing-library/user-event' describe('given default TagsInput', () => { // @ts-expect-error we return empty object @@ -232,4 +233,74 @@ describe('given a TagsInput with objects', async () => { expect(consoleWarnMockFunction).toHaveBeenCalledOnce() expect(consoleWarnMockFunction).toHaveBeenLastCalledWith('You must provide a `convertValue` function when using objects as values.') }) + + describe('given a TagsInput with delimiter', async () => { + const setupDelimiter = (delimiter: string | RegExp) => { + const wrapper = mount(TagsInput, { + props: { + delimiter, + addOnPaste: true, + }, + attachTo: document.body, + }) + + const input = wrapper.find('input') + + return { + wrapper, + input, + } + } + + it('should add tag on typing single delimiter character', async () => { + const { wrapper, input } = setupDelimiter(',') + const user = userEvent.setup() + + await user.type(input.element, 'tag1,') + + const tags = wrapper.findAll('[data-reka-collection-item]') + expect(tags[1].text()).toBe('tag1') + }) + + it('should add tag on typing multiple delimiter characters', async () => { + const { wrapper, input } = setupDelimiter(/[ ,;]+/) + const user = userEvent.setup() + + await user.type(input.element, 'tag1,') + await user.type(input.element, 'tag2 ') + await user.type(input.element, 'tag3;') + + const tags = wrapper.findAll('[data-reka-collection-item]') + expect(tags[1].text()).toBe('tag1') + expect(tags[2].text()).toBe('tag2') + expect(tags[3].text()).toBe('tag3') + }) + + it('should add multiple tags on pasting text with single delimiter character', async () => { + const { wrapper, input } = setupDelimiter(',') + const user = userEvent.setup() + + await user.click(input.element) + await user.paste('tag1,tag2,tag3') + + const tags = wrapper.findAll('[data-reka-collection-item]') + expect(tags[1].text()).toBe('tag1') + expect(tags[2].text()).toBe('tag2') + expect(tags[3].text()).toBe('tag3') + }) + + it('should add multiple tags on pasting text with multiple delimiter characters', async () => { + const { wrapper, input } = setupDelimiter(/[ ,;]+/) + const user = userEvent.setup() + + await user.click(input.element) + await user.paste('tag1, tag2;tag3 tag4') + + const tags = wrapper.findAll('[data-reka-collection-item]') + expect(tags[1].text()).toBe('tag1') + expect(tags[2].text()).toBe('tag2') + expect(tags[3].text()).toBe('tag3') + expect(tags[4].text()).toBe('tag4') + }) + }) }) diff --git a/packages/core/src/TagsInput/TagsInputInput.vue b/packages/core/src/TagsInput/TagsInputInput.vue index b8994a294..54ed83e06 100644 --- a/packages/core/src/TagsInput/TagsInputInput.vue +++ b/packages/core/src/TagsInput/TagsInputInput.vue @@ -64,10 +64,14 @@ async function handleCustomKeydown(event: Event) { function handleInput(event: InputEvent) { context.isInvalidInput.value = false + if (event.data === null) + return + const delimiter = context.delimiter.value - if (delimiter === event.data) { + const matchesDelimiter = delimiter === event.data || (delimiter instanceof RegExp && delimiter.test(event.data)) + if (matchesDelimiter) { const target = event.target as HTMLInputElement - target.value = target.value.replaceAll(delimiter, '') + target.value = target.value.replace(delimiter, '') const isAdded = context.onAddValue(target.value) if (isAdded) diff --git a/packages/core/src/TagsInput/TagsInputRoot.vue b/packages/core/src/TagsInput/TagsInputRoot.vue index 305669c00..1d419f92c 100644 --- a/packages/core/src/TagsInput/TagsInputRoot.vue +++ b/packages/core/src/TagsInput/TagsInputRoot.vue @@ -21,8 +21,8 @@ export interface TagsInputRootProps extends PrimitiveP duplicate?: boolean /** When `true`, prevents the user from interacting with the tags input. */ disabled?: boolean - /** The character to trigger the addition of a new tag. Also used to split tags for `@paste` event */ - delimiter?: string + /** The character or regular expression to trigger the addition of a new tag. Also used to split tags for `@paste` event */ + delimiter?: string | RegExp /** The reading direction of the combobox when applicable.
If omitted, inherits globally from `ConfigProvider` or assumes LTR (left-to-right) reading mode. */ dir?: Direction /** Maximum number of tags. */ @@ -52,7 +52,7 @@ export interface TagsInputRootContext { addOnTab: Ref addOnBlur: Ref disabled: Ref - delimiter: Ref + delimiter: Ref dir: Ref max: Ref id: Ref | undefined