Admin 元件

Date Picker

參考
日期選擇器(@ark-ui/vue)。單日、區間選取。

安裝

Terminal
pnpm dlx shadcn-vue add @admin/date-picker
import {  AdminDatePicker,  AdminDatePickerRange,} from '@/components/admin/date-picker'

用法

AdminDatePicker 提供單選日期,AdminDatePickerRange 提供區間選擇。兩者共用 ark-ui DatePicker 原生組件,採共享外框 layout 與 date-fns 格式化。

<script setup lang="ts">import { ref } from 'vue'import { AdminDatePicker } from '@/components/admin/date-picker'​const value = ref<Date | null>(null)</script>​<template>  <div class="flex w-full max-w-sm flex-col gap-2">    <admin-date-picker v-model="value" emit-as="date" format="yyyy/MM/dd" placeholder="選擇日期" />    <p class="text-sm text-text-50">modelValue: {{ value ?? 'null' }}</p>  </div></template>

場景

每個元件之 playground 提供完整 props 切換 UI 與即時 modelValue JSON 顯示。

單選

<script setup lang="ts">import { computed, ref } from 'vue'import { cn } from '@/lib/utils'import { AdminDatePicker } from '@/components/admin/date-picker'​interface TimeModel {  hours?: number  minutes?: number  seconds?: number}​function toTimeModel(h: number | null, m: number | null, s: number | null): TimeModel | undefined {  const norm = (v: unknown): number | null => {    if (v == null || v === '' || (typeof v === 'number' && Number.isNaN(v))) {      return null    }    return typeof v === 'number' ? v : Number(v)  }  const nh = norm(h)  const nm = norm(m)  const ns = norm(s)  if (nh == null && nm == null && ns == null) {    return undefined  }  const t: TimeModel = {}  if (nh != null) {    t.hours = nh  }  if (nm != null) {    t.minutes = nm  }  if (ns != null) {    t.seconds = ns  }  return t}​// withTime=true 時若 format 不含時間 token,round-trip 會把時間段截掉、stepper 看似動了又被 watch 拉回 0;// playground 自動補時間 token 避免此踩雷。function ensureTimeTokens(fmt: string, withTime: boolean, enableSeconds: boolean): string {  if (!withTime) {    return fmt  }  if (fmt.includes('H') || fmt.includes('h') || fmt.includes('m') || fmt.includes('s')) {    return fmt  }  return enableSeconds ? `${fmt} HH:mm:ss` : `${fmt} HH:mm`}​const FORMAT_PRESETS = [  'yyyy/MM/dd',  'dd/MM/yyyy',  'yyyy-MM-dd',  'yyyy/MM/dd HH:mm',  'yyyy/MM/dd HH:mm:ss']const LOCALES = ['zh-TW', 'en-US', 'ja-JP']​const formatPreset = ref('yyyy/MM/dd')const formatCustom = ref('')const withTime = ref(false)const enableSeconds = ref(false)const disabled = ref(false)const readonly = ref(false)const isError = ref(false)const locale = ref('zh-TW')const placeholder = ref('選擇日期')const minDate = ref<Date | null>(null)const maxDate = ref<Date | null>(null)const minHour = ref<number | null>(null)const minMinute = ref<number | null>(null)const minSecond = ref<number | null>(null)const maxHour = ref<number | null>(null)const maxMinute = ref<number | null>(null)const maxSecond = ref<number | null>(null)const value = ref<Date | string | number | null>(null)​const format = computed(() =>  ensureTimeTokens(    formatCustom.value.trim() || formatPreset.value,    withTime.value,    enableSeconds.value  ))const minTime = computed(() => toTimeModel(minHour.value, minMinute.value, minSecond.value))const maxTime = computed(() => toTimeModel(maxHour.value, maxMinute.value, maxSecond.value))</script>​<template>  <section class="w-full space-y-4 rounded-[8px] border border-text-20 bg-bg-90 p-6">    <div class="grid grid-cols-2 gap-x-4 gap-y-3">      <label class="flex flex-col gap-1 typo-content-medium text-text-70">        <span class="typo-caption-small text-text-50">format(preset)</span>        <select          v-model="formatPreset"          :class="            cn(`              h-9 rounded-md border border-text-30 bg-bg-90 px-2 typo-content-medium text-text-70              outline-none              focus:border-info-40            `)          "        >          <option v-for="opt in FORMAT_PRESETS" :key="opt" :value="opt">{{ opt }}</option>        </select>      </label>      <label class="flex flex-col gap-1 typo-content-medium text-text-70">        <span class="typo-caption-small text-text-50">format(自訂,非空覆蓋 preset)</span>        <input          v-model="formatCustom"          placeholder="留空使用 preset"          type="text"          :class="            cn(`              h-9 rounded-md border border-text-30 bg-bg-90 px-2 typo-content-medium text-text-70              outline-none              focus:border-info-40            `)          "        />      </label>      <label class="flex flex-col gap-1 typo-content-medium text-text-70">        <span class="typo-caption-small text-text-50">locale</span>        <select          v-model="locale"          :class="            cn(`              h-9 rounded-md border border-text-30 bg-bg-90 px-2 typo-content-medium text-text-70              outline-none              focus:border-info-40            `)          "        >          <option v-for="opt in LOCALES" :key="opt" :value="opt">{{ opt }}</option>        </select>      </label>      <label class="flex flex-col gap-1 typo-content-medium text-text-70">        <span class="typo-caption-small text-text-50">placeholder</span>        <input          v-model="placeholder"          type="text"          :class="            cn(`              h-9 rounded-md border border-text-30 bg-bg-90 px-2 typo-content-medium text-text-70              outline-none              focus:border-info-40            `)          "        />      </label>      <div class="flex flex-wrap items-center gap-3 typo-caption-small text-text-70">        <label class="inline-flex items-center gap-1">          <input v-model="withTime" type="checkbox" /> withTime        </label>        <label class="inline-flex items-center gap-1">          <input v-model="enableSeconds" type="checkbox" :disabled="!withTime" />          enableSeconds        </label>        <label class="inline-flex items-center gap-1">          <input v-model="disabled" type="checkbox" /> disabled        </label>        <label class="inline-flex items-center gap-1">          <input v-model="readonly" type="checkbox" /> readonly        </label>        <label class="inline-flex items-center gap-1">          <input v-model="isError" type="checkbox" /> isError        </label>      </div>      <div></div>      <label class="flex flex-col gap-1 typo-content-medium text-text-70">        <span class="typo-caption-small text-text-50">minDate</span>        <admin-date-picker          v-model="minDate"          emit-as="date"          format="yyyy/MM/dd"          placeholder="無下限"        />      </label>      <label class="flex flex-col gap-1 typo-content-medium text-text-70">        <span class="typo-caption-small text-text-50">maxDate</span>        <admin-date-picker          v-model="maxDate"          emit-as="date"          format="yyyy/MM/dd"          placeholder="無上限"        />      </label>      <label v-if="withTime" class="flex flex-col gap-1 typo-content-medium text-text-70">        <span class="typo-caption-small text-text-50">          minTime(時 / 分{{ enableSeconds ? ' / 秒' : '' }})        </span>        <div class="flex items-center gap-1">          <input            v-model.number="minHour"            max="23"            min="0"            placeholder="hh"            type="number"            :class="              cn(`                h-9 w-14 rounded-md border border-text-30 bg-bg-90 px-2 text-center                typo-content-medium text-text-70 outline-none                focus:border-info-40              `)            "          />          <span class="text-text-60">:</span>          <input            v-model.number="minMinute"            max="59"            min="0"            placeholder="mm"            type="number"            :class="              cn(`                h-9 w-14 rounded-md border border-text-30 bg-bg-90 px-2 text-center                typo-content-medium text-text-70 outline-none                focus:border-info-40              `)            "          />          <template v-if="enableSeconds">            <span class="text-text-60">:</span>            <input              v-model.number="minSecond"              max="59"              min="0"              placeholder="ss"              type="number"              :class="                cn(`                  h-9 w-14 rounded-md border border-text-30 bg-bg-90 px-2 text-center                  typo-content-medium text-text-70 outline-none                  focus:border-info-40                `)              "            />          </template>        </div>      </label>      <label v-if="withTime" class="flex flex-col gap-1 typo-content-medium text-text-70">        <span class="typo-caption-small text-text-50">          maxTime(時 / 分{{ enableSeconds ? ' / 秒' : '' }})        </span>        <div class="flex items-center gap-1">          <input            v-model.number="maxHour"            max="23"            min="0"            placeholder="hh"            type="number"            :class="              cn(`                h-9 w-14 rounded-md border border-text-30 bg-bg-90 px-2 text-center                typo-content-medium text-text-70 outline-none                focus:border-info-40              `)            "          />          <span class="text-text-60">:</span>          <input            v-model.number="maxMinute"            max="59"            min="0"            placeholder="mm"            type="number"            :class="              cn(`                h-9 w-14 rounded-md border border-text-30 bg-bg-90 px-2 text-center                typo-content-medium text-text-70 outline-none                focus:border-info-40              `)            "          />          <template v-if="enableSeconds">            <span class="text-text-60">:</span>            <input              v-model.number="maxSecond"              max="59"              min="0"              placeholder="ss"              type="number"              :class="                cn(`                  h-9 w-14 rounded-md border border-text-30 bg-bg-90 px-2 text-center                  typo-content-medium text-text-70 outline-none                  focus:border-info-40                `)              "            />          </template>        </div>      </label>    </div>    <div class="border-t border-text-20 pt-4">      <admin-date-picker        v-model="value"        :disabled="disabled"        :enable-seconds="enableSeconds"        :format="format"        :is-error="isError"        :locale="locale"        :max-date="maxDate ?? undefined"        :max-time="maxTime"        :min-date="minDate ?? undefined"        :min-time="minTime"        :placeholder="placeholder"        :readonly="readonly"        :with-time="withTime"      />      <pre        :class="          cn(`            mt-2 max-h-32 overflow-auto rounded-md bg-bg-70 px-3 py-2 typo-caption-small            text-text-70          `)        "      >modelValue: {{ JSON.stringify(value) }}</pre>    </div>  </section></template>

