Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<template>
<ComboBox
v-model="modelValue"
v-bind="props"
:choices="filteredChoices"
:close-on-select="props.closeOnSelect ?? !Array.isArray(modelValue)"
input-container-class="tw-flex tw-flex-wrap tw-p-2 tw-gap-2 tw-items-baseline tw-w-full form-control tw-h-auto tw-min-h-[46px]"
input-class="tw-border-none tw-px-1 tw-w-[100px] focus:tw-shadow-none focus:tw-outline-none focus:tw-ring-0"
@search="searchQuery = $event"
>
<template #default="{ choice, ...attributes }">
<slot :choice="choice" v-bind="attributes">
<li v-bind="attributes">
{{ choice.name }}
</li>
</slot>
</template>
<template #selected="{ value, deselect, isOpen }">
<slot name="selected" :value="value" :deselect="deselect" :is-open="isOpen">
<template v-if="Array.isArray(value)">
<Tag
v-for="(item, index) in value"
:key="index"
:label="item.name"
removable
@remove="deselect(item)"
/>
</template>
<template v-else>
<p v-if="value">{{ value.name }}</p>
</template>
</slot>
</template>
</ComboBox>
</template>
<script setup lang="ts" generic="T extends { id: ID, name: string }">
import { ID } from '@/api'
import Tag from './generic/Tag.vue'
import ComboBox from './ComboBox.vue'
import type { ComboBoxProps } from '@/components/ComboBox.vue'
import { computed, ref } from 'vue'
import { matchesSearch } from '@/util'
defineOptions({
compatConfig: { MODE: 3 },
})
defineSlots<{
default(
props: Record<string, unknown> & {
id: string
choice: T
index: number
activeIndex: number
},
): unknown
selected(props: {
value: null | T | T[]
deselect: (choice: null | T) => void
isOpen: boolean
}): unknown
}>()
const modelValue = defineModel<null | T | T[]>({ required: true })
const props = defineProps<ComboBoxProps<T>>()
const searchQuery = ref('')
const selectedIds = computed(() => {
if (Array.isArray(modelValue.value)) {
return modelValue.value.map((item) => item.id)
} else if (modelValue.value !== null) {
return [modelValue.value.id]
} else {
return []
}
})
const filteredChoices = computed(() =>
props.choices.filter(
({ id, name }) => !selectedIds.value.includes(id) && matchesSearch(name, searchQuery.value),
),
)
</script>