跳到主要内容
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.file-upload
层级
功能

概览

Web
tanrei-file-upload
schedule
SwiftUI
TanreiFileUpload
radio_button_unchecked
Jetpack Compose
TanreiFileUpload
radio_button_unchecked
Flutter
TanreiFileUpload
radio_button_unchecked
React Native
FileUpload
radio_button_unchecked

生成自 docs/status/status.json, specs/file-upload.spec.json

两种布局的文件上传。选一个文件:它会被列出来,也可以移除。不会发送任何东西。

选择文件选择文件

文件上传把两样东西放在一处:选择文件的途径,以及已选文件的列表。它对应的是最常见的情况,可以直接用在表单里。

组成部分是一个文件选择器和若干文件项。文件选择器是选择的途径:你自己的按钮(它打开平台的文件选择器)、按钮周围的拖放区域、用文字写出的限制,以及对选中内容的判定。文件项是一个已选的文件:它的字形或图片、名称、大小、状态,以及移除和重试两个操作。这两样都不是文件上传自己绘制的。它添加的是把两者联系起来的东西:保留哪些文件、还能再放多少个文件、所有文件相同的形式(行或图块)、把许多变化合成一条的消息、移除之后焦点去哪里,以及表单的值。

行由谁持有,由 rows 属性决定:

  • own,默认值。文件上传保留已选的文件,并自己绘制它们的行。把它放进表单就能用:用户选择文件、查看列表、移除、提交。你不用写任何代码。
  • given。由你的产品给出各行。如果你在文件选定后立即发送它,并且希望每一行都显示状态、进度和重试操作,就用它。

它不上传

文件上传不打开任何连接,也不读取文件的任何一个字节。rows="own" 时,它保留浏览器为每个已选文件提供的句柄,表单在提交时发送这些文件。rows="given" 时,它什么都不保留。文件发往哪里、用什么凭据、分成几块、重试几次,都是你的表单或你的产品的代码要管的事。本页的示例不发送任何东西。

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

  • 文件已经存储好,用户只是打开或选中它们。那是列表。
  • 用户输入的是值,例如收件人或标签。那是标签组里的输入纸片。
  • 你只是显示一项任务进行到了哪里。那是进度指示器。
  • 文件的去处不是选择器下方的列表:放进正在撰写的消息里、放到画布上,或者当场读取、不予保留。那是单独使用的文件选择器,再在你要显示文件的地方放上文件项;参见下一层。
  • 页面必须在不运行脚本的地方接收文件。请在你自己的表单里写一个原生的 <input type="file">。没有脚本,自定义元素既打不开选择器,也无法携带文件。

在 Web 上它是 <tanrei-file-upload>。其他平台尚未实现。

用法

把这个元素放进你的表单,给它一个 name 和一个触发器。这就够了。已选的文件会被列出来,每一行都有移除操作,表单提交的是保留下来的文件。

<form method="post" enctype="multipart/form-data" action="/requests">
  <tanrei-file-upload name="attachments" multiple
                      accept=".pdf,image/png" max-size="10000000"
                      label="附件"
                      constraints-text="PDF 或 PNG,每个不超过 10 MB"
                      remove-label="移除 {name}"
                      type-error-text="只能添加 PDF 和 PNG 文件。"
                      size-error-text="超过 10 MB。">
    <tanrei-button slot="trigger" variant="outlined">选择文件</tanrei-button>
  </tanrei-file-upload>
  <button type="submit">发送</button>
</form>
import { Button, FileUpload } from '@tanrei/react';

<form method="post" encType="multipart/form-data" action="/requests">
  <FileUpload
    name="attachments"
    multiple
    accept={['.pdf', 'image/png']}
    maxSize={10_000_000}
    label="附件"
    constraintsText="PDF 或 PNG,每个不超过 10 MB"
    removeLabel="移除 {name}"
    typeErrorText="只能添加 PDF 和 PNG 文件。"
    sizeErrorText="超过 10 MB。"
  >
    <Button slot="trigger" variant="outlined">选择文件</Button>
  </FileUpload>
  <button type="submit">发送</button>
</form>

什么都不写时会发生的事:

  • 设置 multiple 时,第二次选择的文件加在第一次的后面。maxFiles 计入每一个保留的文件。没有 multiple 时,新文件取代原先保留的那一个。
  • 同一个文件选了两次,只保留一份。
  • 超出限制的文件不会保留。它被绘制成一个失败的行,带有你为这个原因写的句子,并在用户移除它或重新选择时消失。
  • 移除操作会移除这一行。焦点移到下一行。
  • 表单以 name 为名提交保留的文件。重置表单会清空列表。

另有三行可选的代码,用来补上标记表达不了的东西:

const upload = document.querySelector('tanrei-file-upload');

// 文件的大小,用你的产品的写法。没有它,就不绘制大小。
const megabytes = new Intl.NumberFormat('zh-CN', { style: 'unit', unit: 'megabyte', maximumFractionDigits: 1 });
upload.formatSize = (bytes) => megabytes.format(bytes / 1e6);

// 保留的文件因用户的操作而改变了。
upload.addEventListener('change', ({ detail }) => console.log(detail.files));

// 在你的代码里:读取保留的文件、交给它文件,以及清空。
upload.files;          // File[],每次读取都是一个新数组
upload.add(files);     // 与用户的一次选择一样判定;返回被拒绝的文件及其原因
upload.clear();

在 React 中,这三者通过 ref 使用:ref.current.files、ref.current.add(files) 和 ref.current.clear();formatSize 和 onChange 则是属性。

自己发送文件

如果你的产品自己发送每个文件并报告它的情况,就写 rows="given"。在 select 时,你为每个要保留的文件添加一个文件项。在某个文件项的 remove 时,你把它拿掉。这样,组件自己既不添加也不移除任何东西,也不保留任何文件。

<tanrei-file-upload id="attachments" rows="given" layout="drop-area" multiple
                    accept=".pdf,image/png" max-size="10000000"
                    label="附件"
                    drop-text="将文件拖到此处,或选择文件"
                    constraints-text="PDF 或 PNG,每个不超过 10 MB"
                    remove-label="移除 {name}" retry-label="重试 {name}">
  <tanrei-button slot="trigger" variant="outlined">选择文件</tanrei-button>
</tanrei-file-upload>

<script type="module">
  import '@tanrei/web/components/file-upload';
  import '@tanrei/web/components/button';

  const upload = document.querySelector('#attachments');
  const size = new Intl.NumberFormat('zh-CN', { style: 'unit', unit: 'megabyte', maximumFractionDigits: 1 });

  upload.addEventListener('select', ({ detail }) => {
    for (const file of detail.files) {
      const row = document.createElement('tanrei-file-item');
      Object.assign(row, { value: crypto.randomUUID(), name: file.name, sizeText: size.format(file.size / 1e6), status: 'uploading', statusText: '正在上传', progress: 0 });
      upload.append(row);
      // 由你的代码发送文件并回报情况。组件只绘制你设置的内容。
      send(file, {
        onProgress: (share) => (row.progress = share),
        onDone: () => Object.assign(row, { status: 'done', statusText: '已上传' }),
        onError: () => Object.assign(row, { status: 'error', errorText: '连接已断开。', retryable: true }),
      });
    }
  });

  // 被拒绝的文件带有一个原因,它是一个名称,而不是一句话。请用你自己的话来说。
  upload.addEventListener('reject', ({ detail }) => {
    const tooLarge = detail.files.filter((refused) => refused.reason === 'size').length;
    if (tooLarge) upload.announcement = `${tooLarge} 个文件超过了 10 MB。`;
  });

  // 行绝不会自己移除自己。由你把它拿掉,焦点随后移到下一行。
  upload.addEventListener('remove', (event) => event.target.remove());
</script>
import { Button, FileUpload, FileItem } from '@tanrei/react';

<FileUpload
  rows="given"
  layout="drop-area"
  multiple
  accept={['.pdf', 'image/png']}
  maxSize={10_000_000}
  label="附件"
  dropText="将文件拖到此处,或选择文件"
  constraintsText="PDF 或 PNG,每个不超过 10 MB"
  removeLabel="移除 {name}"
  retryLabel="重试 {name}"
  onSelect={(event) => start(event.detail.files)}
  onReject={(event) => explain(event.detail.files)}
>
  <Button slot="trigger" variant="outlined">选择文件</Button>
  {rows.map((row) => (
    <FileItem key={row.id} value={row.id} name={row.name} sizeText={row.sizeText} status={row.status} statusText={row.statusText} progress={row.progress} errorText={row.errorText} retryable={row.retryable} onRemove={() => remove(row.id)} onRetry={() => retry(row.id)} />
  ))}
</FileUpload>

只有由你给出行时,才绘制你写的行

你写在文件上传里的 <tanrei-file-item> 只有在 rows="given" 时才会绘制。没有这个属性时,文件上传保留它自己的文件,不绘制你的行,调试构建会报告这一点。

下一层

用它的两个成员手写出来的同一个东西,画面是一样的,名称、描述和状态也都一样:

<!-- 文件上传 -->
<tanrei-field label="附件">
  <tanrei-file-upload multiple accept=".pdf" max-files="5" rows="given"
      constraints-text="PDF,最多 5 个文件" empty-text="未选择文件" remove-label="移除 {name}" retry-label="重试 {name}">
    <tanrei-button slot="trigger" variant="outlined">选择文件</tanrei-button>
    <tanrei-file-item value="a" name="brief.pdf" size-text="820 KB" status="uploading" status-text="正在上传" progress="0.4"></tanrei-file-item>
  </tanrei-file-upload>
</tanrei-field>

<!-- 下一层:两个成员,由产品来排布 -->
<tanrei-field label="附件">
  <tanrei-file-picker multiple accept=".pdf" max-files="5"
      constraints-text="PDF,最多 5 个文件" selection-text="未选择文件">
    <tanrei-button slot="trigger" variant="outlined">选择文件</tanrei-button>
  </tanrei-file-picker>
</tanrei-field>
<ul class="my-files" aria-label="附件">
  <li><tanrei-file-item value="a" name="brief.pdf" size-text="820 KB" status="uploading" status-text="正在上传" progress="0.4"
        remove-label="移除 {name}" retry-label="重试 {name}"></tanrei-file-item></li>
</ul>

手写出来的同一个东西:一个文件选择器,以及页面自己的一个列表,里面有一个文件项

选择文件

文件的行,无论单独使用还是放在文件上传里,都是同一个元素。手写的版本得不到的,是文件上传所持有的状态:

  • 把许多变化合成一条、以礼貌(polite)的方式朗读的消息。
  • 移除之后移到下一个文件的焦点。
  • 表单的值、重置和校验。
  • 跳过选了两次的文件,并判定还能再放多少个文件。
  • 所有文件采用相同的形式(行或图块)、相同的填充方式(cover 或 contain)和相同的两个操作名称。
  • 包着各个文件项的列表,以及包着整体的组。

当你需要文件上传没有提供的排布方式时,就往下走一层:

你需要 使用
选择的途径、它下方的列表,以及表单的值 文件上传
已选的文件不在选择器下方,而在别处:消息里、预览旁边、表格里 一个文件选择器,再在你要显示文件的地方放上文件项
一个当场读取、不予保留的文件 单独使用的文件选择器
你的产品已经持有、无需选择的文件 单独使用的文件项

规格

由产品给出的行,每种状态一行:等待中、已发送 40%、进行中、已完成、失败但可重试、彻底失败

选择文件

同一个文件上传,改用图块:每个文件是一个带有图片的方框,名称在下方

选择照片

各个成员提供什么

方面 来自文件选择器 来自文件项 文件上传自己的
属性 layout, multiple, accept, maxSize, constraintsText, dropText, disabled, error, required itemForm, itemPreview, itemFit, itemNameHidden, itemWidth, itemHeight(对应文件项的 form, preview, fit, nameHidden, width, height), removeLabel, retryLabel rows, maxFiles, label, emptyText, typeErrorText, sizeErrorText, countErrorText, announcement
事件 — 你给出的文件项的 click, remove, retry select, reject, change
插槽 trigger, icon — 默认插槽:rows="given" 时放你的文件项
令牌 comp.file-picker.* comp.file-item.* comp.file-upload.list.gap, comp.list.gap.small

