Skip to content

Combobox 可搜索下拉 ​

基础用法

触发框就是输入框,打字即过滤。文案与 keywords(别名、拼音)任一包含查询即命中;禁用项搜得到但选不了。

<pt-combobox placeholder="选择成员" aria-label="负责人" style="max-width: 320px">
  <pt-option value="u1" keywords="zhangsan zs">张三</pt-option>
  <pt-option value="u2" keywords="lisi ls">李四</pt-option>
  <pt-option value="u3" keywords="wangwu" disabled>王五(已离职)</pt-option>
  <pt-option value="u4" keywords="zhaoliu">赵六</pt-option>
</pt-combobox>

可搜索的单选下拉:触发框本身就是输入框,打字即过滤选项,方向键移动高亮、回车选中、Esc 收起。 选项写法与 Select 选择器 相同 —— pt-option、pt-option-group、pt-separator。

和 Select 怎么选 ​

需要用
选项不多(一屏放得下)、不需要搜索pt-select
选项多、要按名字 / 别名 / 拼音快速找到一项pt-combobox
系统原生的选择体验pt-native-select

过滤规则 ​

  • 查询先去首尾空白、不分大小写;选项的文案与 keywords(空格分隔)任一包含查询即命中。 搜「zs」能命中 keywords="zhangsan zs" 的「张三」。
  • 保持原顺序,不按匹配度重排 —— 结果列表的顺序可预期。
  • 禁用项命中时照样可见(置灰),只是方向键跳过、点不动。
  • 分组里的选项全被滤掉时整组藏起;有查询时分隔线藏起。一条都没命中时显示 empty-text (不给时取 locale 的 combobox.empty,「无匹配结果」)。
  • 面板收起时查询清空。

value:未选与空串 ​

「全部」这类空串选项

不写 value 是未选(显示占位);value 为空串是选了「全部」那一项,可以高亮、选中,表单提交空串。

<pt-combobox value="" aria-label="环境" style="max-width: 320px">
  <pt-option value="">全部环境</pt-option>
  <pt-option value="prod">生产</pt-option>
  <pt-option value="dev">开发</pt-option>
</pt-combobox>

value 区分两种状态,两者不等价:

value含义表单提交required
undefined未选:显示占位文案(不写 value 属性即是)字段不出现报「必填」
''选了 value 为空串的那一项(「全部」这类兜底项)提交空串算已选

null 视同 undefined。三个框架的双向绑定都保留这一区分:Vue v-model、Angular ngModel (null / undefined 写进去是未选,不会被转成空串)、React value + onPtChange。

value 相同的选项可以有多个:高亮、打勾与触发框上的文案都按被选中的那一行走,不会串到第一个同值的选项上。

分组与宽度 ​

分组与自适应宽度

可以用 pt-option-group 分组、夹 pt-separator;组内全被滤掉时整组藏起。宿主 width: fit-content 时按已选文案自适应,面板至少与触发框等宽。

<pt-combobox value="me" aria-label="发件人" empty-text="没有这个地址" style="width: fit-content">
  <pt-option value="me">我</pt-option>
  <pt-separator></pt-separator>
  <pt-option-group label="团队">
    <pt-option value="team">team-notifications@example.com</pt-option>
    <pt-option value="ops">ops@example.com</pt-option>
  </pt-option-group>
</pt-combobox>

默认撑满父级宽度;给宿主 width: fit-content 时按已选文案(或占位)自适应。已选文案画在输入框下面一层, 打字时只淡出不移除,触发框的宽度不跳。面板至少与触发框等宽,按内容最多撑到 min(420px, 90vw),长邮箱、长路径不会被切掉; 触发框本身比这更宽时(撑满宽表单、移动端全宽)面板与触发框等宽,但不超出视口两侧 8px 留白。

窄屏下不像 pt-select 那样切成底部抽屉:输入框就是触发框,抽屉会盖住它,打字时看不到自己打了什么。 面板照常贴着触发框,下方放不下时翻到上方。

尺寸与状态 ​

尺寸与状态

三档尺寸与 pt-select 对齐(24 / 32 / 40);invalid 描边转危险色并标注 aria-invalid。

<div style="display: grid; gap: 12px; max-width: 320px">
  <pt-combobox size="sm" value="a" aria-label="小">
    <pt-option value="a">小号 24</pt-option>
  </pt-combobox>
  <pt-combobox size="lg" value="a" aria-label="大">
    <pt-option value="a">大号 40</pt-option>
  </pt-combobox>
  <pt-combobox invalid placeholder="出错态" aria-label="出错态">
    <pt-option value="a">选项</pt-option>
  </pt-combobox>
  <pt-combobox disabled value="a" aria-label="禁用">
    <pt-option value="a">禁用</pt-option>
  </pt-combobox>
</div>

键盘 ​

