跳到主要内容
menumenu tanreiexpand_morecontrasttranslate
tanreiexpand_more
开始使用WebReactSwiftUIJetpack ComposeFlutterReact Native预设你自己的设计系统与 AI 代理一起使用 Tanrei品牌设计令牌设计系统与定制Theme Studio颜色字体排印图标形状层级动效交互状态行为布局无障碍文字颜色令牌字阶令牌形状令牌层级令牌动效令牌状态令牌间距与度量令牌组件按钮主操作按钮分段按钮按钮组带菜单的按钮工具栏复制按钮复选框单选按钮开关纸片(Chip)菜单滑块滑块字段日历DateTimePicker字段组单选组复选框组下拉选择框下拉选择字段菜单栏文件上传文件选择器 进阶放置区文本输入框 进阶文本字段纸片字段字段组合框 进阶组合框字段数字输入框 进阶数字字段颜色输入框 进阶颜色字段一次性验证码输入框 进阶一次性验证码字段嵌入式文本输入框 进阶聊天输入栏卡片分隔线模态层 进阶对话框列表列表行底部面板侧边面板轮播代码块表格弹出框步骤折叠面板描述列表图示应用外壳页头树视图时间线图片预览调整手柄可调整大小的框滚动区域徽标进度指示器计量条数字滚动器加载指示器消息条(Snackbar)工具提示键盘快捷键标签(Tag)行内代码提示块(Callout)头像空状态标签组(Tag group)骨架屏文件项色块跑马灯文本标签页标签页视图应用栏导航栏导航抽屉搜索链接分页上一页/下一页链接步骤条面包屑目录链接卡片FAB 菜单状态Tanrei 是如何构建的参与贡献许可与致谢
首页组件一次性验证码字段

一次性验证码字段

以一个完整字段的形式,向用户索取发送给他的简短验证码:标签下方是格子,带有一行帮助文本和验证码错误时的文字;它们为辅助技术关联在一起,并作为一个控件参与表单。

content_copy以 Markdown 格式查看
类别
文本输入
令牌
comp.field
层级
组合

概览

Web
tanrei-one-time-code-field
schedule
SwiftUI
TanreiOneTimeCodeField
radio_button_unchecked
Jetpack Compose
TanreiOneTimeCodeField
radio_button_unchecked
Flutter
TanreiOneTimeCodeField
radio_button_unchecked
React Native
OneTimeCodeField
radio_button_unchecked

生成自 docs/status/status.json, specs/one-time-code-field.spec.json

一次性验证码字段,以及一个验证码被产品拒绝的字段

只要产品要求用户回填它发出的简短验证码,就使用一次性验证码字段:登录、确认某个地址或电话号码、批准一笔付款。它对应的是最常见的情况,可以直接使用:一个标签、一排格子、一行说明验证码发到了哪里的文字,以及验证码错误时的文字。

组成部分是一个字段和一个一次性验证码输入框。字段是框架:标签、必填或选填标记、帮助文本和错误。一次性验证码输入框是控件:格子,以及用户在格子里做的一切。一次性验证码字段自己什么都不绘制,也没有令牌:它的每个属性都是这两者之一的属性,原样交给它们。

它的宽度与所在的位置相同,格子从它的起始边开始排。

在 Web 上它是 <tanrei-one-time-code-field>。其他平台尚未实现。

用法

value 是到目前为止已输入的验证码;无论画出怎样的分组,其中都没有分隔符。四个事件是一次性验证码输入框自己的事件;监听到其中任何一个时,字段的 value 都已经是新的值。

<form>
  <tanrei-one-time-code-field name="code" label="验证码"
    supporting-text="请输入发送到手机的 6 位验证码。"
    error-text="验证码不正确。请核对短信后重试。"
    required required-text="必填"
    group-size="3"></tanrei-one-time-code-field>
</form>

