# 一次性密码字段

一组单字符文本输入，用于处理一次性密码验证。

## Features

- 模仿单个输入字段的键盘导航

- 粘贴时覆盖值

- 密码管理器自动填充支持

- 对数字和字母数字值的输入验证

- 完成后自动提交

- 隐藏输入以为表单数据提供单个值

## [结构](#结构)

导入所有部分并将其组合在一起。

```jsx
import { unstable_OneTimePasswordField as OneTimePasswordField } from "radix-ui";

export default () => (
  <OneTimePasswordField.Root>
    {/* 每个字符一个输入 */}

    <OneTimePasswordField.Input />

    {/* 单个隐藏输入用于存储完整值 */}

    <OneTimePasswordField.HiddenInput />
  </OneTimePasswordField.Root>
);
```

## [API 参考](#api-参考)

### [Root](#root)

包含一次性密码字段的所有部分。

| Prop             | Type       | Default          |
| ---------------- | ---------- | ---------------- |
| `asChild`        | `boolean`  | `false`          |
| `autoComplete`   | `enum`     | `one-time-code`  |
| `autoFocus`      | `boolean`  | No default value |
| `value`          | `string`   | No default value |
| `defaultValue`   | `string`   | No default value |
| `onValueChange`  | `function` | No default value |
| `autoSubmit`     | `boolean`  | `false`          |
| `onAutoSubmit`   | `function` | No default value |
| `disabled`       | `boolean`  | `false`          |
| `dir`            | `enum`     | `"ltr"`          |
| `orientation`    | `enum`     | `"vertical"`     |
| `form`           | `string`   | No default value |
| `name`           | `string`   | No default value |
| `placeholder`    | `string`   | No default value |
| `readOnly`       | `boolean`  | `false`          |
| `sanitizeValue`  | `function` | No default value |
| `type`           | `enum`     | `"text"`         |
| `validationType` | `enum`     | `"numeric"`      |

| Data attribute       | Values                       |
| -------------------- | ---------------------------- |
| `[data-orientation]` | `"vertical" \| "horizontal"` |

### [Input](#input)

呈现一个文本输入，表示值中的单个字符。

| Prop      | Type      | Default |
| --------- | --------- | ------- |
| `asChild` | `boolean` | `false` |

| Data attribute | Values                             |
| -------------- | ---------------------------------- |
| `[data-index]` | 与根字段值相对的字符索引对应的索引 |

### [HiddenInput](#hiddeninput)

| Prop      | Type      | Default |
| --------- | --------- | ------- |
| `asChild` | `boolean` | `false` |

## [示例](#示例)

### [基本用法](#基本用法)

```jsx
// 这将呈现一个包含 6 个输入的字段，用于 6 个字符的密码。

// 为每个字符的密码长度渲染一个 Input 组件。

<OneTimePasswordField.Root>
  <OneTimePasswordField.Input />

  <OneTimePasswordField.Input />

  <OneTimePasswordField.Input />

  <OneTimePasswordField.Input />

  <OneTimePasswordField.Input />

  <OneTimePasswordField.Input />

  <OneTimePasswordField.HiddenInput />
</OneTimePasswordField.Root>
```

### [分段控件](#分段控件)

`Root` 组件接受任意子项，因此在输入之间渲染视觉分隔列表就像在输入之间渲染分隔符一样简单。我们建议使用 `aria-hidden` 隐藏装饰元素，以防辅助技术，并避免在 `Root` 中渲染其他有意义的内容，因为每个子元素预计都属于具有 `group` 角色的父元素。

```jsx
<OneTimePasswordField.Root>
  <OneTimePasswordField.Input />

  <Separator.Root aria-hidden />

  <OneTimePasswordField.Input />

  <Separator.Root aria-hidden />

  <OneTimePasswordField.Input />

  <Separator.Root aria-hidden />

  <OneTimePasswordField.Input />

  <OneTimePasswordField.HiddenInput />
</OneTimePasswordField.Root>
```

### [输入密码时自动提交表单](#输入密码时自动提交表单)

使用 `autoSubmit` 属性，当所有输入都被填写时提交关联的表单。

```jsx
function Verify({ validCode }) {
  const PASSWORD_LENGTH = 6;

  function handleSubmit(event) {
    event.preventDefault();

    const formData = event.formData;

    if (formData.get("otp") === validCode) {
      redirect("/authenticated");
    } else {
      window.alert("无效的代码");
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <OneTimePasswordField.Root name="otp" autoSubmit>
        {PASSWORD_LENGTH.map((_, i) => (
          <OneTimePasswordField.Input key={i} />
        ))}

        {/* HiddenInput 是必需的，以便表单具有与字段相关联的数据 */}

        <OneTimePasswordField.HiddenInput />
      </OneTimePasswordField.Root>

      <button>提交</button>
    </form>
  );
}
```

### [受控值](#受控值)

```jsx
function Verify({ validCode }) {
  const [value, setValue] = React.useState("");

  const PASSWORD_LENGTH = 6;

  function handleSubmit() {
    if (value === validCode) {
      redirect("/authenticated");
    } else {
      window.alert("无效的代码");
    }
  }

  return (
    <OneTimePasswordField.Root
      autoSubmit
      value={value}
      onAutoSubmit={handleSubmit}
      onValueChange={setValue}
    >
      {PASSWORD_LENGTH.map((_, i) => (
        <OneTimePasswordField.Input key={i} />
      ))}
    </OneTimePasswordField.Root>
  );
}
```

## [可访问性](#可访问性)

撰写本文时，WCAG 指南中没有实施一次性密码字段作为单独输入的单一确定的模式。该行为旨在尽可能接近使字段像单个输入一样运行，并根据我们最初的研究、测试和反馈收集进行了一些例外。

此组件作为具有 `group` 角色的容器内的 `input` 元素实现，以指示子输入相关联。可以使用方向键进行导航和聚焦，键入输入将移动焦点到下一个输入，直到到达最后一个输入。

将值粘贴到字段中将替换所有输入的内容，无论当前聚焦的输入是什么。根据我们的研究，这似乎与大多数用户期望相符，因为值通常是从密码管理器或电子邮件中粘贴过来的。

### [键盘交互](#键盘交互)

| Key                   | Description                                                     |
| --------------------- | --------------------------------------------------------------- |
| `Enter`               | 如果找到关联的 `form`，将尝试提交                               |
| `Tab`                 | 将焦点移动到 `Root` 外部的下一个可聚焦元素                      |
| `Shift + Tab`         | 将焦点移动到 `Root` 外部的前一个可聚焦元素                      |
| `ArrowDown`           | 当 `orientation` 为 `vertical` 时，将焦点移动到下一个 `Input`   |
| `ArrowUp`             | 当 `orientation` 为 `vertical` 时，将焦点移动到前一个 `Input`   |
| `ArrowRight`          | 当 `orientation` 为 `horizontal` 时，将焦点移动到下一个 `Input` |
| `ArrowLeft`           | 当 `orientation` 为 `horizontal` 时，将焦点移动到前一个 `Input` |
| `Home`                | 将焦点移动到第一个 `Input`                                      |
| `End`                 | 将焦点移动到最后一个 `Input`                                    |
| `Delete`              | 删除当前聚焦的 `Input` 中的字符，并将以后的值向后移动           |
| `Backspace`           | 删除当前聚焦的 `Input` 中的字符，并将焦点移动到前一个 `Input`   |
| `Command + Backspace` | 清除所有 `Input` 元素的值                                       |

<!--$-->

<!--/$-->
