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

嵌入式文本输入框

只有键入的部分,用于由别的东西来绘制的方框。文本、占位符、插入点和选区,排成一行,或者排成带着动效增减的若干行。它不绘制容器、边缘、标签和焦点指示。方框由容纳它的东西来绘制,焦点也由那个东西来显示。

content_copy以 Markdown 格式查看

进阶

大多数产品直接使用包含此组件的Chat composer。

类别
文本输入
令牌
comp.embedded-text-input
层级
基础

概览

Web
tanrei-embedded-text-input
schedule
SwiftUI
TanreiEmbeddedTextInput
radio_button_unchecked
Jetpack Compose
TanreiEmbeddedTextInput
radio_button_unchecked
Flutter
TanreiEmbeddedTextInput
radio_button_unchecked
React Native
EmbeddedTextInput
radio_button_unchecked

生成自 docs/status/status.json, specs/embedded-text-input.spec.json

页面自己绘制的方框里的一个嵌入式文本输入框。键入几行试试:方框的高度会跟着变化。焦点由方框的边缘来显示。

嵌入式文本输入框只是半个控件。它是用户键入的地方,周围什么都没有:没有填充,没有边缘,没有内边距,没有标签,没有消息。另一半是宿主:围在键入部分外面的那个方框。Tanrei 的组件可以充当宿主,聊天输入栏就是这样。你自己的标记也可以充当宿主。

大多数产品从不直接放置它。方框里除了文本还有别的东西时才用它:旁边带有操作的消息、搜索栏、就地编辑的标题。

以下情况请改用别的组件:

宿主必须显示焦点

无论设计系统的焦点样式是什么,嵌入式文本输入框都不绘制焦点指示。它的插入点不算焦点指示:那只是一条细线,选中文本时不绘制,也说明不了正在哪个方框里键入。宿主如果不在自己的方框上显示焦点,就不符合 WCAG 2.4.7。在 Web 上,一条针对 :focus-within 的规则就够了。

在 Web 上它是 <tanrei-embedded-text-input>。其他平台尚未实现。

用法

方框、它的内边距和它的焦点指示都由你负责。三条声明就能把它们写全。

<div class="capsule">
  <tanrei-embedded-text-input label="消息" placeholder="输入消息"
                              multiline max-rows="6"></tanrei-embedded-text-input>
</div>
.capsule {
  background: var(--tanrei-sys-color-surface-container);
  border-radius: var(--tanrei-sys-shape-corner-large);
  padding: var(--tanrei-sys-space-space150) var(--tanrei-sys-space-space200);
  outline: var(--tanrei-sys-border-thick) solid transparent;
  outline-offset: calc(-1 * var(--tanrei-sys-border-thick));
  cursor: text;
}
.capsule:focus-within { outline-color: var(--tanrei-sys-color-focus-ring); }
@media (forced-colors: active) {
  .capsule { outline-color: CanvasText; }
  .capsule:focus-within { outline-color: Highlight; }
}
// 按在内边距上,就等于按在文本上。
capsule.addEventListener('mousedown', (event) => {
  if (event.target !== capsule) return;
  event.preventDefault();
  input.focusAt(event.clientX, event.clientY);
});
// 进阶组件(用于构成其他组件的部件)从 advanced 入口导入。
import { EmbeddedTextInput } from '@tanrei/react/advanced';

<div className="capsule">
  <EmbeddedTextInput label="消息" placeholder="输入消息" multiline maxRows={6}
                     onAction={(event) => send(event.detail.value)} />
</div>

规格

宿主要负责的事

事项 宿主提供 输入框提供给宿主
方框 一个可见的方框,或者一个看起来像方框的位置 文本后面什么都没有:宿主的填充会透出来
焦点 画在方框上的焦点指示,只要输入框有焦点就一直显示,无论它是怎样获得焦点的 focusin 和 focusout,以及元素上的 :focus-within
状态 错误的标记,以及方框禁用时的外观 error 暴露无效状态,但什么都不绘制。disabled 把文本画成禁用的内容
内边距 文本周围的内边距,并把按在内边距上的操作转交过来 没有自己的内边距,点击区域达到最小目标,以及 focusAt(x, y)
名称 名称,以及它的各种消息所说的内容 label(必须给出)和 description,两者都是文本
操作 操作是什么:发送、添加、搜索、保存 action 事件,它本身不对这个事件做任何处理
强制颜色模式 方框的边线,以及用系统颜色画出的焦点指示 它自己的文本、占位符、选区和插入点采用系统的颜色

高度