文件选择器有它自己的 label、maxFiles 和 selectionText。这三项不会原样转交:文件上传的 label 同时为选择器和列表命名,它的 maxFiles 计入列表里的每一个文件,而选择器的 selectionText 则在列表为空时由文件上传根据 emptyText 写入。

来自文件项的八个属性会交给文件上传里的每一个文件项,这样所有文件都以同样的方式绘制。你的某个文件项如果对其中某一项另有设置,会被覆盖,调试构建会报告这一点。disabled 也会交给每一个文件项。

选择的过程,也就是触发器、拖放区域、accept 和各项限制,写在文件选择器的页面上。文件的行或图块显示什么,也就是按格式选定的字形、图片、各种状态、可按下的主体,写在文件项的页面上。

单独调整某一个文件上传

文件上传有两个自己的令牌,即它的列表的两个间距。它绘制的其余一切,读取的都是文件选择器和文件项的令牌,无论这些令牌设置在哪里。要改动某一个实例,就在它上面设置成员的令牌:

<tanrei-file-upload label="附件" style="--tanrei-comp-file-item-row-container-gap: 16px">…</tanrei-file-upload>

构成

部件 说明
1 group The whole component: the file picker and, below it, the list. Exposed as one named group; it draws nothing of its own.
2 picker The file picker, its member: the trigger with its texts, or the drop area. Its parts — container, outline, icon, label, supporting-text, selection-text — are the file picker's and are drawn from the file picker's tokens.
3 list · 可选 The chosen files, one file item each, in the order they were kept, or in the order the product gives them: a List, which it uses, whose items each hold one file item — a column of rows, or lines of tiles that wrap. The List exposes the list and draws the handles and the motion of a reorderable form; the file upload decides the arrangement and the order.
4 item · 可选 One file: a file item, its member, drawn from the file item's tokens. The product's own while rows is given; made by the file upload for a file it keeps or refused while rows is own.

属性

属性 类型 默认值 说明
layout button | drop-area "button" How choosing is offered. button: the trigger alone, with the line of limits below it; for a form among other fields, a narrow place, a phone, a toolbar. drop-area: a bounded place around the trigger that also takes files dragged onto it and opens the picker when pressed; for a screen whose main task is to hand over files, where a pointer is likely. Both open the same picker from the same trigger. — File picker
rows own | given "own" Who holds the rows. own: the file upload keeps the files a person chose and draws a row for each itself — choose, see the list, remove, submit, with nothing written by the product; it shows no progress and no result of a sending, because it sends nothing. given: the product gives the rows and the file upload draws them and keeps no file — for a product that sends files itself and reports each one's status and progress. The product says which; the file upload never guesses it from what the list holds.
itemForm row | tile "row" How the file is shown. row: one line across the width the layout gives — the glyph or a small picture, the name, the second line, the bar, the actions at the end — for a column of files. tile: a box of a width and a height, holding the picture or the glyph, with the texts beneath it and the actions at its corner — for a strip of attached pictures. The product chooses by where the file stands. A design system may add values. — File item
itemPreview boolean false Whether a picture of the file is drawn in the glyph's place, where the file is an image and the product gave an address (previewSrc) or the file itself in the platform's idiom. False draws the glyph, also for an image. The picture is drawn by the platform; the file item reads no file. — File item
itemFit cover | contain "cover" How the picture fills its box. cover: it fills the box and what does not fit is cut at the edges, for photographs in a strip. contain: the whole picture is drawn inside the box, on the box's ground, for a picture whose edges matter — a scan, a signature, a diagram. — File item
itemNameHidden boolean false In the tile form: the name is not drawn beneath a tile that draws a picture, for a dense strip of pictures. The name stays the file item's name for assistive technology, and is drawn all the same wherever no picture is (FIT-8). Not read in the row form. — File item
itemWidth number null In the tile form: the width of the box, in the platform's layout unit. Null makes it as wide as it is high. — File item
itemHeight number null In the tile form: the height of the box, in the platform's layout unit. Null reads comp.file-item.tile.media.height. — File item
reorderable boolean false Lets people put the files in another order (FUP-59): the List that arranges the rows gives every row a handle, lets the whole row be dragged, and offers the moves in a menu and to the keyboard. While rows is own the file upload reorders the files it keeps itself and emits change; while rows is given it emits move and the product reorders its file items. Ignored while disabled.
multiple boolean false Whether more than one file can be chosen at a time. False: the picker offers one file, and a drop of several is refused whole. True: the picker offers several. The file picker keeps nothing, so whether a second choice adds to the first or takes its place is decided by whoever keeps the files. — File picker
accept strings The kinds of file that are taken: file name extensions with their dot (".pdf") and media types, a whole family as "image/*". The picker is asked to offer these, and a file of another kind — dropped, or chosen in a picker that did not filter — is refused (reject, reason type). Empty takes every kind. It enforces the limit; constraintsText says it. — File picker
maxSize number null The largest size of one file, in bytes. A larger file is refused (reject, reason size). Null sets no limit. It is judged from the size the platform reports; the file is not read. — File picker
maxFiles number null The most files while multiple is true: the files the file upload keeps while rows is own, the rows the list holds while rows is given. Files chosen beyond the room that is left are refused (reject, reason count). Null sets no limit; ignored while multiple is false. It is the file upload's own and is not given to the file picker, which counts within one choice only: the room is measured against what the file upload holds.
label string "" The accessible name of the group ("Attachments"): the file picker and the list together. It is not drawn. Leave it empty inside a Field, whose label names the group and is drawn; give it where the file upload stands without one. It is the file upload's own: the file picker inside it is given no name and adds no group.
constraintsText string "" The limits, in the product's words, shown before the choice ("PDF or PNG, up to 10 MB each, 5 files at most"). Drawn in the area, or below the trigger, and given to assistive technology as the trigger's description. Tanrei builds no such sentence from accept, maxSize and maxFiles: it has no words. — File picker
dropText string "" The drop area's prompt, in the product's words ("Drag files here, or choose them"). Drawn above the trigger in the drop-area layout; ignored in the button layout. — File picker
emptyText string "" What is shown beside the trigger while no file is chosen, in the button layout ("No file chosen"), and read with the trigger. Empty reads the product's words; with neither, nothing is drawn. The file upload knows whether a file is chosen and gives this text to its file picker as selectionText while the list holds no row, and nothing while it holds one.
removeLabel string "" The accessible name of the remove action: the whole name in the product's language with one place, {name}, which the file item fills with the file's name ("Remove {name}", "{name}を削除"), so that the actions of two files are told apart by their names and the product still writes one word, not one per file. Empty reads the product's words. — File item
retryLabel string "" The accessible name of the retry action, with the place {name} filled by the file's name as removeLabel is ("Retry {name}", "{name}を再試行"). Empty reads the product's words. — File item
dragHandleLabel string "" The accessible name of an item's handle: the whole name in the product's language with one place, {label}, which the list fills with the item's name ("Reorder {label}", "{label}を並べ替え"), so that the handles of two items are told apart. The product's: Tanrei has no word for it. Empty reads the product's words. Required while reorderable. Given to the List that arranges the rows (FUP-59).
dragHandleHint string "" The description of every handle: how to move an item from it with the keyboard, in the product's words ("Press Space to lift, the arrow keys to move, Space to drop, Escape to cancel; Enter opens the moves"). Empty reads the product's words; with neither, the handle has no description. Given to the List that arranges the rows (FUP-59).
moveToStartLabel string "" The move menu's item that moves the item to the first place ("Move to top", "Move to start"). Empty reads the product's words. Required while reorderable. Given to the List that arranges the rows (FUP-59).
moveBeforeLabel string "" The move menu's item that moves the item one place toward the start ("Move up" in a column, "Move left" in a line). Empty reads the product's words. Required while reorderable. Given to the List that arranges the rows (FUP-59).
moveAfterLabel string "" The move menu's item that moves the item one place toward the end ("Move down", "Move right"). Empty reads the product's words. Required while reorderable. Given to the List that arranges the rows (FUP-59).
moveToEndLabel string "" The move menu's item that moves the item to the last place ("Move to bottom", "Move to end"). Empty reads the product's words. Required while reorderable. Given to the List that arranges the rows (FUP-59).
liftedText string "" The sentence assistive technology is told when the keyboard lifts an item, with three places the list fills: {label} with the item's name, {position} with its place, counted from 1, and {count} with the number of items ("{label} lifted: {position} of {count}"). Empty reads the product's words. Required while reorderable. Given to the List that arranges the rows (FUP-59).
movedText string "" The sentence assistive technology is told when a lifted item reaches another place, and once a move has taken effect, whether it came from the menu, the keyboard or a pointer: {label}, {position} (the place it has, or would have if dropped now, counted from 1) and {count} ("{label}: {position} of {count}"). Empty reads the product's words. Required while reorderable. Given to the List that arranges the rows (FUP-59).
cancelledText string "" The sentence assistive technology is told when a move with the keyboard is cancelled and the item goes back: {label}, {position} (the place it has again) and {count} ("{label} returned to {position} of {count}"). Empty reads the product's words. Required while reorderable. Given to the List that arranges the rows (FUP-59).
typeErrorText string "" While rows is own: the sentence drawn on the row of a file that was refused with the reason type — its kind is not among accept, or it is a folder — in the product's words ("This kind of file cannot be added."). Empty reads the product's words; with neither, the row is drawn without a sentence and that is reported. Not read while rows is given: there the product writes each row's errorText.
sizeErrorText string "" While rows is own: the sentence drawn on the row of a file that was refused with the reason size — it is larger than maxSize — in the product's words ("This file is too large."). Empty reads the product's words; with neither, the row is drawn without a sentence and that is reported. Not read while rows is given: there the product writes each row's errorText.
countErrorText string "" While rows is own: the sentence drawn on the row of a file that was refused with the reason count — more files were chosen than multiple or maxFiles leaves room for — in the product's words ("No more files can be added."). Empty reads the product's words; with neither, the row is drawn without a sentence and that is reported. Not read while rows is given: there the product writes each row's errorText.
announcement string "" A message the product has assistive technology hear through the file upload's one live region, when it sets or changes it ("3 files added", "2 files were too large"). It is not drawn. Set in the same turn as rows change, it is said in place of what those rows would have said one by one.
disabled boolean false Nothing can be chosen or dropped: the trigger is disabled and the area answers nothing. Inside a Field, disable the Field. — File picker
error boolean false Puts the file picker in the error condition: the drop area's edge is drawn in the error role and the trigger is exposed as invalid. A Field sets it with its own error; the words are the Field's errorText. The file picker never sets it itself. — File picker
required boolean false Exposes the trigger as required, where the platform's button has such a state. A Field that is required sets it. It draws nothing — the mark beside the label is the Field's — and it validates nothing: the file picker has no value that could be missing. — File picker

插槽

插槽 说明
trigger The product's own Button that opens the picker, with the product's own label ("Choose files"), variant and size. Exactly one. The file picker gives it the behaviour and never restyles it. — File picker
icon A glyph in place of the upload glyph at the top of the drop area (a picture for a photo, a table for a spreadsheet). Decorative; ignored in the button layout. — File picker

事件

事件 说明
select Files were chosen in the picker or dropped on the area, were passed by the file picker (its select, FPK-9) and found room. Emitted once per choice by the file upload itself — the file picker's own event does not leave it (FUP-57) — with those files as the platform's own handles, in the order the platform gave them. While rows is given the product adds a row for each and does with the files what it means to, and the file upload keeps none of them. While rows is own the file upload has already kept them and drawn their rows, the event carries the files it newly kept — never one it already had — and the product need not listen. It reads none of them in either case.
reject Files of one choice or one drop were refused, by the file picker (its reject, FPK-9) or by the file upload for want of room. Emitted once, after select where both happen, with each refused file and its reason: type (not among accept, or a folder), size (larger than maxSize) or count (more than one while multiple is false, or more than maxFiles leaves room for). The reasons are names for the product's code, not words: while rows is given the product says what happened, with a Field's errorText, a failed row or announcement; while rows is own the file upload has already drawn a row for each refused file, with the product's sentence for its reason.
change The files the file upload keeps changed by what a person did: a choice or a drop that kept at least one file or replaced the one kept, or a kept file removed by its row's remove action. Emitted once, after select and reject where they happen, with the files now kept, in order, as the platform's own handles. Emitted only while rows is own, and never for what the product did itself (a file handed over, the kept files cleared, a form reset, a change of rows).
move While rows is given and reorderable is true: a person asked for a row to stand in another place, by the move menu, the keyboard or a drag (FUP-59). Carries from, the row's index when the move began, and to, the index it is to have, both counted from 0. Emitted once per move; the file upload has changed nothing — the product puts its file item in the new place. While rows is own the file upload makes the move itself and emits change instead.

