使用场景
用于对输入框提供输入建议,进行自动补全的操作
与可搜索的 Select 组件的区别:
- AutoComplete 本质上是一个增强型的提供了输入建议的 Input 组件,而 Select 是一个选择器
- 点击展开时,Select 会将输入框的值全部清空,而 AutoComplete 会保留上次选中的值
- Select 的已选项渲染(
renderSelectedItem)可以返回任意 JSX 元素,而 AutoComplete 的renderSelectedItem只允许返回字符串
代码演示
如何引入
import { AutoComplete } from '@fibrous/semi-ui-solid';
基本用法
通过 onSearch 监听用户输入,将输入建议通过更新 data 传入。通过 value 与 onChange 保持受控,当输入框变化或选中输入项时会触发 onChange
代码
import { createSignal } from 'solid-js';
import { IconSearch } from '@fibrous/semi-icons-solid';
import { AutoComplete } from '@fibrous/semi-ui-solid';
const domains = ['gmail.com', '163.com', 'qq.com'];
export default function AutoCompleteDemo() {
const [stringData, setStringData] = createSignal<string[]>([]);
const [value, setValue] = createSignal<string | number>('');
const handleStringSearch = (input: string) => {
setStringData(input ? domains.map((domain) => `${input}@${domain}`) : []);
};
const handleChange = (next: string | number) => {
console.log('onChange', next);
setValue(next);
};
return <AutoComplete data={stringData()} value={value()} showClear prefix={<IconSearch />} placeholder="搜索... " onSearch={handleStringSearch} onChange={handleChange} style={{ width: '200px' }} />;
}自定义候选项渲染
需要自定义候选项渲染时,data 可以传入一个对象数组(每个 Object 必须含有 label、value 两个 key,value 为候选项选中的值,label 为候选项展示的文本)。通过 renderItem 返回 JSX 可以自定义候选项的渲染
代码
import { createSignal } from 'solid-js';
import { IconSearch } from '@fibrous/semi-icons-solid';
import { AutoComplete, Avatar } from '@fibrous/semi-ui-solid';
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' },
];
export default function AutoCompleteDemo() {
const [data, setData] = createSignal<ListItem[]>([]);
const handleStringSearch = (input: string) => {
setData(input ? list.map((item) => ({ ...item, value: item.name, label: item.email })) : []);
};
const renderOption = (item: ListItem) => (
<>
<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>
</>
);
return <AutoComplete aria-label="搜索邮箱" data={data()} showClear prefix={<IconSearch />} onSearch={handleStringSearch} renderItem={renderOption} renderSelectedItem={(option) => option.email} style={{ width: '280px' }} />;
}远程搜索
从 onSearch 中获取用户输入值,动态更新 data 值
代码
import { createSignal } from 'solid-js';
import { Dynamic } from 'solid-js/web';
import { debounce } from 'es-toolkit';
import { IconSearch } from '@fibrous/semi-icons-solid';
import { IconButton, IconForm, IconInput, IconSelect, IconTable } from '@fibrous/semi-icons-solid/lab';
import { AutoComplete, type AutoCompleteDataItem } from '@fibrous/semi-ui-solid';
// Icons are stored as components so each row mounts its own instance
const initList = [
{ value: 'select', label: '选择器', icon: IconSelect },
{ value: 'input', label: '输入框', icon: IconInput },
{ value: 'form', label: '表单', icon: IconForm },
{ value: 'button', label: '按钮', icon: IconButton },
{ value: 'table', label: '表格', icon: IconTable },
];
type ListItem = (typeof initList)[number];
export default function AutoCompleteDemo() {
const [loading, setLoading] = createSignal(false);
const [list, setList] = createSignal<ListItem[]>(initList);
const handleSearch = (inputValue: string) => {
setLoading(true);
const newList = inputValue ? initList.filter((item) => item.value.includes(inputValue)) : initList;
setTimeout(() => {
setList(newList);
setLoading(false);
}, 1000);
};
const search = debounce(handleSearch, 200);
const handleSelect = (value: string | number | AutoCompleteDataItem) => {
console.log(value);
};
const renderItem = (item: ListItem) => (
<div style={{ display: 'flex', 'align-items': 'center' }}>
<div style={{ 'font-size': '32px' }}>
<Dynamic component={item.icon} />
</div>
<div style={{ 'margin-left': '12px' }}>
<p>{item.value}</p>
<p>{item.label}</p>
</div>
</div>
);
// Unlike Select, this can return only a string, not a JSX element
const renderSelectedItem = (item: ListItem) => item.value;
return <AutoComplete aria-label="搜索" data={list()} style={{ width: '250px' }} prefix={<IconSearch />} onSearch={search} loading={loading()} renderItem={renderItem} renderSelectedItem={renderSelectedItem} onSelect={handleSelect} />;
}尺寸
通过设置 size 可设置输入框尺寸,可选small,default(默认),large
代码
import { AutoComplete } from '@fibrous/semi-ui-solid';
const data = [1, 2, 3, 4];
export default function AutoCompleteDemo() {
return (
<div>
<AutoComplete data={data} size="small" placeholder="small" style={{ width: '200px' }} />
<br />
<br />
<AutoComplete data={data} size="default" placeholder="default" style={{ width: '200px' }} />
<br />
<br />
<AutoComplete data={data} size="large" placeholder="large" style={{ width: '200px' }} />
</div>
);
}下拉菜单的位置
通过设置 position 可设置下拉菜单位置,可选值参考 Tooltip position
代码
import { createSignal } from 'solid-js';
import { AutoComplete } from '@fibrous/semi-ui-solid';
const domains = ['gmail.com', '163.com', 'qq.com'];
export default function AutoCompleteDemo() {
const [data, setData] = createSignal<string[]>([]);
const change = (input: string) => {
setData(input ? domains.map((domain) => `${input}@${domain}`) : []);
};
return (
<div>
<AutoComplete data={data()} position="top" onSearch={change} placeholder="选项菜单在上方显示" style={{ width: '200px', margin: '10px' }} />
<AutoComplete data={data()} position="rightTop" onSearch={change} placeholder="选项菜单在右侧显示" style={{ width: '200px', margin: '10px' }} />
</div>
);
}禁用
代码
import { AutoComplete } from '@fibrous/semi-ui-solid';
const data = [1, 2, 3, 4];
export default function AutoCompleteDemo() {
return <AutoComplete data={data} placeholder="禁用下拉菜单" disabled style={{ width: '200px' }} />;
}校验状态
可设置不同校验状态,展示不同样式
代码
import { AutoComplete } from '@fibrous/semi-ui-solid';
export default function AutoCompleteDemo() {
return (
<>
<AutoComplete aria-label="Warning status" defaultValue="ies" validateStatus="warning" />
<br />
<br />
<AutoComplete aria-label="Error status" defaultValue="ies" validateStatus="error" />
<br />
<br />
<AutoComplete aria-label="Default status" defaultValue="ies" />
</>
);
}自定义空内容
可设置自定义展示空内容
代码
import { createSignal } from 'solid-js';
import { IllustrationNoContent } from '@fibrous/semi-illustrations-solid';
import { AutoComplete, Empty } from '@fibrous/semi-ui-solid';
export default function AutoCompleteDemo() {
const [data, setData] = createSignal<number[]>([]);
const [loading, setLoading] = createSignal(false);
const fetchData = (input: string) => {
setLoading(true);
setTimeout(() => {
setData(input ? Array.from({ length: 5 }, () => Math.random()) : []);
setLoading(false);
}, 1000);
};
return <AutoComplete aria-label="搜索" loading={loading()} data={data()} emptyContent={<Empty style={{ padding: '12px', width: '300px' }} image={<IllustrationNoContent style={{ width: '150px', height: '150px' }} />} description="暂无内容" />} onSearch={fetchData} />;
}API 参考
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| autoAdjustOverflow | 下拉面板被遮挡时是否自动调整方向。 | boolean | - |
| autofocus | 挂载后是否自动聚焦输入框。 | boolean | false |
| clearIcon | 自定义清除图标,showClear 为 true 时有效。 | JSX.Element | - |
| data | 候选项的数据源,可以是字符串、数字或对象的数组。对象项的 value 是选中值,label 是候选行的文本(须提供,标记内容经 renderItem 渲染),其余字段原样保留,出现在 renderItem 的参数里。需替换为新数组才会更新列表。 | T[] | [] |
| defaultActiveFirstOption | 面板打开后是否默认高亮第一个候选项,此时按 Enter 可直接选中。 | boolean | false |
| defaultOpen | 挂载时是否默认展开下拉面板。 | boolean | - |
| defaultValue | 非受控时输入框的初始值,只在挂载时读取。 | string | number | - |
| disabled | 是否禁用。 | boolean | - |
| dropdownClassName | 下拉列表的类名,落在 role="listbox" 元素上。 | string | - |
| dropdownMatchSelectWidth | 下拉面板的最小宽度是否等于输入框宽度。 | boolean | true |
| dropdownStyle | 下拉列表的内联样式,对象的键用 kebab-case,落在 role="listbox" 元素上。 | JSX.CSSProperties | string | - |
| emptyContent | data 为空时在下拉面板中显示的内容;加载中时代替它显示加载动画。 | JSX.Element | null |
| id | 输入框的 id,供 <label for> 关联;使用 triggerRender 时落在根元素上。 | string | - |
| insetLabel | 输入框内嵌标签,渲染在前缀位置;设置 prefix 或 triggerRender 时不渲染,也不作为可访问名称。 | JSX.Element | - |
| insetLabelId | insetLabel 元素的 id,combobox 与 listbox 的名称引用它;不传时组件在渲染 insetLabel 时生成。 | string | - |
| loading | 下拉列表是否显示加载动画;为 true 时代替候选项和空内容。 | boolean | false |
| maxHeight | 下拉列表的最大高度,数字按 px 处理。 | string | number | 300 |
| motion | 下拉面板显示、隐藏时是否有动画。 | boolean | true |
| onBlur | 焦点离开输入框所在区域时的回调,参数为 FocusEvent;焦点在区域内移动(例如移到清除按钮)时不触发。 | Type(e: FocusEvent) => void | - |
| onChange | 输入框内容变化或选中候选项时的回调,参数为当前值。 | Type(value: string | number) => void | - |
| onChangeWithObject | 不生效:组件不读取该属性。 | boolean | - |
| onClear | 用清除按钮清空内容后的回调。 | Type() => void | - |
| onDropdownVisibleChange | 下拉面板显示或隐藏时的回调,参数为是否可见。 | Type(visible: boolean) => void | - |
| onFocus | 焦点进入输入框所在区域时的回调,参数为 FocusEvent;焦点在区域内移动(例如移到清除按钮)时不触发。 | Type(e: FocusEvent) => void | - |
| onSearch | 用户输入时的回调,参数为输入的文本。 | Type(inputValue: string) => void | - |
| onSelect | 候选项被选中时的回调,参数为选中值;设置 onSelectWithObject 时为去掉内部字段的数据项对象。 | Type(value: string | number | BaseDataItem) => void | - |
| onSelectWithObject | 布尔属性,为 true 时 onSelect 收到去掉内部字段的数据项对象,而不是选中值。 | boolean | false |
| placeholder | 输入框的提示文案。 | string | - |
| position | 下拉面板的位置,可选值同 Tooltip 的 position。未设置时为 bottomLeft,ConfigProvider 的 direction 为 rtl 时为 bottomRight。 | Position | - |
| prefix | 输入框的前缀内容;设置后 insetLabel 不渲染。 | JSX.Element | - |
| ref | 根 div,包裹输入框或 triggerRender 的输出;其 focus() 与 blur() 作用于输入框。 | TypeRef<HTMLDivElement> | - |
| renderItem | 自定义候选项的渲染,参数为数据项本身,返回 JSX 元素;未设置时显示 label。 | Type(option: T) => JSX.Element | - |
| renderSelectedItem | 选中候选项后输入框显示的文本,必须返回字符串;字符串或数字项收到 { value, label },对象项收到数据项本身。 | Type(option: T extends string | number ? BaseDataItem : T) => string | - |
| showClear | 有内容时是否显示清除按钮。 | boolean | false |
| size | 输入框尺寸,可选 small、default 或 large。 | Size | "default" |
| stopPropagation | 是否阻止下拉面板上的 click、mousedown 与焦点进出事件冒泡。 | TooltipProps['stopPropagation'] | true |
| suffix | 输入框的后缀内容。 | JSX.Element | - |
| triggerRender | 自定义触发器,替换输入框。参数含 value(已选项数组)、inputValue、placeholder、disabled、autofocus、size、validateStatus、showClear、prefix、suffix、clearIcon、aria-label、aria-labelledby、aria-describedby、aria-errormessage、aria-invalid、aria-required(其他 aria-* 不会传到自定义触发器上)、onChange(更新输入文本,触发 onSearch 与 onChange 并展开列表)、onClear、onFocus、onBlur 与 componentProps。使用时 id 落在根元素上。 | Type(props: AutoCompleteTriggerRenderProps<T>) => JSX.Element | - |
| validateStatus | 校验状态,可选 default、error 或 warning,只影响展示样式。 | ValidateStatus | "default" |
| value | 当前值。传入后为受控,undefined 时按非受控处理。 | string | number | - |
class、style、ref、data-* 与其他未声明的属性和事件(例如 onClick)落在根 div 上,根 div 的 tabindex 固定为 -1。id、name 与 aria-* 落在输入框(role="combobox")上,因此 <label for> 能关联到输入框。使用 triggerRender 时没有输入框:id 落在根 div 上,name 不落到任何元素,aria-label、aria-labelledby、aria-describedby、aria-errormessage、aria-invalid 与 aria-required 通过参数交给自定义触发器的可聚焦元素,其他 aria-* 不会传到触发器上。
data 的对象项:
| 字段 | 说明 | 类型 |
|---|---|---|
| value | 选中后的值 | string | number |
| label | 候选行的文本,须提供;标记内容经 renderItem 渲染 | string | number |
| disabled | 是否禁用该候选项 | boolean |
| class | 候选行的类名 | string |
| style | 候选行的样式 | JSX.CSSProperties | string |
其他字段原样保留,出现在 renderItem 的参数里;设置 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) | 自动完成菜单选项文本颜色 |