<script type="module">
  import '@tanrei/web/components/one-time-code-field';

  const code = document.querySelector('tanrei-one-time-code-field');
  // 用户改变了值。这时它已经是新的值。
  code.addEventListener('input', () => (code.error = false));
  // 最后一个格子填上了,或者粘贴、自动填充了完整的验证码。除此之外,不会自动发生任何事。
  code.addEventListener('complete', async (e) => {
    code.readOnly = true;
    const right = await verify(e.detail.value);
    code.readOnly = false;
    code.error = !right;
  });
</script>
import { OneTimeCodeField } from '@tanrei/react';

<OneTimeCodeField name="code" label="验证码" error={wrong}
  supportingText="请输入发送到手机的 6 位验证码。"
  errorText="验证码不正确。请核对短信后重试。"
  groupSize={3} value={code}
  onInput={(event) => setCode(event.currentTarget.value)}
  onComplete={(event) => verify(event.detail.value)} />

下一层

用它的两个成员手写出来的同一个字段,画面是一样的,对辅助技术来说也是同一个文本框:

<tanrei-field label="验证码"
  supporting-text="请输入发送到手机的 6 位验证码。"
  error-text="验证码不正确。请核对短信后重试。">
  <tanrei-one-time-code-input name="code" group-size="3"></tanrei-one-time-code-input>
</tanrei-field>

手写出来的同一个字段:一个字段,里面有一个一次性验证码输入框

当你需要一次性验证码字段没有提供的排布方式时,就往下走一层:

你需要 使用
标签、格子、帮助文本和错误 一次性验证码字段
在框架里面、格子旁边放你自己的东西:重新发送验证码的控件、倒计时 一个字段,里面放一个一次性验证码输入框和你自己的元素
只要格子,标签绘制在别处:对话框的标题、某个步骤的标题 单独使用的一次性验证码输入框,由它自己的 label 命名
一个没有格子的输入框,用于长度未知的验证码 带 autocomplete="one-time-code" 的文本字段

下到这一层之后,变的是各样东西写在哪里。name 和 value 写在一次性验证码输入框上,这时它就是表单面对的控件,它的事件也在它上面监听。label、error 和 disabled 仍然写在字段上,由字段转交给控件:不要设置输入框自己的这几项。

规格

带产品文字的必填、选填、禁用,以及遮蔽的 4 位验证码

各个成员提供什么

方面 来自字段 来自一次性验证码输入框
属性 label, labelHidden, supportingText, errorText, error, disabled, required, optional, requiredMark, requiredText, optionalText value, length, type, groupSize, mask, readOnly
事件 — input, change, complete, action
插槽 label-icon —
令牌 comp.field.* comp.one-time-code-input.*

一次性验证码输入框有它自己的 label、error 和 disabled。它们不是一次性验证码字段的属性:字段的 label 为控件命名,字段的 error 让格子进入错误状态,字段的 disabled 禁用格子。所以每一项你都只设置一次,设置在一次性验证码字段上,它的标签也随控件一起变化。

格子做什么,也就是键入、粘贴、方向键和分组,写在一次性验证码输入框的页面上。框架做什么,也就是必填标记、帮助文本、错误以及错误怎样朗读,写在字段的页面上。

单独调整某一个字段

一次性验证码字段没有自己的令牌。它的框架读取字段的令牌,它的格子读取一次性验证码输入框的令牌,无论这些令牌设置在哪里。要改动某一个实例,就在它上面设置成员的令牌:

<tanrei-one-time-code-field label="验证码"
  style="--tanrei-comp-field-label-gap: 12px"></tanrei-one-time-code-field>

构成

构成

绘制在 Web 包所绘的测试样例 supporting 之上。
部件 说明
1 field The frame: a Field, whole — its label row with the label glyph and the requirement mark, and its supporting row, where the error text stands in the help's place or below it (FLD-7). Drawn by the Field from the Field's tokens (FLD-1 to FLD-9).
2 one-time-code-input The frame's one control: a One-time-code input, whole — its cells, its characters, its caret and its group marks. Drawn by the One-time-code input from its own tokens (OTC-1 to OTC-3, OTC-15).

属性

