Admin 元件
Date Picker
日期選擇器(@ark-ui/vue)。單日、區間選取。
安裝
Terminal
pnpm dlx shadcn-vue add @admin/date-pickerimport { 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
| 名稱 | 型別 | 預設值 | 說明 |
|---|---|---|---|
class | unknown | — | — |
placeholder | string | undefined | "" | 顯示於輸入框未填值時的提示文字 |
disabled | boolean | undefined | false | 設為 `true` 時停用整個欄位,calendar trigger 無法開啟 |
readonly | boolean | undefined | false | 設為 `true` 時禁止編輯文字但仍可開啟 calendar 檢視 |
format* | string | — | 設定顯示與輸入解析之 date-fns 格式(如 `yyyy/MM/dd` 或 `yyyy/MM/dd HH:mm`) |
emitAs | TEmitAs | undefined | (() => "string") as () => TEmitAs | 控制 `update:modelValue` payload 之出口型別;`'date'` 回傳 `Date`、`'string'` 依 `format` 回傳字串、`'timestamp'` 回傳 epoch milliseconds |
withTime | boolean | undefined | false | 設為 `true` 時於 calendar 下方啟用時間選擇 stepper |
enableSeconds | boolean | undefined | false | 設為 `true` 時於時間 stepper 加入秒欄 |
minDate | Date | null | undefined | — | 限定可選日期下限 |
maxDate | Date | null | undefined | — | 限定可選日期上限 |
minTime | TimeModel | undefined | — | 限定可選時間下限 |
maxTime | TimeModel | undefined | — | 限定可選時間上限 |
isError | boolean | undefined | false | 設為 `true` 時切換錯誤狀態樣式並設定 aria-invalid |
modelValue | DatePickerModelValue | — | 透過 `v-model` 綁定當前選擇值 |
messages | Partial<DatePickerMessages> | undefined | — | 覆寫元件內 a11y 字串;未提供之 key fallback 至預設字典 |
locale | string | undefined | "zh-TW" | BCP-47 locale tag(如 `'zh-TW'`、`'en-US'`、`'ja-JP'`) |
id | string | undefined | — | 綁定至內部輸入元素之 `id`;純透傳,未提供時不自動生成 |
name | string | undefined | — | 透傳至 ark `DatePicker` root 之 `name`,產生隱藏提交欄位供原生表單提交使用 |
* 代表必填
Events
| 名稱 | Payload | 說明 |
|---|---|---|
blur | FocusEvent | 輸入框失焦且使用者真正離開元件時觸發(內部 focus 移動不觸發) |
invalid | { reason: AdminDatePickerInvalidReason; } | 解析失敗或超出 `minDate` / `maxDate` / `minTime` / `maxTime` 範圍時觸發 |
update:modelValue | DatePickerPayload<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
| 名稱 | 型別 | 預設值 | 說明 |
|---|---|---|---|
class | unknown | — | — |
placeholder | string | undefined | "" | 輸入框未填值之提示文字 |
separator | string | undefined | " - " | 顯示時兩端之分隔字元(預設 `' - '`) |
disabled | boolean | undefined | false | 設為 `true` 時停用輸入框與 calendar trigger |
readonly | boolean | undefined | false | 設為 `true` 時禁止操作(可開啟 calendar 但無法選取) |
format* | string | — | 設定顯示之 date-fns 格式(如 `yyyy/MM/dd` 或 `yyyy/MM/dd HH:mm`) |
emitAs | TEmitAs | undefined | (() => "string") as () => TEmitAs | 控制 `update:modelValue` 各端 payload 之出口型別;`'date'` 回傳 `Date`、`'string'` 依 `format` 回傳字串、`'timestamp'` 回傳 epoch milliseconds |
withTime | boolean | undefined | false | 設為 `true` 時於 calendar 下方啟用兩組垂直堆疊之時間 stepper(位置即語意:上 = start、下 = end) |
enableSeconds | boolean | undefined | false | 設為 `true` 時於時間 stepper 加入秒欄 |
minDate | Date | null | undefined | — | 限定可選日期下限 |
maxDate | Date | null | undefined | — | 限定可選日期上限 |
minTime | TimeModel | undefined | — | 限定可選時間下限 |
maxTime | TimeModel | undefined | — | 限定可選時間上限 |
isError | boolean | undefined | false | 設為 `true` 時切換錯誤狀態樣式並設定 aria-invalid |
modelValue | DatePickerRangeModelValueInput | undefined | — | 透過 `v-model` 綁定當前選擇值;對外形狀為 `Date[]`(length 由 ark 決定,0/1/2 partial 中間態合法) |
messages | Partial<DatePickerMessages> | undefined | — | 覆寫元件內 a11y 字串;未提供之 key fallback 至預設字典 |
locale | string | undefined | "zh-TW" | BCP-47 locale tag(如 `'zh-TW'`、`'en-US'`、`'ja-JP'`) |
* 代表必填
Events
| 名稱 | Payload | 說明 |
|---|---|---|
invalid | { reason: TimeInvalidReason; index: 0 | 1; } | 解析失敗或超出時間範圍時觸發 |
update:modelValue | DatePickerRangePayload<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