CAutocomplete
CAutocomplete — поле выбора с текстовым поиском, собранное из CTextField, CMenu и CList. В отличие от CSelect, поле остаётся редактируемым: ввод фильтрует список опций по началу строки. Компонент поддерживает одиночный и множественный выбор, примитивы и объекты, чипы и очистку через clearable.
Все пропсы поля ввода, которые принимает CTextField/CInput (label, placeholder, details, clearable, disabled, readonly, rules, validate-on, preset и другие), можно передавать прямо в CAutocomplete: они уйдут во внутреннее поле.
Базовое использование
Передайте массив вариантов в items и свяжите выбранное значение через v-model. Фокус открывает меню, ввод фильтрует список; выбор пункта записывает значение в модель и очищает строку поиска.
Показать код
<template>
<c-autocomplete
v-model="destination"
label="City"
placeholder="Start typing — try «B»"
:items="cities"
clearable
/>
<div v-if="destination" class="d-flex items-center gap-2 fs-sm mt-4">
<c-icon name="fas:check" source="fa" :size="12" class="text-green" />
Курьер доступен в <b>{{ destination }}</b>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const destination = ref<string>()
const cities = ['Amsterdam', 'Barcelona', 'Berlin', 'Lisbon', 'London', 'Paris']
</script>Объектные items
Если пункты — объекты, используйте title-key для отображаемого текста и value-key для значения, которое попадёт в v-model. Оба пропса принимают путь к полю, в том числе вложенный: например, user.name. Поиск выполняется по заголовку (title-key).
title-key), the model stores the id (value-key). Показать код
<template>
<c-autocomplete
v-model="assignee"
label="Reviewer"
placeholder="Search a member"
:items="members"
title-key="name"
value-key="id"
clearable
/>
<!-- в v-model лежит id, найденный объект рисует карточку -->
<div v-if="reviewer" class="d-flex items-center gap-2 mt-4">
<b>{{ reviewer.name }}</b>
<span class="fs-xs text-blue-grey">{{ reviewer.role }}</span>
<c-chip>v-model: {{ assignee }}</c-chip>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
const assignee = ref<number>()
const members = [
{ id: 1, name: 'Anna Smith', role: 'Product Designer' },
{ id: 2, name: 'Boris Lee', role: 'Software Engineer' },
{ id: 3, name: 'Clara Diaz', role: 'Product Manager' },
]
const reviewer = computed(() => members.find((member) => member.id === assignee.value))
</script>Без value-key в модель попадёт весь объект. Если заголовки пунктов не уникальны, обязательно задавайте value-key, чтобы записи оставались различимы.
Множественный выбор
Добавьте multiple, чтобы модель стала массивом значений. Проп chips отрисует выбранные значения как удаляемые чипы внутри поля. После выбора пункта меню остаётся открытым, а фокус возвращается в поле — можно сразу продолжать поиск. Backspace в пустом поле удаляет последнее выбранное значение.
Показать код
<template>
<c-autocomplete
v-model="stack"
label="Technologies"
placeholder="Add a technology"
:items="technologies"
multiple
chips
clearable
/>
<c-progress-linear
:value="(stack.length / 5) * 100"
:color="stack.length >= 5 ? 'green' : 'indigo'"
height="6"
class="mt-4"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const stack = ref(['Vue', 'TypeScript'])
const technologies = ['Vue', 'React', 'TypeScript', 'Node.js', 'Nuxt', 'Vite', 'Go', 'Rust']
</script>Кастомное меню
Слот menu заменяет выпадашку целиком, сохраняя фильтрацию и выбор за компонентом: в него приходит уже отфильтрованный массив items (NormalizedItem<T>[]) и функция onSelect. Если внутри слота стоит CList, клавиатура работает из коробки: список сам регистрируется в контуре автокомплита — стрелки из поля, Enter / Space и очистка поиска после выбора работают без обвязки. Вместе со слотом chips из этого собирается полноценный people-picker — аватары, статусы, бейджи ролей и своё пустое состояние:
Показать код
<template>
<c-autocomplete
v-model="invited"
label="Teammates"
placeholder="Search by name"
title-key="name"
:items="members"
multiple
chips
clearable
>
<template #chips>
<div v-for="member of invited" :key="member.email" class="invite-chip">
<span class="avatar" :class="member.color">{{ initials(member) }}</span>
{{ member.name.split(' ')[0] }}
<c-icon name="fas:times" source="fa" :size="10" @click.stop="uninvite(member)" />
</div>
</template>
<!-- Custom dropdown: own layout on top of items + onSelect -->
<template #menu="{ items }">
<div class="invite-menu radius-12 elevation-4">
<div class="invite-menu__head">Team directory — {{ items.length }} matches</div>
<div v-if="!items.length" class="pa-4 fs-sm text-blue-grey">Nobody matches this search</div>
<c-list v-else v-model="invited" multiple variant="menu">
<c-list-item v-for="item of items" :key="item.key" :value="item.raw">
<span class="avatar" :class="item.raw.color">
{{ initials(item.raw) }}
<i class="dot" :class="item.raw.online ? 'bg-green' : 'bg-grey'"></i>
</span>
<c-list-item-content>
<c-list-item-title>{{ item.title }}</c-list-item-title>
<c-list-item-subtitle>{{ item.raw.email }}</c-list-item-subtitle>
</c-list-item-content>
<c-chip>{{ item.raw.role }}</c-chip>
<c-icon
v-if="invited.includes(item.raw)"
name="fas:check"
source="fa"
:size="14"
class="text-teal"
/>
</c-list-item>
</c-list>
</div>
</template>
</c-autocomplete>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const invited = ref<Member[]>([])
const members: Member[] = [
{ name: 'Anna Smith', email: 'anna@vueland.dev', role: 'Design', color: 'bg-pink', online: true },
{
name: 'Boris Lee',
email: 'boris@vueland.dev',
role: 'Frontend',
color: 'bg-indigo',
online: true,
},
// ...
]
const initials = (member: Member) =>
member.name
.split(' ')
.map((part) => part[0])
.join('')
const uninvite = (member: Member) => {
invited.value = invited.value.filter((it) => it !== member)
}
</script>Состояние выбора остаётся за компонентом: onSelect в multiple работает как toggle, строка поиска продолжает фильтровать items, а Backspace по-прежнему удаляет последний выбор.
Поиск
- Список фильтруется по началу заголовка пункта без учёта регистра.
- Каждое изменение строки поиска эмитит событие
update:search— его можно использовать для внешней логики: подсветки, аналитики или подгрузки данных. - Закрытие меню любым способом (выбор,
Escape,Tab, клик вне) сбрасывает строку поиска, поэтому при следующем открытии виден полный список.
Поведение
- В фокусе поле открывает меню.
EscapeиTabзакрывают меню и снимают фокус. ArrowUp/ArrowDownперемещают активный пункт списка,Enter/Spaceвыбирают его.- В одиночном режиме выбор закрывает меню и очищает строку поиска. Повторный выбор текущего пункта не очищает модель.
- В
multipleвыбранный пункт снимается повторным выбором.mandatoryзапрещает снять последний пункт через список. Backspaceв пустом поле удаляет последнее значение (multiple) или очищает модель (одиночный режим).readonlyне открывает меню, аBackspaceне удаляет выбранные значения.
API
Пропы CAutocomplete
modelValueT | T[] | null | undefinedmultipleitemsreadonly T[][]title-keystringvalue-keystringv-modelmultiplebooleanfalsemandatorybooleanfalsemultiple запрещает снять последний пункт через списокchipsbooleanfalseoptions{ noItemsMessage?: string }Также доступны пропсы внутреннего поля: label, placeholder, details, clearable, disabled, readonly, rules, validate-on, preset и другие пропсы CInput / CTextField. Функции из rules вызываются со значением модели (v-model), а не со строкой поиска.
Пресеты компонуются по значению: в поля menu и list пресета инпута подставляются обычные CMenuPreset и CListPreset — тот же формат, что у standalone-компонентов. CMenu, CList и CListItem получают их из того же набора через контекст.
import { menuRounded } from './presets/menu' // CMenuPreset: зона root, состояния opened/closed
import { listCompact } from './presets/list' // CListPreset: зоны root/option, состояния disabled/readonly
const combo: CInputPreset = {
base: {
field: ['text-indigo'],
menu: menuRounded,
list: listCompact,
},
}События CAutocomplete
update:modelValueT | T[] | nullupdate:searchstringСлоты CAutocomplete
chips{ items: unknown[] }menu{ items: NormalizedItem<T>[], onSelect } & KeyboardAPIitems — уже отфильтрованный списокdetails{ errorMessage?: string, details?: string }prependappendno-items-messageВ menu функция onSelect обновляет модель: в multiple повторный вызов с уже выбранным значением снимает его (toggle). Помимо этого слот получает keyboard-api автокомплита: register / unregister — встроить собственную цель в клавиатурный контур, forward — переслать событие активной цели, blur — сбросить её фокусное состояние. Кастомному меню на CList это не нужно — список регистрируется сам.
NormalizedItem
Слот menu получает уже подготовленные элементы:
type NormalizedItem<T> = {
raw: T
title: unknown
value: unknown
key: string
}raw — исходный пункт из items, title вычисляется через title-key, value — через value-key, key используется для стабильного рендера списка.
CSS-переменные
Визуальная настройка наследуется от CTextField, CMenu и CList; чипы настраиваются через переменные CChip.
