Combobox(自動完成)

Combobox 是應用程式中無障礙自動完成和指令面板的基礎,支援健全的鍵盤導覽功能。

首先,透過 npm 安裝 Headless UI。

請注意:此函式庫僅支援 Vue 3

npm install @headlessui/vue

Combobox 使用 ComboboxComboboxInputComboboxButtonComboboxOptionsComboboxOptionComboboxLabel 元件建置。

只要進行搜尋,ComboboxInput 便會自動開啟/關閉 ComboboxOptions

完全由您決定要如何篩選結果,不論是使用模糊搜尋函式庫於用戶端處理,還是向 API 進行伺服器端要求。在此範例中,我們只是為了展示目的而保持邏輯簡潔。

<template> <Combobox v-model="selectedPerson"> <ComboboxInput @change="query = $event.target.value" /> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person" :value="person" > {{ person }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const people = [ 'Durward Reynolds', 'Kenton Towne', 'Therese Wunsch', 'Benedict Kessler', 'Katelyn Rohan', ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

在前面的範例中,我們使用清單中的 字串 值作為資料,但也可以使用有附加資訊的物件。唯一的限制就是您必須向 input 提供 displayValue。這一點很重要,以便您物件的字串版本能顯示在 ComboboxInput 中。

<template> <Combobox v-model="selectedPerson"> <ComboboxInput @change="query = $event.target.value"
:displayValue="(person) => person.name"
/>
<ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" :disabled="person.unavailable" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const people = [ { id: 1, name: 'Durward Reynolds', unavailable: false }, { id: 2, name: 'Kenton Towne', unavailable: false }, { id: 3, name: 'Therese Wunsch', unavailable: false }, { id: 4, name: 'Benedict Kessler', unavailable: true }, { id: 5, name: 'Katelyn Rohan', unavailable: false }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

Headless UI 會記錄每個元件的許多狀態,例如目前選取哪個組合方塊選項,是打開或關閉彈出視窗,以及哪個組合方塊項目目前以鍵盤啟用。

但由於這些元件在未套用樣式之前都是沒有標題和樣式的,因此在你提供自己想要的每個狀態的樣式之前,在你的使用者介面中無法看到這些資訊。

每個元件都透過供你用來有條件套用不同樣式或呈現不同內容的 插槽道具 公開其目前狀態的資訊。

例如:ComboboxOption 元件公開一個 active 狀態,它會告訴你該項目目前是透過滑鼠或鍵盤取得焦點。

<template> <Combobox v-model="selectedPerson"> <ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions> <!-- Use the `active` state to conditionally style the active option. --> <!-- Use the `selected` state to conditionally style the selected option. --> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" as="template"
v-slot="{ active, selected }"
>
<li :class="{
'bg-blue-500 text-white': active,
'bg-white text-black': !active,
}"
>
<CheckIcon v-show="selected" />
{{ person.name }} </li> </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' import { CheckIcon } from '@heroicons/vue/20/solid' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

如需所有可用插槽道具的完整清單,請參閱 元件 API 文件

每個元件也透過你可以用來有條件套用不同樣式的 data-headlessui-state 屬性公開其目前狀態的資訊。

插槽道具 API 中的任何狀態為 true 時,它們會以空格分隔字串的形式列在此屬性中,以便你可以以 [attr~=value] 型式的 CSS 屬性選取器 來鎖定它們。

例如:下列是 ComboboxOptions 元件會在組合框開啟,且第二個項目處於 active 狀態時,連同某些子 ComboboxOption 元件一起呈現

<!-- Rendered `ComboboxOptions` --> <ul data-headlessui-state="open"> <li data-headlessui-state="">Wade Cooper</li> <li data-headlessui-state="active selected">Arlene Mccoy</li> <li data-headlessui-state="">Devon Webb</li> </ul>

如果你正在使用 Tailwind CSS,你可以使用 @headlessui/tailwindcss 外掛程式以使用 ui-open:*ui-active:* 等修飾詞來鎖定此屬性