属性 类型 默认值 说明
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 Always given: the control of a one-time-code field never names itself, so the case of an empty label does not arise.
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
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 The one error of the one-time-code field: the Field passes it to the control (FLD-8, OTC-17), whose own error is not a prop here.
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 The one disabled of the one-time-code field: the Field passes it to the control (FLD-14, OTC-20), whose own disabled is not a prop here.
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
value string "" The code entered so far: at most length characters, digits or, for alphanumeric, digits and upper-case letters, with no separator. One string, whatever the cells and groups drawn. — One-time-code input
length number 6 How many characters the code has, and so how many cells are drawn. A whole number of 1 or more. — One-time-code input
type numeric | alphanumeric "numeric" What a cell takes: numeric is the digits 0 to 9, with a numeric keypad; alphanumeric is digits and the letters A to Z, held in upper case, with a general keyboard. — One-time-code input
groupSize number 0 Draws the cells in groups of this many, counted from the first, with a mark between two groups (3 for a six-character code written "123 456"). 0 draws one group. It changes the drawing only: the value has no separator. — One-time-code input
mask boolean false Draws the platform's obscuring mark in each filled cell in place of its character, and exposes the field as protected. For a screen other people can see; a code that is used once and expires rarely needs it. — One-time-code input
readOnly boolean false Keeps the field focusable and its value readable, and lets nothing change it: for the moment a complete code is being checked, when taking focus away would lose the place of a person who must then correct it. — One-time-code input

插槽

插槽 说明
label-icon A glyph before the label. — Field

事件

事件 说明
input The person changed the value; carries the new value. Emitted for every change, not when the product sets value. — One-time-code input
change The person committed a changed value (focus left the field, or the action key was pressed); carries the value. — One-time-code input
complete A change by the person left the value length characters long; carries the value. Emitted after input, once for that change. The field does nothing else: checking the code is the product's. — One-time-code input
action The person pressed the keyboard's action key (Enter, Return, Done). — One-time-code input

状态

行为

以 EARS 形式写成的需求。每个平台的实现都按这些 ID 逐条测试。需求原文取自规格文件,原样以英文呈现。