条目

该组件重复排列的条目种类。测试样例按顺序列出条目;各平台以各自的惯用方式为每个种类命名。

file

One chosen file as a row of the list: a File item, with the props a row sets for itself. Its form, whether it draws a picture and how, the two action names and whether it is disabled are the file upload's, given to every row (FUP-58). While rows is given the product adds it, changes it and removes it, and the file upload only arranges it; while rows is own the file upload makes one itself for each file it keeps and for each file it refused, and the product gives none.

属性 类型 默认值 说明
value string "" What identifies the row to the product: given back by click, remove and retry. Unique within one file upload.
name string "" The file's name as the person knows it ("report-2026.pdf"). The file item's name for assistive technology in every form, also where it is not drawn.
mediaType string "" The file's kind as the platform reports it ("image/png"), from which the glyph is chosen. Empty, the kind is read from the end of name.
glyph string "" The name of a glyph of the system's icon set, drawn in place of the one the file's format would choose. Empty lets the format choose.
formatText string "" The file's format in the product's words ("PDF", "Spreadsheet"), drawn first on the second line. Empty draws none: the file item has no word for a format.
sizeText string "" The file's size as text, formatted by the product for the reader's locale ("1.2 MB"). Empty draws none.
status pending | uploading | done | error "pending" Where the file stands. pending: chosen or kept, and nothing is happening to it — the whole life of a file in a form that is submitted as one, and of a file that is only shown. uploading: the product is sending it; the bar is drawn. done: the product has it. error: it failed or was refused; errorText says why.
statusText string "" The status in the product's words ("Waiting", "Uploading", "Uploaded"), drawn last on the second line and read with the file. The mark and its color are never the only sign of a status.
progress number null How far the sending has come, from 0 to 1, while status is uploading. Null draws the bar as under way without saying how far. Ignored in every other status.
errorText string "" Why the file failed and what to do, in the product's words ("Larger than 10 MB", "The connection was lost"). Drawn in the status's place while status is error, in every form, and never cut.
retryable boolean false Whether a failed file offers the retry action. True for a failure that trying again may cure (a lost connection); false for one it cannot (a file that is too large).
previewSrc string "" The address of a picture of the file, as the platform's images take one: a small rendition the product's server made of a file it already has. With preview it is drawn for a file of any format. Empty, the picture comes from the file itself where the product gave it.
interactive boolean false Makes the body — the glyph or the picture, and the texts — one pressable action that emits click, for a product that opens the file where it is: an image in an Image preview, a document in a viewer. The actions stay their own. A file item whose action is to go to the file takes href instead and is a link.
href string "" The destination: the address of the file, as the platform's links take one. Not empty, it makes the body a link — to a file the product already has, to open or to download — whether interactive is set or not.
target same | new "same" Where the destination opens. same: where the person is. new: a new context — a new tab or window on the web, outside the current screen elsewhere — which a row marks and targetLabel says. Ignored without href.
targetLabel string "" What assistive technology reads after the link's name while target is new ("opens in a new tab"). The product's words.
readOnly boolean false The file is shown and cannot be taken back: no remove action and no retry action is drawn. For a file of a record that is only read. The body can still be pressed where interactive or href says so.
事件 说明
click The File item's click: the person activated the body of a row that is pressable or a link. Carries the row's value. The file upload opens nothing. Emitted only while rows is given: a row the file upload made itself is never pressable.
remove The File item's remove: the person asked to remove this row, by its remove action. Carries the row's value. The product removes the row — and stops sending the file, if it was — and the file upload then moves focus (FUP-30). The file upload removes nothing itself. Emitted only while rows is given: a row the file upload made for a file it keeps is removed by the file upload, which emits change.
retry The File item's retry: the person asked to try a failed row again, by its retry action. Carries the row's value. The product sends the file again and sets the row's status. Emitted only while rows is given: a row the file upload made itself has no retry action.
规格 Web SwiftUI Jetpack Compose Flutter React Native
条目 tanrei-file-item TanreiFileItem TanreiFileItem TanreiFileItem FileItem
value · 属性 value id key key value
name · 属性 name name name name name
mediaType · 属性 mediaType mediaType mediaType mediaType mediaType
glyph · 属性 glyph glyph glyph glyph glyph
formatText · 属性 formatText formatText formatText formatText formatText
sizeText · 属性 sizeText sizeText sizeText sizeText sizeText
status · 属性 status status status status status
statusText · 属性 statusText statusText statusText statusText statusText
progress · 属性 progress progress progress progress progress
errorText · 属性 errorText errorText errorText errorText errorText
retryable · 属性 retryable isRetryable retryable retryable retryable
previewSrc · 属性 previewSrc previewURL previewModel previewImage previewSource
interactive · 属性 interactive action onClick onPressed onPress
href · 属性 href destination href href href
target · 属性 target opens target target target
targetLabel · 属性 targetLabel targetLabel targetLabel targetLabel targetLabel
readOnly · 属性 readOnly isReadOnly readOnly readOnly readOnly
click · 事件 click action onClick onPressed onPress
remove · 事件 remove onRemove onRemove onRemove onRemove
retry · 事件 retry onRetry onRetry onRetry onRetry
备注 A tanrei-file-item child in the default slot, in the order of the rows, with the attributes of its own spec. Its file property (a File, never an attribute) is what a form submits for it. click, remove and retry bubble, with detail.value. Written by the product only while rows="given"; while rows is own the element renders the same element in its shadow root for each file it keeps. A value in the items array, identified by id. The product keeps the URL it was handed beside it. Passed only while the product gives the rows; left out, the file upload keeps its own files and draws their rows. A value in the items list, identified by key. The product keeps the Uri beside it. Passed only while the product gives the rows; left out, the file upload keeps its own files and draws their rows. A value in the items list, identified by key. Passed only while the product gives the rows; left out, the file upload keeps its own files and draws their rows. An object in the items array, identified by value. Passed only while the product gives the rows; left out, the file upload keeps its own files and draws their rows.

状态

行为

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

