Select 选择器
从已知清单中选择一个或多个值,选项收在浮层内。
用法
单选
组件结构
加粗的是必需部件。
data-scope="select":root · label · control · trigger · value-text · indicator · clear-trigger · tag-list · positioner · content · list · footer · group · group-label · item · item-prefix · item-text · item-description · item-suffix · item-indicator · empty · loading · error · retry-trigger · hidden-select
示例
多选
已选项在触发器里排成标签
已选:apple
受控
由 value 和 value-change 控制
宿主持有的值:banana
禁用
禁止展开和聚焦
变体
outline、subtle 和 ghost
颜色
六种语气
尺寸
小、中、大三档
异步加载
展开时加载选项
当前值:(未选)
宽度
分别设置控件和浮层宽度
自定义内容
自定义选项和当前值
操作入口
通过插槽状态控制开合和值
大量选项
列表内部滚动并支持连打检索
分组
跨分组保持键盘导航
当前值:(未选)
多选标签
超出数量合并为 +N
校验
显示无效状态和错误说明
这一项必填
滚动加载
到达列表底部加载下一页
已加载 20 / 80 条
命令式聚焦
聚焦触发器
清空
有值时显示清空按钮
底部操作区
固定在滚动列表下方
当前值:(未选)
Popover + Listbox
不参与表单的选择
选项副文本
一行放不下的解释写在第 2 行
行首与行尾
两格交给作者,文字与副文本仍由数据铺
长选项虚拟化
一万条选项只写 virtual:浮层里只铺滚到的那一屏,键盘仍跨完整 collection;虚拟化那一块按需加载,没开的不为它付体积
对象值
选项直接用接口取回来的对象:value 与值变化交出的都是对象本身,给 valueKey 按 id 认,同一个人换成内容相同的新对象也选得中
设计指引
何时使用
- 从已知选项中选择一个或多个值。
- 需要分组、标签多选或异步加载。
何时不用
特性
- 通过
hidden-select参与表单。 - 不写结构时按标准结构铺出整套部件(Vue 不写默认插槽、React 不给 children、Web Components 不写部件或只写
root),三端出自同一个结构函数;label给标题文字、clearable带清空按钮。浮层里列表之后铺空态、在途、取数失败的提示与重试钮,露哪一种按loading、error与条数收放;文字取translations,空态文字可由empty给出。Web Components 里标题、空态、条目文字与行首行尾可以写在宿主的直接子节点上接管:slot="label"、slot="empty"、slot="item:<值>"、slot="item-prefix:<值>"、slot="item-suffix:<值>";Vue 用同名插槽,React 给empty一个节点。 - 多选值在触发器里排成标签(不写结构时直接铺出),超出
maxTagCount后合并为+N;组合框、树选择与级联选择的多选用同一套呈现。 - 支持分组、加载、空状态、底部操作区和滚动加载。
- 选项可逐条声明语气,失效或需要留意的那条自带该族字色与高亮底。
- 选项可写副文本,第 2 行放一句解释,与标题同列、走 muted 档。
- 选项来自模板或宿主数据时,用
@xihan-ui/core/options导出的findOptionViolation(collection, { tree?, unique?, requireLabel?, strict?, isValue? })先校验结构(isValue换掉缺省只收字符串的value检查,供取值是数字、布尔的选项列表用),返回第一处问题的 JSON Pointer 与原因;单选组、复选组、组合框、树选择与级联吃同一套节点形状,同样适用。 - 行首与行尾两格各有逐条钩子:只想加个图标或计数,不必把整条重搭。
- 同一层里只要有一条写了行首格,这一层的行首格都至少一个图标宽,没写的条目也空出这一格,正文左缘对齐;整层都没有就不留。
- 控件使用 Field Chrome,浮层使用 M2 磨砂表面;候选面板与字段盒等宽,长选项在条目里截断。
- 选中项保留普通文字,通过末端对号表示状态。
- 关闭时立即退出交互,资源在退场动画结束后释放。
- 占位态:首次加载时在途占位在文案前转一枚加载环;已有选项时后台刷新保留上一帧、只报
aria-busy,选项照常可选、不淡化(淡到禁用透明度会把文字压到 4.5:1 以下),在途占位让位;空态与加载文字取次要文字、上下内距一档。 lazyMount让列表内容第一次展开时才挂载、之后常驻:页面上 Select 很多(表格每行一个、长表单)时,收起态每个实例背着的整份条目是挂载开销的大头。打开前的选中文字与收起态连打按collection计算,所以要配collection用;没给时打开前选中文字退回值本身、连打不生效。Web Components 里把条目写进 list 里的一个<template>,第一次展开才克隆。- 值可以是任意类型:
value、defaultValue与collection里交进来数字、对象都行,onValueChange、v-model、插槽与renderItem拿到的是原值;Vue 与 React 按value或collection推断值的类型,两处对不上、valueKey写错字段编译即报错。数字转成字符串当键,对象值给valueKey(字段名或取值函数),同一个选项每次都是新对象(接口刚取回来)也认得出来,不给按对象身份认;键也是条目的data-value与随表单提交的值。Vue 的h()推断不了泛型,那里按宽松的类型检查;Web Components 经 property 交值,写querySelector<XhSelectElement<number>>("xh-select")收窄。
组合
- 与表单字段组合。
- 不参与表单时使用 气泡卡片 与 列表框。
- 长选项列表不写结构时只写
virtual:浮层里只铺滚到的那一屏条目,方向键、Home / End 与按字跳转仍跨完整 collection,自绘滚动条跟着视口走;条目按estimateSize(缺省 36)起算、渲出后按实测校正,带副文本的两行条目也对得齐。虚拟化那一块按需加载,没开的选择器不为 Virtualizer 的内核付体积;挂载后立刻展开、或lazyMount下第一次展开时它还在路上,到了之后焦点照样补进列表。 - 手写结构时照旧把完整 collection 与 Virtualizer 的
collectionVirtualizer同时交给根,浮层内只渲染virtualItems;virtual只管标准结构。
最佳实践
- 固定触发器宽度,避免选中值改变布局。
- 选项较多或需要搜索时使用 Combobox。
- 自定义内容中的主要文字放在
item-text中。
反模式
- 不要用 Select 承载“导出”“删除”等动作。
- 异步加载时不要省略加载和空状态。
API 参考
产物
| 层 | 值 |
|---|---|
| 自定义元素 | <xh-select> |
| Vue 组件 | XhSelectClearTrigger XhSelectContent XhSelectControl XhSelectEmpty XhSelectError XhSelectFooter XhSelectGroup XhSelectGroupLabel XhSelectIndicator XhSelectItem XhSelectItemDeleteTrigger XhSelectItemDescription XhSelectItemIndicator XhSelectItemPrefix XhSelectItemSuffix XhSelectItemText XhSelectLabel XhSelectList XhSelectLoading XhSelectOverflowTag XhSelectPositioner XhSelectRetryTrigger XhSelectRoot XhSelectTag XhSelectTagLabel XhSelectTagList XhSelectTrigger XhSelectValueText |
| 组合式函数 | useSelect |
| 状态机 | selectMachine |
| 皮肤 | @xihan-ui/styles/select.css |
Props
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
labelled | boolean | 作者渲染了 label 部件时置真,由适配器统计而不是判断标题文字是否有值。 为假时可聚焦部件不把 label 的 id 写进 aria-labelledby:指向未渲染的 id 是无效引用,作者写在控件上的 aria-label 也会被它压住。 | |
ariaLabel | string | 不显示出来的名字,写成 aria-label 落在与字段标签同一个部件上。 放进字段(字段接线)或渲染了 label 部件时名字以它们为准,aria-labelledby 盖过它。 | |
collection | SelectNode<V>[] | 条目数据,显示文本与禁用的事实源。提供后条目部件只需声明 value, 显示文本也不再从 DOM 查询。未提供时回到文本写在条目中、从 DOM 查询的方式。 | |
valueKey | SelectionValueKey<V> | 对象值怎样取键:字段名或取值函数。同一个选项每次都是新对象(接口刚取回来)时给它; 不给时按对象身份认。值不是对象时用不着。键也是条目的 data-value 与随表单提交的值。 | |
virtualizer | CollectionVirtualizer | 完整 collection 与 Virtualizer 的焦点桥;count 必须与 collection.length 一致。 | |
lazyMount | boolean | 列表内容第一次展开时才挂载,默认 false(随组件一起挂载、收起时只隐藏)。挂过之后一直留着,来回开合不重建。 页面上 Select 很多(表格每行一个、长表单)时打开它:收起态每个实例都背着一整份条目,挂载开销大半在这里。 打开前的选中文字与收起态连打按 collection 计算;没给 collection 时,打开前选中文字退回值本身、连打不生效。 | |
value | V | V[] | null | 选中值。裸串是单选的简写,null 是受控且无选中,未提供(undefined)才是非受控;内部一律按数组处理。 受控时 cell 直读 prop,写入只发 onValueChange 不落内部值。 | |
defaultValue | V | V[] | null | 非受控初始选中值。与 value 同样接受裸值与 null。 | |
multiple | boolean | 允许选中多项。单选时选完即收起,多选时保持展开继续选。 | |
open | boolean | 展开态。提供即受控:内部不再自行修改,只发 onOpenChange。 | |
defaultOpen | boolean | ||
disabled | boolean | 整个控件禁用:trigger 使用原生 disabled,隐藏 select 不参与提交。 | |
readOnly | boolean | 只读:浮层照常展开与浏览,但选中值不可修改、也不可清空。 | |
invalid | boolean | 校验错误态:trigger 标红并输出 aria-invalid。 | |
loading | boolean | 条目加载中:列表报告 aria-busy,显示在途占位、隐藏空态占位。 | |
error | boolean | 整表取数失败:浮层里露出 error 部件(role=alert)与 retry-trigger,空态占位让位;loading 同时为真时在途占位优先。 点重试钮、或浮层里没有高亮条目时按 Enter / Space,派发 onRetry;宿主重新取数时撤掉 error、置上 loading。 | |
translations | Partial<SelectTranslations> | 读屏文案,默认英文。 | |
maxTagCount | number | 多选标签最多显示的数量,其余折叠进 overflowCount、合成 +N 标签;默认 3(SELECT_DEFAULT_MAX_TAG_COUNT)。 | |
required | boolean | 原生表单校验:无选中值时提交被拦截。 | |
name | string | 表单字段名。提供后隐藏 select 才带 name,选中值随表单一并提交。 | |
placeholder | string | 无选中时 value-text 显示的占位文字。 | |
placement | Placement | ||
offset | number | ||
loop | boolean | 方向键到达末尾是否回绕,默认 true。 | |
dir | Direction | 文字方向,默认 ltr。 | |
variant | ControlVariant | 形态:outline / subtle / ghost,决定触发器的描边与底色使用方式。默认 outline。 | |
tone | Tone | 语气:brand / neutral / success / warning / danger / info,决定聚焦与选中强调使用哪族颜色。 | |
size | Size | 尺寸:sm / md / lg,决定触发器高度、内边距与字号档位。 | |
onValueChange | (details: SelectValueChangeDetails<V>) => void | value 变化意图回调;受控时是唯一出口,非受控时随内部写入一并通知。 | |
onClear | () => void | 用户按清空钮(clear-trigger)清掉了值;先发值变化,再发它。程序化的 clear() 不发。 | |
onOpenChange | (details: SelectOpenChangeDetails) => void | open 变化意图回调;受控时是唯一出口,非受控时随内部转移一并通知。 | |
onRetry | () => void | 整表取数失败后请求重试:点了 retry-trigger,或浮层里没有高亮条目时按了 Enter / Space。 |
SelectNode
collection 的元素。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
value | V | 是 | |
label | string | 展示文本,也是连打检索的取字来源;默认回退为值(不是字符串的值退回它的键)。 | |
disabled | boolean | 条目禁用:方向键跳过它,但它仍可聚焦、仍是导航起点。 | |
tone | Tone | 该条选项自身的性质:危险选项写 danger、需要留意的写 warning。不写即与其余条目同档。 只换字色与悬停 / 按下的面,不表达选中与校验;选中的标记与禁用都压过它。 彩字不是唯一通道,要紧的差别仍要配图标或文案。整个选择器的 tone 不下发给条目。 | |
description | string | 副文本,写入 item-description 部件;未提供时本条不铺该部件。 它是第 2 行的说明,跟着条目走 muted 档,不跟语气;放不下一行的解释才用它, 一句话能说清的写进 label。 |
事件
自定义元素将载荷放在 detail;Vue 使用同名 emit。
| 事件 | 载荷 | 说明 |
|---|---|---|
value-change | SelectValueChangeDetails | 选中值变化;detail 为 { value: 值[] },值是 value / collection 里交进来的原值(字符串、数字或对象) |
clear | `` | 用户按清空钮(clear-trigger)清掉了值;先发值变化,再发它。程序化的 clear() 不发。 |
open-change | SelectOpenChangeDetails | open 状态变化;detail 为 { open: boolean } |
retry | `` | 整表取数失败后请求重试:点了 retry-trigger,或浮层里没有高亮条目时按了 Enter / Space |
插槽
仅列出带载荷的插槽。
| Vue 组件 | 插槽 | 载荷 | 说明 |
|---|---|---|---|
XhSelectRoot | default | SelectRootSlotProps<V> | |
XhSelectRoot | label | — | |
XhSelectRoot | empty | — | 自动渲染树中空态占位的内容 |
XhSelectRoot | item | SelectNodeMeta<V> | 只填条目的文字槽,副文本与首尾两格照旧各归各的 |
XhSelectRoot | item-prefix | SelectNodeMeta<V> | 只接管行首那一格,其余槽照旧由数据铺 |
XhSelectRoot | item-suffix | SelectNodeMeta<V> | 只接管行尾那一格(计数、徽标、次级图标),其余槽照旧由数据铺 |
React 适配器 props
只列各组件自己声明的那些:继承自 ComponentPropsWithRef 的 DOM 属性不在其中,根组件上与上面 Props 表同名的也不重复列。Vue 的对应物是上面的插槽表。
| React 组件 | 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
XhSelectGroup | value | string | 是 | |
XhSelectItem | value | unknown | 是 | 条目代表的值,与根上的值同类型:字符串、数字或对象。 |
XhSelectItem | disabled | boolean | 默认交给 connect 查询 collection,写死 false 会覆盖数据中的禁用。 | |
XhSelectPositioner | container | () => Element | null | 浮层挂载的容器;未提供时按全局配置,再未提供时挂载到 body。 | |
XhSelectRoot | virtual | boolean | 标准结构下开虚拟化:浮层里只铺滚到的那一屏条目,长列表一个开关即可。虚拟化那一块异步加载,没开的不为它付体积; 手写 children 时不使用它,照旧自己套 Virtualizer 再把 collectionVirtualizer 交给 virtualizer。 | |
XhSelectRoot | estimateSize | number | 虚拟化时每条的预估高度(px),缺省 36;条目渲出后按实测校正。 | |
XhSelectRoot | label | ReactNode | 标题文字。提供后不必再写 label 部件。 | |
XhSelectRoot | empty | ReactNode | 自动渲染树中的空态内容,没给时取 translations.empty。 | |
XhSelectRoot | clearable | boolean | 自动渲染树中是否带清空按钮;手写部件不使用它,写了节点即可清空。 | |
XhSelectRoot | renderItem | (node: SelectNodeMeta<V>) => ReactNode | 每个条目的自定义内容;未提供时使用 collection 中的 label。 | |
XhSelectRoot | renderItemPrefix | (node: SelectNodeMeta<V>) => ReactNode | 只接管条目行首那一格;其余槽仍由数据铺。 | |
XhSelectRoot | renderItemSuffix | (node: SelectNodeMeta<V>) => ReactNode | 只接管条目行尾那一格;其余槽仍由数据铺。 | |
XhSelectRoot | children | SlotChildren<SelectRootSlotProps<V>> | ||
XhSelectTag | value | unknown | 是 | 它代表哪个选中值,与根上的值同类型(标签插槽里的 tag.value 原样交回来)。 |
状态
公开状态写入 data-state。
| 部件 | 取值 |
|---|---|
root | 'open' | 'closed' |
control | 'open' | 'closed' |
trigger | 'open' | 'closed' |
indicator | 'open' | 'closed' |
positioner | 'open' | 'closed' |
content | 'open' | 'closed' |
list | 'open' | 'closed' |
footer | 'open' | 'closed' |
item | 'checked' | 'unchecked' |
item-prefix | 'checked' | 'unchecked' |
item-text | 'checked' | 'unchecked' |
item-description | 'checked' | 'unchecked' |
item-suffix | 'checked' | 'unchecked' |
item-indicator | 'checked' | 'unchecked' |
empty | 'open' | 'closed' |
loading | 'open' | 'closed' |
error | 'open' | 'closed' |
以下名称仅用于内部状态机。
状态:open · closed
事件:OPEN · TOGGLE · CLOSE · CONTROLLED.OPEN · CONTROLLED.CLOSE · ITEM.HIGHLIGHT · HIGHLIGHT.CLEAR · ITEM.LOST · ITEM.SELECT · VALUE.SET · VALUE.CLEAR · FORM.RESET · PRESS.START · PRESS.END · TAG_LIST.TRACKED
判据:isOpenControlled · isMultiple · isReadOnly · canPress
connect API
getXxxProps() 返回对应部件的宿主属性。
| 成员 | 类型 | 说明 |
|---|---|---|
open | boolean | |
collection | readonly SelectNodeMeta<V>[] | 由 collection 推导的条目元信息,按数据顺序排列;未提供 collection 时为空数组。 |
value | V[] | 选中集合,按选中先后排列而非文档顺序。单选恒为长度 ≤ 1。 |
valueKeys | string[] | 选中值的键,与 value 逐项对应:字符串值即它本身,别的值是编出来的键。条目的 data-value 与随表单提交的都是它。 |
valueText | string[] | 选中项的文本,与 value 逐项等长对应;某项在 DOM 中查询不到条目时该项回退为值本身。 |
displayText | string | value-text 实际显示的文字:有选中时取其文本(多选按半角逗号加空格连接),否则取 placeholder。 |
multiple | boolean | 是否允许多选。 |
invalid | boolean | 校验错误态。 |
error | boolean | 整表取数失败。 |
translations | SelectTranslations | 解析后的读屏文案;error 与 retry-trigger 没写内容时显示其中的 loadError 与 retry。 |
readOnly | boolean | 只读态。 |
canClear | boolean | 当前能否清空:有选中且既不禁用也不只读。 |
tags | SelectTagMeta<V>[] | 可见标签(受 maxTagCount 截断),与 value / valueText 同序。 |
overflowCount | number | 被 maxTagCount 折叠的标签数。 |
overflowText | string | +N 标签显示的文字(由 translations.overflowTag 计算);没有折叠的标签时为空串。 |
highlightedValue | V | null | 高亮锚点;收起时为 null。 |
setOpen | (next: boolean) => void | |
setValue | (next: V | V[]) => void | |
clear | () => void | 清空全部选中。 |
deselect | (value: V) => void | 移除一个选中值。 |
getRootProps | () => T['element'] | |
getLabelProps | () => T['element'] | |
getControlProps | () => T['element'] | 触发器与清空按钮的收纳容器:两者在其中并排,有值且指针落在盒上(触屏上有值即)时清空按钮替代展开指示符。 |
getTriggerProps | () => T['button'] | |
getValueTextProps | () => T['element'] | |
getIndicatorProps | () => T['element'] | |
getClearTriggerProps | () => T['button'] | 清空按钮:不占 Tab 位;无法清空时整体隐藏;点击清空全部选中、不展开浮层,焦点送回 trigger。 |
getTagListProps | () => T['element'] | 标签行:收纳可见标签与 +N 标签,放在触发器中;无选中时整体 hidden。 |
getTagProps | (props: SelectTagProps<V>) => T['element'] | 标签:一个选中值一个,即库内 tag 的 root(data-scope="tag"):语气、尺寸与禁用从本控件传下,形态按控件的面派生(outline / ghost / 默认使用淡底标签,subtle 使用描边标签),另带 data-value 记录代表的值。放在触发器中即纯展示(不渲染关闭按钮),放在外部配删除按钮可删除。 |
getTagLabelProps | () => T['element'] | 标签文字所在的块(tag 的 label):截断落在这一层;标签与 +N 共用。 |
getOverflowTagProps | () => T['element'] | 被折叠的标签合成的一个:同样是 tag 的 root,显示 overflowText、带 data-count;没有折叠的标签时 hidden。 |
getItemDeleteTriggerProps | (props: SelectTagProps<V>) => T['button'] | 标签删除按钮:即所在标签那份 tag 的 close-trigger(data-scope="tag"),可及名使用 translations.deleteItem,禁用时保留位置、原生 disabled;点击移除所在标签的选中值;须放在标签中。 |
getPositionerProps | () => T['element'] | |
getContentProps | () => T['element'] | 浮层外壳:描边、底色、阴影与键盘收口都在它身上。 |
isContentMounted | () => boolean | content 里的列表内容此刻该不该挂载。未开 lazyMount 时恒为真;开了的话第一次展开起为真。 content 节点本身始终在场,只有它的内容按此挂载。 |
getListProps | () => T['element'] | 列表框本体,滚动在这一层;role=listbox 与条目的拥有关系都归它。 |
getFooterProps | () => T['element'] | 浮层底部的操作区,是 list 的兄弟;不在列表框的拥有关系中,也不参与方向键与连打检索。 |
getEmptyProps | () => T['element'] | 空态占位:放在 content 中、list 的兄弟。 提供 collection 时由连接层按条数收放;条目手写时不写 hidden,是否显示由作者决定。 |
getLoadingProps | () => T['element'] | 在途占位:与空态占位同一位置,两者不同时显示:加载期间显示它,空态让位。 提供 collection 时由连接层按条数收放;条目手写时只按 loading 收放。 |
getErrorProps | () => T['element'] | 整表取数失败的提示:与空态、在途占位同一位置,role=alert;error 为真且不在途时露出。 |
getRetryTriggerProps | () => T['button'] | 整表取数失败后的重试按钮:不占 Tab 位,键盘从浮层里按 Enter / Space 重试;点击派发 onRetry。 |
getGroupProps | (props: SelectGroupProps) => T['element'] | 分组容器:role=group,条目挂在其中;分组标题经 aria-labelledby 关联。 |
getGroupLabelProps | (props: SelectGroupProps) => T['element'] | 分组标题:不是选项、不进入导航,只作为本组的可及名。 |
getItemProps | (props: SelectItemProps<V>) => T['element'] | |
getItemPrefixProps | (props: SelectItemProps<V>) => T['element'] | |
getItemTextProps | (props: SelectItemProps<V>) => T['element'] | |
getItemDescriptionProps | (props: SelectItemProps<V>) => T['element'] | |
getItemSuffixProps | (props: SelectItemProps<V>) => T['element'] | |
getItemIndicatorProps | (props: SelectItemProps<V>) => T['element'] | |
getHiddenSelectProps | () => T['select'] | 表单出口:一份视觉隐藏的原生 select,由根部件自行渲染(作者不必手写)。 选项由适配器按当前值补齐,原生提交与 required 校验据此获取值。 |
无障碍
键盘
规格出处:W3C APG
| 按键 | 生效条件 | 行为 |
|---|---|---|
Enter / Space | closed, focus in trigger | 展开列表并把高亮落到当前选中项(无选中则落首个可用条目) |
ArrowDown | closed, focus in trigger | 展开列表并把高亮落到选中项的下一个可用条目 |
ArrowUp | closed, focus in trigger | 展开列表并把高亮落到选中项的上一个可用条目 |
单个可打印字符 | closed, focus in trigger | 连打检索命中的条目直接成为选中值(多选是加进集合,已在集合里则不动),列表不展开 |
Delete | closed, focus in trigger, 有选中值且未禁用、未只读 | 清空全部选中,列表不展开 |
Backspace | closed, focus in trigger, 有选中值且未禁用、未只读 | 单选清空;多选去掉最后一个选中值,列表不展开 |
ArrowDown | open, focus in content | 高亮移到下一个条目(禁用项跳过、尽头按 loop 回绕) |
ArrowUp | open, focus in content | 高亮移到上一个条目(禁用项跳过、尽头按 loop 回绕) |
Home | open, focus in content | 高亮移到首个可用条目 |
End | open, focus in content | 高亮移到末个可用条目 |
单个可打印字符 | open, focus in content | 连打检索移动高亮,不改选中值 |
Enter / Space | open, 单选, 高亮条目未禁用 | 选中高亮条目并关闭列表,焦点归还 trigger |
Enter / Space | held in item / clear-trigger, 未禁用、未只读 | 按住期间该部件投影 data-pressed,与指针 :active 同一副按压面;抬起或失焦撤下,条目随浮层收起一并撤下;没有值可清时清空按钮不进 |
Enter / Space | open, 多选, 高亮条目未禁用 | 切换高亮条目的选中态,列表不收起、焦点留在条目上 |
Enter / Space | open, 整表取数失败(error)且没有高亮条目 | 派发重试,浮层不收起;重试钮不占 Tab 位,键盘从这里重试 |
Escape | open | 关闭列表并把焦点归还 trigger,选中值不变 |
Tab / Shift+Tab | open | 关闭列表,焦点不归还 trigger,按 Tab 序列自然离开 |
ARIA
以下属性由 connect 生成。
| 部件 | 属性 | 值 |
|---|---|---|
trigger | aria-controls | content 部件的 id |
trigger | aria-expanded | 'true' | 'false' |
trigger | aria-haspopup | 'listbox' |
trigger | aria-invalid | 'true' | 'false' |
trigger | aria-label | props.ariaLabel |
trigger | aria-labelledby | label 部件的 id value-text 部件的 id | trigger 部件的 id value-text 部件的 id | value-text 部件的 id |
trigger | aria-readonly | 'true' | 'false' |
trigger | role | 'combobox' |
indicator | aria-hidden | 'true' |
clear-trigger | aria-label | translations.clearTrigger |
content | aria-hidden | !open || undefined |
list | aria-busy | 'true' | undefined |
list | aria-label | translations.content |
list | aria-labelledby | label 部件的 id value-text 部件的 id | trigger 部件的 id value-text 部件的 id | value-text 部件的 id |
list | aria-multiselectable | 'true' | 'false' |
list | role | 'listbox' |
group | aria-labelledby | group-label 部件的 id |
group | role | 'group' |
item | aria-disabled | 'true' | 'false' |
item | aria-selected | 'true' | 'false' |
item | role | 'option' |
item-prefix | aria-hidden | 'true' |
item-indicator | aria-hidden | 'true' |
error | role | 'alert' |
hidden-select | aria-hidden | 'true' |
样式参考
皮肤
@xihan-ui/styles/select.css 按 [data-scope="select"][data-part="root"] 部件选择器书写,发布产物以挂载类 .xh-scope-select 代替其中的 data-scope(特异性相同),位于 xihan.components 层。覆盖样式使用 xihan.overrides,部件选择器照常可用。
forced-colors: active 下另有一套规则:颜色交给系统,边框与状态标记改用系统色关键字。
数据属性
由 connect 生成;条件不成立时不输出无值属性。
| 部件 | 属性 | 值 |
|---|---|---|
root | data-disabled | ''(条件成立时才出现) |
root | data-invalid | ''(条件成立时才出现) |
root | data-loading | ''(条件成立时才出现) |
root | data-readonly | ''(条件成立时才出现) |
root | data-size | props.size |
root | data-state | 'open' | 'closed' |
root | data-tone | props.tone |
root | data-variant | props.variant |
label | data-disabled | ''(条件成立时才出现) |
control | data-disabled | ''(条件成立时才出现) |
control | data-invalid | ''(条件成立时才出现) |
control | data-readonly | ''(条件成立时才出现) |
control | data-state | 'open' | 'closed' |
control | data-variant | props.variant |
control | data-xh-action-owner | '' |
control | data-xh-field-chrome | '' |
control | data-xh-field-size | props.size |
trigger | data-disabled | ''(条件成立时才出现) |
trigger | data-invalid | ''(条件成立时才出现) |
trigger | data-placeholder | ''(条件成立时才出现) |
trigger | data-readonly | ''(条件成立时才出现) |
trigger | data-state | 'open' | 'closed' |
value-text | data-disabled | ''(条件成立时才出现) |
value-text | data-placeholder | ''(条件成立时才出现) |
indicator | data-clearable | ''(条件成立时才出现) |
indicator | data-disabled | ''(条件成立时才出现) |
indicator | data-state | 'open' | 'closed' |
clear-trigger | data-pressed | ''(条件成立时才出现) |
clear-trigger | data-xh-action-control | '' |
clear-trigger | data-xh-action-display | 'has-value' |
clear-trigger | data-xh-action-has-value | ''(条件成立时才出现) |
clear-trigger | data-xh-action-profile | 'field-inset' |
clear-trigger | data-xh-action-size | props.size |
clear-trigger | data-xh-action-variant | 'ghost' |
tag-list | data-disabled | ''(条件成立时才出现) |
tag-list | data-instant | ''(条件成立时才出现) |
tag-list | data-xh-tag-list | '' |
positioner | data-hidden | ''(条件成立时才出现) |
positioner | data-placement | 定位引擎算出的实际落位 |
positioner | data-positioned | ''(条件成立时才出现) |
positioner | data-size | props.size |
positioner | data-state | 'open' | 'closed' |
positioner | data-tone | props.tone |
positioner | data-variant | props.variant |
content | data-instant | ''(条件成立时才出现) |
content | data-placement | 定位引擎算出的实际落位 |
content | data-state | 'open' | 'closed' |
content | data-xh-material | 'frosted' |
list | data-state | 'open' | 'closed' |
footer | data-state | 'open' | 'closed' |
item | data-disabled | ''(条件成立时才出现) |
item | data-highlighted | ''(条件成立时才出现) |
item | data-pressed | ''(条件成立时才出现) |
item | data-state | 'checked' | 'unchecked' |
item | data-tone | metaOf.get(item.value)?.tone |
item | data-xh-collection-context | 'overlay' |
item | data-xh-collection-item | '' |
item | data-xh-collection-size | props.size |
item-prefix | data-disabled | ''(条件成立时才出现) |
item-prefix | data-state | 'checked' | 'unchecked' |
item-prefix | data-xh-collection-slot | 'prefix' |
item-text | data-disabled | ''(条件成立时才出现) |
item-text | data-state | 'checked' | 'unchecked' |
item-text | data-xh-collection-slot | 'text' |
item-description | data-disabled | ''(条件成立时才出现) |
item-description | data-state | 'checked' | 'unchecked' |
item-description | data-xh-collection-slot | 'description' |
item-suffix | data-disabled | ''(条件成立时才出现) |
item-suffix | data-state | 'checked' | 'unchecked' |
item-suffix | data-xh-collection-slot | 'suffix' |
item-indicator | data-disabled | ''(条件成立时才出现) |
item-indicator | data-state | 'checked' | 'unchecked' |
item-indicator | data-xh-collection-slot | 'indicator' |
empty | data-state | 'open' | 'closed' |
loading | data-loading | ''(条件成立时才出现) |
loading | data-state | 'open' | 'closed' |
loading | data-xh-loading-ring | '' |
error | data-state | 'open' | 'closed' |
retry-trigger | data-xh-action-control | '' |
retry-trigger | data-xh-action-display | 'always' |
retry-trigger | data-xh-action-profile | 'text' |
retry-trigger | data-xh-action-size | props.size |
retry-trigger | data-xh-action-variant | 'ghost' |
overflow-tag | data-count | String(overflowCount) |
tag | data-value | v |
CSS 变量
本组件公开覆盖槽由独立皮肤的实际消费位生成;默认来源、作用部件和状态均与 CSS 同源。
| 变量 | 部件 | CSS 属性 | 状态 | 默认来源 | 说明 |
|---|---|---|---|---|---|
--xh-select-action-bg | clear-trigger | --xh-ink-surfacebackground-color | defaultxh-ink-surface | --xh-_action-variant-bg-rest | select 的 clear-trigger 部件 --xh-ink-surface、background-color 覆盖槽。 |
--xh-select-action-bg-active | clear-trigger | background-color | disabledis(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressed | --xh-_action-variant-bg-pressed | select 的 clear-trigger 部件 background-color 覆盖槽。 |
--xh-select-action-bg-hover | clear-trigger | background-color | disabledhoverloadingnot([data-disabled])not([data-loading]) | --xh-_action-variant-bg-hover | select 的 clear-trigger 部件 background-color 覆盖槽。 |
--xh-select-action-fg | clear-trigger | color | default | --xh-fg-field-clear | select 的 clear-trigger 部件 color 覆盖槽。 |
--xh-select-action-fg-hover | clear-trigger | color | disabledhoverloadingnot([data-disabled])not([data-loading]) | --xh-fg-default | select 的 clear-trigger 部件 color 覆盖槽。 |
--xh-select-action-font-size | clear-trigger | font-size | default | --xh-text-secondary-size | select 的 clear-trigger 部件 font-size 覆盖槽。 |
--xh-select-action-radius | clear-trigger | border-radius | default | --xh-shape-circle | select 的 clear-trigger 部件 border-radius 覆盖槽。 |
--xh-select-action-size | clear-triggerindicator | block-sizeinline-sizemin-inline-size | defaultxh-action-profile=field-inset | --xh-_action-profile-visual-size--xh-control-action-size | select 的 clear-trigger、indicator 部件 block-size、inline-size、min-inline-size 覆盖槽。 |
--xh-select-content-backdrop | content | -webkit-backdrop-filterbackdrop-filter | xh-material=frosted | --xh-_material-backdrop | select 的 content 部件 -webkit-backdrop-filter、backdrop-filter 覆盖槽。 |
--xh-select-content-bg | content | background | not([data-xh-action-control])xh-material=frosted | --xh-_material-bg | select 的 content 部件 background 覆盖槽。 |
--xh-select-content-border | content | border | not([data-xh-action-control])xh-material=frosted | --xh-_material-border | select 的 content 部件 border 覆盖槽。 |
--xh-select-content-fg | content | color | not([data-xh-action-control])xh-material=frosted | --xh-_material-fg | select 的 content 部件 color 覆盖槽。 |
--xh-select-content-highlight | content | background | not([data-xh-action-control])xh-material=frosted | --xh-_material-highlight | select 的 content 部件 background 覆盖槽。 |
--xh-select-content-max-h | content | max-block-size | default | --xh-overlay-menu-max-h | select 的 content 部件 max-block-size 覆盖槽。 |
--xh-select-content-max-w | content | max-inline-size | default | none | select 的 content 部件 max-inline-size 覆盖槽。 |
--xh-select-content-min-w | content | inline-size | default | --xh-overlay-menu-min-w | select 的 content 部件 inline-size 覆盖槽。 |
--xh-select-content-px | content | padding-inline | default | --xh-space-1 | select 的 content 部件 padding-inline 覆盖槽。 |
--xh-select-content-py | content | padding-block | default | --xh-space-1 | select 的 content 部件 padding-block 覆盖槽。 |
--xh-select-content-radius | content | border-radius | default | --xh-shape-overlay | select 的 content 部件 border-radius 覆盖槽。 |
--xh-select-content-shadow | content | box-shadow | not([data-xh-action-control])xh-material=frosted | --xh-_material-shadow | select 的 content 部件 box-shadow 覆盖槽。 |
--xh-select-control-bg | control | background-color | xh-field-chrome | --xh-_field-variant-bg-rest | select 的 control 部件 background-color 覆盖槽。 |
--xh-select-control-bg-disabled | control | background-color | disabledxh-field-chrome | --xh-_field-variant-bg-disabled | select 的 control 部件 background-color 覆盖槽。 |
--xh-select-control-bg-hover | control | background-color | disabledhoverinvalidloadingnot(:focus-within)not([data-disabled])not([data-invalid])not([data-loading])not([data-readonly])readonlyxh-field-chrome | --xh-_field-variant-bg-hover | select 的 control 部件 background-color 覆盖槽。 |
--xh-select-control-bg-readonly | control | background-color | readonlyxh-field-chrome | --xh-_field-variant-bg-read-only | select 的 control 部件 background-color 覆盖槽。 |
--xh-select-control-border | control | border | xh-field-chrome | --xh-_field-variant-border-rest | select 的 control 部件 border 覆盖槽。 |
--xh-select-control-border-focus | control | border-colorbox-shadow | disabledfocus-withinnot([data-disabled])xh-field-chrome | --xh-_field-variant-border-focus | select 的 control 部件 border-color、box-shadow 覆盖槽。 |
--xh-select-control-border-hover | control | border-color | disabledhoverinvalidloadingnot(:focus-within)not([data-disabled])not([data-invalid])not([data-loading])not([data-readonly])readonlyxh-field-chrome | --xh-_field-variant-border-hover | select 的 control 部件 border-color 覆盖槽。 |
--xh-select-control-border-invalid | control | border-color | invalidxh-field-chrome | --xh-_field-variant-border-invalid | select 的 control 部件 border-color 覆盖槽。 |
--xh-select-control-fg | control | color | xh-field-chrome | --xh-fg-default | select 的 control 部件 color 覆盖槽。 |
--xh-select-control-gap | control | gap | xh-field-chrome | --xh-_select-gap | select 的 control 部件 gap 覆盖槽。 |
--xh-select-control-h | control | block-sizemin-block-size | has([data-xh-field-input][data-xh-field-layout='multi-tag'])has([data-xh-field-input][data-xh-field-layout='single-line'])has([data-xh-field-input][data-xh-field-layout='textarea'])xh-field-chromexh-field-inputxh-field-layout=multi-tagxh-field-layout=single-linexh-field-layout=textarea | --xh-_select-h | select 的 control 部件 block-size、min-block-size 覆盖槽。 |
--xh-select-control-min-w | controlroot | min-inline-size | defaultxh-field-chrome | --xh-control-min-w | select 的 control、root 部件 min-inline-size 覆盖槽。 |
--xh-select-control-px | control | padding-inline | xh-field-chrome | --xh-_select-px | select 的 control 部件 padding-inline 覆盖槽。 |
--xh-select-control-radius | control | border-radius | xh-field-chrome | --xh-shape-control | select 的 control 部件 border-radius 覆盖槽。 |
--xh-select-control-shadow | control | box-shadow | xh-field-chrome | none | select 的 control 部件 box-shadow 覆盖槽。 |
--xh-select-control-w | root | inline-sizemin-inline-size | default | --xh-control-w | select 的 root 部件 inline-size、min-inline-size 覆盖槽。 |
--xh-select-empty-fg | empty | color | default | --xh-material-frosted-fg-muted | select 的 empty 部件 color 覆盖槽。 |
--xh-select-empty-font-size | empty | font-size | default | --xh-_select-font-size | select 的 empty 部件 font-size 覆盖槽。 |
--xh-select-empty-px | empty | padding-inline | default | --xh-_select-item-px | select 的 empty 部件 padding-inline 覆盖槽。 |
--xh-select-empty-py | empty | padding-block | default | --xh-space-3 | select 的 empty 部件 padding-block 覆盖槽。 |
--xh-select-error-fg | error | color | default | --xh-material-frosted-fg-muted | select 的 error 部件 color 覆盖槽。 |
--xh-select-error-font-size | error | font-size | default | --xh-_select-font-size | select 的 error 部件 font-size 覆盖槽。 |
--xh-select-error-px | error | padding-inline | default | --xh-_select-item-px | select 的 error 部件 padding-inline 覆盖槽。 |
--xh-select-error-py | error | padding-block | default | --xh-space-3 | select 的 error 部件 padding-block 覆盖槽。 |
--xh-select-footer-border | footer | border-block-start | default | --xh-material-frosted-separator | select 的 footer 部件 border-block-start 覆盖槽。 |
--xh-select-footer-fg | footer | color | default | --xh-material-frosted-fg-muted | select 的 footer 部件 color 覆盖槽。 |
--xh-select-footer-font-size | footer | font-size | default | --xh-text-secondary-size | select 的 footer 部件 font-size 覆盖槽。 |
--xh-select-footer-gap | footer | gap | default | --xh-space-2 | select 的 footer 部件 gap 覆盖槽。 |
--xh-select-footer-px | footer | padding-inline | default | --xh-space-2 | select 的 footer 部件 padding-inline 覆盖槽。 |
--xh-select-footer-py | footer | padding-block | default | --xh-space-2 | select 的 footer 部件 padding-block 覆盖槽。 |
--xh-select-gap | labelroot | gapmargin-block-end | default | --xh-space-1 | select 的 label、root 部件 gap、margin-block-end 覆盖槽。 |
--xh-select-group-gap | group | gap | default | --xh-list-option-gap | select 的 group 部件 gap 覆盖槽。 |
--xh-select-group-label-fg | group-label | color | default | --xh-material-frosted-fg-muted | select 的 group-label 部件 color 覆盖槽。 |
--xh-select-group-label-font-size | group-label | font-size | default | --xh-text-caption-size | select 的 group-label 部件 font-size 覆盖槽。 |
--xh-select-group-label-font-weight | group-label | font-weight | default | --xh-font-weight-medium | select 的 group-label 部件 font-weight 覆盖槽。 |
--xh-select-group-label-px | group-label | padding-inline | default | --xh-_select-item-px | select 的 group-label 部件 padding-inline 覆盖槽。 |
--xh-select-group-label-py | group-label | padding-block | default | --xh-space-1 | select 的 group-label 部件 padding-block 覆盖槽。 |
--xh-select-group-separator-color | group | border-block-start | default | --xh-material-frosted-separator | select 的 group 部件 border-block-start 覆盖槽。 |
--xh-select-group-spacing | group | padding-block-start | default | --xh-space-1_5 | select 的 group 部件 padding-block-start 覆盖槽。 |
--xh-select-icon-size | controlroot | --xh-icon-size | defaultsize=lgsize=smxh-field-chrome | --xh-_field-size-glyph-size--xh-glyph-size-lg--xh-glyph-size-md--xh-glyph-size-sm | select 的 control、root 部件 --xh-icon-size 覆盖槽。 |
--xh-select-indicator-fg | indicator | color | default | --xh-fg-muted | select 的 indicator 部件 color 覆盖槽。 |
--xh-select-item-bg-hover | item | background-color | disablederrorhighlightedhoveris(:focus-visible, [data-highlighted])is([aria-selected='true'], [data-selected])not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])selectedxh-collection-context=overlay | --xh-bg-subtle | select 的 item 部件 background-color 覆盖槽。 |
--xh-select-item-bg-pressed | item | background-color | disablederroris(:active, [data-pressed])is([aria-selected='true'], [data-selected])not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])pressedselectedxh-collection-context=overlay | --xh-bg-subtle-hover | select 的 item 部件 background-color 覆盖槽。 |
--xh-select-item-check-fg | item | color | disablederrorhighlightedhoveris(:active, [data-pressed])is(:focus-visible, [data-highlighted])is([aria-selected='true'], [data-selected])not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])pressedselectedstate=checkedxh-collection-context=overlayxh-collection-slot=indicator | --xh-_select-accent | select 的 item 部件 color 覆盖槽。 |
--xh-select-item-fg | item | color | defaultdisablederrorhighlightedhoveris(:active, [data-pressed])is(:focus-visible, [data-highlighted])is([aria-selected='true'], [data-selected])not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])pressedselectedxh-collection-context=overlay | --xh-material-frosted-fg | select 的 item 部件 color 覆盖槽。 |
--xh-select-item-fg-selected | item | color | disablederrorhighlightedhoveris(:active, [data-pressed])is(:focus-visible, [data-highlighted])is([aria-selected='true'], [data-selected])not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])pressedselectedxh-collection-context=overlay | --xh-select-item-fg | select 的 item 部件 color 覆盖槽。 |
--xh-select-item-font-size | item | font-size | default | --xh-_select-font-size | select 的 item 部件 font-size 覆盖槽。 |
--xh-select-item-font-weight-selected | item | font-weight | disablederrorhighlightedhoveris(:active, [data-pressed])is(:focus-visible, [data-highlighted])is([aria-selected='true'], [data-selected])not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])pressedselectedxh-collection-context=overlay | --xh-font-weight-medium | select 的 item 部件 font-weight 覆盖槽。 |
--xh-select-item-gap | item | margin-inline-endmargin-inline-start | defaultxh-collection-slot=indicatorxh-collection-slot=prefixxh-collection-slot=shortcutxh-collection-slot=suffix | --xh-_select-gap | select 的 item 部件 margin-inline-end、margin-inline-start 覆盖槽。 |
--xh-select-item-h | item | min-block-size | default | --xh-_select-item-h | select 的 item 部件 min-block-size 覆盖槽。 |
--xh-select-item-indicator-size | item-indicator | --xh-icon-sizeblock-sizeinline-size | default | --xh-control-indicator-size | select 的 item-indicator 部件 --xh-icon-size、block-size、inline-size 覆盖槽。 |
--xh-select-item-leading | item | line-height | default | --xh-leading-normal | select 的 item 部件 line-height 覆盖槽。 |
--xh-select-item-px | item | padding-inline | default | --xh-_select-item-px | select 的 item 部件 padding-inline 覆盖槽。 |
--xh-select-item-py | item | padding-block | default | --xh-_select-item-py | select 的 item 部件 padding-block 覆盖槽。 |
--xh-select-item-radius | item | border-radius | default | --xh-shape-inset | select 的 item 部件 border-radius 覆盖槽。 |
--xh-select-label-fg | label | color | default | --xh-fg-muted | select 的 label 部件 color 覆盖槽。 |
--xh-select-label-fg-disabled | label | color | disabled | --xh-fg-muted | select 的 label 部件 color 覆盖槽。 |
--xh-select-label-font-size | label | font-size | default | --xh-text-label-size | select 的 label 部件 font-size 覆盖槽。 |
--xh-select-label-font-weight | label | font-weight | default | --xh-text-label-weight | select 的 label 部件 font-weight 覆盖槽。 |
--xh-select-label-gap | label | margin-block-end | default | --xh-space-2 | select 的 label 部件 margin-block-end 覆盖槽。 |
--xh-select-layer | positioner | z-index | default | --xh-_layer | select 的 positioner 部件 z-index 覆盖槽。 |
--xh-select-list-gap | list | gap | default | --xh-list-option-gap | select 的 list 部件 gap 覆盖槽。 |
--xh-select-loading-fg | loading | color | default | --xh-material-frosted-fg-muted | select 的 loading 部件 color 覆盖槽。 |
--xh-select-loading-font-size | loading | font-size | default | --xh-_select-font-size | select 的 loading 部件 font-size 覆盖槽。 |
--xh-select-loading-px | loading | padding-inline | default | --xh-_select-item-px | select 的 loading 部件 padding-inline 覆盖槽。 |
--xh-select-loading-py | loading | padding-block | default | --xh-space-3 | select 的 loading 部件 padding-block 覆盖槽。 |
--xh-select-placeholder-fg | value-text | color | placeholder | --xh-fg-subtle | select 的 value-text 部件 color 覆盖槽。 |
--xh-select-tag-list-gap | tag-list | gap | xh-tag-list | --xh-space-1 | select 的 tag-list 部件 gap 覆盖槽。 |
--xh-select-trigger-fg | trigger | color | default | --xh-fg-default | select 的 trigger 部件 color 覆盖槽。 |
--xh-select-trigger-font-size | trigger | font-size | default | --xh-_select-font-size | select 的 trigger 部件 font-size 覆盖槽。 |
--xh-select-trigger-gap | trigger | gap | default | --xh-_select-gap | select 的 trigger 部件 gap 覆盖槽。 |
动效
动效角色:按压 · 状态 · 切换 · 指示与换位 · 出现 · 列表(见动效规范)。
共享关键帧 xh-fade-in · xh-fade-out · xh-overlay-pop-in · xh-pop-in · xh-pop-out 由 family/motion.css 提供,皮肤 @import 它,单独引入仍成立;background-color · border-color · color · rotate · translate 走 transition 过渡。时长与缓动读动效令牌,改令牌即改全局节奏。
皮肤之外还有一段:退场由适配器的退场闸门把关,动画播完才真收起。
系统开启减弱动效时由令牌层统一收敛,皮肤不另作判断。
响应式
皮肤另按输入能力分档:hover: hover · hover: none:同一份皮肤在触屏与带指针的设备上不一样,与视口宽度无关。
RTL
皮肤用逻辑属性排布(inline-start 一族),dir="rtl" 下自动镜像。