ID 需求
OTF-1 THE one-time-code field SHALL be a Field whose one control is a One-time-code input, and SHALL draw nothing of its own: every row, text, mark, cell and line it shows is drawn by one of the two, from that component's own tokens, as FLD-1 to FLD-8 and OTC-1 to OTC-3, OTC-7 and OTC-15 to OTC-17 say. THE one-time-code field SHALL have no token, no size and no gap of its own: the Field places the control and gives it its width (FLD-1), and the One-time-code input lays its row out in that width (OTC-1, OTC-23). What it shows SHALL be the picture a product gets by placing that One-time-code input in that Field by hand with the same values, and what assistive technology is told SHALL be the same name, description and states (FLD-10, FLD-11, OTC-22).
OTF-2 THE one-time-code field SHALL give each of its props, unchanged, to the member it is from and SHALL hold no state between the two: label, labelHidden, supportingText, errorText, error, disabled, required, optional, requiredMark, requiredText and optionalText are the Field's; value, length, type, groupSize, mask and readOnly are the One-time-code input's. A value a member does not know SHALL be passed on as given and answered by that member (FLD-22, OTC-19). THE one-time-code field SHALL never set the One-time-code input's own label, error or disabled: the Field's label names the control (FLD-10, OTC-22), the Field's error puts it in its error condition (FLD-8, OTC-17) and the Field's disabled disables it (FLD-14, OTC-20), so that each is said once, in the place where the label follows.
OTF-3 THE one-time-code field SHALL hold one value, the One-time-code input's. WHEN the person changes it THE one-time-code field's value SHALL be the new one before any event is reported, and THE one-time-code field SHALL report input, change, complete and action as the One-time-code input emits them — the same events, carrying the same value, in the same order and as often (OTC-13, OTC-14) — as its own, adding none and holding none back. WHEN the product sets value THE one-time-code field SHALL show it as OTC-18 says and SHALL report no event.
OTF-4 WHERE the platform has forms (the web) THE one-time-code field SHALL be the one control the form meets, and what OTC-29 says of a One-time-code input SHALL hold of the one-time-code field as a whole: it takes name; it submits the code alone, an empty string WHILE it holds none, and nothing WHILE disabled (OTC-20); it is restored on navigation and resets to the value it was first given; and it reports a missing value WHILE required is true and the value is empty, and nothing for a code shorter than length. Its members SHALL add no entry of their own to the form, and the action key SHALL submit the form the one-time-code field belongs to as OTC-13 says, unless action was cancelled.
OTF-5 THE one-time-code field SHALL be one stop in the focus order, its control's (FLD-12, OTC-2). WHEN the product moves focus to the one-time-code field THE one-time-code field SHALL pass it to the control, whose active cell is then the one OTC-5 names. A press on the label row SHALL move focus to the control and a press on the supporting row SHALL do nothing (FLD-12); THE one-time-code field SHALL take focus for no other press, SHALL add no key, and SHALL draw no focus indicator of its own: the frame's focused tokens and the active cell's focused line are the members' (FLD-13, OTC-7).
OTF-6 THE one-time-code field SHALL hold exactly one control and SHALL take no other from the product: it has no place for a child but the Field's label glyph (FLD-3, FLD-20), so that the cases of FLD-15 — no control, several controls — and of FLD-23 — a control replaced by another — never arise in it. IF the one-time-code field is placed in a Field THEN THE one-time-code field SHALL be drawn as it is and reported in debug builds as a field placed in a Field (FLD-15); WHERE it is placed in a field group THE one-time-code field SHALL follow the group's disabled as a Field does (FGR-14) and SHALL take nothing else from the group.
OTF-7 THE one-time-code field SHALL have no words of its own: every word it shows or says is a prop of the Field's, read as FLD-24 says — the instance's, else the product's words of the same name — and the One-time-code input has none (OTC-30). THE one-time-code field SHALL read no key of the product's words itself.
OTF-8 THE one-time-code field SHALL add nothing to how its members answer the place they are used in: the layout direction (FLD-18 to FLD-21; OTC-27 and OTC-28, by which the cells stay a left-to-right run and stand at the start edge, under the label), enlarged text (FLD-16, OTC-23, OTC-24), a person's own forced colors (FLD-17, OTC-26) and the caret's reduced motion are each member's own answer. THE one-time-code field SHALL have no direction, size, color or motion setting of its own, and SHALL never be wider than its layout gives it.
OTF-9 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 one-time-code 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 one-time-code field is invalid — except those the one-time-code field owns (aria-label, aria-labelledby, aria-disabled), which it SHALL move and not expose: its name is label (OTF-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 one-time-code field SHALL take it off that element. WHEN the product sets an aria- attribute the one-time-code field owns THE one-time-code field SHALL report it in debug builds.

各平台名称对照

对等并不意味着 API 完全相同。同一份契约,由各平台以自己的惯用方式表达。

规格 Web SwiftUI Jetpack Compose Flutter React Native
组件 tanrei-one-time-code-field TanreiOneTimeCodeField TanreiOneTimeCodeField TanreiOneTimeCodeField OneTimeCodeField
label · 属性 label label label labelText label
labelHidden · 属性 labelHidden labelsHidden labelHidden labelHidden labelHidden
supportingText · 属性 supportingText supportingText supportingText helperText supportingText
errorText · 属性 errorText errorText errorText errorText errorText
error · 属性 error error isError isError error
disabled · 属性 disabled disabled enabled enabled disabled
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
value · 属性 value code value controller value
length · 属性 length length length length length
type · 属性 type type type type type
groupSize · 属性 groupSize groupSize groupSize groupSize groupSize
mask · 属性 mask isMasked masked obscureText secureTextEntry
readOnly · 属性 readOnly readOnly readOnly readOnly readOnly
input · 事件 input code onValueChange onChanged onChangeText
change · 事件 change onCommit onCommit onCommit onEndEditing
complete · 事件 complete onComplete onComplete onCompleted onComplete
action · 事件 action onSubmit keyboardActions onSubmitted onSubmitEditing
备注 <tanrei-one-time-code-field name="code" label="Verification code" supporting-text="Enter the 6-digit code we sent to your phone." group-size="3"></tanrei-one-time-code-field>. Composed: a custom element with a shadow root that holds its members' own elements — <tanrei-field part="field" exportparts="label, label-icon, requirement-label, supporting-text"><tanrei-one-time-code-input part="one-time-code-input" exportparts="input, cell, character, caret, separator"></tanrei-one-time-code-input></tanrei-field> — and nothing else but the passage of the label-icon slot; its module imports both members' modules, so where it is defined they are. Each prop is a property and an attribute of the element under its member's own attribute name (label-hidden, supporting-text, error-text, required-mark, required-text, optional-text, group-size, readonly) and is set on that member as a property; the element writes no custom property and reads no token, so every --tanrei-comp-field-* and --tanrei-comp-one-time-code-input-* value reaches the members by inheritance, and one instance is set apart by setting such a property on this element. One level down, the same by hand: <tanrei-field label="Verification code" supporting-text="…"><tanrei-one-time-code-input name="code" group-size="3"></tanrei-one-time-code-input></tanrei-field>. Form-associated itself (static formAssociated = true): its members stand in its shadow root, outside any form, so it takes name and form, repeats on its own ElementInternals the value, the state and the validity its One-time-code input tells its own — the same flags, the same message and the same anchor, the native input, so the browser's message points where it points one level down — resets to its value attribute, and passes a disabled <fieldset> on to its Field (OTF-4); Enter in the cells presses the default button of this element's form. The shadow root does not delegate focus — a press on the supporting row must do nothing (FLD-12) — so focus() and blur() are passed to the control, a click that arrives on the element itself (a page's own <label for>) moves focus there, and autofocus is copied to the control (OTF-5). The four events are the One-time-code input's own composed, bubbling CustomEvents: they leave the shadow root by themselves with the element as their target, and the element's value is at every moment its One-time-code input's — what was last set on the element until the input has taken it, and from then on what the input holds — so event.target.value is the code in every listener, capturing or bubbling (OTF-3); it dispatches nothing itself. The name, the description and the states reach the native input as values, by the tanrei-field-connect exchange between the two members inside the one shadow root, exactly as in a product's markup; the element's own host has no role, and an aria-* attribute the product writes on it moves to data-aria-* and reaches the cells' input, as its accessibility notes say — but for the name: name it with label (and label-hidden), never with aria-label, which a debug build reports. Inside a tanrei-field-group it follows the group's disabled through its Field (OTF-6). custom-elements.json shows it whole — sixteen attributes with their members' descriptions, four events with TanreiOneTimeCodeInputDetail, the label-icon slot, the two member parts and the nine exported ones, and the form attribute — and the React wrapper OneTimeCodeField is generated from that, with onInput, onChange, onComplete and onAction. Before the element is defined it has no size and shows nothing but a label-icon child, which a page hides with tanrei-one-time-code-field:not(:defined) > *; a page that must not jump gives the tag a block size of its own, and a page that must work without script writes a <label> around a plain <input autocomplete="one-time-code" inputmode="numeric"> with its help beside it. The host is display: block. Words: the element has none; required-text and optional-text left empty are read by its Field from the product's words. TanreiOneTimeCodeField("Verification code", code: $code, supportingText: "…", groupSize: 3, onComplete: verify): a view whose body is TanreiField(…) { TanreiOneTimeCodeInput(…) } with each argument passed to the member it is from, and nothing else. code is the input's Binding<String>; labelHidden follows .labelsHidden() and disabled follows .disabled(_:), as the Field's do; the glyph is the Field's labelIcon:. One field is set apart by .tanreiTheme(_:) on this view. Planned: owed after the One-time-code input itself. TanreiOneTimeCodeField(label = "Verification code", value = code, onValueChange = { code = it }, supportingText = …, groupSize = 3, onComplete = ::verify): a composable that calls TanreiField(…) { TanreiOneTimeCodeInput(…) } with each argument passed to the member it is from, and nothing else. enabled is the Field's inverse flag; the glyph is the Field's labelIcon. One field is set apart by a TanreiTheme(…) { } around this call. Planned: owed after the One-time-code input itself. TanreiOneTimeCodeField(labelText: 'Verification code', controller: code, helperText: …, groupSize: 3, onCompleted: verify): a widget whose build returns TanreiField(…, child: TanreiOneTimeCodeInput(…)) with each argument passed to the member it is from, and nothing else. The names follow the members'; enabled is the Field's inverse flag; the glyph is the Field's labelIcon. One field is set apart by a TanreiTheme widget around it. Planned: owed after the One-time-code input itself. <OneTimeCodeField label="Verification code" value={code} onChangeText={setCode} supportingText="…" groupSize={3} onComplete={verify} />: a component that returns <Field …><OneTimeCodeInput … /></Field> with each prop passed to the member it is from, and nothing else. The glyph is the Field's labelIcon prop. One field is set apart by a provider around it. Planned: owed after the One-time-code input itself.