區間

兩個 input 於單一外框內並列,共享一個 trigger 與 clear 按鈕;modelValue 為 Date[],length 0/1/2 partial 中間態合法(第一次 click cell 之後對外可見)。啟用 withTime 後 calendar 下方渲染垂直堆疊之兩組時間 stepper(上 = modelValue[0]、下 = modelValue[1]),需 modelValue length === 2 才啟用 stepper。

<script setup lang="ts">import { computed, ref } from 'vue'import { cn } from '@/lib/utils'import { AdminDatePicker, AdminDatePickerRange } from '@/components/admin/date-picker'​interface TimeModel {  hours?: number  minutes?: number  seconds?: number}​function toTimeModel(h: number | null, m: number | null, s: number | null): TimeModel | undefined {  const norm = (v: unknown): number | null => {    if (v == null || v === '' || (typeof v === 'number' && Number.isNaN(v))) {      return null    }    return typeof v === 'number' ? v : Number(v)  }  const nh = norm(h)  const nm = norm(m)  const ns = norm(s)  if (nh == null && nm == null && ns == null) {    return undefined  }  const t: TimeModel = {}  if (nh != null) {    t.hours = nh  }  if (nm != null) {    t.minutes = nm  }  if (ns != null) {    t.seconds = ns  }  return t}​function ensureTimeTokens(fmt: string, withTime: boolean, enableSeconds: boolean): string {  if (!withTime) {    return fmt  }  if (fmt.includes('H') || fmt.includes('h') || fmt.includes('m') || fmt.includes('s')) {    return fmt  }  return enableSeconds ? `${fmt} HH:mm:ss` : `${fmt} HH:mm`}​const FORMAT_PRESETS = [  'yyyy/MM/dd',  'dd/MM/yyyy',  'yyyy-MM-dd',  'yyyy/MM/dd HH:mm',  'yyyy/MM/dd HH:mm:ss']const LOCALES = ['zh-TW', 'en-US', 'ja-JP']​const formatPreset = ref('yyyy/MM/dd')const formatCustom = ref('')const withTime = ref(false)const enableSeconds = ref(false)const disabled = ref(false)const readonly = ref(false)const isError = ref(false)const locale = ref('zh-TW')const startPlaceholder = ref('開始日期')const endPlaceholder = ref('結束日期')const separator = ref(' - ')const minDate = ref<Date | null>(null)const maxDate = ref<Date | null>(null)const minHour = ref<number | null>(null)const minMinute = ref<number | null>(null)const maxHour = ref<number | null>(null)const maxMinute = ref<number | null>(null)const value = ref<Date[]>([])​const format = computed(() =>  ensureTimeTokens(    formatCustom.value.trim() || formatPreset.value,    withTime.value,    enableSeconds.value  ))const minTime = computed(() => toTimeModel(minHour.value, minMinute.value, null))const maxTime = computed(() => toTimeModel(maxHour.value, maxMinute.value, null))</script>​<template>  <section class="w-full space-y-4 rounded-[8px] border border-text-20 bg-bg-90 p-6">    <div class="grid grid-cols-2 gap-x-4 gap-y-3">      <label class="flex flex-col gap-1 typo-content-medium text-text-70">        <span class="typo-caption-small text-text-50">format(preset)</span>        <select          v-model="formatPreset"          :class="            cn(`              h-9 rounded-md border border-text-30 bg-bg-90 px-2 typo-content-medium text-text-70              outline-none              focus:border-info-40            `)          "        >          <option v-for="opt in FORMAT_PRESETS" :key="opt" :value="opt">{{ opt }}</option>        </select>      </label>      <label class="flex flex-col gap-1 typo-content-medium text-text-70">        <span class="typo-caption-small text-text-50">format(自訂)</span>        <input          v-model="formatCustom"          placeholder="留空使用 preset"          type="text"          :class="            cn(`              h-9 rounded-md border border-text-30 bg-bg-90 px-2 typo-content-medium text-text-70              outline-none              focus:border-info-40            `)          "        />      </label>      <label class="flex flex-col gap-1 typo-content-medium text-text-70">        <span class="typo-caption-small text-text-50">locale</span>        <select          v-model="locale"          :class="            cn(`              h-9 rounded-md border border-text-30 bg-bg-90 px-2 typo-content-medium text-text-70              outline-none              focus:border-info-40            `)          "        >          <option v-for="opt in LOCALES" :key="opt" :value="opt">{{ opt }}</option>        </select>      </label>      <label class="flex flex-col gap-1 typo-content-medium text-text-70">        <span class="typo-caption-small text-text-50">separator</span>        <input          v-model="separator"          type="text"          :class="            cn(`              h-9 rounded-md border border-text-30 bg-bg-90 px-2 typo-content-medium text-text-70              outline-none              focus:border-info-40            `)          "        />      </label>      <label class="flex flex-col gap-1 typo-content-medium text-text-70">        <span class="typo-caption-small text-text-50">startPlaceholder</span>        <input          v-model="startPlaceholder"          type="text"          :class="            cn(`              h-9 rounded-md border border-text-30 bg-bg-90 px-2 typo-content-medium text-text-70              outline-none              focus:border-info-40            `)          "        />      </label>      <label class="flex flex-col gap-1 typo-content-medium text-text-70">        <span class="typo-caption-small text-text-50">endPlaceholder</span>        <input          v-model="endPlaceholder"          type="text"          :class="            cn(`              h-9 rounded-md border border-text-30 bg-bg-90 px-2 typo-content-medium text-text-70              outline-none              focus:border-info-40            `)          "        />      </label>      <div class="flex flex-wrap items-center gap-3 typo-caption-small text-text-70">        <label class="inline-flex items-center gap-1">          <input v-model="withTime" type="checkbox" /> withTime        </label>        <label class="inline-flex items-center gap-1">          <input v-model="enableSeconds" type="checkbox" :disabled="!withTime" />          enableSeconds        </label>        <label class="inline-flex items-center gap-1">          <input v-model="disabled" type="checkbox" /> disabled        </label>        <label class="inline-flex items-center gap-1">          <input v-model="readonly" type="checkbox" /> readonly        </label>        <label class="inline-flex items-center gap-1">          <input v-model="isError" type="checkbox" /> isError        </label>      </div>      <div></div>      <label class="flex flex-col gap-1 typo-content-medium text-text-70">        <span class="typo-caption-small text-text-50">minDate</span>        <admin-date-picker          v-model="minDate"          emit-as="date"          format="yyyy/MM/dd"          placeholder="無下限"        />      </label>      <label class="flex flex-col gap-1 typo-content-medium text-text-70">        <span class="typo-caption-small text-text-50">maxDate</span>        <admin-date-picker          v-model="maxDate"          emit-as="date"          format="yyyy/MM/dd"          placeholder="無上限"        />      </label>      <label v-if="withTime" class="flex flex-col gap-1 typo-content-medium text-text-70">        <span class="typo-caption-small text-text-50">minTime(時 / 分)</span>        <div class="flex items-center gap-1">          <input            v-model.number="minHour"            max="23"            min="0"            placeholder="hh"            type="number"            :class="              cn(`                h-9 w-14 rounded-md border border-text-30 bg-bg-90 px-2 text-center                typo-content-medium text-text-70 outline-none                focus:border-info-40              `)            "          />          <span class="text-text-60">:</span>          <input            v-model.number="minMinute"            max="59"            min="0"            placeholder="mm"            type="number"            :class="              cn(`                h-9 w-14 rounded-md border border-text-30 bg-bg-90 px-2 text-center                typo-content-medium text-text-70 outline-none                focus:border-info-40              `)            "          />        </div>      </label>      <label v-if="withTime" class="flex flex-col gap-1 typo-content-medium text-text-70">        <span class="typo-caption-small text-text-50">maxTime(時 / 分)</span>        <div class="flex items-center gap-1">          <input            v-model.number="maxHour"            max="23"            min="0"            placeholder="hh"            type="number"            :class="              cn(`                h-9 w-14 rounded-md border border-text-30 bg-bg-90 px-2 text-center                typo-content-medium text-text-70 outline-none                focus:border-info-40              `)            "          />          <span class="text-text-60">:</span>          <input            v-model.number="maxMinute"            max="59"            min="0"            placeholder="mm"            type="number"            :class="              cn(`                h-9 w-14 rounded-md border border-text-30 bg-bg-90 px-2 text-center                typo-content-medium text-text-70 outline-none                focus:border-info-40              `)            "          />        </div>      </label>    </div>    <div class="border-t border-text-20 pt-4">      <admin-date-picker-range        v-model="value"        emit-as="date"        :disabled="disabled"        :enable-seconds="enableSeconds"        :end-placeholder="endPlaceholder"        :format="format"        :is-error="isError"        :locale="locale"        :max-date="maxDate ?? undefined"        :max-time="maxTime"        :min-date="minDate ?? undefined"        :min-time="minTime"        :readonly="readonly"        :separator="separator"        :start-placeholder="startPlaceholder"        :with-time="withTime"      />      <pre        :class="          cn(`            mt-2 max-h-32 overflow-auto rounded-md bg-bg-70 px-3 py-2 typo-caption-small            text-text-70          `)        "      >modelValue (length {{ value.length }}): {{          JSON.stringify(value.map((d) => d?.toString?.() ?? d))        }}</pre>    </div>  </section></template>

