Skip to content

Commit

Permalink
feat(TagsInput): add tab or blur event in tags input (unovue#762)
Browse files Browse the repository at this point in the history
* docs(tags-input): add docs on new props

* feat(add-on-tags): create new props 'addOnTab' and 'addOnBlur'

* feat(add-on-tags): create blur event handler and tab event handler

handleEnter was renamed to handleCustomKeydown, because tab handler and enter handler are the same

* test(tags-input): add props for testing

* test(tags-input): add tests for add on blur and add on tab

---------

Co-authored-by: Slava Cherkesov <[email protected]>
  • Loading branch information
Chrtyaka and Slava Cherkesov authored Mar 18, 2024
1 parent 8b0e49e commit 32bc943
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 4 deletions.
12 changes: 12 additions & 0 deletions docs/content/meta/TagsInputRoot.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@
'type': 'boolean',
'required': false
},
{
'name': 'addOnTab',
'description': '<p>When <code>true</code>, allow adding tags on keydown <code> tab </code>. Work in conjunction with delimiter prop.</p>\n',
'type': 'boolean',
'required': false
},
{
'name': 'addOnBlur',
'description': '<p>When <code>true</code>, allow adding tags on input <code> blur </code> event.\n',
'type': 'boolean',
'required': false
},
{
'name': 'as',
'description': '<p>The element or component this component should render as. Can be overwrite by <code>asChild</code></p>\n',
Expand Down
48 changes: 48 additions & 0 deletions packages/radix-vue/src/TagsInput/TagsInput.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ describe('given default TagsInput', () => {
let wrapper: VueWrapper<InstanceType<typeof TagsInput>>
let input: DOMWrapper<HTMLInputElement>
let tags: DOMWrapper<HTMLElement>[]

beforeEach(() => {
wrapper = mount(TagsInput, { attachTo: document.body })
tags = wrapper.findAll('[data-radix-vue-collection-item]')
Expand Down Expand Up @@ -116,4 +117,51 @@ describe('given default TagsInput', () => {
})
})
})

describe('adding values on user actions', () => {
const setValueInInput = (value: string) => {
input = wrapper.find('input')
input.element.focus()
return input.setValue(value)
}

it('should add value on keydown:enter', async () => {
const tag = 'tag:enter'

await setValueInInput(tag)

await input.trigger('keydown.enter')

tags = wrapper.findAll('[data-radix-vue-collection-item]')

expect(wrapper.html()).toContain(tag)
expect(tags[1].text()).toBe(tag)
})

it('should add value on keydown:tab', async () => {
const tag = 'tag:tab'
await wrapper.setProps({ addOnTab: true })
await setValueInInput(tag)

await input.trigger('keydown.tab')

tags = wrapper.findAll('[data-radix-vue-collection-item]')

expect(wrapper.html()).toContain(tag)
expect(tags[1].text()).toBe(tag)
})

it('should add value on blur', async () => {
const tag = 'tag:blur'
await wrapper.setProps({ addOnBlur: true })
await setValueInInput(tag)

await input.trigger('blur')

tags = wrapper.findAll('[data-radix-vue-collection-item]')

expect(wrapper.html()).toContain(tag)
expect(tags[1].text()).toBe(tag)
})
})
})
28 changes: 25 additions & 3 deletions packages/radix-vue/src/TagsInput/TagsInputInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,29 @@ const props = withDefaults(defineProps<TagsInputInputProps>(), {
const context = injectTagsInputRootContext()
const { forwardRef, currentElement } = useForwardExpose()
async function handleEnter(event: Event) {
function handleBlur(event: Event) {
if (!context.addOnBlur.value)
return
const target = event.target as HTMLInputElement
if (!target.value)
return
const isAdded = context.onAddValue(target.value)
if (isAdded)
target.value = ''
}
function handleTab(event: Event) {
if (!context.addOnTab.value)
return
handleCustomKeydown(event)
}
async function handleCustomKeydown(event: Event) {
await nextTick()
// if keydown 'Enter' was prevented, we let user handle updating the value themselves
// if keydown 'Enter' or `Tab` was prevented, we let user handle updating the value themselves
if (event.defaultPrevented)
return
Expand Down Expand Up @@ -106,7 +126,9 @@ onMounted(() => {
:disabled="context.disabled.value"
:data-invalid="context.isInvalidInput.value ? '' : undefined"
@input="handleInput"
@keydown.enter="handleEnter"
@keydown.enter="handleCustomKeydown"
@keydown.tab="handleTab"
@blur="handleBlur"
@keydown="context.onInputKeydown"
@paste="handlePaste"
>
Expand Down
10 changes: 9 additions & 1 deletion packages/radix-vue/src/TagsInput/TagsInputRoot.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ export interface TagsInputRootProps extends PrimitiveProps {
defaultValue?: Array<string>
/** When `true`, allow adding tags on paste. Work in conjunction with delimiter prop. */
addOnPaste?: boolean
/** When `true` allow adding tags on tab keydown */
addOnTab?: boolean
/** When `true` allow adding tags blur input */
addOnBlur?: boolean
/** When `true`, allow duplicated tags. */
duplicate?: boolean
/** When `true`, prevents the user from interacting with the tags input. */
Expand Down Expand Up @@ -43,6 +47,8 @@ export interface TagsInputRootContext {
selectedElement: Ref<HTMLElement | undefined>
isInvalidInput: Ref<boolean>
addOnPaste: Ref<boolean>
addOnTab: Ref<boolean>
addOnBlur: Ref<boolean>
disabled: Ref<boolean>
delimiter: Ref<string>
dir: Ref<Direction>
Expand Down Expand Up @@ -74,7 +80,7 @@ defineSlots<{
}): any
}>()
const { addOnPaste, disabled, delimiter, max, id, dir: propDir } = toRefs(props)
const { addOnPaste, disabled, delimiter, max, id, dir: propDir, addOnBlur, addOnTab } = toRefs(props)
const dir = useDirection(propDir)
const modelValue = useVModel(props, 'modelValue', emits, {
Expand Down Expand Up @@ -191,6 +197,8 @@ provideTagsInputRootContext({
selectedElement,
isInvalidInput,
addOnPaste,
addOnBlur,
addOnTab,
dir,
disabled,
delimiter,
Expand Down
12 changes: 12 additions & 0 deletions packages/radix-vue/src/TagsInput/story/_TagsInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,24 @@ import { ref } from 'vue'
import { TagsInputInput, TagsInputItem, TagsInputItemDelete, TagsInputItemText, TagsInputRoot } from '..'
import { Icon } from '@iconify/vue'
export interface TestProps {
addOnTab?: boolean
addOnBlur: boolean
}
withDefaults(defineProps<TestProps>(), {
addOnBlur: false,
addOnTab: false,
})
const modelValue = ref(['Test'])
</script>

<template>
<TagsInputRoot
v-model="modelValue"
:add-on-blur="addOnBlur"
:add-on-tab="addOnTab"
class="flex gap-2 items-center border p-2 rounded-lg bg-blackA7 w-[300px] flex-wrap border-blackA7"
>
<TagsInputItem v-for="item in modelValue" :key="item" :value="item" class="flex items-center justify-center gap-2 bg-green8 aria-[selected=true]:bg-green9 rounded px-2 py-1">
Expand Down

0 comments on commit 32bc943

Please sign in to comment.