按键行为
打字展开并过滤,高亮第一个可用项
↓ / ↑收起时展开(高亮已选项);展开时移动高亮,跳过禁用项、到头循环
PageDown / PageUp展开时一次跨 10 项
Enter展开时选中高亮项;收起时提交所在的表单(与原生单行输入框一致)
Esc收起(放在弹窗、抽屉里时只收起面板,不关外层)
Tab收起,焦点照常移走
Home / End留给输入框移动光标,不跳到首尾项

输入法组合中(中文 / 日文候选框开着)的回车与方向键是选字、翻候选,组件不响应。

无障碍 ​

  • 焦点始终在输入框上(role="combobox",aria-autocomplete="list"),高亮项经 aria-activedescendant 播报。
  • 输入框的 value 只是查询,已选文案经 aria-describedby 播报。外部关联了描述(pt-field 的说明文字)时, 描述优先,已选文案不再重复播报 —— 这是「输入框只放查询」这种形态的固有代价。
  • 没有可见标签时写 aria-label(包装层里是 ariaLabelText),或用 pt-label for 关联。
  • 触屏上触发框的命中区不小于 44px。

事件 ​

  • ptChange:选中了一项(点选或回车),detail 是它的 value。外部改 value 不发。冒泡(不会自我嵌套)。
  • ptOpen / ptClose:面板开关,ptClose 的 detail.reason 是 select / escape / outside / tab / programmatic。 与其它弹层一样不冒泡,外层统一监听要走捕获阶段。

API ​

属性

属性Attribute类型默认值说明
ariaLabelTextaria-labelstring | undefined—无障碍名称:原生 HTML 里写 aria-label,React / Vue / Angular 包装层里属性名是 ariaLabelText。 没有可见标签(pt-label for / pt-field)时必须给
disableddisabledbooleanfalse禁用。展开中改成禁用会收起面板并清空查询。只反映使用方自己写的值:外层原生 `<fieldset disabled>`、 pt-field / pt-field-set 的禁用另经继承通道生效,不改写它
emptyTextempty-textstring | undefined—没有匹配项时的提示,默认取 locale 的 combobox.empty
inheritedDisabledinherited-disabledbooleanfalse外层 pt-field / pt-field-set 下发的禁用。内部继承通道:由它们以 attribute 维护,不建议使用方直接写。 有效禁用是 `disabled` 与它的并集;父元素只写这一条、不碰 `disabled`,读 `el.disabled` 得到的始终是使用方自己写的值
inheritedInvalidinherited-invalidbooleanfalse外层 pt-field 下发的出错态。内部继承通道:由 pt-field 维护,不建议使用方直接写。有效出错是 `invalid` 与它的并集
inheritedRequiredinherited-requiredbooleanfalse外层 pt-field 下发的必填。内部继承通道:由 pt-field 维护,不建议使用方直接写。有效必填是 `required` 与它的并集
invalidinvalidbooleanfalse出错态:描边转危险色,标注 aria-invalid,并置表单校验的 customError
maxHeightmax-heightnumber320选项列表的最大高度(px),超出后列表内滚动
namenamestring | undefined—提交表单时的字段名
openopenbooleanfalse面板是否展开。可读可写,一般不用手动设置
placeholderplaceholderstring | undefined—未选中时的占位文案,默认取 locale 的 select.placeholder
requiredrequiredbooleanfalse必填:未选(`undefined`)时表单校验报 valueMissing。选了空串算已选
sizesize"lg" | "md" | "sm"'md'尺寸档位。三档对应 24 / 32 / 40 的控件刻度,与 pt-select 一致
valuevaluestring | undefined—当前值,与某个 pt-option 的 value 对应。`undefined` 为未选(`null` 视同 `undefined`); 空串是合法的**已选**值,两者不等价

事件

事件detail 类型说明
ptBlurvoid输入框失去焦点
ptChangestring选中了一项(点选或回车),detail 是它的 value。外部改 value 不发
ptClose{ reason: PtComboboxCloseReason; }面板收起,detail 说明是怎么收起的(与 pt-select 的 ptClose 同形) **不冒泡**:理由同 ptOpen。
ptFocusvoid输入框获得焦点
ptOpenvoid面板展开 **不冒泡**:弹层常互相嵌套(弹窗里的下拉、抽屉里的下拉),冒泡的话外层上的监听会收到内层的开关。

方法

方法说明
hide() => Promise<void>收起面板
refresh() => Promise<void>选项变动时重新同步(动态增删、改文案后调用;增删直接子元素时会自动同步)
setFocus(options?: FocusOptions) => Promise<void>
show() => Promise<void>展开面板

插槽

名称说明
(默认)`pt-option` 列表,可混放 `pt-option-group` 与 `pt-separator`

可定制的内部元素(::part())

part说明
base触发框(带描边的外框)
empty没有匹配项时的提示
input输入框
listbox选项列表(滚动容器)
popup弹出的面板
value已选文案 / 占位文案

CSS 变量

变量说明
--pt-combobox-radius触发框与面板的圆角,默认 --pt-radius-md(sm 档触发框默认 --pt-radius-sm)

Apache-2.0 协议开源