ID 需求
FUP-1 THE file upload SHALL lay out, from top to bottom and from its start edge: its file picker, laid out and drawn as the file picker's own sentences say (FPK-1 to FPK-4), and, WHILE it holds at least one row, the list of rows comp.file-upload.list.gap below it (FUP-17). It SHALL be as wide as its layout gives it and as tall as its parts, SHALL draw no container, fill or edge around the whole, and SHALL draw no label, no help, no error text and no requirement mark of its own: those are the Field's (FUP-32).
FUP-2 WHILE the list holds no row THE file upload SHALL give its file picker emptyText as the text that stands beside the trigger (selectionText, FPK-2), and WHILE the list holds a row SHALL give it none: the rows say what is chosen. Whether nothing is chosen is the file upload's to know; where that text stands, how it is drawn, how it wraps and that the drop-area layout draws none are the file picker's (FPK-2, FPK-3).
FUP-7 THE file upload SHALL NOT upload, as its file picker does not (FPK-7) and its file items do not (FIT-33): it SHALL open no connection and send no file or part of one to anyone, SHALL NOT read a file's bytes — not to check its kind, not to measure it, not to draw a picture of it — and SHALL NOT copy or store a file. The picture of an image is drawn only WHILE itemPreview is true, by the file item and the platform and as FIT-6 and FIT-7 say; the file upload makes no address and no picture itself. WHILE rows is given it SHALL NOT keep a file beyond handing it to the product, and SHALL draw of a file only what the product gives back as a file item (FUP-17); WHILE rows is own it SHALL keep of a file only the platform's own handle of it, for as long as FUP-51 says, and SHALL draw of it only what FUP-44 and FUP-45 name. This holds on every platform, as a component that draws a destination loads nothing about it.
FUP-9 WHEN its file picker reports a choice or a drop — select with the files that passed accept, maxSize and the rule of one file, reject with those it refused (FPK-9) — WHILE rows is given THE file upload SHALL keep none of them and SHALL judge what the file picker cannot know, the room that is left: WHILE multiple is true and maxFiles is a number, it SHALL refuse with the reason count each file that passed beyond maxFiles less the rows the list already holds. It SHALL then emit select once with the files that found room, WHERE there are any, and reject once, WHERE there are any refused, with the files the file picker refused in the order it refused them, then the files refused for want of room in the order the platform gave them, each with its reason; in that order (FUP-57). THE file upload SHALL add no row, replace no row, draw no error and say no word because of a choice: the product adds a row for each file it keeps, and WHILE multiple is false replaces the row it had. WHILE rows is own the file upload keeps what found room (FUP-43).
FUP-16 WHILE error is true THE file upload SHALL give error to its file picker, which draws the drop area's edge for it and exposes the trigger as invalid (FPK-16), and SHALL draw nothing else for it: no file item changes under the error condition. THE file upload SHALL never set error itself: not for a refused file, not for a failed row, not for a missing value.
FUP-17 WHILE the file upload holds at least one row THE file upload SHALL draw each row as one file item (FUP-58) inside one item of its List, named by the file's name — a row of this spec is one file item, whatever its form — in the order the product gave them WHILE rows is given and in the order of FUP-44 and FUP-45 WHILE rows is own: WHILE itemForm is row as a vertical List, comp.list.gap.small apart (the List's small gap), each as wide as the file upload; WHILE itemForm is tile as a horizontal List that wraps (LST-2) — lines from the start edge that wrap onto a further line where the width ends, comp.list.gap.small apart along both directions, the tiles of one line aligned at their tops, each of the size itemWidth and itemHeight give (FIT-3). How a file item is laid out and drawn, and that the name of a row is never cut, are the file item's (FIT-2 to FIT-10). THE file upload SHALL NOT sort, group, number or count the rows, WHILE rows is given SHALL NOT add or remove one itself, SHALL draw no line and no fill between or behind them, and WHILE reorderable is false SHALL give them no order a person can change. WHILE it holds no row THE file upload SHALL draw no list, keep no room for one and expose none. WHEN itemForm names a value the file upload does not know THE file upload SHALL arrange the rows as a column and in debug builds report it.
FUP-21 WHEN a file item of the file upload emits click, remove or retry (FIT-11 to FIT-13) WHILE rows is given THE file upload SHALL let the event reach the product as the row's own, with the row's value, and SHALL do nothing else: it SHALL NOT open a file, remove the row, change its status or move focus because of the activation. WHILE rows is own it answers a row's remove itself (FUP-46), and no row is pressable or has a retry action (FUP-44). The stops of the rows SHALL follow the trigger in the focus order, in the order of the rows, each row's stops in the order the file item gives them — its body where it is pressable, retry, remove; the file upload SHALL add no stop of its own, and SHALL handle no arrow key, Delete or Backspace.
FUP-24 THE file upload SHALL expose itself to assistive technology as a group named by label — or, inside a Field, by the Field's label WHILE label is empty (FUP-32) — holding what its file picker exposes and, WHILE there are rows, a list of them. It SHALL give its file picker no name, so that the file picker exposes no second group inside this one (FPK-17), and SHALL give it what a Field gives the file upload (FUP-32) as the last part of the trigger's description; the rest of that description — constraintsText, then emptyText WHILE it is drawn (FUP-2) — and the trigger's required and invalid states are exposed by the file picker (FPK-17). WHILE label is empty and no Field names it THE file upload SHALL still render and in debug builds report that the group has no accessible name.
FUP-25 THE file upload SHALL expose its rows as one list that takes the group's name — its List's (LST-18) — and each row as one item of that list: the List's item, holding the file item, which is then exposed as the group it is when it stands alone (FIT-19) and not as a list item of its own. What a row reads — the name, the format, the size, the status or the error, the progress bar while it is sent, and the actions named removeLabel and retryLabel with the file's name in them — is the file item's (FIT-19). A platform without a list role SHALL expose a container that holds the rows in order and state that in its README.
FUP-26 THE file upload SHALL have exactly one live region, polite and never assertive, for everything it announces, and SHALL announce through it, once each and in the product's words alone: WHEN a row is added after the file upload was first presented, the row's name and its statusText; WHEN a row's status becomes done, its name and its statusText; WHEN a row's status becomes error, or its errorText changes while it is error, its name and its errorText; and WHEN the product sets announcement to a text that is not empty, or changes it, that text. Changes that arrive in one turn of the platform's rendering SHALL be said as one message, in the order of the rows; WHERE announcement is set or changed in that same turn THE file upload SHALL say announcement alone, so that forty files added are said in one sentence of the product's and not forty times. THE file upload SHALL NOT announce a change of progress, a removal, a drag over the area or, WHILE rows is given, a refused file by itself — the product says those with announcement where they must be said, and FUP-50 says what is announced WHILE rows is own — SHALL NOT announce rows that are present when it is first presented, and SHALL NOT move keyboard or screen-reader focus to announce.
FUP-27 THE file upload SHALL have no words of its own, as its file picker and its file items have none (FPK-18, FIT-20): it SHALL take emptyText from the instance's prop and, WHILE that is empty, from the product's words of the key noFileText; SHALL give removeLabel and retryLabel to every file item as they are given to it, where an empty one reads the product's words of the keys removeLabel and retryLabel and a missing one is reported by the file item (FIT-20); and SHALL take every other word — label, announcement, each row's texts, and the texts it passes to its file picker — from the product alone, and from nowhere else. IF emptyText is not given THE file upload SHALL give its file picker nothing in its place. IF maxFiles is a number WHILE constraintsText is empty THE file upload SHALL still render and in debug builds report that a limit is enforced and not said, as its file picker reports for accept and maxSize (FPK-18). THE file upload SHALL build no sentence from accept, maxSize, maxFiles, a count of rows or a size in bytes.
FUP-28 WHILE disabled is true THE file upload SHALL give disabled to its file picker, which disables the trigger, draws its own parts from their disabled tokens and answers neither a press nor a drag (FPK-19); SHALL give disabled to every file item, which disables its actions and its body and keeps what it shows of the file drawn as it is and readable by assistive technology (FIT-21): what was chosen is still what was chosen; and SHALL emit neither select nor reject, and no row's click, remove or retry. WHEN a disabled file upload first appears its trigger SHALL be drawn disabled in its first frame, from its first render, without moving from the enabled look into it.
FUP-29 WHEN a row is added, is removed or changes its status THE file upload SHALL draw that at once, and WHILE reorderable is false without a motion of the file upload's own, the rows after it taking their new places at once — in a strip of tiles also where that moves a tile to another line; WHILE reorderable is true the rows that change place move as the List moves its items (LST-41). The changes of the drop area between states are the file picker's (FPK-20), and those of a row's body and actions the file item's (FIT-23).
FUP-30 WHEN a row leaves the list while focus is on one of its stops THE file upload SHALL move focus to the remove action of the row that followed it, or of the row before it WHERE none follows, or to the trigger WHERE no row is left, so that a person who removes several files in a row goes on from where they were; WHERE the row it would go to has no remove action (a read-only file item), to that row's body WHERE it is pressable, and otherwise on to the next such row or the trigger. Focus within a row that stays — a retry action that leaves — is the file item's (FIT-30). THE file upload SHALL NOT move focus because a row was added or changed otherwise. WHILE reorderable is true focus after a row left goes as the List says (LST-46): to the handle of the row that now stands where it stood, else of the row before it, else to the trigger.
FUP-31 WHERE the platform has forms that submit named values (the web) WHILE rows is given THE file upload SHALL take part in its form as one control: it SHALL submit under its own name, in the order of the rows, the file of every row the product gave a file to — as the platform's own handle of that file, the one select carried, given to the row in the platform's idiom and not as a prop of this spec, since a handle is no value a fixture or an attribute can hold — WHILE the file upload is not disabled, and nothing otherwise; SHALL be disabled WHILE the form disables the set it is in; SHALL report a missing value to the form's own validation, with the platform's own message, WHILE required is true and no row carries a file; and SHALL change nothing WHEN the form is reset, because the rows are the product's to clear. It SHALL submit a file whatever the row's status: a product that sends files itself gives the file upload no name. WHILE rows is own the form is given the files the file upload keeps (FUP-49).
FUP-32 WHILE the file upload is the control of a Field THE file upload SHALL take from it what a Field gives its control (FLD-8, FLD-10, FLD-11, FLD-14): the Field's label as the group's name WHILE its own label is empty; the Field's supporting text with, in error, the error text after it and optional text, which it SHALL pass to its file picker as the last part of the trigger's description (FUP-24); the invalid state as its own error condition (FUP-16); the required state as required; and the disabled state as disabled. It SHALL pass the three states on to its file picker by the exchange a Field has with a control (FPK-21); the Field's one control SHALL be the file upload, and the file picker inside it SHALL take nothing from the Field directly. WHEN the label of the Field is pressed THE file upload SHALL move focus to the trigger and open nothing.
FUP-33 WHILE the text size is enlarged THE file upload SHALL grow with its parts: its file items as FIT-24 says and its file picker as FPK-22 says, a strip of tiles wrapping sooner and never running past the file upload's width. At 200 % in a width of 320 no text SHALL be lost or lie over another part, and nothing SHALL need scrolling in two directions.
FUP-34 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 file upload SHALL draw nothing of its own that rests on a color: its rows are drawn as FIT-25 says and its drop area as FPK-23 says.
FUP-35 WHERE the layout direction is right-to-left THE file upload SHALL mirror its arrangement as its List mirrors (LST-5): a strip of tiles begins at the right and runs to the left on every line, and each row is mirrored inside as FIT-26 says; its file picker mirrors as FPK-24 says.
FUP-36 THE file upload 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; WHEN that direction changes while the file upload is shown THE file upload SHALL draw itself for the new direction at once, without a motion of its own, keeping its rows and its focus.
FUP-37 THE file upload SHALL leave the direction of each row's texts and the mirroring of each row's glyphs to the file item (FIT-28), and those of its file picker's texts, emptyText among them, to the file picker (FPK-26); THE file upload SHALL mirror no glyph itself.
FUP-38 IF maxFiles is not a number above zero THE file upload SHALL set no such limit; IF two rows have the same value THE file upload SHALL still draw both; and in each case debug builds SHALL report it. A status, a fit or a size that a row does not know is answered by the file item (FIT-29); a layout it does not know, a maxSize that is no number above zero and an entry of accept that cannot be read are answered by the file picker (FPK-27).
FUP-39 WHEN a prop of the file upload or of a row changes while it is shown THE file upload SHALL draw and expose the new value at once, keeping its focus, SHALL give a prop that is its file picker's to the file picker and a prop that is its file items' to every file item in the same turn (FPK-28, FIT-30), and SHALL announce only what FUP-26 names. WHEN accept, maxSize, maxFiles or multiple changes THE file upload SHALL judge the room by the new value from the next choice on and SHALL NOT judge again, mark or remove the rows it already holds.
FUP-40 WHILE the system's icon set cannot yet draw a glyph (its font is still loading) THE file upload SHALL draw nothing of its own in a glyph's place: the glyphs of a row are the file item's (FIT-31) and the glyph of the drop area the file picker's (FPK-29).
FUP-41 IF the file upload holds a child that is neither its trigger, its glyph nor a file item THE file upload SHALL NOT draw it and in debug builds report it; a trigger that is not a Button is answered by the file picker (FPK-30). A platform that cannot inspect its content SHALL state the rule in its README and docs page.
FUP-42 THE file upload SHALL hold its rows in one of two ways, as rows names. own, the default: it keeps the files a person chose and draws a row for each itself (FUP-43 to FUP-51), so that a form works with the file upload placed in it and nothing written. given: the product gives the rows, and the file upload draws them and keeps no file, as FUP-9, FUP-17, FUP-21 and FUP-31 say. The product gives rows by saying so in the platform's idiom — on the web by the value given of rows; where rows are a list the product passes (SwiftUI, Compose, Flutter, React Native) by passing that list, an empty one included, and by leaving it out for own — and never by what the list happens to hold: a product that owns its rows owns them while it has none. WHILE rows is own THE file upload SHALL NOT draw a row the product gives and in debug builds SHALL report it, naming rows; WHILE rows is given it SHALL keep no file, SHALL give nothing to read and take nothing by FUP-48, and in debug builds SHALL report a file handed to it that way. WHEN rows names a value the file upload does not know THE file upload SHALL behave as own and in debug builds report it.
FUP-43 WHEN files arrive — chosen in the picker or dropped on the area, and judged by its file picker (FPK-9), or handed over by the product and judged by the same rule (FUP-48, FPK-31) — WHILE rows is own THE file upload SHALL first remove the rows of the files an earlier arrival refused (FUP-45); SHALL pass over, as if it had not arrived, a file that passed and that it already keeps, and a second copy of a file within the same arrival — the same file being one with the same name, size and time of last change as the platform reports them, or the same location where the platform hands over locations — so that no file is kept twice and none is refused for being chosen again; and, WHILE multiple is true and maxFiles is a number, SHALL refuse with the reason count each remaining file beyond maxFiles less the files it keeps, in the order the platform gave them. WHILE multiple is true it SHALL keep each file that passed after the files it already keeps, in the order the platform gave them: a second choice adds to the first and replaces nothing. WHILE multiple is false and a file passed, it SHALL let go of every file it keeps and keep the new one in their place; a choice that is refused SHALL replace nothing. It SHALL then draw a row for each file it keeps (FUP-44) and for each file it refused (FUP-45) and emit as FUP-47 says. It SHALL NOT move focus because files arrived, except as FUP-30 says where a row that held focus left.
FUP-44 WHILE rows is own THE file upload SHALL draw each file it keeps as a row, in the order it keeps them: a file item (FIT-1 to FIT-33), in the form the file upload gives every row (FUP-58), that is given the platform's handle of the file, whose name and mediaType are the file's as the platform reports them, whose status is pending, and whose formatText, statusText, errorText, progress and previewSrc are not given — the glyph of the file's format, or its picture WHILE itemPreview is true and the file is an image (FIT-6), the name, no status in words, no bar, and the remove action alone: never a retry action, because the file upload sends nothing that could be tried again, and never a pressable body or a link, because the file upload opens nothing and has no address of the file. As that row's sizeText it SHALL draw what the product's own function writes for the file's size in bytes, WHERE the product gave the file upload such a function in the platform's idiom, and nothing WHERE it gave none: THE file upload SHALL NOT write a size itself, because a number with a unit is words, and whether a megabyte is 1000 kilobytes or 1024 is the product's to say. A function that was not given SHALL NOT be reported: a row is whole with its name. THE file upload SHALL draw no status, no progress and no result of a sending on a row of a file it keeps, whatever the product does with the file afterwards.
FUP-45 WHILE rows is own THE file upload SHALL draw each file the last arrival refused as a row after the rows of the files it keeps — first the files its file picker refused, in the order it refused them, then the files refused for want of room, in the order the platform gave them — as a file item whose status is error, which is not retryable, and which is given no handle of the file and so draws no picture: the glyph of the file's format, the file's name, its size as FUP-44 says, the remove action, and the error mark before its errorText, which is the product's sentence for the reason — typeErrorText for type, sizeErrorText for size, countErrorText for count (FUP-53). A refused file SHALL NOT be kept: it is not among the files the product reads (FUP-48), is not submitted (FUP-49), does not count toward maxFiles and does not satisfy required. Its row SHALL stay until the person removes it (FUP-46), until files next arrive (FUP-43), or until the kept files are cleared (FUP-48, FUP-49), and THE file upload SHALL NOT set its own error condition for it (FUP-16). WHERE the platform delivers no drop over an area that refuses (the web, once the file picker has told it that letting go there does nothing, FPK-13) no file arrived: THE file upload SHALL draw no row and say nothing for it, and the area's refusing look is what the person was told. IF neither the instance nor the product's words give the sentence for a reason THE file upload SHALL still draw the row, with its name and the error mark, and in debug builds SHALL report the missing word once per page or screen, naming the prop.
FUP-46 WHEN the person activates the remove action of a row by pointer, touch, Enter or Space WHILE rows is own THE file upload SHALL remove that row itself, at once and without asking again; WHERE the row was a file it keeps, SHALL let go of the file and emit change exactly once (FUP-47); WHERE it was a refused file, SHALL emit nothing, because the files it keeps did not change; and SHALL move focus as FUP-30 says: to the remove action of the row that followed, or of the row before WHERE none follows, or to the trigger WHERE no row is left. It SHALL emit neither remove nor retry: those are events of the product's rows, and here there are none.
FUP-47 WHILE rows is own THE file upload SHALL emit for one choice or one drop, once the files it keeps and the rows are as FUP-43 leaves them, and in this order: select once with the files it newly kept, WHERE there are any; reject once with the refused files and their reasons, WHERE there are any, as FUP-9 says; and change once, WHERE the files it keeps are not the same as before. WHEN the person removes a file it keeps (FUP-46) it SHALL emit change and nothing else. change SHALL carry the files now kept, in order, as the platform's own handles. THE file upload SHALL emit none of the three for what the product did itself — files handed over or the kept files cleared (FUP-48), a form reset (FUP-49), a change of rows (FUP-52) — as a control's events tell what a person did.
FUP-48 WHILE rows is own THE file upload SHALL offer the product, in the platform's idiom: the files it keeps, to read at any time, in order and as the platform's own handles — the product may send them itself, then or later, and the file upload draws nothing of that (FUP-44); a way to hand it files, which it SHALL have its file picker judge (FPK-31) and SHALL then treat as an arrival (FUP-43) — kept or refused, drawn and announced (FUP-50) as a choice is, and without an event (FUP-47) — answering with the refused files and their reasons; and a way to clear, which SHALL let go of every file it keeps and remove every row, kept and refused, SHALL announce nothing, and SHALL move focus by FUP-30 WHERE a row held it. What it gives to read SHALL be a copy of its list: changing the copy SHALL change nothing that is kept. All three SHALL work WHILE disabled is true: disabled stops the person, not the product.
FUP-49 WHERE the platform has forms that submit named values (the web) WHILE rows is own THE file upload SHALL take part in its form as one control: it SHALL submit under its own name every file it keeps, in the order it keeps them and as the platform's own handles, WHILE the file upload is not disabled, and nothing otherwise — never a refused file, and no empty entry while it keeps none; SHALL be disabled WHILE the form disables the set it is in; and SHALL report a missing value to the form's own validation, with the platform's own message, WHILE required is true and it keeps no file, and no longer once it keeps one. WHEN the form is reset THE file upload SHALL do what clearing does (FUP-48). WHEN the form is submitted THE file upload SHALL keep its files and its rows: what follows a submission is the product's. THE file upload SHALL give the platform nothing to restore: WHEN a page is restored — a return through history, a session brought back — it SHALL begin with no file and no row and say nothing of the files it had, because a handle does not outlast the page and a row for a file that can no longer be sent would be untrue. WHILE it has no name THE file upload SHALL submit nothing and still keep and draw its files, for a product that reads them (FUP-48) and sends them itself.
FUP-50 WHILE rows is own THE file upload SHALL announce through the one live region of FUP-26, politely and once for each arrival: the name of each file it newly kept and, for each refused file, its name and its sentence (FUP-45), as one message in the order of the rows. The names are the files' own and the sentences the product's, so that the file upload still says no word of its own. WHERE the product sets or changes announcement in the same turn — in its answer to select, reject or change — THE file upload SHALL say announcement alone. THE file upload SHALL NOT announce a removal, a clearing or a reset: after a removal focus rests on the next row's remove action, which is described by that file's name, or on the trigger, whose description holds emptyText once no row is left (FUP-24, FUP-30). It SHALL NOT announce a file it passed over (FUP-43), and SHALL build no count of files.
FUP-51 THE file upload SHALL hold a file it keeps as the platform's own handle and nothing more: it SHALL NOT read it, copy it, make an address or a picture for it, or measure it beyond the size the platform reports (FUP-7). It SHALL give the handle to the file item that draws the file, which makes nothing of it WHILE itemPreview is false, so that a hundred files kept cost a hundred handles; WHILE itemPreview is true the file item of an image makes what the platform needs to draw its picture and releases it as FIT-7 says — only once that row is in or near what is in view, at most one for a row, and never beyond the life of the row. It SHALL let go of a handle, and take it from the row's file item, WHEN its row is removed (FUP-46), WHEN another file takes its place (FUP-43), WHEN the kept files are cleared or the form is reset (FUP-48, FUP-49) and WHEN rows becomes given (FUP-52); and SHALL keep its files WHILE it is taken out of the page or the screen and put back, as a control keeps its value.
FUP-52 WHEN rows changes from own to given while the file upload is shown THE file upload SHALL let go of every file it keeps, remove every row it drew itself, and draw the rows the product gives from then on; WHEN it changes from given to own THE file upload SHALL begin with no kept file and stop drawing the product's rows (FUP-42). In both directions it SHALL emit nothing and announce nothing, SHALL move focus to the trigger WHERE a row that leaves held it, and SHALL carry no file across by itself: a product that changes its mind reads the kept files first and gives them to rows of its own, or hands the files of its rows over (FUP-48).
FUP-53 THE file upload SHALL take typeErrorText, sizeErrorText and countErrorText from the instance's prop and, WHILE that is empty, from the product's words of the keys fileTypeErrorText, fileSizeErrorText and fileCountErrorText, and from nowhere else (FUP-27); SHALL read them only WHILE rows is own, and need one only once a file was refused for its reason; and SHALL build no sentence for a refusal out of accept, maxSize, maxFiles or a file's kind or size.
FUP-54 WHILE rows is own everything this spec and the file item's say of a row, of the list and of what stands around them SHALL hold for the rows the file upload makes itself as for the product's: the arrangement (FUP-17); the file item's layout, its name that a row never cuts, the states of its actions, their focus ring and their target (FIT-2, FIT-3, FIT-14, FIT-18); emptyText while the list holds no row (FUP-2); disabled — the rows stay readable, the remove actions are disabled and leave the focus order, and the person chooses, drops and removes nothing (FUP-28); rows that appear and leave at once, without a motion (FUP-29); enlarged text (FUP-33); forced colors (FUP-34); and direction, with each file's name laid out in its own (FUP-35 to FUP-37). WHILE disabled is true THE file upload SHALL emit no change.
FUP-55 WHEN accept, maxSize, maxFiles or multiple changes WHILE rows is own THE file upload SHALL judge by the new value from the next arrival on and SHALL NOT judge again, mark or let go of a file it keeps (FUP-39): files kept under multiple stay WHEN multiple becomes false, and the next file that passes takes the place of them all (FUP-43). WHEN the product's function for sizes or one of the three sentences changes THE file upload SHALL draw the rows it holds with the new text at once and announce nothing.
FUP-56 THE file upload SHALL hold exactly one file picker, as its member, and SHALL build no choosing of its own: the Button and the glyph the product places in the file upload's trigger and icon slots SHALL be the file picker's trigger and glyph (FPK-5, FPK-4), and layout, multiple, accept, maxSize, constraintsText, dropText, disabled, error and required SHALL be given to the file picker as they are given to the file upload. THE file upload SHALL give its file picker no maxFiles, because the room that is left is measured against what the file upload holds (FUP-9, FUP-43), and no label (FUP-24). THE file upload SHALL draw the file picker from the file picker's own tokens and SHALL give it no look, no state and no size that a file picker standing alone does not have. Everything the file picker's sentences say (FPK-1 to FPK-31) SHALL hold for it inside the file upload, with two differences: it is named, described and put in its states by the file upload (FUP-24, FUP-32), and its two events are answered by the file upload (FUP-57).
FUP-57 WHEN its file picker emits select or reject THE file upload SHALL answer them itself and SHALL NOT let them reach the product as they are: the product SHALL hear the file upload's own select, reject and change (FUP-9, FUP-47), which tell what the file upload did with the choice — the files that also found room and, WHILE rows is own, the files it newly kept — so that one choice is told once.
FUP-58 THE file upload SHALL draw every row as a file item, its member, and SHALL build no row of its own: WHILE rows is given the file items are the product's own, placed in the file upload in the platform's idiom, and WHILE rows is own the file upload makes one for each file it keeps and for each file it refused (FUP-44, FUP-45). It SHALL give every file item it holds, the product's included, form as itemForm, preview as itemPreview, fit as itemFit, nameHidden as itemNameHidden, width as itemWidth, height as itemHeight, removeLabel and retryLabel as its own, and disabled WHILE it is disabled itself, so that the rows of one file upload are of one form; a file item of the product's that sets one of these otherwise SHALL be drawn as the file upload says, and in debug builds reported. Everything else of a file item — its name, its format and its glyph, its texts, its status, the address of its picture, whether it is pressable or a link, whether it is read-only — SHALL stay the row's own. THE file upload SHALL draw a file item from the file item's own tokens and SHALL give it no look, no state and no size that a file item standing alone does not have. Everything the file item's sentences say (FIT-1 to FIT-33) SHALL hold for it inside the file upload, with three differences: it is an item of the file upload's list (FUP-25); what is said of it is said through the file upload's one live region (FUP-26, FUP-50); and WHILE rows is own its remove is answered by the file upload (FUP-46).
FUP-59 WHILE reorderable is true THE file upload SHALL make its List reorderable (LST-31 to LST-47) — a handle on every row, the whole row dragged by a pointer, the move menu from the handle and the keyboard from the handle — with the nine words given to it as they are given to the file upload, and WHEN the List emits move (LST-33, LST-35, LST-39): WHILE rows is given THE file upload SHALL emit move once with the same from and to and change nothing, the product's file items being the product's to put in their new place; WHILE rows is own THE file upload SHALL put the file in its new place among the files it keeps, draw the rows in the new order, submit the kept files in that order (FUP-49) and emit change exactly once with the files now kept (FUP-47), and SHALL change nothing for a move that would put the row of a refused file among the rows of the files it keeps, or the row of a kept file after a refused one (FUP-45), so that the List takes it as refused (LST-43). WHILE the file upload is disabled THE file upload SHALL change no order and emit no move, as it emits no other event (FUP-28).
FUP-60 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 file upload 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 its group (FUP-24) — an id reference by the elements it names in the host's tree or a tree around it, a description after its own — except those the file upload owns (aria-label, aria-labelledby, aria-disabled, aria-required, aria-invalid, aria-errormessage), which it SHALL move and not expose: its name is label, or its Field's (FUP-24, FUP-32), and its states are its props, which its file picker exposes (FPK-17). 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 file upload SHALL take it off that element. WHEN the product sets an aria- attribute the file upload owns THE file upload SHALL report it in debug builds.