设置 multiline 后,输入框的高度正好等于它的各行,介于 minRows(默认为 1)和 maxRows(默认为 0,即不限)之间。超过 maxRows 后,文本在它里面滚动。

  • 因值而引起的行数变化,在 comp.embedded-text-input.motion.resize-duration 的时长内完成。无论一次多出多少行,都只是一段动效。
  • 在每一帧里,输入框在布局中占的高度就是动效进行到那一刻的高度。按内容确定自身尺寸的宿主不需要任何代码就能跟上。
  • 以下情况下变化会立即完成:输入框第一次绘制时、它的宽度或文字大小发生变化时、指针在文本里按住不放时,以及减弱了动态效果的地方。
  • rowschange 在动效开始之前说明输入框将要显示多少行。rows 随时可以读取这个数目。

Enter

输入框 Enter Shift+Enter Ctrl+Enter、Command+Enter
单行 action — action
多行,enterKey="line-break"(默认) 换行 换行 action
多行,enterKey="action" action 换行 action

输入法的 Enter 用来选择候选或确认文本。它绝不触发 action。在多行输入框里,屏幕键盘的换行键总是另起一行。

action 带有值,输入框除此之外什么都不做。它保留自己的值、选区和焦点。

构成

部件 说明
1 input The platform's own text element, which holds the value, the selection and any input-method composition, drawn with no box of the platform's or of Tanrei's. It is the whole component: as wide as its layout gives it and exactly as tall as its lines.
2 placeholder · 可选 A hint in the empty input, where the first character will be typed; never the name.
3 caret The insertion point, and the only thing the input itself shows of having focus.
4 selection · 可选 The highlight behind selected text and the color of the text on it.

属性

属性 类型 默认值 说明
type text | search | email | url | tel "text" What the value is: chooses the keyboard, autocorrection and autocapitalization, and nothing else. It never validates and never obscures. search makes the action key of an on-screen keyboard read as search. The types that need a box around them are the Text input's: a password (its reveal action), a number (its steppers), a date or a time (its segments and its picker button), code (its face and its sideways scrolling).
value string "" The text.
label string "" The accessible name. Required: the embedded text input draws no label and asks no Field for one, so this is the only name it has. Its holder gives it — a composed component from its own label, a product from the words that stand beside its box. Never drawn.
description string "" The accessible description, as text: what a help line, an error text or a count that its holder draws says about what is typed. Never drawn.
placeholder string "" A hint drawn while the value is empty. Never the name.
multiline boolean false False: one line that scrolls sideways inside itself. True: lines that wrap, and a height that follows them between minRows and maxRows, changed with a motion.
minRows number 1 The fewest lines a multi-line input is tall. 1 by default — a holder's typing part begins as one line and grows — where the Text input, which is a box, begins at 2.
maxRows number 0 The most lines a multi-line input is tall; beyond it the text scrolls inside. 0 is no limit. A holder that stands at the bottom of a screen sets one.
enterKey line-break | action "line-break" What the Enter key of a hardware keyboard does in a multi-line input. line-break: it begins a new line. action: it emits action and inserts nothing, and Shift+Enter begins a new line. Under both, Ctrl+Enter (and Command+Enter on Apple platforms) emits action, the return key of an on-screen keyboard begins a new line, and an input method's Enter does neither. A single-line input ignores it: its action key always emits action. What action means — send, add, search, save — is the holder's.
error boolean false Exposes the input as invalid. It draws nothing: marking an error is the drawing of the holder's box.
required boolean false Exposes the input as required and, on the web in a form of its own, reports a missing value. It draws nothing and validates nothing else.
disabled boolean false Not focusable and not editable; the text and the placeholder read as disabled content. The disabled look of the box is its holder's.
readOnly boolean false Focusable, selectable and copyable, not editable. Drawn as an enabled input.
autocomplete string "" The platform's autofill hint for the value (an HTML autocomplete token; mapped to the platform's content type where it has one). Empty: the platform decides.

事件

事件 说明
input The person changed the value; carries the new value. Emitted for every change, never when value is set.
change The person committed a changed value: focus left the input, or action was emitted. Carries the value.
action The person asked for the action of whatever holds the input: the action key of a single-line input; in a multi-line input, Enter where enterKey is action, and Ctrl+Enter or Command+Enter always. Carries the value. The input does nothing more: it does not empty itself, does not move focus and, where it has several lines, asks no form to submit. Cancelable where the platform has that.
rowsChange The number of lines the input shows is about to change, or it was drawn for the first time; carries the number of lines it is going to show and whether text scrolls inside it. Emitted once per change, before the motion begins, so that a holder can change with it — a corner that is round for one line and less round for several, actions centred on one line and set at the bottom of several.
focusChange The input gained or lost focus; carries true or false. It is the state the holder reads to draw the focus indication on its own box, which the input never draws.

状态

行为

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

