{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "date-picker",
  "title": "Date Picker",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-vue/components/date-picker/DatePicker.vue",
      "content": "<script setup lang=\"ts\">\nimport type { DateValue } from 'reka-ui'\nimport type { HTMLAttributes } from 'vue'\nimport { computed, ref, watch } from 'vue'\nimport { Calendar as CalendarIcon, X } from 'lucide-vue-next'\nimport {\n  CalendarDate,\n  CalendarDateTime,\n  getLocalTimeZone,\n  parseDate,\n  parseDateTime,\n  today,\n} from '@internationalized/date'\nimport { useDateFormatter } from 'reka-ui'\nimport { toDate } from 'reka-ui/date'\nimport { Button } from '@/components/ui/button'\nimport { Calendar } from '@/components/ui/calendar'\nimport { RangeCalendar } from '@/components/ui/range-calendar'\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'\nimport { TimeColumns } from '@/components/ui/time-picker'\nimport { cn } from '@/lib/utils'\n\nexport type DatePickerType = 'single' | 'multiple' | 'range'\nexport type DatePickerLayout = 'default' | 'month-and-year' | 'month-only' | 'year-only'\nexport type DatePickerPicker = 'day' | 'week' | 'month' | 'quarter' | 'year'\nexport type DatePickerStatus = 'error' | 'warning'\nexport type DatePickerSize = 'small' | 'middle' | 'large'\nexport type DatePickerPlacement =\n  | 'top'\n  | 'bottom'\n  | 'left'\n  | 'right'\n  | 'topLeft'\n  | 'topRight'\n  | 'bottomLeft'\n  | 'bottomRight'\n\ntype SingleValue = string | DateValue | null\ntype MultipleValue = (string | DateValue)[] | null\ntype RangeValue = { start: string | DateValue; end: string | DateValue } | null\n\nexport interface DatePickerPreset {\n  label: string\n  value: SingleValue | MultipleValue | RangeValue\n  category?: string\n}\n\nexport interface DisabledTimeResult {\n  disabledHours?: () => number[]\n  disabledMinutes?: (selectedHour: number) => number[]\n  disabledSeconds?: (selectedHour: number, selectedMinute: number) => number[]\n}\n\ntype FormatValue = 'short' | 'medium' | 'long' | 'full' | Intl.DateTimeFormatOptions\n\ninterface Props {\n  /** Controlled value. Shape depends on `type`. ISO `YYYY-MM-DD` (or `YYYY-MM-DDTHH:mm` when `show-time`). */\n  modelValue?: SingleValue | MultipleValue | RangeValue\n  type?: DatePickerType\n  placeholder?: string\n  disabled?: boolean\n  readOnly?: boolean\n  clearable?: boolean\n  /** Format for the trigger label. String presets or Intl.DateTimeFormatOptions for full flexibility. Ignored when `show-time` (always shows date + time). */\n  format?: FormatValue\n  /** Backward-compat alias for `format`. */\n  dateFormat?: FormatValue\n  locale?: string\n  numberOfMonths?: number\n  weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6\n  fixedWeeks?: boolean\n  minValue?: string | DateValue\n  maxValue?: string | DateValue\n  /** Header layout: control which parts (month/year) become dropdowns. */\n  layout?: DatePickerLayout\n  /**\n   * Granularity of selection.\n   * - `day` (default): pick a single day from the calendar grid.\n   * - `week`: pick a full week — value is the week's start date (Monday or per locale).\n   * - `month`: pick a whole month — value snaps to the 1st day of the month.\n   * - `quarter`: pick a quarter — value snaps to the quarter's start date.\n   * - `year`: pick a whole year — value snaps to Jan 1 of the year.\n   * Only honored for `type='single'`.\n   */\n  picker?: DatePickerPicker\n  /** Show \"Today\" shortcut at popover top (single + day picker only). */\n  showCurrentDate?: boolean\n  /** Require clicking OK before applying the selected value. */\n  needConfirm?: boolean\n  /** Validation status — applies colored border to the trigger. */\n  status?: DatePickerStatus\n  /** Size variant of the trigger input. */\n  size?: DatePickerSize\n  /** Placement of the popover relative to the trigger. */\n  placement?: DatePickerPlacement\n  /** Pair the calendar with a time selector. Value becomes `YYYY-MM-DDTHH:mm`. */\n  showTime?: boolean\n  /** Show seconds column in time picker. Only used when `show-time`. */\n  showSeconds?: boolean\n  /** 24h vs AM/PM column. Only used when `show-time`. */\n  use24Hour?: boolean\n  /** Minute step for the time column. Only used when `show-time`. */\n  minuteStep?: number\n  /** Second step for the time column. Only used when `show-time` and `show-seconds`. */\n  secondStep?: number\n  /** Default time for newly picked dates when `show-time` and no prior selection. `HH:mm` or `HH:mm:ss`. */\n  defaultTime?: string\n  /** Preset shortcuts for quick selection. */\n  presets?: DatePickerPreset[]\n  /** Custom separator between range start and end dates. */\n  separator?: string\n  /** Function to determine if a specific date should be disabled. */\n  disabledDate?: (current: DateValue) => boolean\n  /** Function to determine if specific times should be disabled. Only used when `show-time`. */\n  disabledTime?: (current?: DateValue) => DisabledTimeResult\n  triggerClass?: HTMLAttributes['class']\n  class?: HTMLAttributes['class']\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  modelValue: null,\n  type: 'single',\n  placeholder: 'Pick a date',\n  disabled: false,\n  readOnly: false,\n  clearable: true,\n  format: 'medium',\n  locale: 'en-US',\n  weekStartsOn: 0,\n  fixedWeeks: false,\n  layout: 'default',\n  picker: 'day',\n  showCurrentDate: false,\n  needConfirm: false,\n  status: undefined,\n  size: 'middle',\n  placement: 'bottomLeft',\n  showTime: false,\n  showSeconds: false,\n  use24Hour: false,\n  minuteStep: 5,\n  secondStep: 1,\n  defaultTime: '12:00',\n  separator: '~',\n})\n\nconst emits = defineEmits<{\n  'update:modelValue': [value: SingleValue | MultipleValue | RangeValue]\n}>()\n\nconst open = ref(false)\nconst formatter = useDateFormatter(props.locale)\n\nconst effectiveFormat = computed(() => props.dateFormat ?? props.format)\n\nconst effectiveNumberOfMonths = computed(() => props.numberOfMonths ?? (props.type === 'range' ? 2 : 1))\n\nconst effectivePresets = computed<DatePickerPreset[] | undefined>(() => {\n  if (props.presets) return props.presets\n  if (props.type !== 'range') return undefined\n  const t = today(getLocalTimeZone())\n  return [\n    { label: 'Today', value: { start: t, end: t } },\n    { label: 'Yesterday', value: { start: t.subtract({ days: 1 }), end: t.subtract({ days: 1 }) } },\n    { label: 'Last 7 days', value: { start: t.subtract({ days: 6 }), end: t } },\n    { label: 'Last 30 days', value: { start: t.subtract({ days: 29 }), end: t } },\n    { label: 'This month', value: { start: t.set({ day: 1 }), end: t } },\n    {\n      label: 'Last month',\n      value: (() => {\n        const lastMonthEnd = t.set({ day: 1 }).subtract({ days: 1 })\n        const lastMonthStart = lastMonthEnd.set({ day: 1 })\n        return { start: lastMonthStart, end: lastMonthEnd }\n      })(),\n    },\n    { label: 'Year to date', value: { start: t.set({ month: 1, day: 1 }), end: t } },\n  ]\n})\n\nconst presetGroups = computed(() => {\n  const groups = new Map<string | undefined, DatePickerPreset[]>()\n  for (const p of effectivePresets.value ?? []) {\n    const cat = p.category\n    if (!groups.has(cat)) groups.set(cat, [])\n    groups.get(cat)!.push(p)\n  }\n  return Array.from(groups.entries()).map(([category, presets]) => ({ category, presets }))\n})\n\n// ---- Need confirm: preview state ----\nconst previewValue = ref<typeof internal.value>(undefined)\nconst activeValue = computed(() => (props.needConfirm ? (previewValue.value ?? internal.value) : internal.value))\n\nfunction commitPreview() {\n  if (props.needConfirm && previewValue.value !== undefined) {\n    handleUpdate(previewValue.value)\n    previewValue.value = undefined\n  }\n  open.value = false\n}\n\nfunction cancelPreview() {\n  previewValue.value = undefined\n  open.value = false\n}\n\nwatch(open, (v) => {\n  if (!v) previewValue.value = undefined\n})\n\nfunction coerce(v: string | DateValue | null | undefined): DateValue | null {\n  if (!v) return null\n  if (typeof v === 'string') {\n    try {\n      return v.includes('T') ? parseDateTime(v) : parseDate(v)\n    } catch {\n      return null\n    }\n  }\n  return v\n}\n\nfunction coerceShape(v: Props['modelValue']) {\n  if (props.type === 'multiple') {\n    if (!Array.isArray(v)) return undefined\n    return v.map(coerce).filter((x): x is DateValue => x != null)\n  }\n  if (props.type === 'range') {\n    if (!v || Array.isArray(v) || typeof v === 'string' || !('start' in (v as object))) return undefined\n    const r = v as { start: string | DateValue; end: string | DateValue }\n    const start = coerce(r.start)\n    const end = coerce(r.end)\n    if (!start) return undefined\n    return end ? { start, end } : { start }\n  }\n  return coerce(v as SingleValue) ?? undefined\n}\n\nconst internal = ref(coerceShape(props.modelValue))\nwatch(\n  () => props.modelValue,\n  (v) => {\n    internal.value = coerceShape(v)\n  },\n  { deep: true },\n)\n\nconst minDate = computed(() => coerce(props.minValue) ?? undefined)\nconst maxDate = computed(() => coerce(props.maxValue) ?? undefined)\n\nfunction fmtDate(d: DateValue) {\n  const fmt = effectiveFormat.value\n  if (typeof fmt === 'object') {\n    return formatter.custom(toDate(d), fmt)\n  }\n  return formatter.custom(toDate(d), { dateStyle: fmt })\n}\n\nfunction fmtMonth(d: DateValue) {\n  return formatter.custom(toDate(d), { month: 'long', year: 'numeric' })\n}\n\nfunction fmtTime(d: DateValue) {\n  if (!('hour' in d)) return ''\n  const dt = d as CalendarDateTime\n  const h24 = String(dt.hour).padStart(2, '0')\n  const m = String(dt.minute).padStart(2, '0')\n  if (props.showSeconds) {\n    const s = String(dt.second).padStart(2, '0')\n    if (props.use24Hour) return `${h24}:${m}:${s}`\n    const h12 = dt.hour % 12 === 0 ? 12 : dt.hour % 12\n    const period = dt.hour >= 12 ? 'PM' : 'AM'\n    return `${h12}:${m}:${s} ${period}`\n  }\n  if (props.use24Hour) {\n    return `${h24}:${m}`\n  }\n  const h12 = dt.hour % 12 === 0 ? 12 : dt.hour % 12\n  const period = dt.hour >= 12 ? 'PM' : 'AM'\n  return `${h12}:${m} ${period}`\n}\n\nfunction fmtDateTime(d: DateValue) {\n  return `${fmtDate(d)} ${fmtTime(d)}`.trim()\n}\n\nconst display = computed(() => {\n  const v = internal.value\n  if (!v) return ''\n  if (props.type === 'multiple') {\n    const arr = v as DateValue[]\n    if (!arr.length) return ''\n    if (arr.length === 1) return fmtDate(arr[0]!)\n    if (arr.length <= 3) return arr.map(fmtDate).join(', ')\n    return `${arr.length} dates selected`\n  }\n  if (props.type === 'range') {\n    const r = v as { start?: DateValue; end?: DateValue }\n    const fmt = props.showTime ? fmtDateTime : fmtDate\n    if (r.start && r.end) return `${fmt(r.start)} ${props.separator} ${fmt(r.end)}`\n    if (r.start) return `${fmt(r.start)} ${props.separator} …`\n    return ''\n  }\n  if (props.picker === 'week') {\n    const ws = weekStart(v as DateValue)\n    return `Week ${weekNumber(ws)}, ${ws.year}`\n  }\n  if (props.picker === 'month') return fmtMonth(v as DateValue)\n  if (props.picker === 'quarter') {\n    const q = Math.ceil((v as DateValue).month / 3)\n    return `Q${q} ${(v as DateValue).year}`\n  }\n  if (props.picker === 'year') return String((v as DateValue).year)\n  return props.showTime ? fmtDateTime(v as DateValue) : fmtDate(v as DateValue)\n})\n\nconst hasValue = computed(() => {\n  const v = internal.value\n  if (!v) return false\n  if (props.type === 'multiple') return (v as DateValue[]).length > 0\n  if (props.type === 'range') return Boolean((v as { start?: DateValue }).start)\n  return true\n})\n\nfunction emitOut(v: typeof internal.value) {\n  if (props.type === 'multiple') {\n    const arr = (v as DateValue[]) ?? []\n    emits(\n      'update:modelValue',\n      arr.map((d) => d.toString()),\n    )\n    return\n  }\n  if (props.type === 'range') {\n    const r = v as { start?: DateValue; end?: DateValue } | undefined\n    if (!r?.start || !r?.end) return\n    emits('update:modelValue', {\n      start: r.start.toString(),\n      end: r.end.toString(),\n    })\n    return\n  }\n  emits('update:modelValue', v ? (v as DateValue).toString() : null)\n}\n\nfunction handleUpdate(v: typeof internal.value) {\n  internal.value = v\n  emitOut(v)\n  if (props.needConfirm) return\n  // Don't close on day click when show-time is on — user still needs to pick the time.\n  if (props.type === 'single' && v && !props.showTime) open.value = false\n  if (props.type === 'range') {\n    const r = v as { start?: DateValue; end?: DateValue } | undefined\n    if (r?.start && r?.end && !props.showTime) open.value = false\n  }\n}\n\nfunction clear(event: Event) {\n  event.stopPropagation()\n  if (props.disabled || props.readOnly) return\n  if (props.type === 'multiple') handleUpdate([])\n  else handleUpdate(undefined)\n}\n\nfunction applyPreset(preset: DatePickerPreset) {\n  if (props.disabled || props.readOnly) return\n  const v = preset.value\n  if (!v) {\n    handleUpdate(undefined)\n    open.value = false\n    return\n  }\n  if (props.type === 'multiple') {\n    const arr = (v as MultipleValue) ?? []\n    handleUpdate(arr.map(coerce).filter((x): x is DateValue => x != null) as never)\n    open.value = false\n    return\n  }\n  if (props.type === 'range') {\n    const r = v as RangeValue\n    if (!r?.start) {\n      handleUpdate(undefined)\n      open.value = false\n      return\n    }\n    const start = coerce(r.start)\n    const end = coerce(r.end)\n    if (!start) {\n      handleUpdate(undefined)\n      open.value = false\n      return\n    }\n    handleUpdate(end ? { start, end } : ({ start } as never))\n    open.value = false\n    return\n  }\n  handleUpdate(coerce(v as SingleValue) as never)\n  open.value = false\n}\n\nconst placeholderDate = today(getLocalTimeZone())\nconst calendarType = computed(() => {\n  if (props.type === 'multiple') return { type: 'multiple' as const }\n  if (props.type === 'range') return { type: 'range' as const }\n  return undefined\n})\nconst calendarLayout = computed(() => (props.layout === 'default' ? undefined : props.layout))\n\nfunction pickToday() {\n  if (props.type !== 'single') return\n  const t = today(getLocalTimeZone())\n  if (props.picker === 'week') {\n    handleUpdate(weekStart(t))\n    return\n  }\n  handleUpdate(t)\n}\n\n// ---- Week picker utilities ----\nfunction weekStart(d: DateValue): CalendarDate {\n  // ISO week starts on Monday (1), but respect weekStartsOn prop\n  const jsDate = toDate(d)\n  const dayOfWeek = jsDate.getDay()\n  const offset = (dayOfWeek - props.weekStartsOn + 7) % 7\n  return new CalendarDate(d.year, d.month, d.day).subtract({ days: offset })\n}\n\nfunction weekNumber(d: DateValue): number {\n  const jsDate = toDate(d)\n  const target = new Date(jsDate.valueOf())\n  const dayNr = (jsDate.getDay() + 6) % 7\n  target.setDate(target.getDate() - dayNr + 3)\n  const firstThursday = target.valueOf()\n  target.setMonth(0, 1)\n  if (target.getDay() !== 4) {\n    target.setMonth(0, 1 + ((4 - target.getDay() + 7) % 7))\n  }\n  return 1 + Math.ceil((firstThursday - target.valueOf()) / 604800000)\n}\n\nfunction weekAnchorForMonth(year: number, month: number): CalendarDate {\n  const firstOfMonth = new CalendarDate(year, month, 1)\n  return weekStart(firstOfMonth)\n}\n\nconst weekGrid = computed(() => {\n  const anchor = monthYearAnchor.value\n  const start = weekAnchorForMonth(anchor.year, anchor.month)\n  // Generate 6 weeks to cover the month\n  return Array.from({ length: 6 }, (_, i) => {\n    const weekStartDate = start.add({ weeks: i })\n    const weekEndDate = weekStartDate.add({ days: 6 })\n    return {\n      start: weekStartDate,\n      end: weekEndDate,\n      weekNum: weekNumber(weekStartDate),\n    }\n  })\n})\n\nfunction isWeekSelected(ws: CalendarDate) {\n  const v = activeValue.value as DateValue | undefined\n  if (!v) return false\n  const selectedWeekStart = weekStart(v)\n  return selectedWeekStart.compare(ws) === 0\n}\n\nfunction isWeekDisabled(ws: CalendarDate) {\n  if (minDate.value && ws.compare(minDate.value) < 0) return true\n  const weekEnd = ws.add({ days: 6 })\n  if (maxDate.value && weekEnd.compare(maxDate.value) > 0) return true\n  if (props.disabledDate && props.disabledDate(ws)) return true\n  return false\n}\n\nfunction pickWeek(ws: CalendarDate) {\n  if (isWeekDisabled(ws)) return\n  if (props.needConfirm) {\n    previewValue.value = ws\n    return\n  }\n  handleUpdate(ws)\n}\n\n// ---- Quarter picker utilities ----\nconst quarterLabels = ['Q1', 'Q2', 'Q3', 'Q4']\nconst quarterMonths = [1, 4, 7, 10] as const\n\nfunction quarterStart(year: number, quarterIdx: number): CalendarDate {\n  return new CalendarDate(year, quarterMonths[quarterIdx]!, 1)\n}\n\nfunction isQuarterSelected(qIdx: number) {\n  const v = activeValue.value as DateValue | undefined\n  if (!v) return false\n  return v.year === monthYearAnchor.value.year && Math.ceil(v.month / 3) === qIdx + 1\n}\n\nfunction isQuarterDisabled(qIdx: number) {\n  const d = quarterStart(monthYearAnchor.value.year, qIdx)\n  if (minDate.value && d.compare(minDate.value) < 0) return true\n  const lastMonth = d.add({ months: 2 })\n  const lastDay = lastMonth.add({ months: 1 }).subtract({ days: 1 })\n  if (maxDate.value && lastDay.compare(maxDate.value) > 0) return true\n  if (props.disabledDate && props.disabledDate(d)) return true\n  return false\n}\n\nfunction pickQuarter(qIdx: number) {\n  const d = quarterStart(monthYearAnchor.value.year, qIdx)\n  if (isQuarterDisabled(qIdx)) return\n  if (props.needConfirm) {\n    previewValue.value = d\n    return\n  }\n  handleUpdate(d)\n}\n\n// ---- Calendar value passed to Reka must be a CalendarDate (no time component),\n// so it doesn't refuse the value or repaint with a stale time.\nfunction stripTime(d?: DateValue) {\n  if (!d) return d\n  return new CalendarDate(d.year, d.month, d.day)\n}\n\nconst calendarValue = computed(() => {\n  const v = internal.value\n  if (props.type === 'multiple') return (v as DateValue[] | undefined)?.map(stripTime) as never\n  if (props.type === 'range') {\n    const r = v as { start?: DateValue; end?: DateValue } | undefined\n    if (!r) return undefined as never\n    return { start: stripTime(r.start), end: stripTime(r.end) } as never\n  }\n  return stripTime(v as DateValue | undefined) as never\n})\n\n// Wrap update from calendar — re-attach time component if show-time is on.\ntype TimeShape = { h: number; m: number; s: number }\n\nconst lastTime = computed<TimeShape>(() => {\n  const v = internal.value\n  if (props.type === 'single' && v && 'hour' in (v as object)) {\n    const dt = v as CalendarDateTime\n    return { h: dt.hour, m: dt.minute, s: dt.second }\n  }\n  if (props.type === 'range') {\n    const r = v as { start?: DateValue } | undefined\n    if (r?.start && 'hour' in (r.start as object)) {\n      const dt = r.start as CalendarDateTime\n      return { h: dt.hour, m: dt.minute, s: dt.second }\n    }\n  }\n  const m = /^(\\d{1,2}):(\\d{2})(?::(\\d{2}))?$/.exec(props.defaultTime)\n  if (!m) return { h: 12, m: 0, s: 0 }\n  return { h: Number(m[1]), m: Number(m[2]), s: m[3] ? Number(m[3]) : 0 }\n})\n\nfunction withTime(d: DateValue, t: TimeShape = lastTime.value) {\n  return new CalendarDateTime(d.year, d.month, d.day, t.h, t.m, t.s)\n}\n\nfunction handleCalendarUpdate(v: unknown) {\n  if (props.needConfirm) {\n    if (props.type === 'multiple') {\n      previewValue.value = ((v as DateValue[])?.map((d) => withTime(d)) as never) ?? []\n    } else if (props.type === 'range') {\n      const r = v as { start?: DateValue; end?: DateValue } | undefined\n      if (!r?.start) {\n        previewValue.value = undefined\n      } else {\n        const start = props.showTime ? withTime(r.start) : r.start\n        const end = r.end ? (props.showTime ? withTime(r.end) : r.end) : undefined\n        previewValue.value = end ? { start, end } : ({ start } as never)\n      }\n    } else {\n      previewValue.value = v ? ((props.showTime ? withTime(v as DateValue) : (v as DateValue)) as never) : undefined\n    }\n    return\n  }\n  if (!props.showTime) {\n    handleUpdate(v as typeof internal.value)\n    return\n  }\n  if (props.type === 'multiple') {\n    const arr = (v as DateValue[]) ?? []\n    handleUpdate(arr.map((d) => withTime(d)) as never)\n    return\n  }\n  if (props.type === 'range') {\n    const r = v as { start?: DateValue; end?: DateValue } | undefined\n    if (!r?.start) return handleUpdate(undefined)\n    const start = withTime(r.start)\n    const end = r.end ? withTime(r.end) : undefined\n    handleUpdate(end ? { start, end } : ({ start } as never))\n    return\n  }\n  if (v) handleUpdate(withTime(v as DateValue) as never)\n}\n\n// Time column for show-time mode.\nconst timeForColumns = computed(() => {\n  const t = lastTime.value\n  if (props.showSeconds) {\n    return `${String(t.h).padStart(2, '0')}:${String(t.m).padStart(2, '0')}:${String(t.s).padStart(2, '0')}`\n  }\n  return `${String(t.h).padStart(2, '0')}:${String(t.m).padStart(2, '0')}`\n})\n\nconst timeFormat = computed(() => {\n  if (props.showSeconds) return 'HH:mm:ss'\n  return 'HH:mm'\n})\n\nfunction handleTimeUpdate(value: string) {\n  if (!internal.value) return\n  const m = /^(\\d{1,2}):(\\d{2})(?::(\\d{2}))?$/.exec(value)\n  if (!m) return\n  const t: TimeShape = { h: Number(m[1]), m: Number(m[2]), s: m[3] ? Number(m[3]) : 0 }\n  if (props.type === 'single') {\n    handleUpdate(withTime(internal.value as DateValue, t) as never)\n    return\n  }\n  if (props.type === 'range') {\n    const r = internal.value as { start?: DateValue; end?: DateValue }\n    if (!r.start) return\n    const start = withTime(r.start, t)\n    const end = r.end ? withTime(r.end, t) : undefined\n    handleUpdate(end ? { start, end } : ({ start } as never))\n  }\n}\n\nconst disabledTimeConfig = computed(() => {\n  if (!props.disabledTime) return undefined\n  let current: DateValue | undefined\n  if (props.type === 'range') {\n    const r = activeValue.value as { start?: DateValue } | undefined\n    current = r?.start\n  } else {\n    current = activeValue.value as DateValue | undefined\n  }\n  return props.disabledTime(current)\n})\n\n// ---- Month / year picker mode ----\nconst monthYearAnchor = ref<DateValue>((internal.value as DateValue) ?? today(getLocalTimeZone()))\nwatch(open, (v) => {\n  if (v) monthYearAnchor.value = (internal.value as DateValue) ?? today(getLocalTimeZone())\n})\n\nconst monthLabels = computed(() => {\n  const base = today(getLocalTimeZone())\n  return Array.from({ length: 12 }, (_, m) => {\n    const d = new CalendarDate(base.year, m + 1, 1)\n    return formatter.custom(toDate(d), { month: 'short' })\n  })\n})\n\nconst yearGrid = computed(() => {\n  const y = monthYearAnchor.value.year\n  const start = y - (y % 12)\n  return Array.from({ length: 12 }, (_, i) => start + i)\n})\n\nfunction shiftAnchor(dir: -1 | 1) {\n  const a = monthYearAnchor.value\n  if (props.picker === 'month' || props.picker === 'week') {\n    monthYearAnchor.value = new CalendarDate(a.year + dir, a.month, 1)\n  } else if (props.picker === 'year' || props.picker === 'quarter') {\n    monthYearAnchor.value = new CalendarDate(a.year + dir, 1, 1)\n  }\n}\n\nfunction isMonthSelected(monthIdx: number) {\n  const v = activeValue.value as DateValue | undefined\n  if (!v) return false\n  return v.year === monthYearAnchor.value.year && v.month === monthIdx + 1\n}\n\nfunction isYearSelected(year: number) {\n  const v = activeValue.value as DateValue | undefined\n  return !!v && v.year === year\n}\n\nfunction pickMonth(monthIdx: number) {\n  const d = new CalendarDate(monthYearAnchor.value.year, monthIdx + 1, 1)\n  if (isMonthDisabled(monthIdx)) return\n  if (props.needConfirm) {\n    previewValue.value = d\n    return\n  }\n  handleUpdate(d)\n}\n\nfunction pickYear(year: number) {\n  const d = new CalendarDate(year, 1, 1)\n  if (isYearDisabled(year)) return\n  if (props.picker === 'year') {\n    if (props.needConfirm) {\n      previewValue.value = d\n      return\n    }\n    handleUpdate(d)\n  } else {\n    monthYearAnchor.value = d\n  }\n}\n\nfunction isMonthDisabled(monthIdx: number) {\n  const d = new CalendarDate(monthYearAnchor.value.year, monthIdx + 1, 1)\n  if (minDate.value && d.compare(minDate.value) < 0) return true\n  if (maxDate.value && d.compare(maxDate.value) > 0) return true\n  if (props.disabledDate && props.disabledDate(d)) return true\n  return false\n}\n\nfunction isYearDisabled(year: number) {\n  const d = new CalendarDate(year, 1, 1)\n  if (minDate.value && new CalendarDate(year, 12, 31).compare(minDate.value) < 0) return true\n  if (maxDate.value && d.compare(maxDate.value) > 0) return true\n  if (props.disabledDate && props.disabledDate(d)) return true\n  return false\n}\n\n// ---- Popover placement ----\nconst popoverPlacement = computed(() => {\n  const map: Record<string, { side: 'top' | 'bottom' | 'left' | 'right'; align: 'start' | 'center' | 'end' }> = {\n    top: { side: 'top', align: 'center' },\n    bottom: { side: 'bottom', align: 'center' },\n    left: { side: 'left', align: 'center' },\n    right: { side: 'right', align: 'center' },\n    topLeft: { side: 'top', align: 'start' },\n    topRight: { side: 'top', align: 'end' },\n    bottomLeft: { side: 'bottom', align: 'start' },\n    bottomRight: { side: 'bottom', align: 'end' },\n  }\n  return map[props.placement] ?? { side: 'bottom', align: 'start' }\n})\n\n// ---- Size ----\nconst buttonSize = computed(() => {\n  if (props.size === 'small') return 'sm'\n  if (props.size === 'large') return 'lg'\n  return 'default'\n})\n\nconst triggerClasses = computed(() =>\n  cn(\n    props.showTime ? 'min-w-[280px]' : 'min-w-[240px]',\n    'justify-start gap-2 text-left font-normal',\n    !hasValue.value && 'text-muted-foreground',\n    props.status === 'error' && 'border-destructive focus-visible:ring-destructive',\n    props.status === 'warning' && 'border-warning focus-visible:ring-warning',\n    props.triggerClass,\n    props.class,\n  ),\n)\n\nfunction applyAndClose() {\n  open.value = false\n}\n</script>\n\n<template>\n  <Popover v-model:open=\"open\">\n    <PopoverTrigger as-child>\n      <Button\n        type=\"button\"\n        variant=\"outline\"\n        :size=\"buttonSize\"\n        :disabled=\"disabled\"\n        :class=\"triggerClasses\"\n        data-uipkge\n        data-slot=\"date-picker\"\n      >\n        <CalendarIcon class=\"size-4\" aria-hidden=\"true\" />\n        <span class=\"flex-1 truncate\">{{ display || placeholder }}</span>\n        <button\n          type=\"button\"\n          v-if=\"clearable && hasValue && !disabled && !readOnly\"\n          class=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring -mr-1 inline-flex size-9 items-center justify-center rounded transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n          aria-label=\"Clear date\"\n          @click.stop=\"clear\"\n        >\n          <X class=\"size-3.5\" />\n        </button>\n      </Button>\n    </PopoverTrigger>\n    <PopoverContent\n      class=\"w-auto p-0\"\n      :side=\"popoverPlacement.side as 'top' | 'bottom' | 'left' | 'right' | undefined\"\n      :align=\"popoverPlacement.align as 'center' | 'end' | 'start' | undefined\"\n    >\n      <div\n        v-if=\"showCurrentDate && type === 'single' && (picker === 'day' || picker === 'week')\"\n        class=\"flex justify-end border-b px-3 py-2\"\n      >\n        <button\n          type=\"button\"\n          class=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring rounded px-2 py-1.5 text-xs focus-visible:ring-2 focus-visible:outline-none\"\n          @click=\"pickToday\"\n        >\n          Today\n        </button>\n      </div>\n      <div v-if=\"picker !== 'day' && type === 'single'\" class=\"w-[260px] p-3\" data-uipkge data-slot=\"month-year-picker\">\n        <!-- Week picker -->\n        <template v-if=\"picker === 'week'\">\n          <div class=\"mb-3 flex items-center justify-between\">\n            <button\n              type=\"button\"\n              class=\"hover:bg-accent focus-visible:ring-ring inline-flex size-9 items-center justify-center rounded transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n              aria-label=\"Previous\"\n              @click=\"shiftAnchor(-1)\"\n            >\n              <span class=\"text-muted-foreground text-sm\">‹</span>\n            </button>\n            <span class=\"text-sm font-medium\">\n              {{ formatter.custom(toDate(monthYearAnchor as DateValue), { month: 'short', year: 'numeric' }) }}\n            </span>\n            <button\n              type=\"button\"\n              class=\"hover:bg-accent focus-visible:ring-ring inline-flex size-9 items-center justify-center rounded transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n              aria-label=\"Next\"\n              @click=\"shiftAnchor(1)\"\n            >\n              <span class=\"text-muted-foreground text-sm\">›</span>\n            </button>\n          </div>\n          <div class=\"flex flex-col gap-1\">\n            <button\n              type=\"button\"\n              v-for=\"(week, i) in weekGrid\"\n              :key=\"i\"\n              data-uipkge\n              data-slot=\"week-picker-cell\"\n              :data-active=\"isWeekSelected(week.start)\"\n              :disabled=\"isWeekDisabled(week.start)\"\n              :aria-pressed=\"isWeekSelected(week.start)\"\n              :aria-disabled=\"isWeekDisabled(week.start) || undefined\"\n              class=\"hover:bg-accent focus-visible:ring-ring flex items-center justify-between rounded px-3 py-2 text-sm transition-colors focus-visible:ring-2 focus-visible:outline-none disabled:opacity-30 disabled:hover:bg-transparent\"\n              :class=\"isWeekSelected(week.start) ? 'bg-primary text-primary-foreground hover:bg-primary' : ''\"\n              @click=\"pickWeek(week.start)\"\n            >\n              <span class=\"font-medium\">Week {{ week.weekNum }}</span>\n              <span\n                class=\"text-muted-foreground text-xs\"\n                :class=\"isWeekSelected(week.start) ? 'text-primary-foreground' : ''\"\n              >\n                {{ week.start.day }} {{ formatter.custom(toDate(week.start), { month: 'short' }) }} – {{ week.end.day }}\n                {{ formatter.custom(toDate(week.end), { month: 'short' }) }}\n              </span>\n            </button>\n          </div>\n        </template>\n\n        <!-- Quarter picker -->\n        <template v-else-if=\"picker === 'quarter'\">\n          <div class=\"mb-3 flex items-center justify-between\">\n            <button\n              type=\"button\"\n              class=\"hover:bg-accent focus-visible:ring-ring inline-flex size-9 items-center justify-center rounded transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n              aria-label=\"Previous\"\n              @click=\"shiftAnchor(-1)\"\n            >\n              <span class=\"text-muted-foreground text-sm\">‹</span>\n            </button>\n            <span class=\"text-sm font-medium\">{{ monthYearAnchor.year }}</span>\n            <button\n              type=\"button\"\n              class=\"hover:bg-accent focus-visible:ring-ring inline-flex size-9 items-center justify-center rounded transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n              aria-label=\"Next\"\n              @click=\"shiftAnchor(1)\"\n            >\n              <span class=\"text-muted-foreground text-sm\">›</span>\n            </button>\n          </div>\n          <div class=\"grid grid-cols-2 gap-2\">\n            <button\n              type=\"button\"\n              v-for=\"(label, q) in quarterLabels\"\n              :key=\"q\"\n              data-uipkge\n              data-slot=\"quarter-picker-cell\"\n              :data-active=\"isQuarterSelected(q)\"\n              :disabled=\"isQuarterDisabled(q)\"\n              :aria-pressed=\"isQuarterSelected(q)\"\n              :aria-disabled=\"isQuarterDisabled(q) || undefined\"\n              class=\"hover:bg-accent focus-visible:ring-ring rounded px-4 py-6 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none disabled:opacity-30 disabled:hover:bg-transparent\"\n              :class=\"isQuarterSelected(q) ? 'bg-primary text-primary-foreground hover:bg-primary' : ''\"\n              @click=\"pickQuarter(q)\"\n            >\n              {{ label }}\n            </button>\n          </div>\n        </template>\n\n        <!-- Month / Year picker -->\n        <template v-else>\n          <div class=\"mb-3 flex items-center justify-between\">\n            <button\n              type=\"button\"\n              class=\"hover:bg-accent focus-visible:ring-ring inline-flex size-9 items-center justify-center rounded transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n              aria-label=\"Previous\"\n              @click=\"shiftAnchor(-1)\"\n            >\n              <span class=\"text-muted-foreground text-sm\">‹</span>\n            </button>\n            <span class=\"text-sm font-medium\">\n              {{ picker === 'month' ? monthYearAnchor.year : `${yearGrid[0]} – ${yearGrid[yearGrid.length - 1]}` }}\n            </span>\n            <button\n              type=\"button\"\n              class=\"hover:bg-accent focus-visible:ring-ring inline-flex size-9 items-center justify-center rounded transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n              aria-label=\"Next\"\n              @click=\"shiftAnchor(1)\"\n            >\n              <span class=\"text-muted-foreground text-sm\">›</span>\n            </button>\n          </div>\n          <div v-if=\"picker === 'month'\" class=\"grid grid-cols-3 gap-2\">\n            <button\n              type=\"button\"\n              v-for=\"(label, m) in monthLabels\"\n              :key=\"m\"\n              :disabled=\"isMonthDisabled(m)\"\n              data-uipkge\n              data-slot=\"month-picker-cell\"\n              :data-active=\"isMonthSelected(m)\"\n              :aria-pressed=\"isMonthSelected(m)\"\n              :aria-disabled=\"isMonthDisabled(m) || undefined\"\n              class=\"hover:bg-accent focus-visible:ring-ring rounded px-2 py-2 text-sm transition-colors focus-visible:ring-2 focus-visible:outline-none disabled:opacity-30 disabled:hover:bg-transparent\"\n              :class=\"isMonthSelected(m) ? 'bg-primary text-primary-foreground hover:bg-primary' : ''\"\n              @click=\"pickMonth(m)\"\n            >\n              {{ label }}\n            </button>\n          </div>\n          <div v-else class=\"grid grid-cols-3 gap-2\">\n            <button\n              type=\"button\"\n              v-for=\"y in yearGrid\"\n              :key=\"y\"\n              :disabled=\"isYearDisabled(y)\"\n              data-uipkge\n              data-slot=\"year-picker-cell\"\n              :data-active=\"isYearSelected(y)\"\n              :aria-pressed=\"isYearSelected(y)\"\n              :aria-disabled=\"isYearDisabled(y) || undefined\"\n              class=\"hover:bg-accent focus-visible:ring-ring rounded px-2 py-2 text-sm tabular-nums transition-colors focus-visible:ring-2 focus-visible:outline-none disabled:opacity-30 disabled:hover:bg-transparent\"\n              :class=\"isYearSelected(y) ? 'bg-primary text-primary-foreground hover:bg-primary' : ''\"\n              @click=\"pickYear(y)\"\n            >\n              {{ y }}\n            </button>\n          </div>\n        </template>\n      </div>\n      <div v-else class=\"flex\">\n        <aside v-if=\"presetGroups.length\" class=\"flex w-40 flex-col gap-1 border-r p-2\">\n          <template v-for=\"(group, gIdx) in presetGroups\" :key=\"gIdx\">\n            <div\n              v-if=\"group.category\"\n              class=\"text-muted-foreground px-2 pt-1 text-xs font-semibold tracking-wider uppercase\"\n            >\n              {{ group.category }}\n            </div>\n            <button\n              type=\"button\"\n              v-for=\"p in group.presets\"\n              :key=\"p.label\"\n              class=\"hover:bg-accent focus-visible:ring-ring rounded-md px-2 py-1.5 text-left text-xs transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n              @click=\"applyPreset(p)\"\n            >\n              {{ p.label }}\n            </button>\n          </template>\n        </aside>\n        <RangeCalendar\n          v-if=\"type === 'range'\"\n          :model-value=\"calendarValue\"\n          :placeholder=\"placeholderDate\"\n          :number-of-months=\"effectiveNumberOfMonths\"\n          :week-starts-on=\"weekStartsOn\"\n          :fixed-weeks=\"fixedWeeks\"\n          :min-value=\"minDate\"\n          :max-value=\"maxDate\"\n          :read-only=\"readOnly\"\n          :is-date-disabled=\"disabledDate\"\n          @update:model-value=\"handleCalendarUpdate\"\n        />\n        <Calendar\n          v-else\n          :model-value=\"calendarValue\"\n          :type=\"calendarType\"\n          :placeholder=\"placeholderDate\"\n          :number-of-months=\"effectiveNumberOfMonths\"\n          :week-starts-on=\"weekStartsOn\"\n          :fixed-weeks=\"fixedWeeks\"\n          :min-value=\"minDate\"\n          :max-value=\"maxDate\"\n          :read-only=\"readOnly\"\n          :layout=\"calendarLayout\"\n          :is-date-disabled=\"disabledDate\"\n          @update:model-value=\"handleCalendarUpdate\"\n        >\n          <template #cell=\"{ day, month }\">\n            <slot name=\"cell\" :day=\"day\" :month=\"month\">\n              {{ day.day }}\n            </slot>\n          </template>\n        </Calendar>\n        <div v-if=\"showTime\" class=\"flex flex-col border-l\">\n          <div class=\"flex items-center justify-between border-b px-3 py-2\">\n            <span class=\"text-muted-foreground text-xs tracking-widest uppercase\">Time</span>\n            <button\n              type=\"button\"\n              class=\"text-primary focus-visible:ring-ring rounded px-2 py-1.5 text-xs font-medium focus-visible:ring-2 focus-visible:outline-none\"\n              @click=\"applyAndClose\"\n            >\n              Done\n            </button>\n          </div>\n          <TimeColumns\n            :model-value=\"timeForColumns\"\n            :format=\"timeFormat\"\n            :use24-hour=\"use24Hour\"\n            :minute-step=\"minuteStep\"\n            :second-step=\"secondStep\"\n            :visible=\"open\"\n            :disabled-hours=\"disabledTimeConfig?.disabledHours\"\n            :disabled-minutes=\"disabledTimeConfig?.disabledMinutes\"\n            :disabled-seconds=\"disabledTimeConfig?.disabledSeconds\"\n            @update:model-value=\"handleTimeUpdate\"\n          />\n        </div>\n      </div>\n\n      <!-- Need confirm footer -->\n      <div v-if=\"needConfirm\" class=\"flex items-center justify-end gap-2 border-t px-3 py-2\">\n        <button\n          type=\"button\"\n          class=\"hover:bg-accent focus-visible:ring-ring rounded px-3 py-1.5 text-xs transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n          @click=\"cancelPreview\"\n        >\n          Cancel\n        </button>\n        <button\n          type=\"button\"\n          class=\"bg-primary text-primary-foreground hover:bg-primary/90 focus-visible:ring-ring rounded px-3 py-1.5 text-xs transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n          @click=\"commitPreview\"\n        >\n          OK\n        </button>\n      </div>\n    </PopoverContent>\n  </Popover>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/date-picker/DatePicker.vue"
    },
    {
      "path": "packages/registry-vue/components/date-picker/index.ts",
      "content": "export { default as DatePicker } from './DatePicker.vue'\nexport type {\n  DatePickerType,\n  DatePickerPicker,\n  DatePickerStatus,\n  DatePickerLayout,\n  DatePickerSize,\n  DatePickerPlacement,\n  DatePickerPreset,\n  DisabledTimeResult,\n} from './DatePicker.vue'\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/date-picker/index.ts"
    }
  ],
  "dependencies": [
    "@internationalized/date",
    "lucide-vue-next",
    "reka-ui"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/vue/button.json",
    "https://uipkge.dev/r/vue/calendar.json",
    "https://uipkge.dev/r/vue/popover.json",
    "https://uipkge.dev/r/vue/range-calendar.json",
    "https://uipkge.dev/r/vue/time-picker.json"
  ],
  "description": "Date input that opens a Calendar in a Popover. Handles parsing, formatting, min/max bounds, and disabled dates. Use the Range Calendar version for from/to selections.",
  "categories": [
    "date-time"
  ]
}