令牌

令牌 引用 解析值(浅色)
--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 #4b4b4b
--tanrei-comp-field-standard-disabled-counter-color — #1b1b1b · 38%
--tanrei-comp-field-standard-disabled-error-text-color — #1b1b1b · 38%
--tanrei-comp-field-standard-disabled-error-counter-color — #1b1b1b · 38%
--tanrei-comp-field-standard-disabled-error-label-icon-color — #1b1b1b · 38%
--tanrei-comp-field-standard-disabled-error-label-color — #1b1b1b · 38%
--tanrei-comp-field-standard-disabled-error-required-asterisk-color — #1b1b1b · 38%
--tanrei-comp-field-standard-disabled-error-supporting-text-color — #1b1b1b · 38%
--tanrei-comp-field-standard-disabled-error-value-text-color — #1b1b1b · 38%
--tanrei-comp-field-standard-disabled-label-icon-color — #1b1b1b · 38%
--tanrei-comp-field-standard-disabled-label-color — #1b1b1b · 38%
--tanrei-comp-field-standard-disabled-required-asterisk-color — #1b1b1b · 38%
--tanrei-comp-field-standard-disabled-supporting-text-color — #1b1b1b · 38%
--tanrei-comp-field-standard-disabled-value-text-color — #1b1b1b · 38%
--tanrei-comp-field-standard-error-text-color sys.color.error #cd262f
--tanrei-comp-field-standard-error-counter-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-field-standard-error-label-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-field-standard-error-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-field-standard-error-required-asterisk-color sys.color.primary #1b1b1b
--tanrei-comp-field-standard-error-supporting-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-field-standard-error-value-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-field-standard-focused-counter-color — #4b4b4b
--tanrei-comp-field-standard-focused-error-text-color — #cd262f
--tanrei-comp-field-standard-focused-error-counter-color — #4b4b4b
--tanrei-comp-field-standard-focused-error-label-icon-color — #4b4b4b
--tanrei-comp-field-standard-focused-error-label-color — #1b1b1b
--tanrei-comp-field-standard-focused-error-required-asterisk-color — #1b1b1b
--tanrei-comp-field-standard-focused-error-supporting-text-color — #4b4b4b
--tanrei-comp-field-standard-focused-error-value-text-color — #4b4b4b
--tanrei-comp-field-standard-focused-label-icon-color — #4b4b4b
--tanrei-comp-field-standard-focused-label-color — #1b1b1b
--tanrei-comp-field-standard-focused-required-asterisk-color — #1b1b1b
--tanrei-comp-field-standard-focused-supporting-text-color — #4b4b4b
--tanrei-comp-field-standard-focused-value-text-color — #4b4b4b
--tanrei-comp-field-standard-label-icon-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-field-standard-label-color sys.color.on-surface #1b1b1b
--tanrei-comp-field-standard-required-asterisk-color sys.color.primary #1b1b1b
--tanrei-comp-field-standard-supporting-text-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-field-standard-value-text-color sys.color.on-surface-variant #4b4b4b
--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 为每个样例截取快照,以便相互比较。