ID 需求
ETI-1 THE embedded text input SHALL draw only what is typed and what stands for it: the value in comp.embedded-text-input.input.typescale and comp.embedded-text-input.standard.input.color, the placeholder while it is empty (ETI-22), the caret in comp.embedded-text-input.standard.caret.color and the selection highlighted in comp.embedded-text-input.selection.color under comp.embedded-text-input.selection.content.color. It SHALL draw no container, fill, edge, corner, shadow, label, requirement mark, help, error text, count, icon or state layer, and no focus indication (ETI-11), in any state; it SHALL have no padding of its own, be as wide as its layout gives it and exactly as tall as its lines (ETI-7), and leave whatever its holder draws behind it to show through.
ETI-2 WHEN the person changes the value — by typing, deleting, pasting, cutting, dictating, autofill or an input method committing text — THE embedded text input SHALL emit input once per change with the new value; WHEN value is set by its holder or the product THE embedded text input SHALL show it and SHALL NOT emit input or change, and WHILE it has focus SHALL place the caret at the end of the new value unless the value is the one it already shows.
ETI-3 WHEN the input loses focus, or emits action (ETI-6), with a value different from the one last committed (the value when it gained focus, or at the last change) THE embedded text input SHALL emit change once with the value, before the action.
ETI-4 WHILE an input method is composing text THE embedded text input SHALL leave the composition, and every key pressed during it, to the platform's native text element: it SHALL NOT write the value back into the element, trim or reformat it, and SHALL NOT emit change; the Enter that chooses a candidate or commits the composition SHALL NOT emit action, SHALL insert no line break of the input's own and SHALL NOT be counted as a request for the action — also where the platform reports that key only after it has reported the composition ended; a committed composition SHALL always produce an input, a value set during a composition SHALL be applied when the composition ends, and the first Enter pressed after the composition has ended SHALL be read as ETI-6 says.
ETI-5 WHILE multiline is false THE embedded text input SHALL show its value on one line that never wraps: text wider than the input SHALL scroll sideways inside it, by pointer, touch and the caret, which is kept in view, and SHALL NOT widen the input; a line break in pasted, dropped or dictated text SHALL be taken as the platform's own single-line element takes it. WHILE multiline is true THE embedded text input SHALL wrap its text at its own width, break a run with no space that is wider than a line between any two characters, never scroll sideways, and count every line the text wraps onto as a line (ETI-7).
ETI-6 WHEN the person presses the keyboard's action key in a single-line input THE embedded text input SHALL emit action with the value, after any change it causes (ETI-3), and, on the web, WHILE it is a control of a form itself (ETI-19), SHALL then request that form's submission through its default button (BTN-30) unless action was cancelled. WHILE multiline is true: where enterKey is line-break, Enter on a hardware keyboard SHALL begin a new line and emit nothing; where enterKey is action, Enter with no modifier SHALL emit action and insert nothing, and Shift+Enter SHALL begin a new line; under both values Ctrl+Enter and, on Apple platforms, Command+Enter SHALL emit action and insert nothing; the return key of an on-screen keyboard SHALL begin a new line under both values, and the input SHALL ask that keyboard for a return key; and a multi-line input SHALL never request a form's submission. THE embedded text input SHALL do nothing else when it emits action — it SHALL keep its value, its selection and its focus — because what the action is belongs to its holder. WHEN enterKey names a value it does not know it SHALL behave as line-break and in debug builds report the unknown value; WHILE readOnly is true no key SHALL emit action.
ETI-7 WHILE multiline is true THE embedded text input SHALL be exactly as tall as its lines of text, each one line height of its text style — never fewer than minRows lines and never more than maxRows lines; no upper limit while maxRows is 0, exactly minRows lines while maxRows is smaller than minRows, and a minRows below 1 is 1. WHILE the text has more lines than maxRows it SHALL scroll inside the input and keep the caret in view, and WHILE every line fits it SHALL show no scrollbar. THE height SHALL follow every cause of a changed number of lines — typing, deleting, pasting, a value that is set, a changed width, enlarged text. WHILE multiline is false THE embedded text input SHALL be exactly one line tall and ignore minRows, maxRows and enterKey.
ETI-8 WHEN the number of lines a multi-line input shows changes because its value changed — by the person, or because value was set — THE embedded text input SHALL change its height from the one shown to the new one over comp.embedded-text-input.motion.resize-duration with comp.embedded-text-input.motion.resize-easing, as one motion: however many lines arrive or leave at once — forty pasted lines, a value emptied after it was sent — the height SHALL go from the one shown straight to the end height, which maxRows bounds, and never through a height for each line; a change that arrives while a motion is running SHALL continue from the height then shown to the new end height, and changes SHALL never be queued one after another. At every frame of the motion the first line SHALL stay where it is at the input's top edge, the text SHALL keep its size and its line height, the line that holds the caret SHALL stay in view, and no scrollbar SHALL be shown WHILE the end height holds every line.
ETI-9 THE embedded text input SHALL make a change of its height at once, without the motion of ETI-8, WHEN it is first drawn or drawn again after it was not shown; WHEN the height changes because its width, its text size, its text style, minRows, maxRows or multiline changed; WHILE a pointer or a touch is held down in its text, so that text under a selection being dragged never moves away from the finger; and WHERE motion is reduced.
ETI-10 THE embedded text input SHALL change the height it takes in its layout at every frame of a motion — the height its holder and whatever follows it are laid out against, not a drawing moved or scaled over a box that already has the end height — so that a holder that sizes itself to its content follows the motion with no jump at its start or its end, and SHALL add no motion of its own to anything outside itself. THE embedded text input SHALL emit rowsChange once when it is first drawn and once for every later change of the number of lines it shows, before any motion of that change begins, with the number it is going to show and whether text scrolls inside it; it SHALL NOT emit it for a height that changes with the text size alone.
ETI-11 THE embedded text input SHALL show that it has focus by its caret alone and SHALL draw no focus indication of its own — no ring, no line and no glow, whatever sys.behavior.focus-style is — because the box around it is its holder's, and the indication is drawn on the box. THE embedded text input SHALL report its focus so that the holder can draw it: it SHALL emit focusChange with true WHEN its text element gains focus — by the keyboard, a pointer, a touch or a script alike, because a place that is typed in shows where typing will go however it was focused — and with false WHEN it loses it, and on the web its host SHALL match :focus-within for exactly that time.
ETI-12 THE embedded text input SHALL take a press anywhere in an area at least sys.target.touch high for touch input and sys.target.pointer high for fine pointers — its own lines, and beyond them equally above the first and below the last where they are shorter than that — as a press on its text, without changing its drawn size or its layout; a platform that cannot tell the input type SHALL use sys.target.touch. A press there SHALL give the input focus and place the caret, SHALL NOT be answered with a pressed state, a ripple or any feedback, because a press on a place to type is not an action, and THE embedded text input SHALL offer its holder a way to give it focus, so that a press on the holder's box outside its other controls can be passed on to it.
ETI-13 THE embedded text input SHALL be exposed to assistive technology as a text box — a multi-line one WHILE multiline is true — whose accessible name is label and whose description is description, invalid WHILE error is true, required WHILE required is true, read-only WHILE readOnly is true and disabled WHILE disabled is true. It SHALL never use placeholder as the name; IF label is empty it SHALL still render and work, and in debug builds SHALL report that it has no accessible name. THE embedded text input SHALL ask no Field for a name, a description or a state: it is not a control a Field frames — its holder is — and every one of them reaches it as a prop.
ETI-14 WHILE error is true THE embedded text input SHALL draw exactly what it draws while error is false — the same text, placeholder and caret colors — and SHALL NOT set error itself or validate anything: an error is marked on the holder's box and said in the holder's or a Field's words.
ETI-15 WHILE disabled is true THE embedded text input SHALL NOT be focusable or editable, SHALL NOT emit input, change, action or focusChange, SHALL expose the disabled state to assistive technology, SHALL be left out of form submission on the web, and SHALL draw its value and its placeholder from the disabled tokens of those parts.
ETI-16 WHILE readOnly is true and disabled is false THE embedded text input SHALL stay focusable, selectable and copyable, SHALL NOT let the person change the value, SHALL NOT emit input, change or action, SHALL expose the read-only state to assistive technology, SHALL be submitted with its form on the web, and SHALL draw as an enabled input.
ETI-17 THE embedded text input SHALL give its text element the keyboard and the semantics of type: text a general keyboard; search a general keyboard whose action key reads as search; email an email keyboard without autocapitalization or autocorrection; url a URL keyboard without autocapitalization or autocorrection; tel a phone keypad — in a multi-line input too, as far as the platform lets a multi-line element choose a keyboard. It SHALL NOT validate the value against the type. WHEN type names a value it does not know it SHALL behave as text and in debug builds report the unknown value.
ETI-18 THE embedded text input SHALL pass autocomplete to the platform as its autofill hint, mapped to the platform's content type where it has one, and WHEN the platform autofills it SHALL emit input with the filled value.
ETI-19 WHERE the platform has a form element (the web) THE embedded text input SHALL be a control of the form it stands in: it SHALL submit its value under its name, nothing while disabled or while it has no name, reset to the value it was first given, be restored when the page is, follow a disabled fieldset, and report a missing value WHILE required is true and it is empty. WHILE it stands in the shadow tree of a holder it has no form — the holder is the form's control and carries the value — and it SHALL do none of this.
ETI-20 THE embedded text input SHALL take its layout direction from where it is used — the direction of the nearest ancestor that sets one, and the platform's own where none does — and SHALL have no direction setting of its own. It SHALL lay out the typed text by its own direction: the base direction of the value's first strong character and, WHILE the value is empty or holds no strong character, the layout direction, so that the caret of an empty input and its placeholder stand at the start edge; a single-line value SHALL be aligned to the start edge of its own direction, in a multi-line input each paragraph SHALL take the direction and the alignment of its own first strong character, and the values of email, url and tel inputs SHALL be laid out left-to-right, aligned to the start edge. The placeholder SHALL be laid out as text of its own direction, isolated from what is around it, and aligned to the start edge. WHEN the layout direction changes while the input is shown it SHALL draw itself for the new direction at once, keeping its value, its selection, its caret and its focus.
ETI-21 THE embedded text input SHALL let every key the person presses in it reach its holder before the input itself acts on it, and SHALL NOT act on a key the holder has cancelled where the platform lets a key be cancelled; and it SHALL let its holder read where the selection begins and ends and set it, and replace the selected text. With these a holder does what only it can know: an arrow or Backspace at the very start of the text that moves to something before it, text an action of the holder's inserts at the caret. THE embedded text input SHALL itself give no key a meaning beyond those of ETI-6: Tab and Shift+Tab SHALL move focus out of it and never insert a character, and Escape SHALL do nothing.
ETI-22 WHILE the value is empty and no input method is composing THE embedded text input SHALL show placeholder in its text style and comp.embedded-text-input.standard.placeholder.color, starting where the first character will be typed; in a multi-line input a placeholder longer than a line SHALL wrap as text does and the input SHALL be as tall as the placeholder's lines, within minRows and maxRows, so that it is never cut; in a single-line input it SHALL be cut at the end edge. THE placeholder SHALL be kept from the accessible name and SHALL go with the first character typed, without a motion.
ETI-23 WHERE the platform draws with a person's own forced colors (the web's forced-colors mode, and a platform's equivalent where it has one) THE embedded text input SHALL draw its value in the platform's text color, its placeholder and a disabled value in the platform's color for text that is not available, its selection in the platform's highlight colors and its caret as the platform draws one, and SHALL draw nothing else, as everywhere: the edge of the box and the focus indication in those colors are its holder's.
ETI-24 WHILE the text size is enlarged THE embedded text input SHALL grow with its text — a line is the enlarged line height, and minRows and maxRows still count lines — changing its height at once (ETI-9); at twice the text size in a holder 320 wide a multi-line input SHALL show every character, wrapped, with no sideways scrolling and nothing cut, and a single-line input SHALL keep its one line and scroll inside itself.
ETI-25 WHEN a prop changes while the input is shown THE embedded text input SHALL draw the new state at once, keeping its value, its selection and caret, its focus and a composition in progress; WHEN multiline changes it SHALL keep the value as it is — a value with line breaks is shown on one line as the platform's single-line element shows it, and the line breaks are there again when multiline returns — and change its height at once (ETI-9).
ETI-26 THE embedded text input SHALL have no words of its own and read none of the product's words: label, description and placeholder are the instance's props and nothing stands in for an empty one.
ETI-27 WHERE script does not run, or before the element is defined (the web), THE embedded text input SHALL leave the platform's own text element that the product wrote as its child — a textarea, or an input — as the control it is: named, typed in and submitted with its form. WHEN the element is defined it SHALL take that child's text as its first value where value was not given, and SHALL hide and disable the child so that the form submits the input alone.
ETI-28 WHILE the embedded text input is drawn with nothing around it that marks a box THE embedded text input SHALL still render and work exactly as it does in one: it cannot see whether a box is drawn, reports nothing about it, and falls back to no look of its own. That a box is drawn, that it shows focus (ETI-11), marks an error and a disabled input, pads the text and passes a press on (ETI-12) are held where the holder is: in the spec and the tests of a Tanrei component that is built from it, and by the product for a box of its own.
ETI-29 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 embedded text input 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 embedded text input is invalid — except those the embedded text input owns (aria-label, aria-labelledby, aria-description, aria-describedby, aria-errormessage, aria-invalid, aria-required, aria-readonly, aria-disabled), which it SHALL move and not expose: they reach it as props (ETI-13). 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 embedded text input SHALL take it off that element. WHEN the product sets an aria- attribute the embedded text input owns THE embedded text input SHALL report it in debug builds.

