# 数字输入框

## 使用方法

`NumberInput` 是一个专门用于数字输入的组件，提供最小最大值限制、精度控制等功能。用户可以手动输入数字；需要加减按钮时加上 `hasNumberSteppers`。

基本用法只需要绑定一个数字值：

```vue
<script setup lang="ts">
import { NumberInput } from '@astryx-vue/core/NumberInput'
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  <NumberInput v-model="count" :min="0" :max="100" />
</template>
```

通过 `min` 和 `max` 属性设置数值范围，`step` 设置每次增减的步长。超出范围的值会被限制在有效区间内。步进按钮使用向上和向下的箭头。按钮的无障碍名称默认是英文，调用 `provideAstryxLocale('zh-CN')` 后改为中文；如果传了 `label`，名称里会带上它。

还有三个属性让字段贴合它装的数字：`isIntegerOnly` 让小数进不了值；`formatValue` 让字段闲着时显示易读的样子，比如 `1234 件`，聚焦后换回原始数字；`isWheelEnabled={false}` 让滚轮只滚页面、不改数值。

### 结构

- **标签**: 数字输入的标签。
- **说明**: 标签下面的补充说明。
- **图标**: 输入框里的可选图标。
- **占位**: 输入为空时显示的占位文字。
- **数字步进**: 可选按钮，按配置的步长加减。

## 最佳实践

| 指引 | 实践 |
| --- | --- |
| 推荐 | 为数值范围明确的场景设置 `min` 和 `max`。 |
| 推荐 | 根据实际需求设置合理的步长，如数量用 1，价格用 0.01。 |
| 推荐 | 在标签中说明单位（如"数量（件）"），而不是把单位放在输入框内。 |
| 避免 | 对大范围数值使用过小的步长，会让用户点击按钮很多次。 |

## 示例

### 可清除

进度从 0 到 100，带百分号和清除按钮。

```vue
<script setup lang="ts">
import { NumberInput } from '@astryx-vue/core/NumberInput'
import { ref } from 'vue'

const value = ref<number | null>(75)
</script>

<template>
  <div style="width: 300px; max-width: 100%">
    <NumberInput v-model="value" label="Progress" units="%" :min="0" :max="100" has-clear />
  </div>
</template>
```

### 范围

团队人数限制在 1 到 50，并说明这个数字代表什么。

```vue
<script setup lang="ts">
import { NumberInput } from '@astryx-vue/core/NumberInput'
import { ref } from 'vue'

const value = ref<number | null>(3)
</script>

<template>
  <div style="width: 300px; max-width: 100%">
    <NumberInput
      v-model="value"
      label="Team Size"
      placeholder="1–50"
      :min="1"
      :max="50"
      description="Number of people on the team"
    />
  </div>
</template>
```

### 校验状态

预算、人数和完成度上的错误、警告和成功。

```vue
<script setup lang="ts">
import { NumberInput } from '@astryx-vue/core/NumberInput'
import { VStack } from '@astryx-vue/core/Stack'
import { ref } from 'vue'

const error = ref<number | null>(-5)
const warning = ref<number | null>(150)
const success = ref<number | null>(25)
</script>

<template>
  <div style="width: 300px; max-width: 100%">
    <VStack :gap="4">
      <NumberInput
        v-model="error"
        label="Budget"
        :status="{ type: 'error', message: 'Must be a positive amount' }"
      />
      <NumberInput
        v-model="warning"
        label="Headcount"
        :status="{ type: 'warning', message: 'Exceeds typical team size' }"
      />
      <NumberInput
        v-model="success"
        label="Completion"
        units="%"
        :status="{ type: 'success', message: 'On track' }"
      />
    </VStack>
  </div>
</template>
```

### 单位

带百分号的折扣，范围是 0 到 100。

```vue
<script setup lang="ts">
import { NumberInput } from '@astryx-vue/core/NumberInput'
import { ref } from 'vue'

const value = ref<number | null>(50)
</script>

<template>
  <div style="width: 300px; max-width: 100%">
    <NumberInput
      v-model="value"
      label="Discount"
      placeholder="Enter discount"
      :min="0"
      :max="100"
      units="%"
    />
  </div>
</template>
```

## NumberInput Props