生成自 specs/one-time-code-field.spec.json, tokens/comp/field.tokens.json, design/tokens/

Web 上绘制方式的不同之处

  • 这个元素的 shadow root 里有一个 <tanrei-field>,其中放着一个 <tanrei-one-time-code-input>,此外什么都没有。导入这个元素,两个成员也随之完成定义。
  • 这个元素是与表单关联的元素。它接受 name 和 form;提交的只有验证码,禁用时什么都不提交;重置为 value 属性(attribute)的值;跟随被禁用的 <fieldset>;设置了 required 而它为空时,报告缺少值。它的两个成员不向表单添加任何条目。在格子里按 Enter 会按下元素所在表单的默认按钮;取消 action 可以阻止这一点。
  • input、change、complete 和 action 是一次性验证码输入框的 CustomEvent,带有 detail.value。它们的 target 是这个元素,在每个监听器里,event.target.value 都是验证码。
  • 部件:field 和 one-time-code-input 是两个成员。成员自己的部件在这里的名称与下一层相同:label、label-icon、requirement-label、supporting-text;input、cell、character、caret、separator。
  • 这个元素本身不能获得焦点:focus() 会把焦点交给格子,页面自己的 <label for> 也会把焦点移到那里。元素上的 autofocus 会在它第一次绘制时让格子获得焦点,除非焦点已经在别的东西上。
  • 这个元素没有角色。用 label 为它命名,标签绘制在别处时再加上 label-hidden;绝不要用 aria-label,它在这里不为任何东西命名。
  • 属性(attribute)就是成员自己的那些:label-hidden、supporting-text、error-text、required-mark、required-text、optional-text、group-size、readonly。
  • 它只有一个插槽 label-icon。放在它里面的第二个控件不会绘制。
  • 在元素完成定义之前,它没有尺寸,只显示作为子元素的 label-icon。用 tanrei-one-time-code-field:not(:defined) > * 把它隐藏起来,并预留高度。必须在没有脚本时也能工作的页面,用一个 <label> 包住一个普通的 <input autocomplete="one-time-code" inputmode="numeric">,并把帮助文本放在旁边。