各平台名称对照

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

规格 Web SwiftUI Jetpack Compose Flutter React Native
组件 tanrei-embedded-text-input TanreiEmbeddedTextInput TanreiEmbeddedTextInput TanreiEmbeddedTextInput EmbeddedTextInput
type · 属性 type type type type type
value · 属性 value text value controller value
label · 属性 label label label semanticsLabel accessibilityLabel
description · 属性 description hint stateDescription semanticsHint accessibilityHint
placeholder · 属性 placeholder prompt placeholder hintText placeholder
multiline · 属性 multiline axis singleLine multiline multiline
minRows · 属性 minRows lineLimit minLines minLines minRows
maxRows · 属性 maxRows lineLimit maxLines maxLines maxRows
enterKey · 属性 enterKey enterKey enterKey enterKey enterKey
error · 属性 error error isError isError error
required · 属性 required required required required required
disabled · 属性 disabled disabled enabled enabled disabled
readOnly · 属性 readOnly readOnly readOnly readOnly readOnly
autocomplete · 属性 autocomplete textContentType contentType autofillHints autoComplete
input · 事件 input text onValueChange onChanged onChangeText
change · 事件 change onCommit onCommit onCommit onEndEditing
action · 事件 action onSubmit onAction onAction onAction
rowsChange · 事件 rowschange onRowsChange onRowsChange onRowsChanged onRowsChange
focusChange · 事件 focusin / focusout focused interactionSource focusNode onFocus / onBlur
备注 Class TanreiEmbeddedTextInput, React EmbeddedTextInput. A form-associated custom element with a shadow root and delegatesFocus (every point of it is its text) that renders one <textarea> while multiline and one <input> otherwise, with appearance: none, no border, no outline, no background, no padding and no resize handle; the host is display: block. Attributes are kebab-case (min-rows, max-rows, enter-key) except readonly and autocomplete, which keep HTML's names; name is the form's. The name, the description (aria-description), aria-invalid, aria-required and aria-readonly are written on the inner element; the host has no role and a debug build reports an aria-label set on it. It dispatches no tanrei-field-connect (ETI-13). Focus: the platform's focusin and focusout are composed and are the spec's focusChange; a holder styles itself with :focus-within — also from inside its own shadow root, :host(:focus-within) — and needs no script for H2. Height: the host's block size is the animated one (a transition of block-size between two measured heights, from scrollHeight of the inner element at block-size: auto), overflow: hidden during a motion whose end fits; the routine is shared with the Text input (src/primitives/auto-height.ts) and holds no token. rowschange is a composed CustomEvent with detail: { rows, scrolling }; the read-only property rows gives the number at any time, for a listener added after the first draw. action is composed and cancelable, detail: { value }. Keys (ETI-21): keydown leaves the shadow root before the input acts; a keydown whose default was prevented is not read by ETI-6. A composition is recognised by event.isComposing, by key code 229, and by a keydown that follows compositionend in the same task (ETI-4). An on-screen keyboard cannot be told from a hardware one by any web API: a keydown of Enter is read as the return key of an on-screen keyboard while the primary pointer is coarse and cannot hover ((pointer: coarse) and (hover: none)), so a tablet with a keyboard attached begins a new line with Enter and acts with Ctrl+Enter or Command+Enter — safe, and a guess. enterkeyhint is enter while multiline and the browser's own otherwise (search for type search). Selection (ETI-21): selectionStart, selectionEnd, setSelectionRange(), setRangeText() and select() are passed through to the inner element. Types set inputmode on a <textarea>, which has no type. Parts: input. Custom properties: --tanrei-comp-embedded-text-input-*. Without script (ETI-27): the product writes <textarea name aria-label> or <input> as the element's child; it is not slotted, and after definition it is hidden and disabled. Fixtures whose id begins held- are staged by the harness inside its sample holder; every other fixture is drawn bare on the stage. Not built on this platform yet. A TextField with .plain style and axis: .vertical (lineLimit(minRows...maxRows)), which draws no box; text and focused are bindings, and the holder reads focused (a FocusState it owns) for H2. The height is the view's own, so a holder laid out around it follows; the change is wrapped in an animation of the token's duration and curve, dropped when accessibilityReduceMotion is set. A hardware keyboard's Enter is told from the return key by onKeyPress(.return), which only a hardware keyboard sends; a composition by the marked text of the underlying text view. label is accessibilityLabel, description accessibilityHint. Not built on this platform yet. A BasicTextField with no decoration box. singleLine and enabled are the inverses of multiline and disabled. The holder gives a MutableInteractionSource and reads collectIsFocusedAsState() for H2. The height animates with animateContentSize given the token's duration and curve, skipped on the first composition and where the system's animator scale is 0. A hardware Enter is read in onPreviewKeyEvent; the on-screen keyboard's action is ImeAction.None while multiline; a composition is the composition range of the TextFieldValue. Not built on this platform yet. An EditableText with the placeholder painted by the widget, in no InputDecorator. enabled is the inverse of disabled. The holder gives a FocusNode and listens to it for H2. The height animates in an AnimatedSize with the token's duration and curve, with a zero duration on the first build and where MediaQuery.disableAnimations is true. A hardware Enter is read by the focus node's onKeyEvent; the on-screen action is TextInputAction.newline while multiline; a composition is controller.value.composing. Not built on this platform yet. The platform's TextInput with no border and no padding, its height set from onContentSizeChange and animated with the token's duration and curve (Animated, not the native driver: the height is layout), at once where AccessibilityInfo.isReduceMotionEnabled. The holder keeps a focused state from onFocus and onBlur for H2. This platform has no signal that a composition is in progress: until one is found, enterKey action is not offered here — Enter always begins a new line and action comes from Ctrl+Enter, Command+Enter and the holder's own button — because ETI-4 is not negotiable.

