refactor: TagsSelectizeItem typing

This commit is contained in:
axolotle 2024-07-05 17:19:51 +02:00
parent 2977ed4d7a
commit 0f8fbd4830
2 changed files with 60 additions and 53 deletions

View file

@ -1,7 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
// FIXME addTag removeTag types
import type { BDropdown, BFormInput } from 'bootstrap-vue-next' import type { BDropdown, BFormInput } from 'bootstrap-vue-next'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import type {
BaseItemComputedProps,
TagsSelectizeItemProps,
} from '@/types/form'
type TagUpdateArgs = { type TagUpdateArgs = {
action: 'add' | 'remove' action: 'add' | 'remove'
@ -14,30 +19,23 @@ defineOptions({
}) })
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<TagsSelectizeItemProps & BaseItemComputedProps<string[]>>(),
modelValue: string[]
// FIXME typing
options: string[]
id: string
placeholder?: string
limit?: number
name?: string
itemsName: string
disabledItems?: string[]
auto?: boolean
noTags?: boolean
label?: string
tagIcon?: string
}>(),
{ {
placeholder: undefined, id: undefined,
limit: undefined,
name: undefined, name: undefined,
disabledItems: () => [], placeholder: undefined,
touchKey: undefined,
auto: false, auto: false,
noTags: false, disabledItems: undefined,
label: undefined, label: undefined,
limit: undefined,
noTags: false,
tagIcon: undefined, tagIcon: undefined,
ariaDescribedby: undefined,
modelValue: undefined,
state: undefined,
validation: undefined,
}, },
) )
@ -46,18 +44,21 @@ const emit = defineEmits<{
'tag-update': [value: TagUpdateArgs] 'tag-update': [value: TagUpdateArgs]
}>() }>()
const search = ref('') const model = defineModel<string[]>()
const searchElem = ref<InstanceType<typeof BDropdown> | null>(null) const searchElem = ref<InstanceType<typeof BDropdown> | null>(null)
const dropdownElem = ref<InstanceType<typeof BFormInput> | null>(null) const dropdownElem = ref<InstanceType<typeof BFormInput> | null>(null)
const { t } = useI18n()
const search = ref('')
const criteria = computed(() => { const criteria = computed(() => {
return search.value.trim().toLowerCase() return search.value.trim().toLowerCase()
}) })
const availableOptions = computed(() => { const availableOptions = computed(() => {
const options = props.options.filter((opt) => { const options = props.options.filter((opt) => {
return ( return (
props.modelValue.indexOf(opt) === -1 && !props.disabledItems.includes(opt) props.modelValue?.indexOf(opt) === -1 &&
props.disabledItems?.includes(opt)
) )
}) })
if (criteria.value) { if (criteria.value) {
@ -67,7 +68,14 @@ const availableOptions = computed(() => {
} }
return options return options
}) })
const searchI18n = computed(() => {
const params = { items: t('items.' + props.itemsName, 0) }
return {
label: t('search.for', { items: props.itemsName }),
invalidFeedback: t('search.not_found', params, 0),
noItems: t('items_verbose_items_left', params, 0),
}
})
const searchState = computed(() => { const searchState = computed(() => {
return criteria.value && availableOptions.value.length === 0 ? false : null return criteria.value && availableOptions.value.length === 0 ? false : null
}) })
@ -87,7 +95,7 @@ function onRemoveTag(option: string, applyFn: TagUpdateArgs['applyFn']) {
} }
} }
function onDropdownKeydown(e) { function onDropdownKeydown(e: KeyboardEvent) {
// Allow to start searching after dropdown opening // Allow to start searching after dropdown opening
// FIXME check if dropdownElem.value!.firstElementChild works (removed the $el) // FIXME check if dropdownElem.value!.firstElementChild works (removed the $el)
if ( if (
@ -97,18 +105,22 @@ function onDropdownKeydown(e) {
searchElem.value!.$el.focus() searchElem.value!.$el.focus()
} }
} }
// FIXME call touch somewhere?
</script> </script>
<template> <template>
<div class="tags-selectize"> <div class="tags-selectize">
<BFormTags <BFormTags
v-bind="$attrs" v-bind="$attrs"
:modelValue="modelValue"
@update:modelValue="emit('update:modelValue', $event)"
:id="id" :id="id"
v-model="model"
:name="name"
:aria-describedby="ariaDescribedby"
:state="state"
no-outer-focus
size="lg" size="lg"
class="p-0 border-0" class="p-0 border-0"
no-outer-focus
> >
<template #default="{ tags, disabled, addTag, removeTag }"> <template #default="{ tags, disabled, addTag, removeTag }">
<ul <ul
@ -121,10 +133,10 @@ function onDropdownKeydown(e) {
class="list-inline-item" class="list-inline-item"
> >
<BFormTag <BFormTag
@remove="onRemoveTag(tag, removeTag)"
:title="tag" :title="tag"
:disabled="disabled || disabledItems.includes(tag)" :disabled="disabled || disabledItems?.includes(tag)"
class="border border-dark mb-2" class="border border-dark mb-2"
@remove="onRemoveTag(tag, removeTag)"
> >
<YIcon v-if="tagIcon" :iname="tagIcon" /> {{ tag }} <YIcon v-if="tagIcon" :iname="tagIcon" /> {{ tag }}
</BFormTag> </BFormTag>
@ -145,30 +157,22 @@ function onDropdownKeydown(e) {
<BDropdownGroup class="search-group"> <BDropdownGroup class="search-group">
<BDropdownForm @submit.stop.prevent="() => {}"> <BDropdownForm @submit.stop.prevent="() => {}">
<BFormGroup <BFormGroup
:label="$t('search.for', { items: itemsName })" :label="searchI18n.label"
:label-for="id + '-search-input'"
label-cols-md="auto" label-cols-md="auto"
label-size="sm" label-size="sm"
:label-for="id + '-search-input'" :invalid-feedback="searchI18n.invalidFeedback"
:invalid-feedback="
$t(
'search.not_found',
{
items: $t('items.' + itemsName, 0),
},
0,
)
"
:state="searchState" :state="searchState"
:disabled="disabled" :disabled="disabled"
class="mb-0" class="mb-0"
> >
<BFormInput <BFormInput
:id="id + '-search-input'"
ref="searchElem" ref="searchElem"
v-model="search" v-model="search"
:id="id + '-search-input'"
type="search"
size="sm"
autocomplete="off" autocomplete="off"
size="sm"
type="search"
/> />
</BFormGroup> </BFormGroup>
</BDropdownForm> </BDropdownForm>
@ -184,15 +188,7 @@ function onDropdownKeydown(e) {
</BDropdownItemButton> </BDropdownItemButton>
<BDropdownText v-if="!criteria && availableOptions.length === 0"> <BDropdownText v-if="!criteria && availableOptions.length === 0">
<YIcon iname="exclamation-triangle" /> <YIcon iname="exclamation-triangle" />
{{ {{ searchI18n.noItems }}
$t(
'items_verbose_items_left',
{
items: $t('items.' + itemsName, 0),
},
0,
)
}}
</BDropdownText> </BDropdownText>
</BDropdown> </BDropdown>
</template> </template>

View file

@ -82,3 +82,14 @@ export type TagsItemProps = BaseWritableItemProps & {
// FIXME no options on BFormTags? // FIXME no options on BFormTags?
// options?: string[] // options?: string[]
} }
export type TagsSelectizeItemProps = BaseWritableItemProps & {
itemsName: string
options: string[]
auto?: boolean
disabledItems?: string[]
label: string
limit?: number
noTags?: boolean
tagIcon?: string
}