组合框字段
让用户通过键入找到一个值,并作为表单的一个字段——一个组合框,带有随键入不断收窄的选项列表,用户从中选择,在产品允许的地方也可以填入自己的值;它位于可以说明必填或选填的标签之下,带有一行帮助文本,以及值被拒绝的原因。
- 类别
- 文本输入
- 令牌
comp.field- 层级
- 组合
- Web
tanrei-combobox-fieldschedule - SwiftUI
TanreiComboboxFieldradio_button_unchecked - Jetpack Compose
TanreiComboboxFieldradio_button_unchecked - Flutter
TanreiComboboxFieldradio_button_unchecked - React Native
ComboboxFieldradio_button_unchecked
- 不提供的变体
- tanrei: filled, outlined
生成自 docs/status/status.json, specs/combobox-field.spec.json
一个只接受自身选项的组合框字段,以及一个还接受用户自己的值的组合框字段
表单里凡是要填一个用户宁愿键入也不愿滚动查找的值的地方,都使用组合框字段:几百个城市中的一个、一个人、一个令牌。它也适用于既给出建议值、又接受用户自己的值的字段,例如字体。它是最普通的情形,拿来就能用:标签、带列表的方框、一行帮助文本,以及值有误时的文字。方框的绘制方式和尺寸与同一变体、同一尺寸的文本字段的方框相同,列表则按下拉选择框的样子绘制。
组成部分是一个字段和一个组合框。字段是框架:标签、必填或选填标记、帮助文本和错误。组合框是控件:方框、键入其中的文本,以及带有选项的列表。组合框字段自己什么都不绘制,也没有令牌:它的每个属性都是这两者之一的属性,原样交给它们;你给它的选项,就是它的组合框的选项。
它是一个块,放在哪里就和那里一样宽,它的方框会把它填满。它没有自己的宽度:想要窄一些的字段,就把它放进窄一些的地方。它的宽度绝不跟随选项或值,因为用户在还不知道有哪些选项时就开始键入了。
在 Web 上它是 <tanrei-combobox-field>,子元素是 <tanrei-option>,也就是下拉选择框所包含的那种元素。其他平台尚未实现。
组合框字段保存的就是它的组合框保存的东西,一共两样。value 是已确认的选择:选中选项的值,或者一段自定义文本。inputValue 是输入框里的文本。只有用户选择某个选项、确认文本(在没有活动选项时按 Enter,或者离开字段)或按下清除按钮时,值才会改变;键入和在列表中移动绝不会改变它。
<form>
<tanrei-combobox-field name="city" label="城市" value="tokyo" required required-text="必填"
clearable clear-label="清除城市" empty-text="无匹配项">
<tanrei-option value="kyoto">京都</tanrei-option>
<tanrei-option value="tokyo">东京</tanrei-option>
</tanrei-combobox-field>
</form>
<!-- 只要方框,用在标签位于别处的地方:一个组合框,由它自己的 label 命名。导入组合框字段时,它也随之完成定义。 -->
<tanrei-combobox label="令牌" placeholder="查找令牌" no-chevron style="inline-size: 16rem">
<tanrei-option value="primary">primary</tanrei-option>
</tanrei-combobox>
<!-- 选项只是建议:与任何选项都不对应的文本会成为值。 -->
<tanrei-combobox-field label="字体" value="Recursive" allow-custom-value>
<tanrei-option value="Inter">Inter</tanrei-option>
</tanrei-combobox-field>
<!-- 由你来筛选:在 input 时向服务器查询。状态行说明你的数据源正在做什么。 -->
<tanrei-combobox-field id="assignee" label="负责人" filter="none" no-chevron placeholder="查找成员">
<span slot="leading-icon">person_search</span>
</tanrei-combobox-field>
<script type="module">
import '@tanrei/web/components/combobox-field';
const assignee = document.querySelector('#assignee');
// 用户修改了文本。组合框不会延迟或丢弃这个事件:防抖由你自己来做。
assignee.addEventListener('input', async () => {
assignee.status = 'loading';
assignee.statusText = '正在加载成员';
const people = await find(assignee.inputValue);
assignee.replaceChildren(...people.map(option));
assignee.status = 'idle';
assignee.statusText = `共 ${total} 人,已显示 ${people.length} 人`;
});
// 已确认的值变了。value 已经是新的值。
assignee.addEventListener('change', (e) => save(e.detail.value));
// 由用户打开或关闭;关闭时带有原因。它不冒泡:请在字段本身上监听。
assignee.addEventListener('toggle', (e) => console.log(e.detail.open, e.detail.reason));
</script>import { ComboboxField, Option } from '@tanrei/react';
<ComboboxField label="城市" value={city} clearable clearLabel="清除城市" emptyText="无匹配项" onChange={(event) => setCity(event.detail.value)}>
<Option value="kyoto">京都</Option>
<Option value="tokyo">东京</Option>
</ComboboxField>组合框字段是一个里面放着组合框的字段。需要字段没有提供的东西时——在标签和方框之间,或者在帮助文本下方放你自己的内容——就自己把这两者写出来。画面、名称、描述和状态都相同。
<!-- 组合框字段 -->
<tanrei-combobox-field name="city" label="城市" required required-text="必填"
supporting-text="订单将寄往该城市。" value="tokyo">
<tanrei-option value="kyoto">京都</tanrei-option>
<tanrei-option value="tokyo">东京</tanrei-option>
</tanrei-combobox-field>
<!-- 手写的同一个字段:框架的属性写在字段上,其余的一切——以及选项——写在组合框上 -->
<tanrei-field label="城市" required required-text="必填" supporting-text="订单将寄往该城市。">
<tanrei-combobox name="city" value="tokyo">
<tanrei-option value="kyoto">京都</tanrei-option>
<tanrei-option value="tokyo">东京</tanrei-option>
</tanrei-combobox>
</tanrei-field>里面放着组合框的字段:画面与组合框字段相同
不同的是表单的控件在哪里:手写时它是组合框,name 写在组合框上;在组合框字段里,它是字段。
分组、带辅助文本和一个禁用选项、错误、只读,以及禁用的组合框字段
| 部件 | 它是什么 |
|---|---|
| 标签 | 容器上方由字段绘制的那一行(stacked),或者浮动在容器内部的、组合框自己的标签(filled、outlined)。它是无障碍名称,无论是画出来的,还是用 labelHidden 隐藏的 |
| 必填和选填标记 | 位于标签之后:required 时是“必填”小标签、星号,或者什么都不画;optional 时是“选填”小标签。浮动标签带的是星号 |
| 输入框 | 文本:用户为查找选项而键入的内容;用户没有在键入时,是选中选项的标签或自定义值。保持在一行之内,放不下时滚动 |
| 占位符 | 输入框为空时显示的提示。它绝不是标签,也绝不是一个值 |
| 前置图标 | leading-icon 插槽里的字形;没有时,采用选中选项自己的字形 |
| 清除按钮 | 设置了 clearable 且文本不为空时出现:清空文本和值 |
| V 形箭头 | 表明这个字段有一个可以浏览的列表,并用来打开和关闭它。它不会转动。只针对已键入的内容给出建议的地方,不要画它(chevron 设为 false) |
| 辅助文本 | 字段的那一行:一行帮助文本。error 为 true 时,errorText 取代它的位置(设计系统两者都保留时,位于它的下方)。两者都从输入框的文本开始的位置起笔 |
标签行、标记和辅助文本那一行属于字段,用字段的令牌绘制;其余的一切属于组合框。三种变体和三种尺寸——small(32)、medium(40)和 large(56)——就是文本字段的那些。默认系统只提供 stacked。
列表打开期间,标签和帮助文本随方框一起画成聚焦的样子,无论输入框是否有焦点。
列表在容器下方相距 comp.combobox.list.offset 处打开,与容器正好一样宽;下方没有空间时,在容器上方打开。它的高度等于各行加起来的高度,上限是 list.max-height(320),超过时滚动。
| 部件 | 它是什么 |
|---|---|
| 选项 | 一行,带有标签,还可以有一行辅助文本和一个字形。选中的那一个带有低调的填充和一个对勾 |
| 匹配部分 | 使用 filter="contains" 时,每个标签中与文本匹配的部分以更粗的字重绘制 |
| 组 | 一串选项,上方可以有一个标题。筛选之后没有选项的组不画出来 |
| 状态行 | 位于选项之后:statusText;status 为 loading 时是加载指示器,为 error 时是失败信息;没有任何匹配时是 emptyText |
filter="contains" 显示标签中包含这段文本的选项,不区分大小写、字符的全角半角和字母上的附加符号。使用 filter="none" 时,组合框显示你给它的每一个选项,由你在 input 时筛选。组合框绝不会自己获取数据、等待、排序或排名。
| 输入 | 发生什么 |
|---|---|
| 键入 | 编辑文本并打开经过筛选的列表。第一个匹配项是活动选项,输入停顿后会被读出;设置了 allowCustomValue 时,没有活动选项 |
| 列表关闭时的下方向键、上方向键;V 形箭头 | 打开列表并显示所有选项。活动选项是选中的那一个;没有选中的选项时,是第一个启用的选项 |
| 列表打开时的下方向键、上方向键 | 下一个或上一个选项,可以跨组,到两端时循环(设置了 allowCustomValue 时经过文本本身);wrap 为 false 时到头停住 |
| Page Down、Page Up | 向下或向上移动十个选项 |
| Home、End、左方向键、右方向键 | 在文本中移动插入点 |
| 有活动选项时的 Enter;按下某个选项 | 选择它并关闭列表。不会提交表单 |
| 没有活动选项时的 Enter | 确认文本,触发 action,并提交表单,除非 action 被取消 |
| Escape | 关闭打开的列表,保留文本。再按一次,恢复为已确认的值所对应的文本。它绝不会清除值 |
| Tab;在外部按下 | 离开字段:文本得到处理,列表关闭。活动选项不会被选中 |
| 按下标签 | 把焦点移到输入框,不打开任何东西。列表打开时,这算在组合框外部按下:列表关闭,焦点留在输入框里 |
离开字段时,文本会得到处理:由组合框决定这段文本代表哪个值。文本为空,值就清空。文本与某个选项的整个标签相同,就选择那个选项。其他任何文本,在设置了 allowCustomValue 时成为值,没有设置时被放弃。
列表不会在获得焦点时打开。如果你需要这样,请自己设置 open。
输入法正在组合输入时,每一次按键都属于输入法。用来确认转换的那一次 Enter 不选择任何选项,也不提交任何内容。
toggle 带有关闭的原因:select、commit、escape、outside、press、tab、focus-out、anchor-hidden 或 empty。
构成
构成
stacked-empty 之上。| 部件 | 说明 |
|---|---|
1 field | The Field: the label row with its glyph and its requirement mark, and the supporting row with the help and the error text. In a look whose combobox floats the label it draws no label row. |
2 combobox | The combobox, as the Field's one control: the box and the text typed in it, the leading glyph, the clear button and the chevron, the list with its options, groups and status row, and the label that floats in the box in the looks that float one. |
属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
variant | stacked | filled | outlined | "stacked" | The text input's three looks, so that a combobox is drawn as the text inputs beside it: stacked is a filled box that shows focus and error with a line inside its edge and draws no label of its own; filled and outlined float the label inside the box. Use one style per form. A design system may leave some out (the Tanrei system ships stacked only) or add values. — Combobox |
size | small | medium | large | "medium" | Minimum height of the container: stacked 32 / 40 / 56, as tall as a Button of the same size, so that the box and a Button side by side in one row stand as tall as each other; filled and outlined 40 / 48 / 56, which hold the label that floats inside them. These are the text input's three sizes, small for a bar, a toolbar or a dense row of controls. The hit area always meets the minimum target. A design system may add values. — Combobox |
value | string | "" | The committed choice: the chosen option's value, or, with allowCustomValue, a text the person typed that names no option. Empty while nothing is chosen. It is one string, for ever: several values are another component. — Combobox Held by the combobox, which changes it by a choice, a commit or the clear button; the combobox field's is the same at every moment (CBF-3). |
inputValue | string | "" | The text in the input. The combobox writes it as the person types and when a value is chosen or restored; the product may set it, and reads it from input to load options. — Combobox Held by the combobox, which writes it as the person types and when a value is chosen or restored; the combobox field's is the same at every moment (CBF-3). |
label | string | "" | Names the control, visibly and to assistive technology. Required, unless the control names itself (a checkbox or a switch with its own label): then leave it empty and the field adds only help and an error. — Field Names the combobox. In a filled or outlined look the combobox draws it, floating in its box, and the Field draws no label row (FLD-25, CMB-8). |
labelHidden | boolean | false | Does not draw the label, which stays the accessible name. For a control whose visible label is elsewhere (a table column, a toolbar). — Field Hides the label wherever it would be drawn: the Field's row, or the combobox's floating label (FLD-25). |
placeholder | string | "" | A hint shown while the input is empty and no label is in the input line. Never a replacement for a name. — Combobox |
supportingText | string | "" | Help shown below the control and given to assistive technology as the control's description. While the field is in error it gives way to errorText, unless the design system keeps both (comp.field.error-text.placement append). — Field |
errorText | string | "" | Shown while error is true, in supportingText's place — or below it, which then stays, where the design system's comp.field.error-text.placement is append: what is wrong and how to put it right, in words. — Field |
error | boolean | false | Puts the field in the error condition: the error text is drawn, the control is exposed as invalid and, where it has an error condition of its own, drawn in it. What a value means is the product's to validate; the field never sets this itself. — Field Puts the Field in its error condition, which puts the combobox in its own (FLD-8, CMB-10). |
disabled | boolean | false | Disables the control and draws the frame as disabled. Disable a framed control here, not on the control, so that its label follows. — Field Disables the Field, which disables the combobox (FLD-14, CMB-46). |
readOnly | boolean | false | The value is shown, can be focused, selected and copied, and is submitted, but cannot be changed: the list does not open and neither the chevron nor the clear button is drawn. — Combobox |
required | boolean | false | What the form asks of the control: true exposes it as required and draws the required mark beside the label. It never validates: a missing answer is the product's to report with error. — Field |
optional | boolean | false | Draws the optional tag after the label and adds optionalText to the control's description, because assistive technology has no state for it. Ignored while required is true: a control is never both. — Field |
requiredMark | auto | tag | asterisk | none | "auto" | How this control shows that it is required: tag is a small filled Tag with requiredText, asterisk is "*" after the label, none draws nothing and keeps the semantics. Auto takes comp.field.requirement.mark, which follows sys.behavior.requirement-mark, the system's one answer. Ignored unless required is true. — Field |
requiredText | string | "" | The text of the required tag, in the product's language ("Required", "必須"). The product's: Tanrei has no word for it. Empty reads the product's words; with neither, FLD-24 says what is drawn. — Field |
optionalText | string | "" | The text of the optional tag, in the product's language ("Optional", "任意"); also read to assistive technology as part of the description. The product's: Tanrei has no word for it. Empty reads the product's words. — Field |
open | boolean | false | Whether the list is shown. The combobox opens and closes itself and emits toggle; the product may also set it (to open on focus, for one). Ignored while the combobox is disabled or read-only or has nothing to show. — Combobox Held by the combobox, which opens and closes its list itself; the combobox field's is the same at every moment (CBF-3). |
allowCustomValue | boolean | false | False: the value is always one of the options, and a text that names none is given up when the person leaves. True: the options are suggestions, and a text that names none becomes the value as typed (a font family, a tag, an address). — Combobox |
filter | contains | none | "contains" | Who decides which options answer the text. contains: the combobox shows the options whose label contains what was typed, ignoring case, and marks the match. none: the combobox shows every option it is given and the product filters — on input, from its own data or a server — and says what it is doing with status. — Combobox |
wrap | boolean | true | Whether the arrow keys go round at the ends of the list: Down Arrow on the last option to the first, Up Arrow on the first to the last — with allowCustomValue true, through the text itself, where no option is active (CMB-26). False stops them at the ends. — Combobox |
chevron | boolean | true | Draws the chevron that opens the whole list without typing. False for a field whose suggestions exist only for what was typed (a remote search): there is no list to look through. — Combobox |
clearable | boolean | false | Draws a clear button while the text is not empty, for a combobox whose value may be none. Without it, emptying the text and leaving the field clears the value. — Combobox |
clearLabel | string | "" | The clear button's accessible name, in the product's language ("Clear"). Required while clearable is true. — Combobox |
status | idle | loading | error | "idle" | What the product's source of options is doing, which the product sets: idle (the options given are final), loading (options are on their way: the status row shows a loading indicator), error (the source failed: the status row shows statusText in the error color). — Combobox |
statusText | string | "" | The status row's text, in the product's language, announced to assistive technology when it changes: a count ("12 of 340 shown"), that loading failed, or, while status is loading, the loading indicator's name ("Loading options"). — Combobox |
emptyText | string | "" | Shown in the list while the text names nothing the combobox can show, allowCustomValue is false and no status is set, in the product's language ("No matches"). The product's: Tanrei has no words for it. Empty reads the product's words; with neither, no such row is drawn and it is reported (CMB-32). — Combobox |
separation | auto | divider | gap | "auto" | How a group of options is told from what is next to it. divider: a line between them. gap: empty space and no line. Auto takes comp.combobox.separation, which follows the design system's choice. — Combobox |
iconSpace | auto | always | never | "auto" | Whether a shown option keeps the room of the leading icon column where it has no icon, so that the labels of a section start on one line — a section being the shown options of a group, or a run of adjacent shown options outside any group. auto: an option keeps the column while another shown option of its section has a leading icon; an option the filter hides does not count. always: every option keeps it. never: only an option that has an icon has the column. The menu's and the select's iconSpace, with the same values and default (CMB-67). — Combobox |
density | regular | compact | "regular" | How much room an option of the list takes: its least height, its paddings, the gap between its columns and the size of its glyphs (the leading icon, the check), and never its text style. regular (a 48 row in the default system) is for a product used by touch as well as by pointer; compact (a 32 row with 20 glyphs, the menu's compact row) is for a tool used with a pointer, whose lists are long. For touch an option is never lower than the touch target in either (CMB-34). The field's own box is not changed: its height is size's. A design system may add values. — Combobox |
插槽
| 插槽 | 说明 |
|---|---|
leading-icon | A glyph at the start of the container that stands for the combobox whatever is chosen (a magnifier before a field that searches people). It takes the place of the chosen option's own glyph. — Combobox |
label-icon | A glyph before the label. — Field |
事件
| 事件 | 说明 |
|---|---|
input | The person changed the text (typing, deleting, pasting, dictating, the clear button, Escape restoring it); carries the new text. Not emitted when the product sets inputValue or value, or when a choice writes its label. It is the product's cue to load options; the combobox does not delay or drop it. — Combobox |
change | The committed value changed by the person: an option was chosen, a custom text was committed, or the value was cleared; carries the new value and the text now shown. Not emitted when the product sets value, when the chosen option is chosen again, or while the person only types or moves through the list. — Combobox |
toggle | The list was opened or closed by the person or by having nothing left to show; carries the new open and, for a close, the reason: select, commit, escape, back, outside, press, tab, focus-out, anchor-hidden or empty (in the platform's casing). After a choice it follows change. — Combobox |
action | The person pressed Enter with no option active and the text was committed; emitted after any change it caused. On the web it is followed by the form's submission unless cancelled. — Combobox |
条目
该组件重复排列的条目种类。测试样例按顺序列出条目;各平台以各自的惯用方式为每个种类命名。
option
One choice of the combobox: a label, the value it stands for, and optionally a supporting line and a glyph. The same kind a select holds.
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
label | string | "" | The option's text and its accessible name: what the filter searches and what the input shows once the option is chosen. Required. |
value | string | "" | Identifies the choice: the option is the chosen one while the combobox's value equals it. Unique within the combobox and not empty: the empty value means that nothing is chosen. |
supportingText | string | "" | A line below the label that says more about the choice. Shown in the list, not in the input; not searched. |
leadingIcon | string | "" | The name of an icon (a glyph of the system's icon set) before the label; the container shows it too while the option is chosen and the combobox has no leading icon of its own. |
disabled | boolean | false | Prevents the option from being chosen. It stays reachable with the arrow keys. |
| 规格 | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| 条目 | tanrei-option | TanreiComboboxOption | TanreiComboboxOption | TanreiComboboxOption | ComboboxOption |
label · 属性 | label | title | text | label | label |
value · 属性 | value | tag | value | value | value |
supportingText · 属性 | supportingText | supportingText | supportingText | supportingText | supportingText |
leadingIcon · 属性 | leadingIcon | leadingIcon | leadingIcon | leadingIcon | leadingIcon |
disabled · 属性 | disabled | disabled | enabled | enabled | disabled |
| 备注 | <tanrei-option value="tokyo">Tokyo</tanrei-option>: the element a select holds, and the same data: the label is the label attribute or, without one, the element's text. Attributes supporting-text, leading-icon, disabled. It draws nothing itself. tanrei-option and tanrei-option-group are one pair of elements for the select and the combobox, defined once; an option with the empty value is refused by the combobox (CMB-17, CMB-57), not by the element, because a select allows it. | TanreiComboboxOption(_ title:, supportingText:, leadingIcon:).tag(value); disabled follows .disabled(_:). | TanreiComboboxOption(text, value, supportingText, leadingIcon, enabled), called in the combobox's content lambda. | TanreiComboboxOption<String>(value:, label:, supportingText:, leadingIcon:, enabled:), in the combobox's options list, as DropdownMenuEntry. | { label, value, supportingText?, leadingIcon?, disabled? }: an element of the options array. |
option-group · 分组 — 包含条目
A run of related options, optionally titled. The combobox separates a group from what is next to it by a divider or a gap, and draws neither a group nor its title while the filter leaves it empty.
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
title | string | "" | A short heading drawn above the group's options, which also names the group; empty draws none and leaves the group unnamed. |
| 规格 | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| 条目 | tanrei-option-group | TanreiComboboxSection | TanreiComboboxGroup | TanreiComboboxGroup | ComboboxOptionGroup |
title · 属性 | label | title | title | title | title |
| 备注 | The title is the label attribute, as <optgroup label>: a title attribute would also draw the browser's tooltip. | TanreiComboboxSection(_ title:) { … }, as Section. | TanreiComboboxGroup(title) { … }. | TanreiComboboxGroup(title:, options: […]), in the same list. | { kind: 'group', title?, options: ComboboxOption[] } in the same array. |
状态
行为
以 EARS 形式写成的需求。每个平台的实现都按这些 ID 逐条测试。需求原文取自规格文件,原样以英文呈现。
| ID | 需求 |
|---|---|
CBF-1 | THE combobox field SHALL be a Field that frames one combobox, and nothing else. It SHALL give label, labelHidden, supportingText, errorText, error, disabled, required, optional, requiredMark, requiredText and optionalText to its Field and every other prop to its combobox, each unchanged; SHALL hold the label-icon slot for its Field and the leading-icon slot for its combobox; SHALL give its options to its combobox (CBF-2); and SHALL emit its combobox's events as its own, each once. THE combobox field SHALL be as wide as the place it stands in, and its combobox SHALL fill it: it is a block with no width of its own, as its Field is (FLD-1), around a control that fills the width it is given (CMB-49). It SHALL draw no part of its own and have no token of its own: what it shows, how it behaves and what it exposes to assistive technology SHALL be exactly what that Field and that combobox do when a product places the one in the other (FLD-1 to FLD-28; CMB-1 to CMB-62) — the label row and its requirement mark (FLD-2 to FLD-6), or the label that floats in the box with the Field's row left out (FLD-25, CMB-7, CMB-8); the supporting row and the error (FLD-7 to FLD-9, FLD-26, CMB-10); the name, the description and the states (FLD-10, FLD-11, FLD-14, CMB-12, CMB-46); and the list, which is the combobox's alone and as wide as its container (CMB-22). |
CBF-2 | THE combobox field SHALL take the options and the option groups a product gives it — in the platform's form for a combobox's own (its child elements on the web) — and give them to its combobox as that combobox's options, in the order given, each with its label, its value, its supporting text, its leading icon and its disabled, and each group with its title: the option a product wrote SHALL be the option its combobox reads, never a copy of it. THE combobox field SHALL NOT read, filter, order, count or keep an option, and SHALL make no option active or chosen: which options are shown, which is active and which is the chosen one are its combobox's (CMB-25, CMB-26, CMB-36). WHEN the product adds, removes, reorders or changes an option or a group THE combobox field's combobox SHALL meet the change at once and as CMB-31 says, as though the product had changed that combobox's own options. |
CBF-3 | THE combobox field SHALL hold no value, no text and no open state of its own: its value, its inputValue and its open SHALL at every moment be its combobox's. WHEN its combobox changes one of them — a choice, a commit or the clear button (CMB-17, CMB-20, CMB-27 to CMB-29); typing, or a text the combobox writes itself for a choice, for a commit that was given up or for Escape (CMB-16, CMB-27, CMB-29, CMB-30); the list opening or closing, or open taken back because there is nothing to show (CMB-21, CMB-30, CMB-57) — THE combobox field's SHALL be the new one before any event that reports the change is emitted, and equally WHERE no event reports it. WHEN the product sets one on the combobox field THE combobox field SHALL set it on its combobox and nothing else, so that no event follows (CMB-31). |
CBF-4 | WHERE the platform has forms (the web) THE combobox field SHALL be the one control the form sees: it SHALL take name, submit its combobox's value — never the text of an edit that was not committed — reset to the value it was first given, restore it on navigation, report to the form's constraint validation exactly what its combobox would report there (CMB-59), and be left out WHILE disabled is true or a disabled group of the form holds it; its combobox and its Field SHALL take no part in the form themselves. WHEN Enter asks for the form's submission (CMB-28) the form asked SHALL be the combobox field's own, through that form's default button (BTN-30), and not WHEN action was cancelled. |
CBF-5 | WHEN the combobox field is given focus — by the product, by a press on any part of it that FLD-12 or CMB-14 gives to the input, or by a form that reports its validity — THE combobox field SHALL pass the focus to its combobox's input and SHALL add no stop to the focus order: the stops inside it are its combobox's, in that combobox's order (the input, then the clear button while it is shown). WHILE focus is anywhere inside it, or its combobox's list is open, its Field's rows SHALL be drawn focused, because its combobox says so to its Field (FLD-13, CMB-61): the combobox field arranges nothing for it. A press on its Field's label row WHILE the list is open is a press outside the combobox: the list SHALL close with the reason outside, choosing nothing (CMB-30), and focus SHALL go to the input (FLD-12). |
CBF-6 | IF the combobox field is placed in a Field THE combobox field SHALL in debug builds report that it is a field already and that the bare control is the combobox, and both SHALL still render (FLD-15). WHILE it stands in a field group THE combobox field SHALL follow that group's disabled, through its own Field, and take nothing else from the group (FGR-20). |
CBF-7 | THE combobox field SHALL have no words of its own and SHALL read none: requiredText and optionalText are read by its Field (FLD-24), emptyText and clearLabel by its combobox (CMB-60), each from the instance's prop and then from the product's words. |
CBF-8 | WHERE the platform has ARIA attributes (the web), WHEN the product sets an aria- attribute on the host, by the attribute or by the ARIA property that reflects it, THE combobox field SHALL move it to the data-aria- attribute of the same name, so that the host is no second node with its name, description or state, and SHALL expose it on the element that carries its role — an id reference by the elements it names in the host's tree or a tree around it, an error message as part of the description while the combobox field is invalid — except those the combobox field owns (aria-label, aria-labelledby, aria-disabled, aria-haspopup, aria-expanded, aria-controls), which it SHALL move and not expose: its name is label (CBF-1). WHEN the product removes the aria- attribute or its data-aria- twin, or sets the ARIA property that reflects it (ariaLabel, ariaDescribedByElements…) to null, THE combobox field SHALL take it off that element. WHEN the product sets an aria- attribute the combobox field owns THE combobox field SHALL report it in debug builds. |
各平台名称对照
对等并不意味着 API 完全相同。同一份契约,由各平台以自己的惯用方式表达。
| 规格 | Web | SwiftUI | Jetpack Compose | Flutter | React Native |
|---|---|---|---|---|---|
| 组件 | tanrei-combobox-field | TanreiComboboxField | TanreiComboboxField | TanreiComboboxField | ComboboxField |
variant · 属性 | variant | variant | variant | variant | variant |
size · 属性 | size | size | size | size | size |
value · 属性 | value | selection | value | value | value |
inputValue · 属性 | inputValue | text | inputValue | controller | inputValue |
label · 属性 | label | label | label | labelText | label |
labelHidden · 属性 | labelHidden | labelsHidden | labelHidden | labelHidden | labelHidden |
placeholder · 属性 | placeholder | prompt | placeholder | hintText | placeholder |
supportingText · 属性 | supportingText | supportingText | supportingText | helperText | supportingText |
errorText · 属性 | errorText | errorText | errorText | errorText | errorText |
error · 属性 | error | error | isError | isError | error |
disabled · 属性 | disabled | disabled | enabled | enabled | disabled |
readOnly · 属性 | readOnly | readOnly | readOnly | readOnly | readOnly |
required · 属性 | required | isRequired | required | required | required |
optional · 属性 | optional | isOptional | optional | optional | optional |
requiredMark · 属性 | requiredMark | requiredMark | requiredMark | requiredMark | requiredMark |
requiredText · 属性 | requiredText | requiredText | requiredText | requiredText | requiredText |
optionalText · 属性 | optionalText | optionalText | optionalText | optionalText | optionalText |
open · 属性 | open | isPresented | expanded | menuController | open |
allowCustomValue · 属性 | allowCustomValue | allowCustomValue | allowCustomValue | allowCustomValue | allowCustomValue |
filter · 属性 | filter | filter | filter | filter | filter |
wrap · 属性 | wrap | wrap | wrap | wrap | wrap |
chevron · 属性 | noChevron | chevron | chevron | chevron | chevron |
clearable · 属性 | clearable | clearable | clearable | clearable | clearable |
clearLabel · 属性 | clearLabel | clearLabel | clearContentDescription | clearSemanticLabel | clearAccessibilityLabel |
status · 属性 | status | status | status | status | status |
statusText · 属性 | statusText | statusText | statusText | statusText | statusText |
emptyText · 属性 | emptyText | emptyText | emptyText | emptyText | emptyText |
separation · 属性 | separation | separation | separation | separation | separation |
iconSpace · 属性 | iconSpace | iconSpace | iconSpace | iconSpace | iconSpace |
density · 属性 | density | density | density | density | density |
input · 事件 | input | text | onInputValueChange | onInputChanged | onInputValueChange |
change · 事件 | change | selection | onValueChange | onChanged | onValueChange |
toggle · 事件 | toggle | isPresented | onExpandedChange | onOpenChanged | onOpenChange |
action · 事件 | action | onSubmit | keyboardActions | onSubmitted | onSubmitEditing |
| 备注 | The tag tanrei-combobox-field, the class TanreiComboboxField, the React name ComboboxField. It takes the combobox's attributes, children and listeners and the Field's, name for name. The element is composed as every composed element on the web is (the web package's README, Composed elements); what follows is what is particular to it, and the three places where this element needs more than every composed element has. Members: <tanrei-field> holding one <tanrei-combobox> in its default slot; the product places no member. The host is display: block. Value member: tanrei-combobox. The host is the form's element — name, the value and its state, validity anchored on the combobox's input, reset to the value attribute, restore, form, a disabled fieldset — and the combobox inside has no name. Props, set as properties on the member each is from. On the Field: label, labelHidden, supportingText, errorText, error, disabled, required, optional, requiredMark, requiredText, optionalText. On the combobox: variant, size, value, inputValue, placeholder, readOnly, open, allowCustomValue, filter, noChevron (the inverse of chevron, attribute no-chevron), clearable, clearLabel, status, statusText, emptyText, separation. Attributes are the members' own: kebab-case, except readonly. Props of one name on both members — label, labelHidden, error, disabled — are the Field's; the combobox's are never set by the element and reach the combobox through the connection. **1. Three props the member writes** (a composed element sets each prop on its member, with one exception, the value, which it reads back on the member's event): the combobox writes value, inputValue and open itself, and writes the text with no event when a commit is given up (CMB-29). The host therefore keeps no copy of the three: each is an accessor over the member's property — a get reads the member's, a set writes the member's, and a value set before the member exists is kept and given at its first render — so that event.target.value, .inputValue and .open are the new ones in every listener and between events (CBF-3). open reflects on the host as it does on the member: the host writes its own attribute when the member tells it of an update, by the primitive through which the member already tells the host its form value. **2. The options are passed by the default slot** (other passed-on children use named slots): the shadow root is <tanrei-field><slot name="label-icon" slot="label-icon"></slot><tanrei-combobox><slot></slot><slot name="leading-icon" slot="leading-icon"></slot></tanrei-combobox></tanrei-field>. The product's <tanrei-option> and <tanrei-option-group> children stay the host's light-DOM children and are assigned to the unnamed <slot>, which is a child of the combobox; the combobox reads a <slot> among its children as the elements assigned to it and reads again on slotchange and on a change of an assigned element (primitives/option-list.ts, the combobox's web note; the select reads its options the same way, with the same reader). Unlike a passing named slot, the unnamed one is always rendered: the combobox draws no room for options, so an empty slot is no option. The host never reads its option children. Slots: label-icon is passed to the Field and leading-icon to the combobox, each by a slot element of the same name rendered only while the host has a child for it. **3. A forwarded attribute that is no prop**: inline, the combobox's web-only attribute that draws an open list in place for the fixtures, is forwarded as a property, and select() is passed on as focus() and blur() are. Events: input is the native, composed event of the text element, and change and action are the combobox's own composed, bubbling events; the three leave by themselves, with the host as their target, and action stays cancelable. toggle is composed and does not bubble: it is heard on the host, as on any shadow host, and by no ancestor — which needs the combobox's toggle to be composed, as it is not today (the combobox's web note). None is stopped and none is copied. Parts: the member hosts are field and combobox. Exported from the Field: label, label-icon, requirement-label, supporting-text. Exported from the combobox: container, outline, active-indicator, label, requirement-label, input, leading-icon, clear-button, chevron, list, option, option-label, option-supporting-text, option-leading-icon, mark, group, group-title, divider, status. Two names collide, label and requirement-label. Each is exported with its member's name before it, always (field-label, combobox-label, field-requirement-label, combobox-requirement-label), and under the bare name from both members as well, because the two are one thing to a person and are never drawn at once: FLD-25 is the sentence that holds it. Focus: the shadow root does not delegate. focus(), blur() and select() call the combobox's; a press on the label row focuses the input by FLD-12, a press on the box by CMB-14, and a press on the help does nothing. The combobox's list is a popover of the combobox's own shadow root, in the top layer; the combobox is its opener, so a press on the Field's rows while it is open is a press outside (CBF-5). The connection is the Field's with its control, with one value more upward, that the control is drawn focused (FLD-13, FLD-25, FLD-26, CMB-61); the element takes no part in it. Parts and tokens of its own: none. The element writes no custom property: --tanrei-comp-field-*, --tanrei-comp-input-box-* and --tanrei-comp-combobox-* reach the members by inheritance, and one combobox field is set apart by setting any of them on the element. What its twin cannot do that it does: nothing. The twin: <tanrei-field label="City" supporting-text="Where the order is sent." required><tanrei-combobox name="city" value="tokyo"><tanrei-option value="kyoto">Kyoto</tanrei-option><tanrei-option value="tokyo">Tokyo</tanrei-option></tanrei-combobox></tanrei-field>. Before the element is defined its options are running text: a page hides them with tanrei-combobox-field:not(:defined) > * { display: none } and gives the tag a block size. Words: none of its own; required-text and optional-text are read by the Field, empty-text and clear-label by the combobox. | TanreiComboboxField("City", selection: $city, text: $query) { TanreiComboboxOption("Tokyo").tag("tokyo") … }: a view built from TanreiField and TanreiCombobox, with the combobox's two bindings and its content builder passed through and the Field's arguments beside them. It takes TanreiCombobox's arguments, name for name. One instance is set apart by .tanreiTheme(_:) on the view. The options are TanreiComboboxOption and TanreiComboboxSection, the combobox's own. Planned, after the Field and the combobox. | TanreiComboboxField(value, onValueChange, label = "City") { TanreiComboboxOption("Tokyo", "tokyo") … }: a composable that calls TanreiField with TanreiCombobox as its content and passes the content lambda through. enabled is the inverse flag and isError keeps Compose's name. One instance is set apart by TanreiTheme(…) { } around it. Planned, after the Field and the combobox. | TanreiComboboxField(value:, onChanged:, labelText:, helperText:, errorText:, options: […]): a widget that builds TanreiField around TanreiCombobox and passes options through; labelText, hintText, helperText, errorText and enabled keep InputDecoration's names, enabled the inverse flag. TanreiComboboxFormField wraps the combobox field as a FormField<String>. One instance is set apart by a TanreiTheme around it. Planned, after the Field and the combobox. | <ComboboxField label="City" supportingText="Where the order is sent." value={city} onValueChange={setCity} options={[…]} />: a component that renders Field around Combobox and passes options through as given. inputValue / onInputValueChange and open / onOpenChange are the combobox's optional pairs. One instance is set apart by a provider around it. Planned, after the Field and the combobox. |
令牌
| 令牌 | 引用 | 解析值(浅色) |
|---|---|---|
--tanrei-comp-field-counter-gap | sys.space.space200 | 16px |
--tanrei-comp-field-error-text-gap | sys.space.space50 | 4px |
--tanrei-comp-field-error-text-placement | — | 0 |
--tanrei-comp-field-label-icon-gap | sys.space.space100 | 8px |
--tanrei-comp-field-label-icon-size | sys.icon.small | 20px |
--tanrei-comp-field-label-gap | sys.space.space100 | 8px |
--tanrei-comp-field-motion-state-duration | sys.motion.role.state.default | 250ms |
--tanrei-comp-field-motion-state-easing | sys.motion.easing.state | cubic-bezier(0.2, 0, 0, 1) |
--tanrei-comp-field-requirement-asterisk-gap | sys.space.space25 | 2px |
--tanrei-comp-field-requirement-gap | sys.space.space100 | 8px |
--tanrei-comp-field-requirement-mark | sys.behavior.requirement-mark | 0 |
--tanrei-comp-field-standard-counter-color | sys.color.on-surface-variant | |
--tanrei-comp-field-standard-disabled-counter-color | — | |
--tanrei-comp-field-standard-disabled-error-text-color | — | |
--tanrei-comp-field-standard-disabled-error-counter-color | — | |
--tanrei-comp-field-standard-disabled-error-label-icon-color | — | |
--tanrei-comp-field-standard-disabled-error-label-color | — | |
--tanrei-comp-field-standard-disabled-error-required-asterisk-color | — | |
--tanrei-comp-field-standard-disabled-error-supporting-text-color | — | |
--tanrei-comp-field-standard-disabled-error-value-text-color | — | |
--tanrei-comp-field-standard-disabled-label-icon-color | — | |
--tanrei-comp-field-standard-disabled-label-color | — | |
--tanrei-comp-field-standard-disabled-required-asterisk-color | — | |
--tanrei-comp-field-standard-disabled-supporting-text-color | — | |
--tanrei-comp-field-standard-disabled-value-text-color | — | |
--tanrei-comp-field-standard-error-text-color | sys.color.error | |
--tanrei-comp-field-standard-error-counter-color | sys.color.on-surface-variant | |
--tanrei-comp-field-standard-error-label-icon-color | sys.color.on-surface-variant | |
--tanrei-comp-field-standard-error-label-color | sys.color.on-surface | |
--tanrei-comp-field-standard-error-required-asterisk-color | sys.color.primary | |
--tanrei-comp-field-standard-error-supporting-text-color | sys.color.on-surface-variant | |
--tanrei-comp-field-standard-error-value-text-color | sys.color.on-surface-variant | |
--tanrei-comp-field-standard-focused-counter-color | — | |
--tanrei-comp-field-standard-focused-error-text-color | — | |
--tanrei-comp-field-standard-focused-error-counter-color | — | |
--tanrei-comp-field-standard-focused-error-label-icon-color | — | |
--tanrei-comp-field-standard-focused-error-label-color | — | |
--tanrei-comp-field-standard-focused-error-required-asterisk-color | — | |
--tanrei-comp-field-standard-focused-error-supporting-text-color | — | |
--tanrei-comp-field-standard-focused-error-value-text-color | — | |
--tanrei-comp-field-standard-focused-label-icon-color | — | |
--tanrei-comp-field-standard-focused-label-color | — | |
--tanrei-comp-field-standard-focused-required-asterisk-color | — | |
--tanrei-comp-field-standard-focused-supporting-text-color | — | |
--tanrei-comp-field-standard-focused-value-text-color | — | |
--tanrei-comp-field-standard-label-icon-color | sys.color.on-surface-variant | |
--tanrei-comp-field-standard-label-color | sys.color.on-surface | |
--tanrei-comp-field-standard-required-asterisk-color | sys.color.primary | |
--tanrei-comp-field-standard-supporting-text-color | sys.color.on-surface-variant | |
--tanrei-comp-field-standard-value-text-color | sys.color.on-surface-variant | |
--tanrei-comp-field-supporting-text-gap | sys.space.space50 | 4px |
--tanrei-comp-field-value-text-gap | sys.space.space200 | 16px |
--tanrei-comp-field-label-typescale | sys.typescale.label | 500 14px/1.4286 |
--tanrei-comp-field-supporting-text-typescale | sys.typescale.label-small | 500 12px/1.3333 |
--tanrei-comp-field-value-text-typescale | sys.typescale.label | 500 14px/1.4286 |
测试样例
作为基准的渲染结果。所有平台都以相同的 ID 为每个样例截取快照,以便相互比较。
variant: stacked (46)
stacked-empty
variant: stacked label: City items: 5 stacked-value
variant: stacked value: tokyo label: City items: 5 stacked-open
variant: stacked value: tokyo label: City open: true items: 5 单独绘制在 312 × 472 的窗口中stacked-large
variant: stacked size: large value: tokyo label: City items: 5 stacked-error
variant: stacked label: City errorText: Choose the city you are sending to. error: true items: 5 stacked-disabled
variant: stacked value: tokyo label: City disabled: true items: 5 stacked-placeholder
label: City placeholder: Type to find a city items: 5 stacked-label-hidden
value: tokyo label: City labelHidden: true items: 5 stacked-supporting
value: tokyo label: City supportingText: Where the order is sent. items: 5 stacked-error-value
value: nairobi label: City supportingText: Where the order is sent. errorText: We do not deliver to Nairobi yet. error: true items: 5 stacked-read-only
value: tokyo label: City readOnly: true items: 5 stacked-clearable
value: tokyo label: City clearable: true clearLabel: Clear city items: 5 stacked-no-chevron
label: Address placeholder: Start typing an address filter: none chevron: false items: 1 stacked-custom-value
value: Recursive label: Font family allowCustomValue: true items: 4 stacked-required
label: City required: true requiredText: Required items: 5 stacked-optional
label: City optional: true optionalText: Optional items: 5 stacked-required-asterisk
label: City required: true requiredMark: asterisk items: 5 stacked-required-error
label: City errorText: Choose the city you are sending to. error: true required: true requiredText: Required items: 5 stacked-required-disabled
value: tokyo label: City disabled: true required: true requiredText: Required items: 5 stacked-label-icon
value: tokyo label: City items: 5 slots: label-icon stacked-leading-icon
label: Assignee placeholder: Find a person items: 2 slots: leading-icon stacked-option-icon
value: train label: Transport items: 3 open-none-chosen
label: City open: true items: 5 单独绘制在 312 × 473 的窗口中open-filtered
value: tokyo inputValue: to label: City open: true items: 5 单独绘制在 312 × 473 的窗口中open-filtered-none
inputValue: zz label: City open: true emptyText: No matches items: 5 单独绘制在 312 × 473 的窗口中open-icons
value: train label: Transport open: true items: 3 单独绘制在 312 × 473 的窗口中open-supporting-text
value: sys.color.on-primary inputValue: primary label: Token open: true items: 4 单独绘制在 312 × 473 的窗口中open-disabled-chosen
value: sys.color.secondary label: Token open: true items: 4 单独绘制在 312 × 473 的窗口中open-groups
value: Europe/Lisbon label: Time zone open: true items: 3 单独绘制在 312 × 473 的窗口中open-groups-divider
value: Europe/Lisbon label: Time zone open: true separation: divider items: 3 单独绘制在 312 × 473 的窗口中open-groups-gap
value: Europe/Lisbon label: Time zone open: true separation: gap items: 3 单独绘制在 312 × 473 的窗口中open-groups-filtered
inputValue: in label: Time zone open: true items: 3 单独绘制在 312 × 473 的窗口中open-many
value: lisbon label: City open: true items: 24 单独绘制在 312 × 473 的窗口中open-long-options
label: Licence open: true items: 2 单独绘制在 312 × 473 的窗口中open-custom-suggestions
inputValue: no label: Font family open: true allowCustomValue: true items: 4 单独绘制在 312 × 473 的窗口中open-loading
inputValue: ai label: Assignee open: true filter: none chevron: false status: loading statusText: Loading people items: 1 单独绘制在 312 × 473 的窗口中open-status
inputValue: a label: Assignee open: true filter: none chevron: false statusText: 2 of 48 people shown. Keep typing to nar… items: 2 单独绘制在 312 × 473 的窗口中open-status-error
inputValue: ai label: Assignee open: true filter: none chevron: false status: error statusText: People could not be loaded. Check your c… items: 1 单独绘制在 312 × 473 的窗口中long-value
value: cc-by-nc-sa label: Licence items: 2 long-label
label: Which of the cities we deliver to is nea… supportingText: If none of them is near enough, leave th… required: true requiredText: Required items: 5 stacked-required-ja
value: kyoto label: 都市 required: true requiredText: 必須 items: 3 open-filtered-ja
inputValue: 京 label: 都市 optional: true optionalText: 任意 open: true emptyText: 一致する項目がありません items: 3 单独绘制在 312 × 473 的窗口中stacked-required-rtl
value: cairo label: المدينة supportingText: إلى أين نرسل الطلب. required: true requiredText: مطلوب clearable: true clearLabel: مسح المدينة direction: rtl items: 2 slots: label-icon open-rtl
value: Asia/Riyadh inputValue: ال label: المنطقة الزمنية open: true direction: rtl items: 2 单独绘制在 312 × 473 的窗口中long-value-rtl
value: cc-by-nc-sa label: רישיון direction: rtl items: 2 stacked-error-rtl
label: עיר errorText: יש לבחור עיר מהרשימה. error: true optional: true optionalText: רשות direction: rtl items: 2 variant: filled (6)
filled-empty
variant: filled label: City items: 5 filled-value
variant: filled value: tokyo label: City items: 5 filled-open
variant: filled value: tokyo label: City open: true items: 5 单独绘制在 312 × 473 的窗口中filled-large
variant: filled size: large value: tokyo label: City items: 5 filled-error
variant: filled label: City errorText: Choose the city you are sending to. error: true items: 5 filled-disabled
variant: filled value: tokyo label: City disabled: true items: 5 variant: outlined (8)
outlined-empty
variant: outlined label: City items: 5 outlined-value
variant: outlined value: tokyo label: City items: 5 outlined-open
variant: outlined value: tokyo label: City open: true items: 5 单独绘制在 312 × 473 的窗口中outlined-large
variant: outlined size: large value: tokyo label: City items: 5 outlined-error
variant: outlined label: City errorText: Choose the city you are sending to. error: true items: 5 outlined-disabled
variant: outlined value: tokyo label: City disabled: true items: 5 outlined-required
variant: outlined label: City required: true requiredText: Required items: 5 outlined-rtl
variant: outlined value: cairo label: المدينة direction: rtl items: 2 生成自 specs/combobox-field.spec.json, tokens/comp/field.tokens.json, design/tokens/
<tanrei-option>和<tanrei-option-group>是数据:它们什么都不画。它们就是下拉选择框所包含的那些元素。值为空字符串的选项会画出来,但不能被选中,因为空值表示什么都没有选。- 这个元素的 shadow root 里有一个包着
<tanrei-combobox>的<tanrei-field>。你的<tanrei-option>子元素仍然是它自己的子元素,通过插槽到达组合框;没有任何东西被移动或复制。 - 组合框字段是与表单关联的元素:表单看到的唯一控件。它接受
name,提交value,绝不提交尚未确认的编辑文本;重置时回到value属性(attribute)的值,并在required且value为空时向表单报告缺少值。没有活动选项时按 Enter 会提交它的表单。 value、inputValue和open在任何时刻都是组合框的:在每个监听器里,它们都已经是新的值。chevron和wrap对应含义相反的属性(attribute)no-chevron和no-wrap,readOnly对应readonly。input是文本元素的原生事件。change和action带有detail.value和detail.inputValue。这四个事件是组合框自己的事件,以组合框字段为目标;toggle不冒泡。focus()会把焦点交给输入框。这个元素自己不能获得焦点,也没有角色:用label为它命名,绝不要用aria-label。- 部件:
field和combobox是两个成员;每个成员的部件都以自己的名称导出,label和requirement-label还另外导出为field-label、combobox-label、field-requirement-label和combobox-requirement-label。 - 要单独调整某一个字段,就在它上面设置自定义属性:各行用
--tanrei-comp-field-*,方框用--tanrei-comp-input-box-*,列表用--tanrei-comp-combobox-*。 - 列表是顶层(top layer)中的
popover="manual"元素。在外部按下会关闭它,这次按下也会传到它下方的元素。 - 状态行位于选项下方,不随选项滚动,所以数量或失败信息始终留在视野之内。
- 在元素定义之前,它的选项是页面上的普通文本。请隐藏
tanrei-combobox-field:not(:defined) > *,并预留高度。 - Web 没有自己的返回手势,所以
toggle绝不会带有原因back。
| 方面 | 组合框字段 | 下拉选择框 | 搜索 | 文本字段 |
|---|---|---|---|---|
| 里面是什么 | 通过键入找到的一个值 | 封闭列表中的一个值 | 查询词 | 任意文本 |
| 如何显示选项 | 随用户键入而显示 | 按下之后显示 | 显示建议和结果 | 不显示 |
| 是否接受列表之外的值 | 设置了 allowCustomValue 时接受 |
否 | 是 | 是 |
| 是否属于表单 | 是 | 是 | 否 | 是 |
- 选项不超过五个左右、都很短时,把它们全部显示出来:用单选按钮。
- 如果列表短到可以一眼扫完,并且是封闭的,那是下拉选择框。
- 如果用户宁愿键入也不愿滚动,或者字段要接受列表之外的值,那是组合框字段。
- 如果键入通向的是要去的地方,而不是一个值,那是搜索。
- 要选多个值时,用别的组件:组合框只保存一个值。
- 如果标签在别处——工具栏里的筛选框、表格的单元格——那是单独使用的组合框,由它自己的
label命名。
- 写一个标签,说明选择的是什么,并让它在表单中保持可见。
- 只有当列表之外的值确实是一个有效的回答时,才使用
allowCustomValue。 - 值可以为空的组合框字段,请设置
clearable,并用clearLabel为按钮命名。 - 加载选项时,设置
status和statusText,让列表说明它正在等待、显示了多少项,或者加载失败了。 - 给出
emptyText:写在组合框字段上,或者用setWords({ emptyText })为整个产品只给一次。Tanrei 不自带任何文字:没有它,文本没有任何匹配时不绘制这一行,列表也就没有东西可显示。
- 不要把占位符当作标签。用户一开始键入,它就消失了。
- 不要在
input时保存、跳转或提交。每按一次键它都会触发;change才代表已确认的值。 - 不要把与任何选项都不对应的文本当作字段的错误。用户离开时,这段文本就被放弃了。如果你想提示“请从列表中选择一项”,请自己设置
error和errorText。 - 不要把组合框字段放进字段。它已经是一个字段;字段框住的控件是组合框。
- 在字段很多的表单里,不要让列表在获得焦点时打开:每停靠一次,它都会盖住下方的字段。
- 角色
combobox- 标注
- The combobox field adds nothing to what its members expose and coordinates nothing: the Field gives the combobox its name, its description and its required, invalid and disabled states (FLD-8, FLD-10, FLD-11, FLD-14), and the combobox carries them on its own input, with its expanded state, its list and its active option (CMB-12, CMB-48). Assistive technology meets one editable combobox named by label, with a list of the same name, exactly as it meets a Field around a combobox written by hand.
- 最小目标
- 即使外观尺寸更小,点击区域也满足最小目标尺寸。 minimum 32px · pointer 44px · spacing 8px · touch 48px
键盘操作
| 按键 | 动作 |
|---|---|
| Every key | The combobox's (its accessibility block; CMB-16, CMB-20, CMB-21, CMB-26 to CMB-30). The combobox field handles no key and adds no focus stop (CBF-5). |
备注
- One level down is the same thing: a Field with a combobox in it, holding the same options. Every fixture has that pair as its twin, and the accessibility tree of the two is the same (CBF-1).
- The options are the product's, and they are the combobox's: the combobox field passes them on and does nothing with them (CBF-2). Value, text and open are the combobox's too, and the combobox field keeps no second copy that could disagree with it (CBF-3).
- On the web the two members stand in the combobox field's own shadow root, so the form meets the combobox field and not the combobox (CBF-4); focus passes through to the input (CBF-5); and the product's option elements stay its own children, read by the combobox through a slot.
- A list is not a second thing the component reports: choosing an option is setting its one value, so the combobox field is composed, not a feature.
生成自 specs/combobox-field.spec.json, tokens/sys/measure.tokens.json
- 输入框是一个带列表框的可编辑组合框。它的名称是
label,无论是画出来的还是隐藏的,绝不是占位符。字段把它交给组合框,组合框把它带在自己的输入框上。 - 显示出来的辅助文本或错误文本是它的描述;字段为选填时,还会加上
optionalText。 - 焦点在字段内时出现的错误,会以礼貌(polite)的方式朗读一次。
error为 true 时,它暴露为无效;设置了required时,暴露为必填;只读时暴露为只读;列表打开时,暴露为已展开。- 列表打开期间,焦点留在输入框里。活动选项是输入框的活动后代,所以键入时对文本的编辑绝不会中断。键入使之成为活动的选项,会在输入停顿半秒后才被指向,所以屏幕阅读器先读出刚输入的字母,再读出 Enter 会选中的选项;方向键移到的选项会立即被指向。
- 列表是一个以标签命名的列表框。每个选项都有自己的名称、描述、是否选中、是否禁用,以及它在所显示的选项中的位置。组以它的标题命名。
- 匹配部分靠字重来标记,绝不只靠颜色。标签仍作为一整段文本朗读。
- 状态行是一个实时区域(live region):它的文本变化时,以礼貌(polite)的方式朗读;数据源失败时,立即朗读。加载期间,列表标记为忙碌,不朗读任何内容。
- 输入法正在组合输入时,组合框不处理任何按键。
- V 形箭头是供指针和触摸浏览使用的按钮,不在焦点顺序之内。键盘用下方向键打开列表。
- 清除按钮是一个以
clearLabel命名的按钮,也是输入框之后的下一个焦点停靠点。 - Escape 关闭列表,并且不再向外传递,所以包含组合框的对话框保持打开。
- 字段的点击区域达到最小目标,每个选项的高度也不小于最小目标。
- 文本放大到 200% 时,标签、帮助文本、选项、标题和状态行会换行。只有输入框里的文本保持在一行之内,并且可以滚动。
- 在强制颜色模式下,字段和列表会多出一道边线,活动选项采用系统的高亮颜色,选中的选项靠对勾来表示。
- 在从右到左的布局中,字段和每一行都会镜像,每段文本保持自己的方向。V 形箭头、清除按钮的字形和对勾不镜像。
Web 上的已知差距:
- 两个组之间的分隔线没有作为分隔符暴露:列表框只能包含选项和组。
- 筛选对所有文字一律忽略大小写、全角半角和附加符号的差别。它不会按首辅音匹配谚文,也不会按读音匹配假名。
- 在元素定义之前,选项是页面上的文本,没有对应的控件。
- 为每个组合框字段提供
label,并为每个选项提供标签和一个不为空的值。 - 为可清除的组合框字段提供
clearLabel。 - 在组合框字段上编写
statusText。emptyText、requiredText、optionalText和clearLabel可以写在组合框字段上,也可以用setWords()为整个产品只给一次。Tanrei 不自带任何文字;缺少的文字会在开发时报告。参见产品的文字。 - 自己校验值,设置
error,并编写说明如何改正的errorText。 - 在同一个组合框字段里,让各个选项的值保持唯一。