令牌

令牌 引用 解析值(浅色)
--tanrei-comp-embedded-text-input-motion-resize-duration sys.motion.role.resize.default 300ms
--tanrei-comp-embedded-text-input-motion-resize-easing sys.motion.easing.spatial cubic-bezier(0.2, 0, 0, 1)
--tanrei-comp-embedded-text-input-selection-color sys.color.primary #1b1b1b
--tanrei-comp-embedded-text-input-selection-content-color sys.color.on-primary #ffffff
--tanrei-comp-embedded-text-input-standard-caret-color sys.color.primary #1b1b1b
--tanrei-comp-embedded-text-input-standard-disabled-caret-color — #1b1b1b · 38%
--tanrei-comp-embedded-text-input-standard-disabled-input-color — #1b1b1b · 38%
--tanrei-comp-embedded-text-input-standard-disabled-placeholder-color — #1b1b1b · 38%
--tanrei-comp-embedded-text-input-standard-input-color sys.color.on-surface #1b1b1b
--tanrei-comp-embedded-text-input-standard-placeholder-color sys.color.on-surface-variant #4b4b4b
--tanrei-comp-embedded-text-input-input-typescale sys.typescale.body-large 400 16px/1.625

测试样例

作为基准的渲染结果。所有平台都以相同的 ID 为每个样例截取快照,以便相互比较。

