forked from noxious/client
Fix chips input component
This commit is contained in:
parent
63758e67b3
commit
736ddddc54
@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-wrap items-center input-field gap-1">
|
<div class="flex flex-wrap items-center input-field gap-1" @click="focusInput">
|
||||||
<div v-for="(chip, i) in internalValue" :key="i" class="flex gap-2.5 items-center bg-cyan rounded py-1 px-2">
|
<div v-for="(chip, i) in internalValue" :key="i" class="flex gap-2.5 items-center bg-cyan rounded py-1 px-2" role="listitem">
|
||||||
<span class="text-xs text-white">{{ chip }}</span>
|
<span class="text-xs text-white">{{ chip }}</span>
|
||||||
<button type="button" class="text-xs cursor-pointer text-white font-light font-default not-italic hover:text-gray-50" @click="deleteChip(i)" aria-label="Remove chip">×</button>
|
<button type="button" class="text-xs cursor-pointer text-white font-light font-default not-italic hover:text-gray-50" @click.stop="deleteChip(i)" aria-label="Remove tag">×</button>
|
||||||
</div>
|
</div>
|
||||||
<input class="outline-none border-none p-1 text-gray-300" placeholder="Tag name" v-model="currentInput" @keypress.enter.prevent="addChip" @keydown.backspace="handleBackspace" />
|
<input ref="inputRef" class="outline-none border-none p-1 text-gray-300 min-w-[60px] flex-grow" :placeholder="placeholder" v-model.trim="currentInput" @keydown="handleKeydown" @paste="handlePaste" :maxlength="maxChipLength" aria-label="Add new tag" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -14,20 +14,29 @@ import type { Ref } from 'vue'
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
modelValue?: string[]
|
modelValue?: string[]
|
||||||
|
maxChips?: number
|
||||||
|
maxChipLength?: number
|
||||||
|
placeholder?: string
|
||||||
|
allowDuplicates?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
modelValue: () => []
|
modelValue: () => [],
|
||||||
|
maxChips: 10,
|
||||||
|
maxChipLength: 20,
|
||||||
|
placeholder: 'Add tag',
|
||||||
|
allowDuplicates: false
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'update:modelValue', value: string[]): void
|
(e: 'update:modelValue', value: string[]): void
|
||||||
|
(e: 'error', message: string): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const currentInput: Ref<string> = ref('')
|
const currentInput: Ref<string> = ref('')
|
||||||
const internalValue = ref<string[]>([])
|
const internalValue = ref<string[]>([])
|
||||||
|
const inputRef = ref<HTMLInputElement | null>(null)
|
||||||
|
|
||||||
// Initialize internalValue with props.modelValue
|
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
@ -36,9 +45,27 @@ watch(
|
|||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const validateChip = (chip: string): boolean => {
|
||||||
|
if (!chip) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.allowDuplicates && internalValue.value.includes(chip)) {
|
||||||
|
emit('error', 'Duplicate tags are not allowed')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (internalValue.value.length >= props.maxChips) {
|
||||||
|
emit('error', `Maximum ${props.maxChips} tags allowed`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
const addChip = () => {
|
const addChip = () => {
|
||||||
const trimmedInput = currentInput.value.trim()
|
const trimmedInput = currentInput.value.trim()
|
||||||
if (trimmedInput && !internalValue.value.includes(trimmedInput)) {
|
if (validateChip(trimmedInput)) {
|
||||||
internalValue.value.push(trimmedInput)
|
internalValue.value.push(trimmedInput)
|
||||||
emit('update:modelValue', internalValue.value)
|
emit('update:modelValue', internalValue.value)
|
||||||
currentInput.value = ''
|
currentInput.value = ''
|
||||||
@ -50,10 +77,36 @@ const deleteChip = (index: number) => {
|
|||||||
emit('update:modelValue', internalValue.value)
|
emit('update:modelValue', internalValue.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleBackspace = (event: KeyboardEvent) => {
|
const handleKeydown = (event: KeyboardEvent) => {
|
||||||
if (event.key === 'Backspace' && currentInput.value === '' && internalValue.value.length > 0) {
|
switch (event.key) {
|
||||||
internalValue.value.pop()
|
case 'Enter':
|
||||||
emit('update:modelValue', internalValue.value)
|
event.preventDefault()
|
||||||
|
addChip()
|
||||||
|
break
|
||||||
|
case 'Backspace':
|
||||||
|
if (currentInput.value === '' && internalValue.value.length > 0) {
|
||||||
|
deleteChip(internalValue.value.length - 1)
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handlePaste = (event: ClipboardEvent) => {
|
||||||
|
event.preventDefault()
|
||||||
|
const pastedText = event.clipboardData?.getData('text')
|
||||||
|
if (pastedText) {
|
||||||
|
const chips = pastedText
|
||||||
|
.split(/[,\n]/)
|
||||||
|
.map((chip) => chip.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
chips.forEach((chip) => {
|
||||||
|
currentInput.value = chip
|
||||||
|
addChip()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const focusInput = () => {
|
||||||
|
inputRef.value?.focus()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user