<template> <Combobox v-model="selectedPerson"> <ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person"
class="ui-active:bg-blue-500 ui-active:text-white ui-not-active:bg-white ui-not-active:text-black"
>
<CheckIcon class="hidden ui-selected:block" />
{{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' import { CheckIcon } from '@heroicons/vue/20/solid' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

與僅允許你提供字串作為值的原生 HTML 表單控制元件不同,Headless UI 也支援繫結複雜物件。

<template>
<Combobox v-model="selectedPerson">
<ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id"
:value="person"
:disabled="person.unavailable" >
{{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue'
const people = [
{ id: 1, name: 'Durward Reynolds', unavailable: false },
{ id: 2, name: 'Kenton Towne', unavailable: false },
{ id: 3, name: 'Therese Wunsch', unavailable: false },
{ id: 4, name: 'Benedict Kessler', unavailable: true },
{ id: 5, name: 'Katelyn Rohan', unavailable: false },
]
const selectedPerson = ref(people[1]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) )
</script>

在將物件繫結為值時,務必確保你將物件的相同執行個體用於 Comboboxvalue 和相對應的 ComboboxOption,否則它們將無法相等,並導致組合框無法正確運作。

為了更輕鬆地處理相同物件的不同執行個體,你可以使用 by 道具,透過特定欄位來比較物件,而非比較物件識別。

<template> <Combobox :modelValue="modelValue" @update:modelValue="value => emit('update:modelValue', value)"
by="id"
>
<ComboboxInput @change="query = $event.target.value" :displayValue="(department) => department.name" /> <ComboboxOptions> <ComboboxOption v-for="department in filteredDepartments" :key="department.id" :value="department" > {{ department.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const props = defineProps({ modelValue: Object }) const emit = defineEmits(['update:modelValue']) const departments = [ { id: 1, name: 'Marketing', contact: 'Durward Reynolds' }, { id: 2, name: 'HR', contact: 'Kenton Towne' }, { id: 3, name: 'Sales', contact: 'Therese Wunsch' }, { id: 4, name: 'Finance', contact: 'Benedict Kessler' }, { id: 5, name: 'Customer service', contact: 'Katelyn Rohan' }, ] const query = ref('') const filteredDepartments = computed(() => query.value === '' ? departments : departments.filter((department) => { return department.name .toLowerCase() .includes(query.value.toLowerCase()) }) ) </script>

如果你想完全控制比較物件的方式,也可以將自己的比較函式傳遞給 by 道具

<template> <Combobox :modelValue="modelValue" @update:modelValue="value => emit('update:modelValue', value)"
:by="compareDepartments"
>
<ComboboxInput @change="query = $event.target.value" :displayValue="(department) => department.name" /> <ComboboxOptions> <ComboboxOption v-for="department in departments" :key="department.id" :value="department" > {{ department.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const props = defineProps({ modelValue: Object }) const emit = defineEmits(['update:modelValue'])
function compareDepartments(a, b) {
return a.name.toLowerCase() === b.name.toLowerCase()
}
const departments = [ { id: 1, name: 'Marketing', contact: 'Durward Reynolds' }, { id: 2, name: 'HR', contact: 'Kenton Towne' }, { id: 3, name: 'Sales', contact: 'Therese Wunsch' }, { id: 4, name: 'Finance', contact: 'Benedict Kessler' }, { id: 5, name: 'Customer service', contact: 'Katelyn Rohan' }, ] const query = ref('') const filteredDepartments = computed(() => query.value === '' ? departments : departments.filter((department) => { return department.name .toLowerCase() .includes(query.value.toLowerCase()) }) )
</script>

Combobox 元件允許您選擇多個值。您可以透過提供值的陣列(而非單一值)來啟用此功能。

<template>
<Combobox v-model="selectedPeople" multiple>
<ul v-if="selectedPeople.length > 0"> <li v-for="person in selectedPeople" :key="person.id"> {{ person.name }} </li> </ul> <ComboboxInput /> <ComboboxOptions> <ComboboxOption v-for="person in people" :key="person.id" :value="person"> {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] const selectedPeople = ref([people[0], people[1]]) </script>

這會在您選擇選項時保持組合框開啟,選擇選項也會在該位置切換。

只要加入或移除選項,您的 v-model 繫結就會更新為包含所有選取選項的陣列。

預設情況下,Combobox 會使用輸入內容作為螢幕閱讀器的標籤。如果您想進一步控制協助技術所宣告的內容,請使用 ComboboxLabel 元件。

<template> <Combobox v-model="selectedPerson">
<ComboboxLabel>Assignee:</ComboboxLabel>
<ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxLabel, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

如果您為組合框加入 name 屬性,將會產生隱藏的 input 元素,並與您的選定值保持同步。

<template> <form action="/projects/1/assignee" method="post">
<Combobox v-model="selectedPerson" name="assignee">
<ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> <button>Submit</button> </form> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

這樣便能讓您在原生 HTML <form> 內部使用組合框,並進行傳統的表單提交,就好像您的組合框是原生的 HTML 表單控制項一樣。

字串等基本值會呈現為包含該值的單一隱藏輸入,但像物件等複雜的值會使用名稱的方括號表示法編碼為多個輸入。

<input type="hidden" name="assignee[id]" value="1" /> <input type="hidden" name="assignee[name]" value="Durward Reynolds" />

如果您提供 defaultValue 而非 value 屬性給 Combobox,Headless UI 會為您在內部追蹤其狀態,讓您可以將其作為 非受控元件 使用。

<template> <form action="/projects/1/assignee" method="post">
<Combobox name="assignee" :defaultValue="people[0]">
<ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> <button>Submit</button> </form> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

這可以在組合框 與 HTML 表單 或使用 FormData 而非使用 React 狀態追蹤其狀態的表單 API 搭配使用時簡化您的程式碼。

如果您在元件值變更時需要執行任何副作用,您提供的任何 @update:modelValue 屬性仍會被呼叫,但您不需要使用它自行追蹤元件的狀態。

您可以透過根據 query 值加入動態的 ComboboxOption,允許使用者輸入清單中不存在的自身值。

<template> <Combobox v-model="selectedPerson"> <ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions>
<ComboboxOption v-if="queryPerson" :value="queryPerson">
Create "{{ query }}"
</ComboboxOption>
<ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] const selectedPerson = ref(people[0]) const query = ref('')
const queryPerson = computed(() => {
return query.value === '' ? null : { id: null, name: query.value }
})
const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) )
</script>

根據您建立的項目,有時在 <ComboboxOptions> 外部渲染關於目前選取選項的附加資訊可能會比較有意義。例如,在命令面板內容中預覽目前的選取選項。在這種情況下,您可以讀取 `activeOption` 插槽 prop 參數存取這些資訊。

<template>
<Combobox v-model="selectedPerson" v-slot="{ activeOption }">
<ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions>
<div v-if="activeOption">
The current active user is: {{ activeOption.name }}
</div>
</Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

目前 `activeOption` 會是目前 `ComboboxOption` 的 `value`。

預設情況下,您的 ComboboxOptions 實例會根據 Combobox 組合元件本身內部追蹤的內部 `open` 狀態自動顯示/隱藏。

<template> <Combobox v-model="selectedPerson"> <ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <!-- By default, the `ComboboxOptions` will automatically show/hide when typing in the `ComboboxInput`, or when pressing the `ComboboxButton`. --> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

如果您想要自行處理(可能是因為某種原因需要額外加上一個包覆元素),您可以將 `static` prop 加入到 ComboboxOptions 實例,指示它總是渲染,並檢查 Combobox 提供的 `open` 插槽 prop 以控制您顯示/隱藏的元素。

<template>
<Combobox v-model="selectedPerson" v-slot="{ open }">
<ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" />
<div v-show="open">
<!-- Using the `static` prop, the `ComboboxOptions` are always rendered and the `open` state is ignored. -->
<ComboboxOptions static>
<ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </div> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

使用 `disabled` prop 停用 ComboboxOption。此動作會透過滑鼠和鍵盤讓其無法選取,且在按下上/下箭頭時也會略過它。

<template> <Combobox v-model="selectedPerson"> <ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <ComboboxOptions> <!-- Disabled options will be skipped by keyboard navigation. --> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person"
:disabled="person.unavailable"
>
<span :class='{ "opacity-75": person.unavailable }'> {{ person.name }} </span> </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const people = [ { id: 1, name: 'Durward Reynolds', unavailable: true }, { id: 2, name: 'Kenton Towne', unavailable: false }, { id: 3, name: 'Therese Wunsch', unavailable: false }, { id: 4, name: 'Benedict Kessler', unavailable: true }, { id: 5, name: 'Katelyn Rohan', unavailable: false }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

預設情況下,一旦您在組合方塊選取一個值,就無法將組合方塊清除回空白值,當您清除輸入並離開標籤頁時,值會變回之前選取的值。

如果您想要在您的組合方塊中支援空白值,請使用 nullable prop。

<template>
<Combobox v-model="selectedPerson" nullable>
<ComboboxInput @change="query = $event.target.value"
:displayValue="(person) => person?.name"
/>
<ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const people = [ { id: 1, name: 'Durward Reynolds', unavailable: true }, { id: 2, name: 'Kenton Towne', unavailable: false }, { id: 3, name: 'Therese Wunsch', unavailable: false }, { id: 4, name: 'Benedict Kessler', unavailable: true }, { id: 5, name: 'Katelyn Rohan', unavailable: false }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

當使用 nullable prop 時,清除輸入並離開元素將會更新您的 v-model 繫結,並使用 null 呼叫您的 displayValue 回呼。

允許 選取多個值 時,此 prop 不會進行任何操作,因為選項會切換開啟和關閉,如果沒有選擇任何選項,則會產生空陣列 (而不是 null)。

若要對組合方塊的開啟/關閉進行動畫,你可以使用 Vue 內建的 <transition> 組件。你所要做的就是將你的 ComboboxOptions 執行個體包裝在一個 <transition> 中,然後會自動套用轉場。

<template> <Combobox v-model="selectedPerson"> <ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <!-- Use Vue's built-in `transition` component to add transitions. -->
<transition
enter-active-class="transition duration-100 ease-out"
enter-from-class="transform scale-95 opacity-0"
enter-to-class="transform scale-100 opacity-100"
leave-active-class="transition duration-75 ease-out"
leave-from-class="transform scale-100 opacity-100"
leave-to-class="transform scale-95 opacity-0"
>
<ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions>
</transition>
</Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

如果你要為組合方塊的相異子項協調多個轉場,請查看 Headless UI 中包含的轉場組件

預設情況下,Combobox 及其子組件各會呈現對於該組件而言明智的預設元素。

例如,ComboboxLabel 預設會呈現一個 labelComboboxInput 會呈現一個 inputComboboxButton 會呈現一個 buttonComboboxOptions 會呈現一個 ul,而 ComboboxOption 則是會呈現一個 li。與之相反,Combobox 並不會呈現一個元素,而是直接呈現其子項。

這很容易透過 as 屬性進行更改,它存在於每個組件。

<template> <!-- Render a `div` instead of nothing -->
<Combobox as="div" v-model="selectedPerson">
<ComboboxInput @change="query = $event.target.value" :displayValue="(person) => person.name" /> <!-- Render a `div` instead of a `ul` -->
<ComboboxOptions as="div">
<!-- Render a `span` instead of a `li` --> <ComboboxOption
as="span"
v-for="person in filteredPeople" :key="person.id" :value="person" >
{{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

若要告訴元素以沒有包裝的元素直接呈現其子項,請使用 as="template"

<template> <Combobox v-model="selectedPerson"> <!-- Render children directly instead of an `input` --> <ComboboxInput
as="template"
@change="query = $event.target.value" :displayValue="(person) => person.name" >
<input /> </ComboboxInput> <ComboboxOptions> <ComboboxOption v-for="person in filteredPeople" :key="person.id" :value="person" > {{ person.name }} </ComboboxOption> </ComboboxOptions> </Combobox> </template> <script setup> import { ref, computed } from 'vue' import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from '@headlessui/vue' const people = [ { id: 1, name: 'Durward Reynolds' }, { id: 2, name: 'Kenton Towne' }, { id: 3, name: 'Therese Wunsch' }, { id: 4, name: 'Benedict Kessler' }, { id: 5, name: 'Katelyn Rohan' }, ] const selectedPerson = ref(people[0]) const query = ref('') const filteredPeople = computed(() => query.value === '' ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.value.toLowerCase()) }) ) </script>

當組合方塊被切換為開啟時,ComboboxInput 會保持焦點。

對於預設的標籤流,ComboboxButton 會被忽略,這表示在 ComboboxInput 中按 Tab 將會略過 ComboboxButton

按一下 ComboboxButton 會切換選項清單開啟和關閉。按一下選項清單以外的任何地方都會關閉組合方塊。

指令說明

ArrowDown, ArrowUpComboboxInput 獲得焦點時

開啟下拉式選單並讓焦點在選定的項目上

Enter, Space, ArrowDown, or ArrowUpComboboxButton 被聚焦時

開啟下拉式選單,讓焦點在輸入欄位並選取選定項目

Esc 當下拉式選單開啟時

關閉下拉式選單並在輸入欄位中復原選定項目

ArrowDown or ArrowUp當下拉式選單開啟時

讓焦點在先前的/下一個非停用的項目

Home or PageUp 當下拉式選單開啟時

讓焦點在第一個非停用的項目

End or PageDown 當下拉式選單開啟時

讓焦點在最後一個非停用的項目

Enter 當下拉式選單開啟時

選取目前的項目

Enter 當下拉式選單關閉且在表單中時

提交表單

Tab 當下拉式選單開啟時

選取目前已啟用的項目並關閉下拉式選單

A–Za–z 開啟選單時

呼叫 onChange,能讓你篩選清單

自動管理所有相關的 ARIA 屬性。

主要的 Combobox 元件。

屬性預設值說明
as範本
字串 | 元件

Combobox 的渲染元素或元件。

v-model
T

選取的值。

defaultValue
T

當使用非受控元件時預設值。

by
keyof T | ((a: T, z: T) => boolean)

透過此屬性,可以根據特定欄位比較物件,或傳入自訂比較函式來完全控制物件的比較方式。

disabledfalse
布林值

使用此屬性停用整個 combobox 元件及其關聯子代。

name
字串

在表單中使用此元件時的名稱。

nullable
布林值

是否可以清除 combobox。

multiplefalse
布林值

是否允許多個選項。

插槽屬性說明
value

T

選取的值。

open

布林值

combobox 是否開啟。

disabled

布林值

combobox 是否已停用。

activeIndex

數字 | null

目前選項的索引,或為 null。

activeOption

T | null

目前選項,或為 null。

Combobox 的輸入框。

屬性預設值說明
asinput
字串 | 元件

ComboboxInput 的渲染元素或元件。

displayValue
(item: T) => 字串

你的 value 的字串表示形式。

渲染屬性說明
open

布林值

Combobox 是否開啟。

disabled

布林值

Combobox 是否已停用。

Combobox 的按鈕。

屬性預設值說明
asbutton
字串 | 元件

ComboboxButton 的渲染元素或元件。

插槽屬性說明
value

T

選取的值。

open

布林值

Combobox 是否開啟。

disabled

布林值

Combobox 是否已停用。

一種標籤,可用於更控制您的 Combobox 將宣布給螢幕閱讀器的文字。它的 id 屬性將自動產生,並透過 aria-labelledby 屬性連結至根 Combobox 組件。

屬性預設值說明
as標籤
字串 | 元件

ComboboxLabel 應作為其渲染的元素或組件。

插槽屬性說明
open

布林值

Combobox 是否開啟。

disabled

布林值

Combobox 是否已停用。

直接包裝您自訂 Combobox 中選項清單的組件。

屬性預設值說明
asul
字串 | 元件

ComboboxOptions 應作為其渲染的元素或組件。

staticfalse
布林值

元素是否應忽略內部管理的開啟/關閉狀態。

unmounttrue
布林值

元素是否應基於開啟/關閉狀態解除安裝或隱藏。

holdfalse
布林

當滑鼠離開活躍選項時,活躍選項是否應保持活躍狀態。

插槽屬性說明
open

布林值

Combobox 是否開啟。

用於包裝 Combobox 中的每個項目。

屬性預設值說明
value
T

選項值。

asli
字串 | 元件

ComboboxOption 應作為其渲染的元素或組件。

disabledfalse
布林值

對於鍵盤導覽和 ARIA 目的,選項是否應禁用。

插槽屬性說明
active

布林值

選項是否為活躍/焦點選項。

selected

布林值

選項是否為已選取選項。

disabled

布林值

對於鍵盤導覽和 ARIA 目的,選項是否禁用。

如果您有興趣使用 Headless UI 和 Tailwind CSS 的預先設計組件範例,請查看 Tailwind UI — 由我們打造的精美好設計、製作精良的組件合輯。

這是對我們從事此類開源專案的支持,並讓我們得以改進和保持它們的完善維護。