生成自 specs/embedded-text-input.spec.json, tokens/comp/embedded-text-input.tokens.json, design/tokens/

Web 上绘制方式的不同之处

  • 屏幕键盘和实体键盘无法区分。在主指针是粗略指针、并且不能悬停的地方,Enter 被当作屏幕键盘的换行键,另起一行。因此,接了键盘的平板电脑要用 Ctrl+Enter 或 Command+Enter 来执行操作。
  • 在浏览器说得出某个点上是哪个字符的地方,focusAt(x, y) 把插入点放到最近的字符处。浏览器说不出时,这个点在文本上方或之前,插入点就放到文本开头;否则放到文本末尾。
  • 在它自己的表单里,它是表单的控件之一:它在 name 之下提交自己的值。在作为 Tanrei 组件的宿主里,它没有表单,值由宿主来携带。
  • 没有脚本时,你写成它的子元素的 <textarea> 或 <input> 就是控件。元素完成定义时,它取走那个子元素的文本,并把子元素隐藏起来。

指南

嵌入式文本输入框还是文本输入框

方面 嵌入式文本输入框 文本输入框
方框 宿主自己的方框,别处都不绘制 文本字段的方框,具备设计系统的每一种外观和尺寸
焦点 由宿主显示 由输入框自己显示
标签和消息 由宿主以文本的形式传进来 它自己的,或者所在字段的
类型 文本、搜索、电子邮件、URL、电话 这些之外,还有密码、数字、日期、时间和代码