指南

推荐

  • 在 supportingText 里说明应该有几个字符,以及验证码发到了哪里。
  • 按消息里的写法画出分组:以“123 456”的形式发送的验证码,用 groupSize="3"。
  • 用文字说明验证码不正确,使用 error 和 errorText。错误的验证码要不要清空,由你自己决定:很多时候只是错了一位数字。
  • 校验已填完的验证码期间,使用 readOnly,而不是 disabled。禁用的字段会失去焦点,随后需要改正验证码的用户就找不到原来的位置了。
  • 把重新获取验证码的途径,以及任何时间限制,放在字段旁边。如果它们应该放在框架里面,就在下一层手写这个字段。

避免

  • 不要把一次性验证码字段放进字段。它本身已经是字段;调试构建会报告这一点。
  • 不要用 aria-label 为它命名。不该绘制标签的地方,给出 label 和 labelHidden。
  • 不要阻止粘贴,无论是否遮蔽;也不要在没有事先告知用户的情况下移走焦点或提交表单。
  • 不要习惯性地使用 mask。只用一次、过后就失效的验证码很少需要隐藏,而隐藏会让错误更难发现。
  • 不要为了单独改动这一个字段的格子样式而把格子照抄一份。请在字段上设置成员的令牌。

无障碍

角色
textbox
标注
One text box, the One-time-code input's (OTC-2); the frame has no role. Its accessible name is label, drawn or not (labelHidden), and its description is the supporting text with, in error, the error text after it, with optionalText while optional is true (FLD-10, FLD-11, OTC-22). It is exposed as invalid while error is true, required while required is true, disabled while disabled is true, read-only while readOnly is true, and as a protected text entry while mask is true. The label's drawn text, the requirement mark, the cells, the caret and the group marks are not exposed a second time (FLD-11, OTC-2).
最小目标
即使外观尺寸更小,点击区域也满足最小目标尺寸。 minimum 32px · pointer 44px · spacing 8px · touch 48px

