{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "date-picker",
  "title": "Date Picker",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-angular/components/date-picker/date-picker.component.ts",
      "content": "import {\n  ChangeDetectorRef,\n  Component,\n  ElementRef,\n  EventEmitter,\n  Input,\n  OnInit,\n  Output,\n  TemplateRef,\n  ViewChild,\n  booleanAttribute,\n  forwardRef,\n  inject,\n  numberAttribute,\n  signal,\n  ChangeDetectionStrategy,\n} from '@angular/core'\nimport { NG_VALUE_ACCESSOR, type ControlValueAccessor } from '@angular/forms'\nimport { cn } from '@/lib/utils'\nimport { buttonVariants, type ButtonSize } from '@/ui/button/button.component'\nimport { UiCalendarComponent } from '@/ui/calendar/calendar.component'\nimport type { CaptionLayout, DateRange, DayContentContext, DayPickerSelected, Matcher } from '@/ui/calendar/day-picker'\nimport {\n  UiPopoverComponent,\n  UiPopoverContentComponent,\n  type PopoverAlign,\n  type PopoverSide,\n} from '@/ui/popover/popover.component'\nimport { UiRangeCalendarComponent } from '@/ui/range-calendar/range-calendar.component'\nimport { UiTimeColumnsComponent } from '@/ui/time-picker/time-picker.component'\nimport {\n  coerceDate,\n  coerceShape,\n  defaultRangePresets,\n  fmtDate,\n  fmtMonth,\n  fmtTime,\n  parseTimeShape,\n  stripTime,\n  toISODate,\n  toISODateTime,\n  weekNumber,\n  weekStart,\n  withTime,\n  type DatePickerLayout,\n  type DatePickerPicker,\n  type DatePickerPlacement,\n  type DatePickerPreset,\n  type DatePickerSize,\n  type DatePickerStatus,\n  type DatePickerType,\n  type DisabledTimeResult,\n  type FormatValue,\n  type InternalMultiple,\n  type InternalRange,\n  type InternalSingle,\n  type MultipleValue,\n  type RangeValue,\n  type SingleValue,\n  type TimeShape,\n} from './date-picker-utils'\n\nexport type {\n  DatePickerType,\n  DatePickerLayout,\n  DatePickerPicker,\n  DatePickerStatus,\n  DatePickerSize,\n  DatePickerPlacement,\n  FormatValue,\n  SingleValue,\n  MultipleValue,\n  RangeValue,\n  DatePickerPreset,\n  DisabledTimeResult,\n} from './date-picker-utils'\n\nexport type DatePickerValue = SingleValue | MultipleValue | RangeValue\ntype InternalValue = InternalSingle | InternalMultiple | InternalRange | undefined\n\nconst QUARTER_LABELS = ['Q1', 'Q2', 'Q3', 'Q4']\nconst QUARTER_MONTHS = [1, 4, 7, 10] as const\n\nconst compareDates = (a: Date, b: Date) => stripTime(a).getTime() - stripTime(b).getTime()\nconst fmtDateTime = (d: Date, locale: string, format: FormatValue, showSeconds: boolean, use24Hour: boolean) =>\n  `${fmtDate(d, locale, format)} ${fmtTime(d, showSeconds, use24Hour)}`.trim()\nconst quarterStart = (year: number, q: number) => new Date(year, QUARTER_MONTHS[q]! - 1, 1)\nconst weekAnchorForMonth = (year: number, month: number, ws: number) => weekStart(new Date(year, month - 1, 1), ws)\nconst pad = (n: number) => String(n).padStart(2, '0')\n\nfunction layoutToCaptionLayout(layout: DatePickerLayout): CaptionLayout {\n  if (layout === 'month-and-year') return 'dropdown'\n  if (layout === 'month-only') return 'dropdown-months'\n  if (layout === 'year-only') return 'dropdown-years'\n  return 'label'\n}\n\nfunction rangeFromCalendar(r: DateRange | undefined): InternalRange | undefined {\n  if (!r?.from) return undefined\n  return r.to ? { start: r.from, end: r.to } : { start: r.from }\n}\n\nfunction rangeToCalendar(r: InternalRange | undefined): DateRange | undefined {\n  if (!r?.start) return undefined\n  return { from: stripTime(r.start), to: r.end ? stripTime(r.end) : undefined }\n}\n\nfunction getLastTime(internal: InternalValue, type: DatePickerType, defaultTime: string): TimeShape {\n  if (type === 'single' && internal instanceof Date) {\n    if (internal.getHours() || internal.getMinutes() || internal.getSeconds()) {\n      return { h: internal.getHours(), m: internal.getMinutes(), s: internal.getSeconds() }\n    }\n  }\n  if (type === 'range') {\n    const r = internal as InternalRange\n    if (r?.start && (r.start.getHours() || r.start.getMinutes() || r.start.getSeconds())) {\n      return { h: r.start.getHours(), m: r.start.getMinutes(), s: r.start.getSeconds() }\n    }\n  }\n  return parseTimeShape(defaultTime, { h: 12, m: 0, s: 0 })\n}\n\nconst PLACEMENT: Record<DatePickerPlacement, { side: PopoverSide; align: PopoverAlign }> = {\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\nconst NAV_BTN =\n  'border-input hover:bg-accent focus-visible:ring-ring inline-flex size-7 items-center justify-center rounded-md border bg-transparent transition-colors focus-visible:ring-1 focus-visible:outline-none'\nconst CAL_SVG = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-calendar size-4\" aria-hidden=\"true\"><path d=\"M8 2v4\" /><path d=\"M16 2v4\" /><rect width=\"18\" height=\"18\" x=\"3\" y=\"4\" rx=\"2\" /><path d=\"M3 10h18\" /></svg>`\nconst X_SVG = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-x size-3.5\" aria-hidden=\"true\"><path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" /></svg>`\nconst LEFT_SVG = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-chevron-left text-muted-foreground size-4\" aria-hidden=\"true\"><path d=\"m15 18-6-6 6-6\" /></svg>`\nconst RIGHT_SVG = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-chevron-right text-muted-foreground size-4\" aria-hidden=\"true\"><path d=\"m9 18 6-6-6-6\" /></svg>`\nconst CELL_TAIL =\n  'transition-colors focus-visible:ring-2 focus-visible:outline-none disabled:opacity-30 disabled:hover:bg-transparent'\nconst ACTIVE = 'bg-primary text-primary-foreground hover:bg-primary'\n\n/**\n * Angular port of the UIPKGE React DatePicker. Put it on a button\n * (`<button ui-date-picker>`): the host IS the outline trigger Button (React renders\n * PopoverTrigger asChild > Button) with the calendar icon, the formatted value /\n * placeholder and a clear control; the popover holds Calendar / RangeCalendar (single,\n * multiple, range), week / month / quarter / year grids, presets, the time pane, the\n * \"Today\" shortcut and the confirm footer. Same props, defaults and class strings as\n * React; `value` / `defaultValue` / `valueChange` emit ISO strings (`YYYY-MM-DD`, or\n * `YYYY-MM-DDTHH:mm[:ss]` with `showTime`). Also a ControlValueAccessor.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'button[ui-date-picker]',\n  standalone: true,\n  imports: [\n    UiPopoverComponent,\n    UiPopoverContentComponent,\n    UiCalendarComponent,\n    UiRangeCalendarComponent,\n    UiTimeColumnsComponent,\n  ],\n  providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => UiDatePickerComponent), multi: true }],\n  host: {\n    type: 'button',\n    '[class]': 'hostClass',\n    '[disabled]': 'isDisabled',\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"date-picker\"',\n    '[attr.data-variant]': '\"outline\"',\n    '[attr.data-size]': 'buttonSize',\n    '[attr.aria-haspopup]': '\"dialog\"',\n    '[attr.aria-expanded]': 'open()',\n    '[attr.aria-controls]': 'open() ? (popover?.contentId ?? null) : null',\n    '[attr.data-state]': 'open() ? \"open\" : \"closed\"',\n    '(click)': 'toggle()',\n  },\n  template: `\n    ${CAL_SVG}<span class=\"flex-1 truncate\">{{ display || placeholder }}</span>\n    @if (clearable && hasValue && !isDisabled && !readOnly) {\n      <span\n        role=\"button\"\n        tabindex=\"0\"\n        class=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring -mr-1 inline-flex size-9 cursor-pointer items-center justify-center rounded transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n        aria-label=\"Clear date\"\n        (click)=\"$event.stopPropagation(); clear($event)\"\n        (keydown)=\"onClearKeyDown($event)\"\n        >${X_SVG}</span\n      >\n    }\n    <ui-popover [open]=\"open()\" (openChange)=\"setOpen($event)\">\n      <ui-popover-content class=\"w-auto p-0\" [side]=\"popoverPlacement.side\" [align]=\"popoverPlacement.align\">\n        @if (showCurrentDate && type === 'single' && (picker === 'day' || picker === 'week')) {\n          <div class=\"flex justify-end border-b px-3 py-2\">\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        }\n        @if (showAlternatePicker) {\n          <div class=\"w-[260px] p-3\" data-uipkge=\"\" data-slot=\"month-year-picker\">\n            <div class=\"mb-3 flex items-center justify-between\">\n              <button type=\"button\" class=\"${NAV_BTN}\" aria-label=\"Previous\" (click)=\"shiftAnchor(-1)\">\n                ${LEFT_SVG}\n              </button>\n              <span class=\"text-sm font-medium\">{{ monthYearLabel }}</span>\n              <button type=\"button\" class=\"${NAV_BTN}\" aria-label=\"Next\" (click)=\"shiftAnchor(1)\">${RIGHT_SVG}</button>\n            </div>\n            @if (picker === 'week') {\n              <div class=\"flex flex-col gap-1\">\n                @for (week of weekGrid; track $index) {\n                  <button\n                    type=\"button\"\n                    data-uipkge=\"\"\n                    data-slot=\"week-picker-cell\"\n                    [attr.data-active]=\"isWeekSelected(week.start) || null\"\n                    [disabled]=\"isWeekDisabled(week.start)\"\n                    [attr.aria-pressed]=\"isWeekSelected(week.start)\"\n                    [attr.aria-disabled]=\"isWeekDisabled(week.start) || null\"\n                    [class]=\"weekCellClass(week.start)\"\n                    (click)=\"pickWeek(week.start)\"\n                  >\n                    <span class=\"font-medium\">Week {{ week.weekNum }}</span\n                    ><span [class]=\"weekRangeClass(week.start)\"\n                      >{{ week.start.getDate() }} {{ shortMonth(week.start) }} – {{ week.end.getDate() }}\n                      {{ shortMonth(week.end) }}</span\n                    >\n                  </button>\n                }\n              </div>\n            }\n            @if (picker === 'quarter') {\n              <div class=\"grid grid-cols-2 gap-2\">\n                @for (label of quarterLabels; track $index; let q = $index) {\n                  <button\n                    type=\"button\"\n                    data-uipkge=\"\"\n                    data-slot=\"quarter-picker-cell\"\n                    [attr.data-active]=\"isQuarterSelected(q) || null\"\n                    [disabled]=\"isQuarterDisabled(q)\"\n                    [attr.aria-pressed]=\"isQuarterSelected(q)\"\n                    [attr.aria-disabled]=\"isQuarterDisabled(q) || null\"\n                    [class]=\"cellClass('rounded px-4 py-6 text-sm font-medium', isQuarterSelected(q))\"\n                    (click)=\"pickQuarter(q)\"\n                  >\n                    {{ label }}\n                  </button>\n                }\n              </div>\n            }\n            @if (picker === 'month') {\n              <div class=\"grid grid-cols-3 gap-2\">\n                @for (label of monthLabels; track $index; let m = $index) {\n                  <button\n                    type=\"button\"\n                    data-uipkge=\"\"\n                    data-slot=\"month-picker-cell\"\n                    [attr.data-active]=\"isMonthSelected(m) || null\"\n                    [disabled]=\"isMonthDisabled(m)\"\n                    [attr.aria-pressed]=\"isMonthSelected(m)\"\n                    [attr.aria-disabled]=\"isMonthDisabled(m) || null\"\n                    [class]=\"cellClass('rounded px-2 py-2 text-sm', isMonthSelected(m))\"\n                    (click)=\"pickMonth(m)\"\n                  >\n                    {{ label }}\n                  </button>\n                }\n              </div>\n            }\n            @if (picker === 'year') {\n              <div class=\"grid grid-cols-3 gap-2\">\n                @for (y of yearGrid; track y) {\n                  <button\n                    type=\"button\"\n                    data-uipkge=\"\"\n                    data-slot=\"year-picker-cell\"\n                    [attr.data-active]=\"isYearSelected(y) || null\"\n                    [disabled]=\"isYearDisabled(y)\"\n                    [attr.aria-pressed]=\"isYearSelected(y)\"\n                    [attr.aria-disabled]=\"isYearDisabled(y) || null\"\n                    [class]=\"cellClass('rounded px-2 py-2 text-sm tabular-nums', isYearSelected(y))\"\n                    (click)=\"pickYear(y)\"\n                  >\n                    {{ y }}\n                  </button>\n                }\n              </div>\n            }\n          </div>\n        } @else {\n          <div class=\"flex\">\n            @if (presetGroups.length > 0) {\n              <aside class=\"flex w-40 flex-col gap-1 border-r p-2\">\n                @for (group of presetGroups; track $index) {\n                  @if (group.category) {\n                    <div class=\"text-muted-foreground px-2 pt-1 text-xs font-semibold tracking-wider uppercase\">\n                      {{ group.category }}\n                    </div>\n                  }\n                  @for (p of group.presets; track p.label) {\n                    <button\n                      type=\"button\"\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                  }\n                }\n              </aside>\n            }\n            @if (type === 'range') {\n              <ui-range-calendar\n                [selected]=\"$any(calendarValue)\"\n                [numberOfMonths]=\"effectiveNumberOfMonths\"\n                [weekStartsOn]=\"weekStartsOn\"\n                [fixedWeeks]=\"fixedWeeks\"\n                [disabled]=\"calendarDisabled\"\n                [startMonth]=\"minDate ? strip(minDate) : undefined\"\n                [endMonth]=\"maxDate ? strip(maxDate) : undefined\"\n                (select)=\"handleCalendarUpdate($event)\"\n              />\n            } @else {\n              <ui-calendar\n                [mode]=\"type === 'multiple' ? 'multiple' : 'single'\"\n                [selected]=\"calendarValue\"\n                [numberOfMonths]=\"effectiveNumberOfMonths\"\n                [weekStartsOn]=\"weekStartsOn\"\n                [fixedWeeks]=\"fixedWeeks\"\n                [disabled]=\"calendarDisabled\"\n                [captionLayout]=\"captionLayout\"\n                [startMonth]=\"captionLayout !== 'label' && minDate ? strip(minDate) : undefined\"\n                [endMonth]=\"captionLayout !== 'label' && maxDate ? strip(maxDate) : undefined\"\n                [dayContent]=\"renderCell ?? null\"\n                (select)=\"handleCalendarUpdate($event)\"\n              />\n            }\n            @if (showTime) {\n              <div 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)=\"onDone()\"\n                  >\n                    Done\n                  </button>\n                </div>\n                <ui-time-columns\n                  [value]=\"timeForColumns\"\n                  [format]=\"showSeconds ? 'HH:mm:ss' : 'HH:mm'\"\n                  [use24Hour]=\"use24Hour\"\n                  [minuteStep]=\"minuteStep\"\n                  [secondStep]=\"secondStep\"\n                  [visible]=\"open()\"\n                  [disabledHours]=\"disabledTimeConfig?.disabledHours\"\n                  [disabledMinutes]=\"disabledTimeConfig?.disabledMinutes\"\n                  [disabledSeconds]=\"disabledTimeConfig?.disabledSeconds\"\n                  (valueChange)=\"handleTimeUpdate($event)\"\n                />\n              </div>\n            }\n          </div>\n        }\n        @if (needConfirm) {\n          <div 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        }\n      </ui-popover-content>\n    </ui-popover>\n  `,\n})\nexport class UiDatePickerComponent implements OnInit, ControlValueAccessor {\n  private readonly host = inject<ElementRef<HTMLButtonElement>>(ElementRef).nativeElement\n  private readonly cdr = inject(ChangeDetectorRef)\n  @ViewChild(UiPopoverComponent, { static: true }) popover!: UiPopoverComponent\n\n  /** Controlled value. Shape depends on `type`. ISO `YYYY-MM-DD` (or `YYYY-MM-DDTHH:mm` when `showTime`). */\n  @Input() value?: DatePickerValue\n  /** Uncontrolled initial value. */\n  @Input() defaultValue: DatePickerValue = null\n  @Input() type: DatePickerType = 'single'\n  @Input() placeholder = 'Pick a date'\n  @Input({ transform: booleanAttribute }) disabled = false\n  @Input({ transform: booleanAttribute }) readOnly = false\n  @Input({ transform: booleanAttribute }) clearable = true\n  /** Trigger label format: presets or Intl.DateTimeFormatOptions. */\n  @Input() format: FormatValue = 'medium'\n  /** Backward-compat alias for `format`. */\n  @Input() dateFormat?: FormatValue\n  @Input() locale = 'en-US'\n  @Input() numberOfMonths?: number\n  @Input() weekStartsOn: 0 | 1 | 2 | 3 | 4 | 5 | 6 = 0\n  @Input({ transform: booleanAttribute }) fixedWeeks = false\n  @Input() minValue?: string | Date\n  @Input() maxValue?: string | Date\n  /** Header layout: which parts (month / year) become dropdowns. */\n  @Input() layout: DatePickerLayout = 'default'\n  /** Granularity of selection (single type only): day / week / month / quarter / year. */\n  @Input() picker: DatePickerPicker = 'day'\n  /** \"Today\" shortcut at the popover top (single + day / week picker). */\n  @Input({ transform: booleanAttribute }) showCurrentDate = false\n  /** Require clicking OK before applying the selection. */\n  @Input({ transform: booleanAttribute }) needConfirm = false\n  @Input() status?: DatePickerStatus\n  @Input() size: DatePickerSize = 'middle'\n  @Input() placement: DatePickerPlacement = 'bottomLeft'\n  /** Pair the calendar with a time selector. Value becomes `YYYY-MM-DDTHH:mm`. */\n  @Input({ transform: booleanAttribute }) showTime = false\n  @Input({ transform: booleanAttribute }) showSeconds = false\n  @Input({ transform: booleanAttribute }) use24Hour = false\n  @Input({ transform: numberAttribute }) minuteStep = 5\n  @Input({ transform: numberAttribute }) secondStep = 1\n  /** Default time for newly picked dates when `showTime`. `HH:mm` or `HH:mm:ss`. */\n  @Input() defaultTime = '12:00'\n  @Input() presets?: DatePickerPreset[]\n  /** Separator between range start and end. */\n  @Input() separator = '~'\n  @Input() disabledDate?: (current: Date) => boolean\n  @Input() disabledTime?: (current?: Date) => DisabledTimeResult\n  /** Custom day-cell content (React `renderCell(day)`); context `$implicit` is the Date. */\n  @Input() renderCell?: TemplateRef<DayContentContext>\n  @Input() triggerClassName?: string\n  @Input('class') className?: string\n\n  @Output() readonly valueChange = new EventEmitter<DatePickerValue>()\n\n  readonly open = signal(false)\n  private readonly previewValue = signal<InternalValue>(undefined)\n  private readonly internalState = signal<InternalValue>(undefined)\n  private readonly formDisabled = signal(false)\n  private readonly anchor = signal<Date>(stripTime(new Date()))\n  private onChange: (v: DatePickerValue) => void = () => {}\n  private onTouched: () => void = () => {}\n\n  readonly quarterLabels = QUARTER_LABELS\n  readonly strip = stripTime\n\n  ngOnInit(): void {\n    this.popover.trigger = this.host\n    this.internalState.set(coerceShape(this.type, this.defaultValue ?? null))\n    const v = this.internalState()\n    this.anchor.set(v instanceof Date ? v : stripTime(new Date()))\n  }\n\n  // ---- derived state ----\n\n  get isControlled(): boolean {\n    return this.value !== undefined\n  }\n\n  get internal(): InternalValue {\n    return this.isControlled ? coerceShape(this.type, this.value) : this.internalState()\n  }\n\n  get activeValue(): InternalValue {\n    return this.needConfirm ? (this.previewValue() ?? this.internal) : this.internal\n  }\n\n  get isDisabled(): boolean {\n    return this.disabled || this.formDisabled()\n  }\n\n  get effectiveFormat(): FormatValue {\n    return this.dateFormat ?? this.format\n  }\n\n  get effectiveNumberOfMonths(): number {\n    return this.numberOfMonths ?? (this.type === 'range' ? 2 : 1)\n  }\n\n  get effectivePresets(): DatePickerPreset[] | undefined {\n    return this.presets ?? (this.type === 'range' ? defaultRangePresets() : undefined)\n  }\n\n  get presetGroups(): { category?: string; presets: DatePickerPreset[] }[] {\n    const groups = new Map<string | undefined, DatePickerPreset[]>()\n    for (const p of this.effectivePresets ?? []) {\n      if (!groups.has(p.category)) groups.set(p.category, [])\n      groups.get(p.category)!.push(p)\n    }\n    return Array.from(groups.entries()).map(([category, presets]) => ({ category, presets }))\n  }\n\n  get minDate(): Date | undefined {\n    return coerceDate(this.minValue) ?? undefined\n  }\n\n  get maxDate(): Date | undefined {\n    return coerceDate(this.maxValue) ?? undefined\n  }\n\n  get captionLayout(): CaptionLayout {\n    return layoutToCaptionLayout(this.layout)\n  }\n\n  get popoverPlacement(): { side: PopoverSide; align: PopoverAlign } {\n    return PLACEMENT[this.placement] ?? { side: 'bottom', align: 'start' }\n  }\n\n  get buttonSize(): ButtonSize {\n    return this.size === 'small' ? 'sm' : this.size === 'large' ? 'lg' : 'default'\n  }\n\n  get hasValue(): boolean {\n    const v = this.internal\n    if (!v) return false\n    if (this.type === 'multiple') return (v as InternalMultiple).length > 0\n    if (this.type === 'range') return Boolean((v as InternalRange).start)\n    return true\n  }\n\n  get hostClass(): string {\n    return cn(\n      buttonVariants({ variant: 'outline', size: this.buttonSize }),\n      cn(\n        this.showTime ? 'min-w-[280px]' : 'min-w-[240px]',\n        'justify-start gap-2 text-left font-normal',\n        !this.hasValue && 'text-muted-foreground',\n        this.status === 'error' && 'border-destructive focus-visible:ring-destructive',\n        this.status === 'warning' && 'border-warning focus-visible:ring-warning',\n        this.triggerClassName,\n        this.className,\n      ),\n    )\n  }\n\n  get display(): string {\n    const v = this.internal\n    if (!v) return ''\n    const locale = this.locale\n    const f = this.effectiveFormat\n    if (this.type === 'multiple') {\n      const arr = v as InternalMultiple\n      if (!arr.length) return ''\n      if (arr.length === 1) return fmtDate(arr[0]!, locale, f)\n      if (arr.length <= 3) return arr.map((d) => fmtDate(d, locale, f)).join(', ')\n      return `${arr.length} dates selected`\n    }\n    if (this.type === 'range') {\n      const r = v as InternalRange\n      const fmt = this.showTime\n        ? (d: Date) => fmtDateTime(d, locale, f, this.showSeconds, this.use24Hour)\n        : (d: Date) => fmtDate(d, locale, f)\n      if (r.start && r.end) return `${fmt(r.start)} ${this.separator} ${fmt(r.end)}`\n      if (r.start) return `${fmt(r.start)} ${this.separator} …`\n      return ''\n    }\n    if (this.picker === 'week') {\n      const ws = weekStart(v as Date, this.weekStartsOn)\n      return `Week ${weekNumber(ws)}, ${ws.getFullYear()}`\n    }\n    if (this.picker === 'month') return fmtMonth(v as Date, locale)\n    if (this.picker === 'quarter') {\n      const d = v as Date\n      return `Q${Math.ceil((d.getMonth() + 1) / 3)} ${d.getFullYear()}`\n    }\n    if (this.picker === 'year') return String((v as Date).getFullYear())\n    return this.showTime\n      ? fmtDateTime(v as Date, locale, f, this.showSeconds, this.use24Hour)\n      : fmtDate(v as Date, locale, f)\n  }\n\n  /** Prefer preview (confirm mode) so time columns track the uncommitted selection. */\n  get lastTime(): TimeShape {\n    return getLastTime(this.activeValue, this.type, this.defaultTime)\n  }\n\n  get timeForColumns(): string {\n    const t = this.lastTime\n    return this.showSeconds ? `${pad(t.h)}:${pad(t.m)}:${pad(t.s)}` : `${pad(t.h)}:${pad(t.m)}`\n  }\n\n  private timeCache?: { key: string; fn: unknown; value: DisabledTimeResult | undefined }\n  get disabledTimeConfig(): DisabledTimeResult | undefined {\n    if (!this.disabledTime) return undefined\n    const v = this.activeValue\n    let current: Date | undefined\n    if (this.type === 'range') current = (v as InternalRange | undefined)?.start\n    else if (v instanceof Date) current = v\n    else if (Array.isArray(v)) current = v[0]\n    // Memoized (React useMemo): the callbacks feed TimeColumns inputs, so keep identities stable.\n    const key = String(current?.getTime())\n    if (this.timeCache?.key !== key || this.timeCache.fn !== this.disabledTime) {\n      this.timeCache = { key, fn: this.disabledTime, value: this.disabledTime(current) }\n    }\n    return this.timeCache.value\n  }\n\n  private matchersCache?: { key: string; value: Matcher[] | undefined }\n  get calendarDisabled(): Matcher[] | undefined {\n    const min = this.minDate\n    const max = this.maxDate\n    const key = `${min?.getTime()}|${max?.getTime()}|${this.disabledDate ? 'fn' : ''}`\n    if (this.matchersCache?.key !== key) {\n      const m: Matcher[] = []\n      if (min) m.push({ before: stripTime(min) })\n      if (max) m.push({ after: stripTime(max) })\n      if (this.disabledDate) m.push(this.disabledDate)\n      this.matchersCache = { key, value: m.length ? m : undefined }\n    }\n    return this.matchersCache.value\n  }\n\n  private valueCache?: { key: string; value: DayPickerSelected }\n  /** Memoized (React useMemo) so the calendar only re-renders when the selection changes. */\n  get calendarValue(): DayPickerSelected {\n    const v = this.activeValue\n    const t = (d?: Date) => (d ? stripTime(d).getTime() : '')\n    const key =\n      this.type === 'multiple'\n        ? `m${((v as InternalMultiple | undefined) ?? []).map((d) => t(d)).join(',')}${v ? '' : 'u'}`\n        : this.type === 'range'\n          ? `r${t((v as InternalRange | undefined)?.start)}-${t((v as InternalRange | undefined)?.end)}`\n          : `s${v instanceof Date ? t(v) : ''}`\n    if (this.valueCache?.key !== key) {\n      let value: DayPickerSelected\n      if (this.type === 'multiple') value = (v as InternalMultiple | undefined)?.map(stripTime)\n      else if (this.type === 'range') value = rangeToCalendar(v as InternalRange | undefined)\n      else value = v instanceof Date ? stripTime(v) : undefined\n      this.valueCache = { key, value }\n    }\n    return this.valueCache.value\n  }\n\n  get showAlternatePicker(): boolean {\n    return this.picker !== 'day' && this.type === 'single'\n  }\n\n  get monthLabels(): string[] {\n    const f = new Intl.DateTimeFormat(this.locale, { month: 'short' })\n    return Array.from({ length: 12 }, (_, i) => f.format(new Date(2024, i, 1)))\n  }\n\n  get yearGrid(): number[] {\n    const y = this.anchor().getFullYear()\n    const start = y - (y % 12)\n    return Array.from({ length: 12 }, (_, i) => start + i)\n  }\n\n  get weekGrid(): { start: Date; end: Date; weekNum: number }[] {\n    const a = this.anchor()\n    const start = weekAnchorForMonth(a.getFullYear(), a.getMonth() + 1, this.weekStartsOn)\n    return Array.from({ length: 6 }, (_, i) => {\n      const s = new Date(start)\n      s.setDate(s.getDate() + i * 7)\n      const e = new Date(s)\n      e.setDate(e.getDate() + 6)\n      return { start: s, end: e, weekNum: weekNumber(s) }\n    })\n  }\n\n  get monthYearLabel(): string {\n    const a = this.anchor()\n    if (this.picker === 'week')\n      return new Intl.DateTimeFormat(this.locale, { month: 'short', year: 'numeric' }).format(a)\n    if (this.picker === 'quarter' || this.picker === 'month') return String(a.getFullYear())\n    const g = this.yearGrid\n    return `${g[0]} – ${g[g.length - 1]}`\n  }\n\n  shortMonth(d: Date): string {\n    return new Intl.DateTimeFormat(this.locale, { month: 'short' }).format(d)\n  }\n\n  cellClass(base: string, active: boolean): string {\n    return cn(`hover:bg-accent focus-visible:ring-ring ${base} ${CELL_TAIL}`, active && ACTIVE)\n  }\n\n  weekCellClass(ws: Date): string {\n    return cn(\n      '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      this.isWeekSelected(ws) && ACTIVE,\n    )\n  }\n\n  weekRangeClass(ws: Date): string {\n    return cn('text-muted-foreground text-xs', this.isWeekSelected(ws) && 'text-primary-foreground')\n  }\n\n  // ---- open state ----\n\n  toggle(): void {\n    if (this.isDisabled) return\n    this.setOpen(!this.open())\n  }\n\n  setOpen(v: boolean): void {\n    if (v === this.open()) return\n    this.open.set(v)\n    if (v) this.syncAnchor()\n    else {\n      this.previewValue.set(undefined)\n      this.onTouched()\n    }\n  }\n\n  /** React effect on [open, internal]: anchor the alternate grids on the value (or today). */\n  private syncAnchor(): void {\n    const v = this.internal\n    this.anchor.set(v instanceof Date ? v : stripTime(new Date()))\n  }\n\n  // ---- emit ----\n\n  private serializeDate(d: Date): string {\n    return this.showTime ? toISODateTime(d, this.showSeconds) : toISODate(d)\n  }\n\n  private emit(v: DatePickerValue): void {\n    this.valueChange.emit(v)\n    this.onChange(v)\n  }\n\n  private emitOut(v: InternalValue): void {\n    if (this.type === 'multiple') {\n      this.emit(((v as InternalMultiple) ?? []).map((d) => this.serializeDate(d)))\n      return\n    }\n    if (this.type === 'range') {\n      // Clear emits null; an incomplete range (start only) stays local until both ends exist.\n      if (!v) return this.emit(null)\n      const r = v as InternalRange\n      if (!r.start || !r.end) return\n      this.emit({ start: this.serializeDate(r.start), end: this.serializeDate(r.end) })\n      return\n    }\n    const single = v as InternalSingle\n    this.emit(single ? this.serializeDate(single) : null)\n  }\n\n  private handleUpdate(v: InternalValue): void {\n    if (!this.isControlled) this.internalState.set(v)\n    this.emitOut(v)\n    // React effect on [open, internal]: re-anchor the alternate grids on the new value.\n    if (this.open()) this.anchor.set(v instanceof Date ? v : stripTime(new Date()))\n    if (this.needConfirm) return\n    if (this.type === 'single' && v && !this.showTime) this.setOpen(false)\n    if (this.type === 'range') {\n      const r = v as InternalRange | undefined\n      if (r?.start && r?.end && !this.showTime) this.setOpen(false)\n    }\n  }\n\n  clear(e: Event): void {\n    e.stopPropagation()\n    if (this.isDisabled || this.readOnly) return\n    this.handleUpdate(this.type === 'multiple' ? [] : undefined)\n  }\n\n  onClearKeyDown(e: KeyboardEvent): void {\n    if (e.key === 'Enter' || e.key === ' ') {\n      e.preventDefault()\n      e.stopPropagation()\n      this.clear(e)\n    }\n  }\n\n  applyPreset(preset: DatePickerPreset): void {\n    if (this.isDisabled || this.readOnly) return\n    const v = preset.value\n    const done = (next: InternalValue) => {\n      this.handleUpdate(next)\n      this.setOpen(false)\n    }\n    if (!v) return done(undefined)\n    if (this.type === 'multiple') {\n      return done(((v as MultipleValue) ?? []).map(coerceDate).filter((x): x is Date => x != null))\n    }\n    if (this.type === 'range') {\n      const r = v as RangeValue\n      if (!r?.start) return done(undefined)\n      const start = coerceDate(r.start)\n      const end = r.end ? coerceDate(r.end) : undefined\n      if (!start) return done(undefined)\n      return done(end ? { start, end } : { start })\n    }\n    done(coerceDate(v as SingleValue) ?? undefined)\n  }\n\n  commitPreview(): void {\n    const p = this.previewValue()\n    if (this.needConfirm && p !== undefined) {\n      this.handleUpdate(p)\n      this.previewValue.set(undefined)\n    }\n    this.setOpen(false)\n  }\n\n  cancelPreview(): void {\n    this.previewValue.set(undefined)\n    this.setOpen(false)\n  }\n\n  onDone(): void {\n    // In confirm mode, Done must commit the preview -- closing alone discards it.\n    if (this.needConfirm) this.commitPreview()\n    else this.setOpen(false)\n  }\n\n  handleCalendarUpdate(sel: DayPickerSelected): void {\n    if (this.readOnly) return\n    const t = this.lastTime\n    const time = (d: Date) => (this.showTime ? withTime(d, t) : d)\n    if (this.needConfirm) {\n      if (this.type === 'multiple') this.previewValue.set(((sel as Date[]) ?? []).map(time))\n      else if (this.type === 'range') {\n        const r = rangeFromCalendar(sel as DateRange | undefined)\n        if (!r?.start) this.previewValue.set(undefined)\n        else {\n          const start = time(r.start)\n          const end = r.end ? time(r.end) : undefined\n          this.previewValue.set(end ? { start, end } : { start })\n        }\n      } else {\n        const d = sel as Date | undefined\n        this.previewValue.set(d ? time(d) : undefined)\n      }\n      return\n    }\n    if (!this.showTime) {\n      if (this.type === 'range') this.handleUpdate(rangeFromCalendar(sel as DateRange | undefined))\n      else if (this.type === 'multiple') this.handleUpdate((sel as Date[]) ?? [])\n      else this.handleUpdate(sel as InternalSingle)\n      return\n    }\n    if (this.type === 'multiple') return this.handleUpdate(((sel as Date[]) ?? []).map((d) => withTime(d, t)))\n    if (this.type === 'range') {\n      const r = rangeFromCalendar(sel as DateRange | undefined)\n      if (!r?.start) return this.handleUpdate(undefined)\n      const start = withTime(r.start, t)\n      const end = r.end ? withTime(r.end, t) : undefined\n      return this.handleUpdate(end ? { start, end } : { start })\n    }\n    const d = sel as Date | undefined\n    if (d) this.handleUpdate(withTime(d, t))\n  }\n\n  handleTimeUpdate(timeValue: string): void {\n    const base = this.needConfirm ? (this.previewValue() ?? this.internal) : this.internal\n    if (!base) return\n    const t = parseTimeShape(timeValue, this.lastTime)\n    if (this.type === 'single') {\n      const next = withTime(base as Date, t)\n      if (this.needConfirm) return this.previewValue.set(next)\n      return this.handleUpdate(next)\n    }\n    if (this.type === 'range') {\n      const r = base as InternalRange\n      if (!r.start) return\n      const start = withTime(r.start, t)\n      const end = r.end ? withTime(r.end, t) : undefined\n      const next = end ? { start, end } : { start }\n      if (this.needConfirm) return this.previewValue.set(next)\n      this.handleUpdate(next)\n    }\n  }\n\n  pickToday(): void {\n    if (this.type !== 'single') return\n    const t = stripTime(new Date())\n    this.handleUpdate(this.picker === 'week' ? weekStart(t, this.weekStartsOn) : t)\n  }\n\n  shiftAnchor(dir: -1 | 1): void {\n    const a = this.anchor()\n    const y = a.getFullYear()\n    if (this.picker === 'month' || this.picker === 'week') this.anchor.set(new Date(y + dir, a.getMonth(), 1))\n    else if (this.picker === 'year' || this.picker === 'quarter') this.anchor.set(new Date(y + dir, 0, 1))\n  }\n\n  private pickAlt(d: Date, disabled: boolean): void {\n    if (disabled || this.readOnly) return\n    if (this.needConfirm) return this.previewValue.set(d)\n    this.handleUpdate(d)\n  }\n\n  isWeekSelected(ws: Date): boolean {\n    const v = this.activeValue\n    return v instanceof Date && compareDates(weekStart(v, this.weekStartsOn), ws) === 0\n  }\n\n  isWeekDisabled(ws: Date): boolean {\n    if (this.minDate && compareDates(ws, this.minDate) < 0) return true\n    const end = new Date(ws)\n    end.setDate(end.getDate() + 6)\n    if (this.maxDate && compareDates(end, this.maxDate) > 0) return true\n    return !!this.disabledDate?.(ws)\n  }\n\n  pickWeek(ws: Date): void {\n    this.pickAlt(ws, this.isWeekDisabled(ws))\n  }\n\n  isQuarterSelected(q: number): boolean {\n    const v = this.activeValue\n    return (\n      v instanceof Date &&\n      v.getFullYear() === this.anchor().getFullYear() &&\n      Math.ceil((v.getMonth() + 1) / 3) === q + 1\n    )\n  }\n\n  isQuarterDisabled(q: number): boolean {\n    const d = quarterStart(this.anchor().getFullYear(), q)\n    if (this.minDate && compareDates(d, this.minDate) < 0) return true\n    const last = new Date(d.getFullYear(), d.getMonth() + 3, 0)\n    if (this.maxDate && compareDates(last, this.maxDate) > 0) return true\n    return !!this.disabledDate?.(d)\n  }\n\n  pickQuarter(q: number): void {\n    this.pickAlt(quarterStart(this.anchor().getFullYear(), q), this.isQuarterDisabled(q))\n  }\n\n  isMonthSelected(m: number): boolean {\n    const v = this.activeValue\n    return v instanceof Date && v.getFullYear() === this.anchor().getFullYear() && v.getMonth() === m\n  }\n\n  isMonthDisabled(m: number): boolean {\n    const d = new Date(this.anchor().getFullYear(), m, 1)\n    if (this.minDate && compareDates(d, this.minDate) < 0) return true\n    if (this.maxDate && compareDates(d, this.maxDate) > 0) return true\n    return !!this.disabledDate?.(d)\n  }\n\n  pickMonth(m: number): void {\n    this.pickAlt(new Date(this.anchor().getFullYear(), m, 1), this.isMonthDisabled(m))\n  }\n\n  isYearSelected(y: number): boolean {\n    const v = this.activeValue\n    return v instanceof Date && v.getFullYear() === y\n  }\n\n  isYearDisabled(y: number): boolean {\n    const d = new Date(y, 0, 1)\n    if (this.minDate && compareDates(new Date(y, 11, 31), this.minDate) < 0) return true\n    if (this.maxDate && compareDates(d, this.maxDate) > 0) return true\n    return !!this.disabledDate?.(d)\n  }\n\n  pickYear(y: number): void {\n    const d = new Date(y, 0, 1)\n    if (this.isYearDisabled(y) || this.readOnly) return\n    if (this.picker === 'year') this.pickAlt(d, false)\n    else this.anchor.set(d)\n  }\n\n  // ---- ControlValueAccessor ----\n\n  writeValue(v: DatePickerValue): void {\n    this.internalState.set(coerceShape(this.type, v ?? null))\n    this.cdr.markForCheck()\n  }\n\n  registerOnChange(fn: (v: DatePickerValue) => void): void {\n    this.onChange = fn\n  }\n\n  registerOnTouched(fn: () => void): void {\n    this.onTouched = fn\n  }\n\n  setDisabledState(isDisabled: boolean): void {\n    this.formDisabled.set(isDisabled)\n    this.cdr.markForCheck()\n  }\n}\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/date-picker/date-picker.component.ts"
    },
    {
      "path": "packages/registry-angular/components/date-picker/date-picker-utils.ts",
      "content": "export 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' | 'bottom' | 'left' | 'right' | 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight'\n\nexport type FormatValue = 'short' | 'medium' | 'long' | 'full' | Intl.DateTimeFormatOptions\n\nexport type SingleValue = string | Date | null\nexport type MultipleValue = (string | Date)[] | null\nexport type RangeValue = { start: string | Date; end?: string | Date } | 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\nexport type TimeShape = { h: number; m: number; s: number }\n\nexport type InternalSingle = Date | undefined\nexport type InternalMultiple = Date[]\nexport type InternalRange = { start?: Date; end?: Date }\n\nexport function coerceDate(v: string | Date | null | undefined): Date | null {\n  if (!v) return null\n  if (v instanceof Date) return Number.isNaN(v.getTime()) ? null : v\n  const dt = /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2})(?::(\\d{2}))?/.exec(v)\n  if (dt) {\n    const d = new Date(\n      Number(dt[1]),\n      Number(dt[2]) - 1,\n      Number(dt[3]),\n      Number(dt[4]),\n      Number(dt[5]),\n      dt[6] ? Number(dt[6]) : 0,\n    )\n    return Number.isNaN(d.getTime()) ? null : d\n  }\n  // Date-only after date-time: the React source tests this unanchored pattern first, which\n  // swallows `YYYY-MM-DDTHH:mm` and drops the time (a showTime value never keeps its hour).\n  const m = /^(\\d{4})-(\\d{2})-(\\d{2})/.exec(v)\n  if (m) {\n    const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]))\n    return Number.isNaN(d.getTime()) ? null : d\n  }\n  const d = new Date(v)\n  return Number.isNaN(d.getTime()) ? null : d\n}\n\nexport function toISODate(d: Date): string {\n  const y = d.getFullYear()\n  const mo = String(d.getMonth() + 1).padStart(2, '0')\n  const da = String(d.getDate()).padStart(2, '0')\n  return `${y}-${mo}-${da}`\n}\n\nexport function toISODateTime(d: Date, showSeconds: boolean): string {\n  const date = toISODate(d)\n  const h = String(d.getHours()).padStart(2, '0')\n  const m = String(d.getMinutes()).padStart(2, '0')\n  if (showSeconds) {\n    const s = String(d.getSeconds()).padStart(2, '0')\n    return `${date}T${h}:${m}:${s}`\n  }\n  return `${date}T${h}:${m}`\n}\n\nexport function stripTime(d: Date): Date {\n  return new Date(d.getFullYear(), d.getMonth(), d.getDate())\n}\n\nexport function parseTimeShape(value: string, fallback: TimeShape): TimeShape {\n  const m = /^(\\d{1,2}):(\\d{2})(?::(\\d{2}))?$/.exec(value)\n  if (!m) return fallback\n  return { h: Number(m[1]), m: Number(m[2]), s: m[3] ? Number(m[3]) : 0 }\n}\n\nexport function withTime(d: Date, t: TimeShape): Date {\n  return new Date(d.getFullYear(), d.getMonth(), d.getDate(), t.h, t.m, t.s)\n}\n\nexport function weekStart(d: Date, weekStartsOn: number): Date {\n  const dayOfWeek = d.getDay()\n  const offset = (dayOfWeek - weekStartsOn + 7) % 7\n  const out = new Date(d)\n  out.setDate(out.getDate() - offset)\n  return stripTime(out)\n}\n\nexport function weekNumber(d: Date): number {\n  const jsDate = new Date(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\nexport function defaultRangePresets(): DatePickerPreset[] {\n  const t = stripTime(new Date())\n  const yesterday = new Date(t)\n  yesterday.setDate(yesterday.getDate() - 1)\n  const last7 = new Date(t)\n  last7.setDate(last7.getDate() - 6)\n  const last30 = new Date(t)\n  last30.setDate(last30.getDate() - 29)\n  const monthStart = new Date(t.getFullYear(), t.getMonth(), 1)\n  const lastMonthEnd = new Date(t.getFullYear(), t.getMonth(), 0)\n  const lastMonthStart = new Date(lastMonthEnd.getFullYear(), lastMonthEnd.getMonth(), 1)\n  const ytd = new Date(t.getFullYear(), 0, 1)\n  return [\n    { label: 'Today', value: { start: t, end: t } },\n    { label: 'Yesterday', value: { start: yesterday, end: yesterday } },\n    { label: 'Last 7 days', value: { start: last7, end: t } },\n    { label: 'Last 30 days', value: { start: last30, end: t } },\n    { label: 'This month', value: { start: monthStart, end: t } },\n    { label: 'Last month', value: { start: lastMonthStart, end: lastMonthEnd } },\n    { label: 'Year to date', value: { start: ytd, end: t } },\n  ]\n}\n\nexport function coerceShape(\n  type: DatePickerType,\n  v: SingleValue | MultipleValue | RangeValue | undefined,\n): InternalSingle | InternalMultiple | InternalRange | undefined {\n  if (type === 'multiple') {\n    if (!Array.isArray(v)) return undefined\n    return v.map(coerceDate).filter((x): x is Date => x != null)\n  }\n  if (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 | Date; end?: string | Date }\n    const start = coerceDate(r.start)\n    const end = r.end ? coerceDate(r.end) : undefined\n    if (!start) return undefined\n    return end ? { start, end } : { start }\n  }\n  return coerceDate(v as SingleValue) ?? undefined\n}\n\nexport function fmtDate(d: Date, locale: string, format: FormatValue) {\n  const opts: Intl.DateTimeFormatOptions = typeof format === 'object' ? format : { dateStyle: format }\n  return new Intl.DateTimeFormat(locale, opts).format(d)\n}\n\nexport function fmtMonth(d: Date, locale: string) {\n  return new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric' }).format(d)\n}\n\nexport function fmtTime(d: Date, showSeconds: boolean, use24Hour: boolean) {\n  const opts: Intl.DateTimeFormatOptions = showSeconds\n    ? { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: !use24Hour }\n    : { hour: '2-digit', minute: '2-digit', hour12: !use24Hour }\n  return new Intl.DateTimeFormat(undefined, opts).format(d)\n}\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/date-picker/date-picker-utils.ts"
    },
    {
      "path": "packages/registry-angular/components/date-picker/index.ts",
      "content": "export {\n  UiDatePickerComponent,\n  type DatePickerValue,\n  type DatePickerType,\n  type DatePickerLayout,\n  type DatePickerPicker,\n  type DatePickerStatus,\n  type DatePickerSize,\n  type DatePickerPlacement,\n  type FormatValue,\n  type SingleValue,\n  type MultipleValue,\n  type RangeValue,\n  type DatePickerPreset,\n  type DisabledTimeResult,\n} from './date-picker.component'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/date-picker/index.ts"
    }
  ],
  "dependencies": [
    "@angular/forms"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/calendar.json",
    "https://uipkge.dev/r/angular/popover.json",
    "https://uipkge.dev/r/angular/range-calendar.json",
    "https://uipkge.dev/r/angular/time-picker.json"
  ],
  "description": "Form date control: trigger + popover + Calendar/RangeCalendar + optional time. Single/multiple/range, week/month/quarter/year pickers, presets, confirm mode, min/max and disabled dates. For an always-visible grid, use Calendar instead.",
  "categories": [
    "date-time"
  ]
}