錯誤狀態

<script setup lang="ts">import { ref } from 'vue'import { AdminDatePicker } from '@/components/admin/date-picker'​const value = ref<Date | null>(null)</script>​<template>  <div class="flex w-full max-w-sm flex-col gap-2">    <admin-date-picker      v-model="value"      emit-as="date"      format="yyyy/MM/dd"      placeholder="選擇日期"      is-error    />  </div></template>
<script setup lang="ts">import { ref } from 'vue'import { AdminDatePickerRange } from '@/components/admin/date-picker'​const value = ref<Date[]>([])</script>​<template>  <div class="flex w-full max-w-md flex-col gap-2">    <admin-date-picker-range      v-model="value"      emit-as="date"      end-placeholder="結束日期"      format="yyyy/MM/dd"      start-placeholder="開始日期"      is-error    />  </div></template>

API 參考

組成

<template>  <!-- single -->  <admin-date-picker v-model="value" format="yyyy/MM/dd" placeholder="選擇日期" />​  <!-- range -->  <admin-date-picker-range    v-model="range"    end-placeholder="結束"    format="yyyy/MM/dd"    start-placeholder="開始"  /></template>
透傳上游 API

AdminDatePicker / AdminDatePickerRange 直接以 @ark-ui/vue 之 DatePicker.Input / DatePicker.Trigger / DatePicker.ClearTrigger 為 input 元素;下列僅列出 admin 對外承諾之 prop / event / expose。