各平台名称对照

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

规格 Web SwiftUI Jetpack Compose Flutter React Native
组件 tanrei-file-upload TanreiFileUpload TanreiFileUpload TanreiFileUpload FileUpload
layout · 属性 layout layout layout layout layout
rows · 属性 rows items items items items
itemForm · 属性 itemForm itemForm itemForm itemForm itemForm
itemPreview · 属性 itemPreview itemPreview itemPreview itemPreview itemPreview
itemFit · 属性 itemFit itemFit itemFit itemFit itemFit
itemNameHidden · 属性 itemNameHidden itemNameHidden itemNameHidden itemNameHidden itemNameHidden
itemWidth · 属性 itemWidth itemWidth itemWidth itemWidth itemWidth
itemHeight · 属性 itemHeight itemHeight itemHeight itemHeight itemHeight
reorderable · 属性 reorderable onMove onMove onMove reorderable
multiple · 属性 multiple allowsMultipleSelection multiple multiple multiple
accept · 属性 accept allowedContentTypes mimeTypes accept accept
maxSize · 属性 maxSize maxSize maxSize maxSize maxSize
maxFiles · 属性 maxFiles maxFiles maxFiles maxFiles maxFiles
label · 属性 label label contentDescription semanticLabel accessibilityLabel
constraintsText · 属性 constraintsText constraintsText constraintsText constraintsText constraintsText
dropText · 属性 dropText dropText dropText dropText dropText
emptyText · 属性 emptyText emptyText emptyText emptyText emptyText
removeLabel · 属性 removeLabel removeLabel removeContentDescription removeSemanticLabel removeAccessibilityLabel
retryLabel · 属性 retryLabel retryLabel retryContentDescription retrySemanticLabel retryAccessibilityLabel
dragHandleLabel · 属性 dragHandleLabel dragHandleLabel dragHandleLabel handleSemanticLabel dragHandleLabel
dragHandleHint · 属性 dragHandleHint dragHandleHint dragHandleHint handleSemanticHint dragHandleHint
moveToStartLabel · 属性 moveToStartLabel moveToStartLabel moveToStartLabel moveToStartLabel moveToStartLabel
moveBeforeLabel · 属性 moveBeforeLabel moveBeforeLabel moveBeforeLabel moveBeforeLabel moveBeforeLabel
moveAfterLabel · 属性 moveAfterLabel moveAfterLabel moveAfterLabel moveAfterLabel moveAfterLabel
moveToEndLabel · 属性 moveToEndLabel moveToEndLabel moveToEndLabel moveToEndLabel moveToEndLabel
liftedText · 属性 liftedText liftedText liftedText liftedText liftedText
movedText · 属性 movedText movedText movedText movedText movedText
cancelledText · 属性 cancelledText cancelledText cancelledText cancelledText cancelledText
typeErrorText · 属性 typeErrorText typeErrorText typeErrorText typeErrorText typeErrorText
sizeErrorText · 属性 sizeErrorText sizeErrorText sizeErrorText sizeErrorText sizeErrorText
countErrorText · 属性 countErrorText countErrorText countErrorText countErrorText countErrorText
announcement · 属性 announcement announcement announcement announcement announcement
disabled · 属性 disabled disabled enabled enabled disabled
error · 属性 error isError isError isError error
required · 属性 required isRequired required required required
select · 事件 select onSelect onSelect onSelect onSelect
reject · 事件 reject onReject onReject onReject onReject
change · 事件 change onChange onChange onChanged onChange
move · 事件 move onMove onMove onMove onMove
备注 A row is a File item: **every row is a tanrei-file-item** — the product's own child in the default slot while rows="given", and one the element makes in its shadow root for each file it keeps or refused while rows is own. The six forwarded attributes item-form, item-preview, item-fit, item-name-hidden, item-width, item-height, with remove-label, retry-label and disabled, are given to every file item through an internal channel that is not part of the file item's public surface, and win over what a product's item says of them (FUP-58); through the same channel each item is told that it is not an item of a list itself (role="group", FUP-25): the List's items are. The list is a <tanrei-list part="list"> in the shadow root, one <tanrei-list-item> per row named by the file's name: vertical, or with item-form="tile" horizontal with wrap; its gap is small. While rows="given" the shadow root assigns its slots by hand (slotAssignment: "manual"): each product row to a slot of its own inside its list item, the trigger and the glyph to the two forwarding slots, and anything else to nothing (FUP-41). reorderable and the nine words (drag-handle-label …) are given to the List; the List's move is answered inside, and while rows="given" the file upload dispatches its own move (detail: { from, to }, bubbles). While rows is own each made item is given its File as its file property, and the item, not this element, makes and revokes the object URL of a picture (FIT-7, FUP-51); item-preview absent, none is ever made. A product's click, remove and retry listeners hear the items' own events, which bubble out of the light DOM; those of made items are handled inside and do not leave the shadow root. The parts of a made item leave the file upload under the prefix item- (exportparts="container: item-container, media: item-media, icon: item-icon, preview: item-preview, headline: item-headline, supporting-text: item-supporting-text, status-icon: item-status-icon, progress: item-progress, action: item-action"), since three of them would collide with the file picker's; a product's own item is styled as itself. A custom element with a shadow root that holds **its member's own element**. An instance: <tanrei-file-upload layout="drop-area" multiple accept=".pdf,image/png" max-size="10000000" drop-text="…" constraints-text="…" remove-label="…" name="attachments"><tanrei-button slot="trigger" variant="outlined">Choose files</tanrei-button></tanrei-file-upload>. The shadow root is <tanrei-file-picker exportparts="container, icon, label, supporting-text, selection-text"><slot name="trigger" slot="trigger"></slot><slot name="icon" slot="icon"></slot></tanrei-file-picker>, then the list and the live region. **Children passed on**: the product's trigger and glyph stay in the product's light DOM and reach the file picker through the two forwarding slots, so the trigger is still the product's own tanrei-button, focusable where it stands; the product's rows are assigned to slots of the file upload's own, one inside each list item. **Props**: the nine forwarded attributes are reflected onto the file picker as they change; max-files and label are not set on it, and selection-text is set to empty-text (or the product's noFileText) while the list holds no row. **Events**: the file picker's select and reject are heard on the member and stopped there; the file upload dispatches its own select, reject and change from its host (FUP-57). **Focus**: no delegatesFocus — the one stop the file picker has is the slotted trigger, which is outside every shadow root; a press on the Field's label calls focus() on it (FUP-32). **The Field's connection**: the file upload is the Field's control and asks for the connection; the file picker inside asks too, and the file upload answers it in the Field's place with the description and the three states and with no name (FUP-24, FPK-21). **Parts**: the file picker's parts leave under the file picker's names; list is the file upload's own. **Tokens**: nothing is written on the member — --<namespace>-comp-file-picker-… set on the file upload or above it is inherited into the file picker. The validation message is anchored on the slotted trigger, a descendant of the host. Form-associated (static formAssociated = true): ElementInternals.setFormValue receives a FormData with one entry per row that has a file, under the name attribute; setValidity({ valueMissing }) carries FUP-31 with the message of a native required file input. The live region is one visually hidden aria-live="polite" element; row changes are collected until the task that made them is over, and then written once. The file upload keeps its own files: the attribute rows is own (the default, also when absent or unknown) or given; the product's rows are drawn only with rows="given", and a tanrei-file-item child while rows is own is not drawn and is reported. <form><tanrei-file-upload name="attachments" multiple remove-label="…"><tanrei-button slot="trigger" variant="outlined">Choose files</tanrei-button></tanrei-file-upload></form> is the whole of a plain form's markup. While rows is own: the element keeps File objects in a private list and renders one tanrei-file-item for each, and for each refused file, in its own shadow root (the same row element, so a row is built once, in the file item; their remove is handled inside and does not leave the shadow root); the same file is one with equal name, size and lastModified. files is a read-only property returning a new File[] on each read; add(files: Iterable<File>): { file: File, reason: 'type' | 'size' | 'count' }[] hands files over and returns the refused; clear(): void empties; formatSize is a property, never an attribute, ((bytes: number) => string) | null, null by default — upload.formatSize = (bytes) => megabytes.format(bytes / 1e6). change is a CustomEvent that bubbles, detail.files a File[]; select and reject keep their details. Attributes type-error-text, size-error-text, count-error-text; left empty they read setWords({ fileTypeErrorText, fileSizeErrorText, fileCountErrorText }). The form: setFormValue(formData, null) with one entry per kept file under name — the second argument null, so that the browser has no state to restore, and formStateRestoreCallback restores nothing; formResetCallback does what clear() does; setValidity({ valueMissing }) while required and files is empty, anchored on the trigger, with the message of a native required file input. A native file input with no file submits one empty entry and this element submits none (FUP-49): a server that reads the field checks for both. A product that sends the files itself gives no name, reads files in its own submit handler, and — for a status and a bar on each row — uses rows="given". A drop over an area that refuses delivers no drop event in a browser (the file picker has set dropEffect to none), so nothing arrives and no refused row is drawn for it (FUP-45): a refused row comes from a choice in the picker, from a drop the area could not judge beforehand (a size, an extension, the room that is left) and from add(), which calls the file picker's judge() (FUP-48). A fixture whose rows is own and that holds file items cannot be markup alone, since a file is no attribute, and the fixture format has no field that asks for a step in script: the harness, the gallery and the documentation's live samples read rows and the items, and after the element is defined set formatSize and then call add() once with one File per file item, in order — named by the item's name, holding as many zero bytes as its place among the items (1, 2, 3 …), so that no two are the same file (FUP-43), and with one fixed lastModified. formatSize answers each of those sizes with its item's sizeText, and is left null where no item has one. The six own-* fixtures are the ones staged so; nothing is recorded before add() has returned and the rows are rendered. React: FileUpload takes FileItem children while the product gives the rows, and gains rows, onChange, formatSize, the three texts, and a ref with files, add() and clear(). Each row is a TanreiFileItem (FIT-1 to FIT-33), the same view a product places alone, given the six item props, the two action names and the disabled state by this view; the items the product passes are values of that view's props. The view is also built from TanreiFilePicker (FPK-1 to FPK-31) and passes its trigger, its glyph and the nine forwarded props on to it; what this note says of the picker, of accept and of the drop target is that component's, and stays here until this platform is built. TanreiFileUpload(items: [TanreiFileItem], layout: .dropArea, onSelect: { urls in … }) { TanreiButton("Choose files") { } }. The picker is fileImporter(isPresented:allowedContentTypes:allowsMultipleSelection:); accept is a list of UTType (an extension or a media type is resolved to one), and the files are security-scoped URLs the product opens and releases. The drop area is dropDestination(for: URL.self) with its isTargeted binding for the receiving condition; SwiftUI tells the kinds before the drop, not sizes. With VoiceOver the area is a drop point named by dropText. disabled follows .disabled(_:) and the environment; the Field's name, description and states arrive through the environment. The announcement is AccessibilityNotification.Announcement, one per turn of the run loop. No form submission (FUP-31 does not apply). rows is which initialiser is used — TanreiFileUpload(items:) is given, also with an empty array; without items the view keeps its own files. TanreiFileUpload(files: $urls) { TanreiButton("Choose files") { } } binds the kept files to the product's [URL] (reading them is reading the binding; handing over and clearing are writing it, judged by FUP-43), and without the binding the view keeps them in its own state for onChange to report. The view starts and stops security-scoped access for a URL it keeps and releases it when it lets go (FUP-51). formatSize: ((Int64) -> String)?; the same file is the same standardised URL. No form: FUP-49 does not apply, and a product reads the binding when it sends. The rows arranged by the List and the reorderable form (FUP-59) are not built on this platform yet. Each row is a TanreiFileItem (FIT-1 to FIT-33), the same view a product places alone, given the six item props, the two action names and the disabled state by this view; the items the product passes are values of that view's props. The view is also built from TanreiFilePicker (FPK-1 to FPK-31) and passes its trigger, its glyph and the nine forwarded props on to it; what this note says of the picker, of accept and of the drop target is that component's, and stays here until this platform is built. TanreiFileUpload(items = …, onSelect = { uris -> … }, trigger = { TanreiButton(onClick = {}) { Text("Choose files") } }). The picker is rememberLauncherForActivityResult with OpenDocument or OpenMultipleDocuments; accept is given as MIME types (an extension is resolved through MimeTypeMap), and the files are content Uris whose name and size come from the resolver's metadata, not from reading them. The drop area is Modifier.dragAndDropTarget, which is meaningful on large screens and with a pointer; on a phone the drop-area layout is still drawn and still pressed. disabled is the inverse flag enabled. The group is semantics(mergeDescendants = false) { contentDescription }; rows carry CollectionItemInfo; the announcement is a liveRegion = Polite node. No form submission. rows is whether items is passed — items = null (the default) is own, a list, also an empty one, is given. TanreiFileUpload(state = rememberTanreiFileUploadState(), trigger = { … }): the state holder has files: List<Uri> (a snapshot), add(uris) and clear(), and survives recomposition, not process death (FUP-49's restoration: it begins empty). formatSize: ((Long) -> String)? = null; the same file is the same Uri. A kept Uri is held with the read permission the picker granted, and nothing is opened. No form. The rows arranged by the List and the reorderable form (FUP-59) are not built on this platform yet. Each row is a TanreiFileItem (FIT-1 to FIT-33), the same view a product places alone, given the six item props, the two action names and the disabled state by this view; the items the product passes are values of that view's props. The view is also built from TanreiFilePicker (FPK-1 to FPK-31) and passes its trigger, its glyph and the nine forwarded props on to it; what this note says of the picker, of accept and of the drop target is that component's, and stays here until this platform is built. The framework has no file picker and no drop target for files; both are plugins, and the package depends on none. TanreiFileUpload(items: …, trigger: TanreiButton(…), onPick: (request) async => …, onSelect: …): onPick is the product's function that opens whatever picker it uses with the request's multiple and accept and returns the files as TanreiPickedFile(name, size, mimeType, handle); the file upload judges them by FUP-9 and emits. Drops arrive the same way: the product's drop plugin calls TanreiFileUploadController.dragEntered / dragExited / dropped(files), and without it the drop-area layout is drawn and pressed and takes no drop — the README says so. disabled is the inverse flag enabled. The announcement is SemanticsService.announce. No form submission. Whether the package should depend on a picker plugin instead is not settled. rows is whether items is passed — null (the default) is own, a list, also an empty one, is given. The TanreiFileUploadController that already takes drops gains files (an unmodifiable view), add(files) and clear(); without a controller the widget makes its own. formatSize: String Function(int bytes)?; the same file is the same handle, or the same name, size and time of last change where the product's picker gives no location. No form submission: inside a Form it is a FormField whose value is the kept files, so that reset() clears and a validator of the product's can ask for one. The rows arranged by the List and the reorderable form (FUP-59) are not built on this platform yet. Each row is a FileItem (FIT-1 to FIT-33), the same view a product places alone, given the six item props, the two action names and the disabled state by this view; the items the product passes are values of that view's props. The view is also built from FilePicker (FPK-1 to FPK-31) and passes its trigger, its glyph and the nine forwarded props on to it; what this note says of the picker, of accept and of the drop target is that component's, and stays here until this platform is built. React Native has no file picker of its own; the package depends on no picker library. <FileUpload items={…} trigger={<Button label="Choose files" />} onPick={async ({ multiple, accept }) => files} onSelect={…} />: onPick is the product's function that opens its picker and returns { name, size, type, uri }[]; the file upload judges them by FUP-9 and emits. No drop of files on phones: the drop-area layout is drawn and pressed and takes no drop, and the README says so. The announcement is AccessibilityInfo.announceForAccessibility. No form submission. Whether the package should depend on a picker plugin instead is not settled. rows is whether items is passed — undefined is own, an array, also an empty one, is given. Own is the uncontrolled form: <FileUpload ref={upload} trigger={…} onPick={…} onChange={(files) => …} />, the ref holding files, add(files) and clear(). formatSize?: (bytes: number) => string; the same file is the same uri. No form. The rows arranged by the List and the reorderable form (FUP-59) are not built on this platform yet.

令牌

令牌 引用 解析值(浅色)
--tanrei-comp-file-upload-list-gap sys.space.space200 16px

测试样例

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

生成自 specs/file-upload.spec.json, tokens/comp/file-upload.tokens.json, design/tokens/

Web 上绘制方式的不同之处

  • 这个元素的 shadow root 里有一个 <tanrei-file-picker>、列表和一个实时区域。rows="own" 时,列表里是文件上传创建的 <tanrei-file-item>;rows="given" 时,你的文件项留在你的 DOM 里,位于默认插槽。导入这个元素,两个成员也随之完成定义。
  • 你放进插槽的触发器和字形会转交给文件选择器,并留在你的 DOM 里。触发器仍然是你自己的按钮,在它原来的位置上就可以获得焦点。
  • 这个元素是与表单关联的元素,它的两个成员则不是:它们不向表单添加任何条目。
  • select、reject 和 change 是文件上传自己的 CustomEvent。文件选择器的 select 和 reject 不会离开文件上传。你给出的文件项的 click、remove 和 retry 会冒泡到你的监听器;文件上传创建的文件项的这些事件则在内部处理。
  • 部件:file-picker、list,以及文件上传创建的每个文件项对应的 file-item;文件选择器的 container、icon、label、supporting-text 和 selection-text 沿用各自的名称;文件上传创建的文件项的部件则加上前缀 item-:item-container、item-media、item-icon、item-preview、item-headline、item-supporting-text、item-status-icon、item-progress、item-action。你给出的文件项按它自己来设置样式:tanrei-file-item::part(icon)。
  • 设置 item-preview 且 rows="own" 时,每个文件项为自己的图片生成地址,并负责释放它。文件上传不生成任何地址。
  • 这个元素本身不能获得焦点:每个焦点停靠点要么是你的触发器,要么在某个文件项里面。

指南

选择与限制

选择的途径就是文件选择器的那一套:button 和 drop-area 两种布局、trigger 插槽里你自己的按钮、icon 插槽里的字形,以及 accept、maxSize 和 multiple 这几项限制,外加用来说明限制的 constraintsText。它的页面有完整的说明。它们在文件上传上是同名的属性。

文件上传在限制上添加的内容:

  • maxFiles 是整个列表最多容纳的文件数,而不是一次选择的上限。rows="own" 时,它计入保留的文件;rows="given" 时,它计入你给出的文件项。一次选择按还能再放的文件数来判定。
  • rows="own" 时,同一个文件选了两次,只保留一份。
  • rows="own" 时,每个被拒绝的文件都绘制成一个失败的行,带有你为它的原因写的句子:typeErrorText、sizeErrorText 或 countErrorText。把它们写在元素上,或者用 setWords() 为整个产品只给一次。
  • rows="given" 时,拒绝不会绘制任何东西。reject 会把每个被拒绝的文件连同原因交给你:type、size 或 count。请用你自己的话说明发生了什么:用字段的错误文本、一个失败的行,或者 announcement。
  • emptyText 是按钮布局中尚未选择任何文件时,显示在触发器旁边的文本。

行由谁持有

方面 rows="own"(默认) rows="given"
用在哪里 整体一次提交的表单。 自己发送每个文件的产品。
行 文件上传创建的文件项,每个保留的文件和每个被拒绝的文件各一个。 由你添加和移除的 <tanrei-file-item> 子元素。
第二次选择 有 multiple 时追加;没有时替换。同一个文件只保留一份。 由你决定。
被拒绝的文件 一个失败的行,带有你写的句子。 只有 reject。
一行显示什么 文件名、大小、移除。没有状态,也没有进度:没有任何东西在发送。 文件名、大小、状态、进度、错误、重试、移除。
移除 移除这一行,并触发 change。 触发这一行的 remove。由你来移除它。
表单的值 保留的文件。 你给了 file 的每一行的 file。
重置表单 清空列表。 什么都不改变。

文件上传绝不根据子元素来猜测模式。自己持有行的产品在第一次选择之前一行也没有,所以它要写明 rows="given"。

在文件上传显示期间更改 rows,会释放所有保留的文件,并且不触发任何事件。如果你需要这些文件,请先读取 files。

行还是图块

itemForm 决定所有文件的形式:默认的 row 是一列行;tile 是一排会换行的方框。同一个文件上传里的每个文件,都以同一种形式绘制。

属性 对应每个文件项的 作用
itemForm form row 或 tile。
itemPreview preview 在字形的位置绘制一张图片:文件项的 previewSrc 这个地址上的图片;文件是图片时,则是文件本身。
itemFit fit cover 或 contain。
itemNameHidden nameHidden 在绘制了图片的图块里,不绘制名称。
itemWidth, itemHeight width, height 图块的方框。

rows="own" 且设置了 itemPreview 时,保留的文件如果是图片,就显示它自己的图片。每张图片都有代价,这个代价是文件项的,写在它的页面上。不设置 itemPreview(默认)时,一百个保留的文件的开销就是一百个句柄,仅此而已。

顺序可由用户调整的文件

设置 reorderable 后,用户可以调整文件的顺序。每一行都有一个手柄,整行都可以拖动——用鼠标移动几个像素后、用手指长按后,或从手柄立即开始——其他行会滑开让位。手柄也是一个按钮:它会打开一个包含四种移动的菜单;用键盘时,Space 提起该行,方向键移动它,Space 放下,Escape 放回原处。拖动从来不是唯一的方式(WCAG 2.5.7),每次移动都会用你的措辞播报。

rows="own" 时,文件上传自己调整所保留文件的顺序,按新顺序提交,并发出 change。rows="given" 时,它只发出带有 from 和 to 的 move,什么也不移动:由你把自己的文件项放到新位置。

这些文字是列表的:drag-handle-label(如 调整{label}的顺序,填入文件名)、四个移动的文字和三个句子。

一个可调整顺序、自己保留文件的文件上传。先选择一些文件,然后拖动一行,或使用其手柄的菜单或键盘。不会发送任何内容。

选择文件
<tanrei-file-upload name="attachments" multiple reorderable remove-label="移除{name}"
  drag-handle-label="调整{label}的顺序" move-to-start-label="移到开头" move-before-label="上移"
  move-after-label="下移" move-to-end-label="移到末尾"
  lifted-text="已提起{label}:第{position}项,共{count}项" moved-text="{label}:第{position}项,共{count}项"
  cancelled-text="{label}已回到第{position}项,共{count}项">
  <tanrei-button slot="trigger" variant="outlined">选择文件</tanrei-button>
</tanrei-file-upload>

你给出的行

rows="given" 时,每一行都是你写的一个文件项,带有它自己的属性:value、name、mediaType、formatText、sizeText、status、statusText、progress、errorText、retryable、previewSrc、interactive、href 和 readOnly。每个属性绘制什么,写在它的页面上。文件上传自己创建的行,对保留的文件是一个 pending 的文件项,对被拒绝的文件是一个不带重试的 error 文件项。

status 名称前面 第二行 进度条 操作
pending 表示格式的字形 大小,然后是 statusText 无 移除
uploading 表示格式的字形 大小,然后是 statusText 填充到 progress;未设置 progress 时是不确定的形式 移除
done 表示格式的字形 大小,然后是对勾和 statusText 无 移除
error 表示格式的字形 大小,然后是错误标记和 errorText 无 retryable 时是重试,然后是移除
  • 大小由你自己格式化,再作为 sizeText 给出;rows="own" 时,则通过 formatSize 函数给出。1 MB 是 1000 KB 还是 1024 KB,由你的产品决定。
  • progress 是你对自己的发送所做的报告。组件绝不会自己移动进度条。
  • 文件上传交给每一行的东西,不要在某一行上单独设置:形式、预览、两个操作的名称、disabled。

在字段里

表单要显示标签、帮助文本、错误或必填标记时,把文件上传放进字段。字段绘制它们,并把它们交给文件上传。

<tanrei-field label="附件" required required-text="必填"
              error error-text="请选择不超过 10 MB 的 PDF。">
  <tanrei-file-upload name="attachment" accept=".pdf" max-size="10000000"
                      constraints-text="PDF,不超过 10 MB" remove-label="移除 {name}">
    <tanrei-button slot="trigger" variant="outlined">选择文件</tanrei-button>
  </tanrei-file-upload>
</tanrei-field>
  • 有 name 时,文件上传按顺序提交每个保留的文件。被拒绝的文件绝不会提交。没有保留任何文件时,它根本不提交任何条目;而原生的文件输入框会提交一个空条目,所以读取这个字段的服务器要同时检查这两种情况。
  • 没有保留任何文件时,required 会向表单报告缺少值。这条消息是浏览器自己的。用户读到的句子是字段的 errorText。按钮没有必填状态,所以必填的文件上传要放进字段:字段在表单提交之前就会说明这一点。
  • 重置表单会清空列表。提交之后,文件和行都会留着;请重置表单,或者调用 clear()。
  • 通过历史记录回到页面时,什么都不会恢复。文件上传从空的状态开始。
  • 没有 name 时,文件上传不提交任何东西,但仍然保留并列出它的文件。要自己发送它们,请在你自己的提交处理函数里读取 files。
  • rows="given" 时,值是每一个带有 file 的行的 file:row.file = file。这时重置什么都不改变,因为行是你的。
  • 文件上传绝不会自己设置 error。由你在字段上设置它,并用文字说明哪里不对。

文字

文件上传没有自己的文字。其中六项可以用 setWords() 为整个产品只给一次;其余的都是实例的属性。

属性 产品文字的键 没有它时
emptyText noFileText 触发器旁边什么都不绘制。
removeLabel removeLabel 移除操作照样绘制,但没有名称。调试构建会报告这一点。
retryLabel retryLabel 重试操作照样绘制,但没有名称。调试构建会报告这一点。只有你给出的行才有重试操作。
typeErrorText fileTypeErrorText 因种类被拒绝的文件绘制成一个没有句子的失败行。调试构建会报告这一点。
sizeErrorText fileSizeErrorText 同上,针对过大的文件。
countErrorText fileCountErrorText 同上,针对数量已满、放不进去的文件。

这三个句子只在 rows="own" 时读取,并且只有在某个文件因相应的原因被拒绝之后才会读取。文件的大小不是文字的键:它是 formatSize 函数,是实例的一个属性。

每个操作的名称里都有它的文件名:removeLabel 和 retryLabel 是带占位符 {name} 的句子,由文件项填入文件名(“移除 {name}”)。一个句子对每个文件都够用,用户按你的语言的语序听到“移除 brief.pdf,按钮”,屏幕阅读器列出页面上的按钮时也是如此。没有 {name} 的句子会让每个文件的操作同名,调试构建会报告这一点。

从右到左

在从右到左的布局中,触发器和行的字形位于右侧,行的操作位于左侧,其中移除在最外侧,进度条从右侧开始填充;一排图块从右侧开始排。用拉丁字母写的文件名保持自己的顺序,连同它的点和扩展名。字形只在图标集有相应标记时才镜像,图片则绝不镜像。

推荐

  • 把文件上传放进表单,让它来保留文件,除非你的产品自己发送每个文件。
  • 让行的列表保持简短,并按用户选择文件的顺序排列。
  • 在用户正看着的地方说明拒绝:字段的错误文本,或者一个失败的行。
  • 用 announcement 把许多变化概括成一句话:“已添加 40 个文件”。

避免

  • 不要把拖放区域当作选择文件的唯一途径。它里面始终有触发器。
  • 不要在它里面放触发器、图标和文件项以外的任何东西。其他内容不会绘制,调试构建会报告这一点。
  • 不要在没有 rows="given" 的情况下写行。它们不会绘制。
  • 不要用 rows="own" 来显示上传的进度。保留的文件的行没有状态,因为文件上传什么都不发送。
  • 不要在它里面显示数量、总大小或总进度条。请把这些绘制在它旁边,用你自己的话来写。

无障碍

角色
group
标注
A group named by label, or by the label of the Field around it, holding the file picker and the list. The file picker is given no name and adds no group; its trigger is a button named by its own label and described by constraintsText, by emptyText while nothing is chosen, and by the Field's help and, in error, its error text, and is exposed as required, invalid and disabled as the props say (FPK-17). The rows are a list, and each file item an item of it: it reads the file's name, its format, its size, its status or its error in words, a progress bar named by the file while it is sent, and its actions — buttons named retryLabel and removeLabel, each with the file's name in it (FIT-19).
最小目标
即使外观尺寸更小,点击区域也满足最小目标尺寸。 minimum 32px · pointer 44px · spacing 8px · touch 48px

键盘操作

按键 动作
Tab / Shift+Tab Moves focus to the trigger, then to each row's stops in the order of the rows — the body of a row that is pressable or a link, retry where a row has it, then remove. The drop area is not a stop, and a row that cannot be pressed is none.
Enter / Space On the trigger: opens the platform's file picker, as the Button's own activation. On a row's action: removes that file, where the file upload keeps its own; asks the product to retry or to remove it, where the product gives the rows. On a row's body that is pressable: the product opens the file (the file item's click).
The picker's own keys Inside the platform's picker everything is the platform's; leaving it without choosing returns focus to the trigger and changes nothing.

备注

  • Choosing is the file picker's, and so is everything a person meets while choosing: that dragging is never the only way, that the limits are words before the choice, that a drag over the area is not announced and is told without color (the file picker's notes; FPK-5, FPK-12, FPK-13, FPK-18, FPK-23). A file is shown by a file item, and everything a person meets on one file is the file item's: that its status is never color alone, that its name is its name in every form, that a failure is never hidden, how it is named and described (the file item's notes; FIT-4, FIT-8, FIT-19). This spec says what the file upload adds: the arrangement, which files there are, the one live region, where focus goes, the form.
  • Without script, and until the component is defined, on the web: the trigger is a button that opens nothing, no row is drawn and no file is submitted. A custom element cannot open the platform's picker or carry a file by markup alone, and the file upload does not pretend to: a page that must take files where script does not run writes a native file input in its own form, as it would without Tanrei, and loses only the drawing.
  • One live region, polite (FUP-26). A row says its own name and status when it is added, finishes or fails, in the product's words; many changes in one turn are one message, and the product can replace them with one sentence of its own (announcement). Progress is never announced: a person reads it on the row's progress bar when they go there.
  • A refused file must be said (WCAG 3.3.1), and only the product has the words. While rows is given, on reject it sets the Field's error text, adds a failed row with errorText, or sets announcement, and the file upload draws and says nothing for a refusal by itself, because it has no sentence for it. While rows is own the file upload draws a row for each refused file, with the error glyph, the file's name and the product's sentence for the reason (typeErrorText, sizeErrorText, countErrorText, or the product's words), and announces the name and the sentence (FUP-45, FUP-50); a product that gave no sentence is told so in debug builds.
  • Status is never color alone (WCAG 1.4.1): a done or failed row has words and a mark with a shape of its own, before those words (FIT-4). The glyph before the name says the file's format in every status.
  • Focus is never lost on removal (FUP-30): it goes to the next row's remove action, the previous one's, or the trigger.
  • The remove and retry actions are named once for the whole list and described by each file's name, so a person tabbing through them hears "Remove, button, brief.pdf" without the product writing a sentence per row.
  • A file's name is never cut in a column of rows (FIT-2): two names that differ only at the end must be told apart, and the extension is at the end. In a strip of tiles it is one line, cut in the middle so that its ending stays (FIT-9), and whole for assistive technology.
  • Compose and React Native have no list role for an arbitrary layout: the rows are a container with collection information (Compose) or a labelled container (React Native), and each row keeps its parts.
  • While the file upload keeps its own files (rows own, FUP-42 to FUP-55) a person hears what was added — each file's name, once, politely, in one message for one choice — and nothing of the file upload's own invention: no count and no word such as "added", which Tanrei does not have. A product that wants a sentence sets announcement in its answer to change, and that sentence is said alone.
  • A removal is told by where focus goes, not by a message (FUP-46, FUP-50): the next row's remove action is described by that file's name, and once the last row is gone focus is on the trigger, whose description holds emptyText ("No file chosen"). A form reset and a clearing by the product move focus the same way and announce nothing; a product that clears the list for a reason a person must hear says it with announcement.
  • A plain form's missing file is the form's own validation (FUP-49): the message is the platform's own, as for every Tanrei control, and stands at the trigger, which takes focus as an invalid control does; the product's own sentence is the errorText of the Field around the file upload, which is what is drawn and described there. The file upload sets no error condition itself, for a missing value or for a refused file (FUP-16). Before a submission, that a file is required is said only inside a Field, whose required text joins the trigger's description: where the trigger is a button the platform gives it no required state (the web), and the file upload has no word for it. A form that must say it frames the file upload in a Field; placed alone, with required, it works and reports the missing file only when the form validates.
  • The Field's fixture file-upload-required-error (the Field's spec) holds a file upload with no rows prop, so it is one that keeps its own files, with none kept: the trigger and the line of limits, no list and no emptyText, since none is given; the label, the required mark and the error text are the Field's, and the trigger is exposed as invalid and described by that error text (FUP-32). It is drawn as it was before rows existed. A missing file would be a missing value there (FUP-49), and what a person reads for it is the Field's errorText.
  • A fixture whose rows is own and that holds file items cannot be drawn from props alone, because a file is no value a fixture holds: each file item stands for one file the stage hands over, in order, by the platform's way of FUP-48, before anything is recorded — its name is the file's name, and its sizeText is what the stage's size function answers for that file; no other prop of the item is read, and whether the file is kept or refused is the component's own judging of the fixture's accept, multiple and maxFiles (FUP-43). That is the mark, and the only one: the fixture format has no field for a step after the component exists, so every harness, gallery and documentation renderer reads rows and the items. A fixture whose rows is given draws its items as the product's file items, as before. A file the stage hands over is empty, so no picture of it can be drawn: a staged row of an image shows the image glyph also where itemPreview is true (FIT-6), and a picture in a fixture comes from a row's previewSrc, which only a fixture whose rows is given has.
  • One level down: a file picker, and file items the product arranges. The product hears the file picker's select and reject, keeps the files, places a file item for each — in a column, in a strip, or each as the content of an item of a reorderable List — judges the room itself or sets the file picker's maxFiles to it, says what was added and refused through a live region of its own, moves focus when it removes one, and sends the files or gives them to a form control of its own. What it does not get by hand, because it is the state the file upload holds: the one message for many changes (FUP-26); the focus that goes on after a removal (FUP-30); the form's value (FUP-31, FUP-49); and that no file is kept twice (FUP-43). The picture is the same: a file item is the same element alone and inside a file upload.
  • A strip of tiles (itemForm tile) is still one list of items in the order of the files, read and tabbed through line by line in the reading direction; wrapping changes no order. A tile whose name is hidden is named by that name (FIT-8), and its error text is drawn beneath it.
  • Files in an order people change are the file upload's own reorderable form (FUP-59): its List gives every row a handle, lets the whole row be dragged, and offers the four moves in a menu and the keyboard from the handle, so that dragging is never the only way (WCAG 2.5.7) and the keyboard can do it (WCAG 2.1.1); where the row now stands is announced by the List in the product's words.