推荐

  • 给它一个 label。没有别的东西为它命名。
  • 把帮助文本、错误文本或计数所说的内容,以文本的形式放进 description。
  • 把错误标记在方框上。同时把 error 传给输入框,让它暴露为无效。
  • 宿主位于小屏幕底部时,设置 maxRows。
  • 让文字保持在 16 px 或更大。触摸设备的浏览器会放大更小的文字。

避免

  • 不要在需要文本字段的方框的地方使用它。请使用文本输入框。
  • 不要在输入框上再画一个焦点环。焦点由方框显示一次。
  • 不要自己给宿主的高度做动效。它会跟随输入框的高度。
  • 不要把占位符当作名称。
  • 不要在输入法用到的 Enter 上发送。请监听 action,它已经把这些按键排除在外。

无障碍

角色
textbox
标注
The accessible name is label, which is required because nothing is drawn that could name the input and it asks no Field. The description is description: the holder puts there, as text, what its own help, error text or count says. The placeholder is never the name. Invalid, required, read-only and disabled are exposed from the props of those names; error and required draw nothing.
最小目标
即使外观尺寸更小,点击区域也满足最小目标尺寸。 minimum 32px · pointer 44px · spacing 8px · touch 48px

键盘操作

按键 动作
Tab, Shift+Tab Move focus to the input and out of it; never insert a character. The input is one stop.
Typing, Backspace, Delete, arrows, Home, End, selection and clipboard shortcuts Edit the value as the platform's own text element does. The holder sees each key first and may take one (ETI-21).
Enter (the action key) in a single-line input Commits the value and emits action; in a form of its own on the web, then submits the form (ETI-6).
Enter in a multi-line input Begins a new line (enterKey line-break), or emits action and inserts nothing (enterKey action). Never while an input method is composing (ETI-4).
Shift+Enter in a multi-line input Begins a new line.
Ctrl+Enter, Command+Enter in a multi-line input Emits action and inserts nothing, under both values of enterKey.
The return key of an on-screen keyboard in a multi-line input Begins a new line, always.
Escape Nothing: it is left to whatever holds the input.