admin-date-picker

Props

名稱型別預設值說明
classunknown——
placeholderstring | undefined""顯示於輸入框未填值時的提示文字
disabledboolean | undefinedfalse設為 `true` 時停用整個欄位,calendar trigger 無法開啟
readonlyboolean | undefinedfalse設為 `true` 時禁止編輯文字但仍可開啟 calendar 檢視
format*string—設定顯示與輸入解析之 date-fns 格式(如 `yyyy/MM/dd` 或 `yyyy/MM/dd HH:mm`)
emitAsTEmitAs | undefined(() => "string") as () => TEmitAs控制 `update:modelValue` payload 之出口型別;`'date'` 回傳 `Date`、`'string'` 依 `format` 回傳字串、`'timestamp'` 回傳 epoch milliseconds
withTimeboolean | undefinedfalse設為 `true` 時於 calendar 下方啟用時間選擇 stepper
enableSecondsboolean | undefinedfalse設為 `true` 時於時間 stepper 加入秒欄
minDateDate | null | undefined—限定可選日期下限
maxDateDate | null | undefined—限定可選日期上限
minTimeTimeModel | undefined—限定可選時間下限
maxTimeTimeModel | undefined—限定可選時間上限
isErrorboolean | undefinedfalse設為 `true` 時切換錯誤狀態樣式並設定 aria-invalid
modelValueDatePickerModelValue—透過 `v-model` 綁定當前選擇值
messagesPartial<DatePickerMessages> | undefined—覆寫元件內 a11y 字串;未提供之 key fallback 至預設字典
localestring | undefined"zh-TW"BCP-47 locale tag(如 `'zh-TW'`、`'en-US'`、`'ja-JP'`)
idstring | undefined—綁定至內部輸入元素之 `id`;純透傳,未提供時不自動生成
namestring | undefined—透傳至 ark `DatePicker` root 之 `name`,產生隱藏提交欄位供原生表單提交使用