| 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `modelValue` | `number \| null` | `null` | 当前值。空值是 `null`。 |
| `label` | `string` | — | 字段标签，同时作为无障碍名称。 |
| `description` | `string` | — | 标签下方的说明。 |
| `placeholder` | `string` | `''` | 占位文本。 |
| `min` | `number \| null` | — | 最小值。离开或按 Enter 时，比它小的输入收到这个值。输入过程中先标成无效。 |
| `max` | `number \| null` | — | 最大值。离开或按 Enter 时，比它大的输入收到这个值。输入过程中先标成无效。 |
| `step` | `number \| null` | `1` | 每次增减的步长。 |
| `hasNumberSteppers` | `boolean` | `false` | 在输入框末尾显示加、减按钮。这一步不会改变值时，对应按钮置灰。 |
| `isWheelEnabled` | `boolean` | `true` | 输入框聚焦时，滚轮是否改变值。页面滚动应该优先的场景把它关掉。 |
| `isIntegerOnly` | `boolean` | `false` | 只提交整数。写小数时文字留在框里，离开输入框后丢弃。键盘也切到数字键盘。 |
| `formatValue` | `(value: number) => string` | — | 未聚焦时显示的文本，例如 `(v) => `${v} 件``。聚焦后显示原始数字方便编辑，格式化后的文本通过 `aria-valuetext` 暴露给读屏。 |
| `units` | `string \| null` | — | 显示在输入框末尾的单位，例如 %。 |
| `hasClear` | `boolean` | `false` | 有值时显示清除按钮。提示只说「清除」，读屏名称带字段名。清除后值为 `null`。把内容删空后离开，只有开了这个按钮才会提交 `null`，否则回到原值。 |
| `isDisabled` | `boolean` | `false` | 是否禁用。 |
| `disabledMessage` | `string` | — | 禁用原因。与 `isDisabled` 一起用时，悬停或键盘聚焦会显示提示；输入框通过 `aria-disabled` 保持可聚焦，不会被移出 Tab 顺序。 |
| `isReadOnly` | `boolean` | `false` | 值照常显示、照常提交，但不能编辑。只读时方向键、滚轮、加减按钮全部关闭。和 `isDisabled` 同时设置时以禁用以准。 |
| `isOptional` | `boolean` | `false` | 字段可选。与 `isRequired` 互斥。 |
| `isRequired` | `boolean` | `false` | 字段必填。与 `isOptional` 互斥。 |
| `isLabelHidden` | `boolean` | `false` | 标签只留给读屏，页面上不显示。 |
| `labelTooltip` | `string` | — | 标签末尾信息图标里的提示文本。 |
| `labelIcon` | `Component` | — | 标签文字前的图标。也可以用 `labelIcon` 插槽。 |
| `startIcon` | `Component` | — | 输入框起始位置的图标。也可以用 `icon` 插槽。 |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | 尺寸。 |
| `width` | `number \| string` | — | 整个字段的宽度，标签和状态说明一起对齐。数字按像素算，字符串原样使用，例如 `'100%'`。 |
| `status` | `'default' \| 'error' \| 'warning' \| 'success' \| { type, message? }` | — | 校验状态。对象带 `message` 时，说明会显示在字段下方。 |
| `statusVariant` | `'attached' \| 'detached' \| 'tooltip'` | `'attached'` | 状态说明的呈现方式。`'tooltip'` 不显示说明框，改为从状态图标里点开。 |
| `htmlName` | `string` | — | 表单字段名。 |
| `autoComplete` | `string` | — | 原生 `autocomplete` 属性。 |
| `hasAutoFocus` | `boolean` | `false` | 挂载后自动聚焦输入框。 |
| `id` | `string` | — | 输入框元素的 id。不传时自动生成。 |
| `disabled` | `boolean` | `false` | 等同于 `isDisabled`。 |
| `disabledReason` | `string` | — | 等同于 `disabledMessage`。 |
| `clearable` | `boolean` | `false` | 等同于 `hasClear`。 |
| `name` | `string` | — | 等同于 `htmlName`。 |
| `containerClass` | `string \| object \| array` | — | 加到字段外壳的 class。没有外壳时不生效，开发环境会警告。在 InputGroup 和横向标签的 FormLayout 里也是这样。 |
| `containerStyle` | `StyleValue` | — | 加到字段外壳的行内样式。 |
| `controlClass` | `string \| object \| array` | — | 加到数字框本体的 class。直接写在组件上的 class 也落在本体，不会警告。 |
| `controlStyle` | `StyleValue` | — | 加到数字框本体的行内样式。直接写在组件上的 style 也落在本体。 |

## NumberInput 事件

| 名称 | 载荷 | 说明 |
| --- | --- | --- |
| `update:modelValue` | `value: number \| null` | 值变化时触发。 |
| `change` | `value: number \| null` | 值变化时触发。`null` 表示用户清空了输入框。 |
| `focus` | `event: FocusEvent` | 输入框获得焦点时触发。 |
| `blur` | `event: FocusEvent` | 输入框失去焦点时触发。 |
| `enter` | `event: KeyboardEvent` | 在输入框里按回车时触发。 |
| `keydown` | `event: KeyboardEvent` | 输入框按键时触发，方向键步进之前发出。在回调里调用 `preventDefault()` 可以阻止这次步进。 |

## NumberInput 插槽

| 名称 | 说明 |
| --- | --- |
| `icon` | 输入框起始位置的图标。 |
| `labelIcon` | 标签文字前的图标。 |