备注

  • The holder's contract (H1 to H7). The input draws no box and no focus indication, so whatever holds it owes: H1 a visible box, or a place that reads as one; H2 a focus indication on that box for as long as the input has focus — however it was focused — that meets the contrast every Tanrei focus indicator is held to (WCAG 2.4.7, 2.4.13; on the web a rule on :focus-within, elsewhere the focusChange event); H3 the mark of an error and the look of a disabled box; H4 padding around the text, and a press on that padding passed on to the input (ETI-12); H5 the name, as label, and what its messages say, as description; H6 what action means; H7 in forced colors, an edge for the box and the focus indication in the platform's colors. A Tanrei component built from the input states each of these as sentences of its own and is tested there; a product's own box is the product's.
  • The caret is not accepted as the focus indication. It is one line a text style wide, it is not drawn while a selection is shown, and it does not say which box on the screen is the one being typed in. A holder that forgets H2 fails WCAG 2.4.7.
  • Text entry happens in the platform's native element, so input methods, autofill, dictation, selection and the platform's own text services work as they do everywhere. A composition's Enter never becomes an action (ETI-4): this is a functional requirement for every language written through candidates.
  • A height that follows the text changes with a short motion; where motion is reduced it changes at once (WCAG 2.3.3). The motion is the height's alone: text never slides, fades or scales.
  • At 200 % text in a width of 320 a multi-line input shows every character and scrolls in no direction but its own vertical one beyond maxRows (WCAG 1.4.4, 1.4.10). maxRows counts lines, so a holder at the bottom of a small screen should set one that leaves the conversation in view at that size.
  • Web: a touch browser zooms the page into a text element whose text is smaller than 16 px. The default text style is 16 px; a layer that sets a smaller one brings the zoom back.

生成自 specs/embedded-text-input.spec.json, tokens/sys/measure.tokens.json

组件负责的部分

  • 它是一个文本框,multiline 为 true 时是多行文本框。它的名称是 label,它的描述是 description。
  • 它根据同名的属性暴露为无效、必填、只读和禁用。required(字段的说法)也会像 required 一样使它必填。
  • 占位符绝不是名称。
  • 对 Tab 键来说,它只是一个停靠点。Tab 绝不插入字符,Escape 留给宿主。
  • 输入法的 Enter 绝不触发 action。
  • 点击区域在各行的上方和下方都达到最小目标,并且不改变它的尺寸。
  • 文本放大到 200% 时,多行输入框会换行,并显示每一个字符。没有任何内容横向滚动。
  • 在强制颜色模式下,它的文本、占位符、选区和插入点采用系统的颜色。
  • 在任何语言里,它都没有自己的文字。

Web 上的已知差距:

  • 输入法在 Chromium 中实际操作过,在 Firefox 和 Safari 中则只是用它们的事件模拟的。
  • 是否有屏幕键盘,是根据主指针是粗略指针并且不能悬停推测出来的。对接了键盘的平板电脑来说,这个推测是错的。

需要你做的事

  • 画一个方框,并且只要输入框有焦点,就在方框上显示焦点。焦点指示与它所在的背景之间,对比度要保持在 3:1 或以上。
  • 给输入框一个 label,用的是用户自己称呼这个方框时会说的词。
  • 在方框旁边用文字说明错误,并把这些文字放进 description 传进去。
  • 在强制颜色模式下,给方框一道采用系统文本颜色的边线,并让它的焦点指示采用高亮颜色。