* 代表必填

Events

名稱Payload說明
blurFocusEvent輸入框失焦且使用者真正離開元件時觸發(內部 focus 移動不觸發)
invalid{ reason: AdminDatePickerInvalidReason; }解析失敗或超出 `minDate` / `maxDate` / `minTime` / `maxTime` 範圍時觸發
update:modelValueDatePickerPayload<TEmitAs>選擇值變更時觸發,payload 型別依 `emitAs` 而定

Expose

名稱簽名說明
open() => datePickerApi.value.setOpen(true)開啟 calendar popover
close() => datePickerApi.value.setOpen(false)關閉 calendar popover
focus() => { rootRef.value?.querySelector<HTMLInputElement>('input')?.focus() }將焦點移至內部輸入框
blur() => { rootRef.value?.querySelector<HTMLInputElement>('input')?.blur() }將焦點移出內部輸入框
clear() => datePickerApi.value.clearValue()清除目前選擇值並 emit `update:modelValue` 為 `null`

admin-date-picker-range

Props

名稱型別預設值說明
classunknown——
placeholderstring | undefined""輸入框未填值之提示文字
separatorstring | undefined" - "顯示時兩端之分隔字元(預設 `' - '`)
disabledboolean | undefinedfalse設為 `true` 時停用輸入框與 calendar trigger
readonlyboolean | undefinedfalse設為 `true` 時禁止操作(可開啟 calendar 但無法選取)
format*string—設定顯示之 date-fns 格式(如 `yyyy/MM/dd` 或 `yyyy/MM/dd HH:mm`)
emitAsTEmitAs | undefined(() => "string") as () => TEmitAs控制 `update:modelValue` 各端 payload 之出口型別;`'date'` 回傳 `Date`、`'string'` 依 `format` 回傳字串、`'timestamp'` 回傳 epoch milliseconds
withTimeboolean | undefinedfalse設為 `true` 時於 calendar 下方啟用兩組垂直堆疊之時間 stepper(位置即語意:上 = start、下 = end)
enableSecondsboolean | undefinedfalse設為 `true` 時於時間 stepper 加入秒欄
minDateDate | null | undefined—限定可選日期下限
maxDateDate | null | undefined—限定可選日期上限
minTimeTimeModel | undefined—限定可選時間下限
maxTimeTimeModel | undefined—限定可選時間上限
isErrorboolean | undefinedfalse設為 `true` 時切換錯誤狀態樣式並設定 aria-invalid
modelValueDatePickerRangeModelValueInput | undefined—透過 `v-model` 綁定當前選擇值;對外形狀為 `Date[]`(length 由 ark 決定,0/1/2 partial 中間態合法)
messagesPartial<DatePickerMessages> | undefined—覆寫元件內 a11y 字串;未提供之 key fallback 至預設字典
localestring | undefined"zh-TW"BCP-47 locale tag(如 `'zh-TW'`、`'en-US'`、`'ja-JP'`)

