# 日期时间输入框

## 使用方法

`DateTimeInput` 把日期和时间放在一起。值是 `YYYY-MM-DDTHH:MM`。两部分都有了才会写出这个值，否则是 `null`。

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

const meeting = shallowRef<string | null>(null)
</script>

<template>
  <DateTimeInput v-model="meeting" label="会议时间" has-clear />
</template>
```

日期用日期输入框，时间用和时间输入框相同的字段。两个字段默认左右并排，并随行宽一起变窄。行宽小于 16rem 时才换行。`bottom-sheet` 时日期和时间共用一个底部面板，顶上在日期和时间之间切换。清除会同时清掉日期和时间。日期那一半可以一次显示两个月，并指定一周从哪一天开始。正在保存时两个字段都不能改。

### 结构

- **标签**: 输入框上方的文字，说明期望的是什么日期时间。
- **分段行**: 不带边框的布局行，装着两段带边框的输入。容器变窄时会换行。
- **日期输入**: 精细指针下是带日历浮层的 Astryx 日期字段；原生模式是真正的 input type=date；presentation 为 adaptive-bottom-sheet 且指针较粗时，是只读分段，打开 Astryx 触摸面板。
- **日历图标**: 打开当前日期表面的按钮：平台选择器、Astryx 日历浮层，或 Astryx 触摸面板。
- **日期选择器**: 原生模式用浏览器或系统选择器；精细指针用 Astryx 的月份网格浮层；presentation 为 adaptive-bottom-sheet 且指针较粗时，用 Astryx 底部面板里的日期页。
- **时间输入**: 默认的分钟精度原生模式是真正的 input type=time；需要秒、自定义步进或预设选项时，是文本或组合框时间字段；presentation 为 adaptive-bottom-sheet 且指针较粗时，是只读分段，打开可访问的时间滚轮。
- **时钟图标**: 时间符号。在 Astryx 的指针字段上作为前导标识，在原生模式和触摸面板的时间分段上作为打开选择器的操作。
- **时间选项浮层**: 按 timeOptionInterval 的间隔列出预设时间。设置这个属性后，即使 nativePicker 本来会选原生控件，也会保留 Astryx 的文本或组合框时间字段；Astryx 触摸面板则改用滚轮。
- **时间选项**: 可选时间选项浮层里的一个预设时间。
- **清除按钮**: 把日期时间清掉的 × 按钮。
- **状态消息**: 输入框下面的错误、警告或成功提示。

## 最佳实践

| 指引 | 实践 |
| --- | --- |
| 推荐 | 时间可选时打开 `hasClear`。 |
| 避免 | 只需要日期时使用它。DateInput 更合适。 |

## 示例

### 校验

已填好的日期时间上的错误、警告和成功。

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

const errorVal = shallowRef('2026-01-25T09:00')
const warningVal = shallowRef('2026-12-25T14:00')
const successVal = shallowRef('2026-03-10T10:30')
</script>

<template>
  <div class="date-time-demo">
    <VStack :gap="4">
      <DateTimeInput v-model="errorVal" label="Meeting time" :status="{ type: 'error', message: 'This time slot is already booked' }" />
      <DateTimeInput v-model="warningVal" label="Preferred time" :status="{ type: 'warning', message: 'This falls outside business hours' }" />
      <DateTimeInput v-model="successVal" label="Start time" :status="{ type: 'success', message: 'Time confirmed' }" />
    </VStack>
  </div>
</template>

<style scoped>
.date-time-demo {
  width: 400px;
  max-width: 100%;
}
</style>
```
### 底部面板

一个底部面板里切换日期和时间。

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

const meeting = shallowRef<string | null>('2026-03-21T14:30')
</script>

<template>
  <div style="width: 320px; max-width: 100%">
    <DateTimeInput
      v-model="meeting"
      label="Meeting"
      presentation="bottom-sheet"
      has-clear
    />
  </div>