生成自 specs/file-upload.spec.json, tokens/sys/measure.tokens.json

组件负责的部分

  • 整体是一个组,由 label 命名,或者由包着它的字段命名。触发器是你的按钮。它的描述来自限制、说明尚未选择任何文件的文本,以及字段的帮助文本或错误文本。
  • 拖放区域是同一个按钮的一个大的指针目标。它没有角色,没有名称,在焦点顺序中没有停靠点,也不响应任何按键。
  • 各行是一个列表,每个文件项是其中的一项。每一项读出文件名、格式、大小,以及状态或错误。正在发送的文件有一个进度条,由它的文件命名。
  • Tab 先到触发器,然后依次到每个文件:先是它的主体(如果你把它设成了可按下的),再是重试,然后是移除。
  • 一个礼貌(polite)的实时区域用你的文字说出添加的行、完成的行和失败的行。同时到来的变化合成一条消息。进度绝不朗读。
  • rows="own" 时,一次选择就是一条消息:先是每个保留下来的文件的名称,然后是每个被拒绝的文件的名称,加上你写的句子。文件上传不添加任何文字,也不添加数量。如果想改说你自己的一句话,请在 change 监听器里设置 announcement。
  • 移除不会朗读。一行被移除后,焦点移到下一行的移除操作;后面没有行时,移到上一行的移除操作;一行都不剩时,移到触发器。rows="own" 时,文件上传自己完成这件事。
  • 文本放大到 200% 时,所有文本都会换行,没有任何内容被截断。在强制颜色模式下,区域保留一道边线,正在接收拖动的区域边线更粗,行的进度条仍然可见。