* 代表必填

Events

名稱Payload說明
invalid{ reason: TimeInvalidReason; index: 0 | 1; }解析失敗或超出時間範圍時觸發
update:modelValueDatePickerRangePayload<TEmitAs>選擇值變更時觸發,payload 為 `Date[]` / `string[]` / `number[]`(依 `emitAs` narrow,length 0/1/2)

Expose

名稱簽名說明
open() => datePickerApi.value.setOpen(true)開啟 calendar popover
close() => datePickerApi.value.setOpen(false)關閉 calendar popover
focus() => { rootRef.value?.querySelector<HTMLInputElement>('input')?.focus() }將焦點移至輸入框
blur() => { rootRef.value?.querySelector<HTMLInputElement>('input')?.blur() }將焦點移出輸入框
clear() => datePickerApi.value.clearValue()清除 modelValue

更新紀錄

  • chore: 導入 vite-doctor 雙 config 並收斂 portable 型別斷言000d326
  • docs: 廢除 tailwind-class-style 規範,改由 eslint 自動處理05902f9
  • fix: 修正 error 邊框被 focus/open/checked 蓋過dfbeab5
  • BREAKING refactor!: 移除 AdminDatePickerPartialRange 並修正 review 問題ce16775
  • BREAKING refactor!: 移除 admin i18n 共享模組,date-picker messages 改本地合併deff201
  • feat: 表單元件補原生 id/name 屬性與 input/blur/change 事件2003547
  • fix: 將多個元件的按鈕類型設為 type="button",避免在表單中觸發提交事件f4f1bdd
  • chore: 啟用 vue/no-useless-v-bind 與 vue/prefer-separate-static-class16c34a8