键盘操作

按键 动作
Tab / Shift+Tab Moves focus to the control and on from it: the one-time-code field is one stop, its control's, and adds none (OTF-5).
Every other key The One-time-code input's own, as its spec's keyboard table says (OTC-6, OTC-8 to OTC-11, OTC-13). The one-time-code field handles no key.

备注

  • What the one-time-code field adds to its members is their being one thing: one tag or one call, one set of props, one value and one entry in a form (OTF-2 to OTF-4). The tie between the two — the name, the description, required, invalid, disabled — is not the one-time-code field's: it is what a Field gives any control it frames, and it runs here exactly as it runs in a product's own Field.
  • One level down: a Field with a One-time-code input in it, written by the product, is the same picture and the same text box, and the Field's fixtures code-supporting, code-error, code-disabled and code-required-rtl are this component's twins. A product drops to it for an arrangement the one-time-code field does not offer: something of its own inside the frame beside the cells (a resend control, a countdown), or the One-time-code input alone under a dialog's headline, named by its own label.
  • The members' notes hold as written and are not repeated: errors are words (the Field's); nothing happens by itself when the code is complete, and paste and the platform's autofill are never blocked (the One-time-code input's).
  • The hit area is the control's (OTC-25); the label widens where a press can land without widening what a press does (FLD-12).
  • Not pressable: a press focuses the control and is not an action (OTC-25), so BTN-8, BTN-9 and BTN-10 are not copied.
  • How a state looks is a value decision of the active design system; components read the state's tokens and never compute the effect themselves.

生成自 specs/one-time-code-field.spec.json, tokens/sys/measure.tokens.json

组件负责的部分

  • 它是一个文本框,也就是一次性验证码输入框的那一个。它的名称是 label,无论标签是否绘制;它的描述是显示出来的辅助文本或错误文本,字段为选填时还加上选填文字。
  • error 为 true 时,它暴露为无效;设置了 required 时,暴露为必填;disabled 为 true 时,暴露为禁用;readOnly 为 true 时,暴露为只读;mask 为 true 时,暴露为密码框。
  • 名称、描述和状态的关联方式,与字段把它们关联到它框住的任何控件时完全一样。在下一层手写的字段,是同一个文本框。
  • 画出来的标签、必填和选填标记、格子、插入点和分组标记,不会再暴露第二次。
  • 它只是一个 Tab 停靠点。按下标签会把焦点移到格子上,不操作任何东西;按下帮助文本什么都不会发生。
  • 焦点在一次性验证码字段里时出现的错误,由它的字段成员以礼貌(polite)的方式朗读一次。
  • 方向、放大的文本和强制颜色模式,由各个成员各自应对:在从右到左的布局中,框架会镜像,格子仍然从左到右排列,位于起始边;文本放大到 200% 时,没有任何内容被截断,也没有任何内容横向滚动。
  • 它没有自己的文字。requiredText 和 optionalText 留空时,由字段从产品的文字里读取。

Web 上的已知差距都是它的成员的差距:键入验证码时屏幕阅读器会说什么,是从各浏览器的无障碍树里读出来的,还没有用屏幕阅读器实际听过;在元素完成定义之前,什么都不绘制。

需要你做的事

  • 始终给出 label,用的是用户自己称呼这个验证码时会说的词。
  • 用文字说明期望的内容:有几个字符,验证码发到了哪里。格子只向看得见的人显示长度。
  • 用 errorText、supportingText 和 required 说明验证码不正确、已过期或必填。颜色绝不是唯一的提示。
  • 输入验证码的时间有限时,要说明这一点,并让用户可以申请更多时间或新的验证码。