它在 Web 上的构造方式:文件选择器是文件上传的成员,位于它的 shadow root 里;文件选择器内部的选择器是一个真正的 <input type="file">,它看不见,不在焦点顺序中,也对辅助技术隐藏。触发器留在你的 DOM 里,你给出的文件项也是。组、列表、实时区域,以及文件上传自己创建的文件项,都在 shadow root 里。

Web 上的已知差距都是它的成员的差距:按钮没有必填状态,区域拒绝拖动时浏览器不会把放下的内容交给它,Safari 不会让被点击的按钮获得焦点,虚线边线无法贴合平滑过的圆角(见文件选择器的页面);会动的图片由浏览器播放(见文件项的页面)。只有 required 而文件上传外面没有字段时,只有表单的校验会说明缺少文件。

需要你做的事

  • 在 trigger 插槽里放一个按钮,它的标签要说明它做什么。
  • 给这个组一个 label,或者把文件上传放进字段。
  • 只要设置了 accept、maxSize 或 maxFiles,就要给出 constraintsText。
  • 给出 removeLabel:写在元素上,或者用 setWords() 只给一次。如果你给出的行可以重试,还要给出 retryLabel。
  • rows="own" 时,针对你设置的限制,给出被拒绝文件的三个句子:typeErrorText、sizeErrorText 和 countErrorText。
  • 每一次拒绝、每一次失败,都要用文字说明。
  • 许多行同时变化时,以及你移除行的方式必须让用户听到时,设置 announcement。