</template>
```

## DateTimeInput 属性

| 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `v-model` | `string \| null` | `null` | `YYYY-MM-DDTHH:MM`。两部分都存在才有值。 |
| `label` | `string` | — | 必填。这一对字段的无障碍名称。 |
| `placeholder` | `string` | — | 日期字段的占位符。 |
| `hourFormat` | `'12h' \| '24h'` | `'12h'` | 时间的显示格式。模型值仍是 ISO。 |
| `presentation` | `'popover' \| 'bottom-sheet' \| 'native' \| 'adaptive-bottom-sheet' \| 'adaptive-native' \| 'text-input'` | — | 日期和时间共用的打开方式。bottom-sheet，以及粗指针上的 adaptive-bottom-sheet，合成一个底部面板，在日期和时间之间切换。text-input 只作用于时间。不传时细指针弹出，粗指针用系统控件。显式值压过 nativePicker。 |
| `isLabelHidden` | `boolean` | `false` | 视觉上隐藏标签，读屏仍然读得到。旧名 `labelHidden` 同样可用，两者任一为真就隐藏。 |
| `containerClass` | `string \| object \| array` | — | 加到字段外壳的 class。在 InputGroup 和横向标签的 FormLayout 里没有外壳，此时不生效，开发环境会警告。 |
| `containerStyle` | `StyleValue` | — | 加到字段外壳的行内样式。落点规则同 containerClass。 |
| `controlClass` | `string \| object \| array` | — | 加到带边框本体的 class。直接写在组件上的 class 也落在本体。 |
| `controlStyle` | `StyleValue` | — | 加到带边框本体的行内样式。 |
| `hasClear` | `boolean` | `false` | 同时清除日期和时间。 |
| `min` / `max` | `string` | — | 时间部分的 ISO 边界。 |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | 两个字段的高度。 |
| `status` | `FieldStatusInput` | — | 这一对字段的错误、警告或成功。 |
| `statusVariant` | `'attached' \| 'detached' \| 'tooltip'` | `'attached'` | 状态消息的位置。 |
| `isDisabled` | `boolean` | `false` | 同时禁用两个字段。 |
| `disabledMessage` | `string` | — | 为什么禁用。和 `isDisabled` 一起用时，悬停或键盘聚焦会显示这段提示，两段都仍可聚焦（带 `aria-disabled`）。 |
| `hasSeconds` | `boolean` | `false` | 时间那段带秒，模型值变成 `YYYY-MM-DDTHH:MM:SS`。系统时间控件表达不了秒，所以固定用 Astryx 的时间字段。 |
| `timeIncrement` | `1 \| 5 \| 10 \| 15 \| 30` | `1` | 时间那段方向键调整分钟的步长。不是默认值时固定用 Astryx 的时间字段。 |
| `timeOptionInterval` | `5 \| 10 \| 15 \| 30 \| 60` | — | 预设时间的分钟间隔。设置后点时钟图标会按这个间隔列出可选时间，并按 `min`、`max` 的时刻裁剪；即使处于系统控件模式也用 Astryx 的时间字段。 |
| `timeLabel` | `string` | `时间` | 时间那段的读屏标签。 |
| `timePlaceholder` | `string` | `选择时间` | 时间那段没选值时显示的占位文本。 |
| `dateConstraints` | `Array<(date: Date) => boolean>` | — | 日期那一半里，任一函数返回 false 时该日不可选。 |
| `numberOfMonths` | `1 \| 2` | — | 日期月历并排的月份数。 |
| `weekStartsOn` | `0-6 \| 'sun' \| 'mon' \| ...` | — | 日期月历从哪一天开始一周。 |
| `nativePicker` | `'touch' \| 'always' \| 'never'` | — | 旧的打开方式，同时传给日期和时间。 |
| `isLoading` | `boolean` | `false` | 两个字段都不可改。 |
| `changeAction` | `(value) => void \| Promise<void>` | — | 日期和时间都齐了之后再跑。返回 Promise 时保持加载。清除也会跑。 |
| `isOptional` | `boolean` | — | 在标签旁标成可选。 |
| `isRequired` | `boolean` | — | 把这一对字段标成必填。 |
| `labelTooltip` | `string` | — | 标签末尾信息图标里的说明。 |
