使用场景
用于对输入框提供输入建议,进行自动补全的操作
与可搜索的 Select 组件的区别:
- AutoComplete 本质上是一个增强型的提供了输入建议的 Input 组件,而 Select 是一个选择器
- 点击展开时,Select 会将输入框的值全部清空,而 AutoComplete 会保留上次选中的值
- Select 的已选项渲染可以是任意 snippet 内容,而 AutoComplete 的
renderSelectedItem只允许返回字符串
代码演示
如何引入
<script lang="ts">
import { AutoComplete } from '@fibrae/semi-ui-svelte';
</script>
基本用法
通过 onSearch 监听用户输入,将输入建议通过更新 data 传入。通过 bind:value 保持值同步,当输入框变化或选中输入项时会更新。没有 defaultValue:单向 value={x} 只作初值,要完全受控用函数绑定 bind:value={get, set}
当前输入:
代码
<script lang="ts">
import { IconSearch } from '@fibrae/semi-icons-svelte';
import { AutoComplete } from '@fibrae/semi-ui-svelte';
let stringData = $state<string[]>([]);
let value = $state<string | number>('');
const handleStringSearch = (input: string) => {
stringData = input ? ['gmail.com', '163.com', 'qq.com'].map((domain) => `${input}@${domain}`) : [];
};
</script>
<AutoComplete bind:value data={stringData} onSearch={handleStringSearch} placeholder="搜索... " showClear style="width: 200px">
{#snippet prefix()}
<IconSearch />
{/snippet}
</AutoComplete>
<p>当前输入:{value}</p>自定义候选项渲染
需要自定义候选项渲染时,data 可以传入一个对象数组(每个 Object 含有 label、value 两个 key,value 为候选项选中的值,label 为候选项展示的文本,缺省时回落到 value)
通过 item snippet(参数为 { item, index })可以自定义候选项内容:item 是未经加工的数据项,数据项里只放数据,标记写在 snippet 里
代码
<script lang="ts">
import { IconSearch } from '@fibrae/semi-icons-svelte';
import { AutoComplete, Avatar } from '@fibrae/semi-ui-svelte';
interface ListItem {
name: string;
email: string;
abbr: string;
color: 'amber' | 'indigo' | 'blue' | 'cyan';
value?: string;
label?: string;
}
const list: ListItem[] = [
{ name: '夏可漫', email: 'xiakeman@example.com', abbr: 'XK', color: 'amber' },
{ name: '申悦', email: 'shenyue@example.com', abbr: 'SY', color: 'indigo' },
{ name: '曲晨一', email: 'quchenyi@example.com', abbr: 'CY', color: 'blue' },
{ name: '文嘉茂', email: 'wenjiamao@example.com', abbr: 'JM', color: 'cyan' },
];
let data = $state<ListItem[]>([]);
const handleStringSearch = (value: string) => {
data = value ? list.map((item) => ({ ...item, value: item.name, label: item.email })) : [];
};
const renderSelectedItem = (option: ListItem) => option.email;
</script>
<AutoComplete aria-label="搜索邮箱" {data} onSearch={handleStringSearch} {renderSelectedItem} showClear style="width: 280px">
{#snippet prefix()}
<IconSearch />
{/snippet}
{#snippet item({ item })}
<Avatar color={item.color} size="small">{item.abbr}</Avatar>
<div style="margin-left: 4px">
<div style="font-size: 14px; margin-left: 4px">{item.name}</div>
<div style="margin-left: 4px">{item.email}</div>
</div>
{/snippet}
</AutoComplete>远程搜索
从 onSearch 中获取用户输入值,动态更新 data,并更新 loading
已选择:
代码
<script lang="ts">
import { debounce } from 'es-toolkit';
import { IconSearch } from '@fibrae/semi-icons-svelte';
import { IconButton, IconForm, IconInput, IconSelect, IconTable } from '@fibrae/semi-icons-svelte/lab';
import { AutoComplete, type AutoCompleteDataItem } from '@fibrae/semi-ui-svelte';
interface ListItem {
value: 'select' | 'input' | 'form' | 'button' | 'table';
label: string;
}
const icons = {
select: IconSelect,
input: IconInput,
form: IconForm,
button: IconButton,
table: IconTable,
};
const initList: ListItem[] = [
{ value: 'select', label: '选择器' },
{ value: 'input', label: '输入框' },
{ value: 'form', label: '表单' },
{ value: 'button', label: '按钮' },
{ value: 'table', label: '表格' },
];
let loading = $state(false);
let list = $state(initList);
let selected = $state('');
const handleSearch = (inputValue: string) => {
loading = true;
const newList = inputValue ? initList.filter((item) => item.value.includes(inputValue)) : initList;
setTimeout(() => {
list = newList;
loading = false;
}, 1000);
};
const search = debounce(handleSearch, 200);
const handleSelect = (value: string | number | AutoCompleteDataItem) => {
selected = String(value);
};
// Unlike Select, this can return only a string, not a node
const renderSelectedItem = (item: ListItem) => item.value;
</script>
<AutoComplete aria-label="搜索" data={list} {loading} onSearch={search} onSelect={handleSelect} {renderSelectedItem} style="width: 250px">
{#snippet prefix()}
<IconSearch />
{/snippet}
{#snippet item({ item })}
{@const Icon = icons[item.value]}
<div style="display: flex; align-items: center">
<div style="font-size: 32px">
<Icon />
</div>
<div style="margin-left: 12px">
<p>{item.value}</p>
<p>{item.label}</p>
</div>
</div>
{/snippet}
</AutoComplete>
<p>已选择:{selected}</p>尺寸
通过设置 size 可设置输入框尺寸,可选small,default(默认),large
代码
<script lang="ts">
import { AutoComplete } from '@fibrae/semi-ui-svelte';
const data = [1, 2, 3, 4];
</script>
<div>
<AutoComplete {data} placeholder="small" size="small" style="width: 200px" />
<br />
<br />
<AutoComplete {data} placeholder="default" size="default" style="width: 200px" />
<br />
<br />
<AutoComplete {data} placeholder="large" size="large" style="width: 200px" />
</div>下拉菜单的位置
通过设置 position 可设置下拉菜单位置,可选值参考 Tooltip position
代码
<script lang="ts">
import { AutoComplete } from '@fibrae/semi-ui-svelte';
let data = $state<string[]>([]);
const change = (input: string) => {
data = input ? ['gmail.com', '163.com', 'qq.com'].map((domain) => `${input}@${domain}`) : [];
};
</script>
<div>
<AutoComplete {data} onSearch={change} placeholder="选项菜单在上方显示" position="top" style="width: 200px; margin: 10px" />
<AutoComplete {data} onSearch={change} placeholder="选项菜单在右侧显示" position="rightTop" style="width: 200px; margin: 10px" />
</div>禁用
代码
<script lang="ts">
import { AutoComplete } from '@fibrae/semi-ui-svelte';
const data = [1, 2, 3, 4];
</script>
<AutoComplete {data} disabled placeholder="禁用下拉菜单" style="width: 200px" />校验状态
可设置不同校验状态,展示不同样式
代码
<script lang="ts">
import { AutoComplete } from '@fibrae/semi-ui-svelte';
let warning = $state('ies');
let error = $state('ies');
let normal = $state('ies');
</script>
<AutoComplete aria-label="Warning status" bind:value={warning} validateStatus="warning" />
<br />
<br />
<AutoComplete aria-label="Error status" bind:value={error} validateStatus="error" />
<br />
<br />
<AutoComplete aria-label="Default status" bind:value={normal} />自定义空内容
可设置自定义展示空内容
代码
<script lang="ts">
import { IllustrationNoContent } from '@fibrae/semi-illustrations-svelte';
import { AutoComplete, Empty } from '@fibrae/semi-ui-svelte';
let data = $state<number[]>([]);
let loading = $state(false);
const fetchData = (input: string) => {
loading = true;
setTimeout(() => {
data = input ? Array.from({ length: 5 }, () => Math.random()) : [];
loading = false;
}, 1000);
};
</script>
<AutoComplete aria-label="搜索" {data} {loading} onSearch={fetchData}>
{#snippet emptyContent()}
<Empty style="padding: 12px; width: 300px">
{#snippet image()}
<IllustrationNoContent style="width: 150px; height: 150px" />
{/snippet}
{#snippet description()}暂无内容{/snippet}
</Empty>
{/snippet}
</AutoComplete>API 参考
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| autoAdjustOverflow | 下拉面板被遮挡时是否自动调整方向。 | boolean | - |
| autofocus | 是否在挂载后自动聚焦输入框。 | boolean | false |
| clearIcon | 自定义清除图标的 snippet,showClear 为 true 时有效。 | Snippet | - |
| data | 候选项的数据源,可以是字符串、数字或对象的数组。对象项用 value 作选中值,label 作展示文本(缺省显示 value),disabled、class、style 与 data-* 字段落在对应的候选行上,class 接受字符串、数组或对象,style 是字符串;节点内容用 item snippet。 | T[] | [] |
| defaultActiveFirstOption | 面板打开后是否默认高亮第一个候选项,此时按 Enter 可直接选中。 | boolean | false |
| defaultOpen | 挂载时是否默认展开下拉面板;AutoComplete 没有受控的展开状态。 | boolean | - |
| disabled | 是否禁用。 | boolean | - |
| dropdownClassName | 下拉列表的类名,落在 role="listbox" 元素上。 | string | - |
| dropdownMatchSelectWidth | 下拉面板的最小宽度是否等于输入框宽度。 | boolean | true |
| dropdownStyle | 下拉列表的内联样式字符串,落在 role="listbox" 元素上。 | string | - |
| emptyContent | data 为空时在下拉面板中显示的内容的 snippet。 | Snippet | - |
| insetLabel | 输入框内嵌标签的 snippet,只要传入就当作有内容。设置 prefix 或 trigger 时不渲染,也不作为可访问名称。 | Snippet | - |
| insetLabelId | insetLabel 前缀元素的 id,combobox 与 listbox 的名称引用它;不传时组件在渲染 insetLabel 时生成。 | string | - |
| item | 候选项行内容的 snippet,参数 { item, index }:item 是未经加工的数据项,index 是它在已渲染候选项中的位置;不提供时显示 label。 | TypeSnippet<[AutoCompleteItemSnippetProps<T>]> | - |
| loading | 下拉列表是否显示加载动画;为 true 时代替候选项和空内容。 | boolean | false |
| maxHeight | 下拉列表的最大高度,数字按 px 处理。 | string | number | 300 |
| motion | 下拉面板显示、隐藏时是否有动画。 | boolean | true |
| onBlur | 焦点离开输入框时调用一次。 | Type(event: FocusEvent) => void | - |
| onChange | 输入框内容变化或选中候选项时调用。 | Type(value: string | number) => void | - |
| onClear | 用清除按钮清空内容后调用。 | Type() => void | - |
| onDropdownVisibleChange | 下拉面板显示或隐藏时调用,参数为是否可见。 | Type(visible: boolean) => void | - |
| onFocus | 焦点进入输入框时调用一次。 | Type(event: FocusEvent) => void | - |
| onSearch | 用户输入时调用,参数为输入的文本。 | Type(inputValue: string) => void | - |
| onSelect | 选中候选项时调用,参数为选中值;设置 onSelectWithObject 时为去掉内部字段的数据项。 | Type(value: string | number | AutoCompleteDataItem) => void | - |
| onSelectWithObject | 布尔属性,不是回调:为 true 时,onSelect 收到去掉内部字段的数据项对象,而不是选中值。 | boolean | false |
| placeholder | 输入框的提示文案。 | string | - |
| position | 下拉面板的位置,可选值同 Tooltip 的 position。未设置时为 bottomLeft,ConfigProvider 的 direction 为 rtl 时为 bottomRight。 | Position | - |
| prefix | 输入框前缀的 snippet。 | Snippet | - |
| renderSelectedItem | 选中候选项后输入框显示的文本,必须返回字符串;字符串或数字项收到 { value, label },对象项收到数据项本身。 | Type(option: AutoCompleteSelectedItem<T>) => string | - |
| showClear | 有内容时是否显示清除按钮。 | boolean | false |
| size | 输入框尺寸。 | Size | - |
| stopPropagation | 是否阻止下拉面板上的 click、mousedown 与焦点进出事件冒泡。 | boolean | string | true |
| suffix | 输入框后缀的 snippet。 | Snippet | - |
| trigger | 替换输入框的 snippet。参数含 value、inputValue、placeholder、disabled、autofocus、size、validateStatus、showClear、insetLabelId、onChange(更新输入文本,触发 onSearch 与 onChange 并展开列表)、onClear、onFocus、onBlur、controlAttrs(传给组件的 aria-* 属性,放到自定义触发器的可聚焦元素上)。使用时 id 落在根元素上。 | TypeSnippet<[AutoCompleteTriggerSnippetProps]> | - |
| validateStatus | 校验状态,只影响展示样式。 | ValidateStatus | "default" |
| value | 输入框的当前值,可 bind:value。不传时由组件自行管理;单向 value={x} 只作初值,要完全受控用函数绑定 bind:value={get, set}。没有 defaultValue。 | string | number | - |
Methods
| 方法 | 说明 | 类型 |
|---|---|---|
| blur | 使输入框失去焦点。 | Type() => void |
| focus | 使输入框获得焦点。 | Type(options?: FocusOptions) => void |
| getNativeElement | 返回根 div,它包裹输入框或自定义触发器。 | Type() => HTMLDivElement | undefined |
id、name 以及 aria-describedby、aria-errormessage、aria-invalid、aria-label、aria-labelledby、aria-required 落在输入框(role="combobox")上,因此 <label for> 能关联到输入框。class(字符串、数组或对象)、style(字符串)、data-* 与其他未声明的属性和事件处理(例如 onclick)落在根 div 上,根 div 的 tabindex 固定为 -1;onkeydown 在组件处理完按键之后调用。小写的 onfocus、onblur、onchange、onselect 不是原生透传,请使用回调 onFocus、onBlur、onChange、onSelect。使用 trigger snippet 时没有输入框:id 落在根 div 上,name 不落到任何元素,aria-* 通过 controlAttrs 参数交给自定义触发器的可聚焦元素。
data 的对象项:
| 字段 | 说明 | 类型 |
|---|---|---|
| value | 选中后的值 | string | number |
| label | 候选项的文本,缺省回落到 value;标记请用 item snippet | string | number |
| disabled | 是否禁用该候选项 | boolean |
| class | 候选行的类名 | string | object | array |
| style | 候选行的样式 | string |
其他字段原样保留,出现在 item snippet 的 item 里;设置 onSelectWithObject 时,onSelect 收到的数据项对象同样保留这些字段。以 data- 开头的字段落在候选行上。字符串和数字项在 renderSelectedItem 中以 { value, label } 形式收到。
Accessibility
键盘和焦点
- AutoComplete 的 input 框带有
role="combobox",并通过aria-expanded、aria-controls、aria-autocomplete="list"和aria-activedescendant与选项面板关联 - combobox 的名称按优先级取:
aria-labelledby、aria-label、实际渲染出来的 insetLabel(combobox 的aria-labelledby指向它,未传insetLabelId时组件生成 id;有 prefix 时 insetLabel 不渲染,也不作名称)。listbox 使用同一个名称来源。AutoComplete 没有默认名称:以上都没有时,请用aria-label或aria-labelledby为输入框命名;<label for>只命名输入框,listbox 仍没有名称 - AutoComplete 的 input 框可被聚焦,聚焦后,键盘用户可以通过
上箭头或下箭头打开选项面板;data 为空时面板里显示 emptyContent(没有提供时为空),加载中显示加载动画 - AutoComplete 也支持通过
Enter键打开和收起面板 - 若用户将 defaultActiveFirstOption 属性设置为 true 时,选项面板打开后默认高亮第一个选项
- 若下拉菜单打开时:
- 使用
Esc可以关闭菜单 - 使用
Tab可以关闭菜单,焦点照常移到下一个可聚焦元素 - 使用
上箭头或下箭头可以切换选项,被高亮的选项(aria-activedescendant指向的项)aria-selected为true - 被聚焦的选项可以通过
Enter键选中,并收起面板 - 用鼠标选中选项后,焦点留在输入框中
- 使用
showClear开启且有内容时,清除按钮与 Input 相同:原生button,名称为语言包中的「清空输入」,从输入框按Tab移到它,Enter或空格键清空并把焦点放回输入框
文案规范
- 需要清晰地展示内容,让用户显而易见地感知到可用的各个选项
- 限制一次性展示的选项数量
设计变量
| Sass 变量 | 值 | 用法 |
|---|---|---|
| $transition_duration-autoComplete_option-bg | var(--semi-transition_duration-none) | 选择器-选项-动画持续时间 |
| $transition_function-autoComplete_option-bg | var(--semi-transition_function-easeIn) | 选择器-选项-过渡曲线 |
| $transition_delay-autoComplete_option-bg | var(--semi-transition_delay-none) | 选择器-选项-延迟时间 |
| $spacing-autoComplete_loading_wrapper-paddingTop | $spacing-tight | 加载搜索结果时的顶部内边距 |
| $spacing-autoComplete_loading_wrapper-paddingBottom | $spacing-tight | 加载搜索结果时的底部内边距 |
| $color-autoComplete_option-icon-default | transparent | 自动完成菜单选项图标颜色 - 默认态 |
| $color-autoComplete_option-bg-hover | var(--semi-color-fill-0) | 自动完成菜单选项背景颜色 - 悬停态 |
| $color-autoComplete_option-bg-active | var(--semi-color-fill-1) | 自动完成菜单选项背景颜色 - 按下态 |
| $color-autoComplete_option_disabled-text | var(--semi-color-disabled-text) | 禁用自动完成菜单选项文字颜色 |
| $width-autoComplete_option_tick | $width-icon-small | 自动完成菜单项选中对勾图标大小 |
| $spacing-autoComplete_option_tick-marginRight | $spacing-tight | 自动完成菜单选中对勾右侧外边距 |
| $spacing-autoComplete_option-paddingLeft | $spacing-base-tight | 自动完成菜单项左侧内边距 |
| $spacing-autoComplete_option-paddingRight | $spacing-base-tight | 自动完成菜单项右侧内边距 |
| $spacing-autoComplete_option-paddingTop | $spacing-tight | 自动完成菜单项顶部内边距 |
| $spacing-autoComplete_option-paddingBottom | $spacing-tight | 自动完成菜单项底部内边距 |
| $spacing-autoComplete_option_first-marginTop | $spacing-extra-tight | 自动完成第一个菜单项顶部外边距 |
| $spacing-autoComplete_option_last-marginBottom | $spacing-extra-tight | 自动完成最后一个菜单项顶部外边距 |
| $radius-autoComplete_option | 0px | 自动完成待选项圆角 |
| $color-autoComplete_option_main-text | var(--semi-color-text-0) | 自动完成菜单选项文本颜色 |