{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "event-calendar",
  "title": "Event Calendar",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-angular/components/event-calendar/event-calendar.component.ts",
      "content": "import {\n  Component,\n  Directive,\n  EmbeddedViewRef,\n  EventEmitter,\n  Input,\n  OnChanges,\n  OnDestroy,\n  OnInit,\n  Output,\n  SimpleChanges,\n  TemplateRef,\n  ViewContainerRef,\n  booleanAttribute,\n  inject,\n  signal,\n  computed,\n  ChangeDetectionStrategy,\n} from '@angular/core'\nimport { cn } from '@/lib/utils'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport {\n  UiPopoverComponent,\n  UiPopoverContentComponent,\n  UiPopoverTriggerComponent,\n} from '@/ui/popover/popover.component'\nimport {\n  calculateTimedEventPositions,\n  formatDateKey,\n  formatHourLabel,\n  formatTime,\n  getCurrentTimePosition,\n  getEventMinutes,\n  getMonthDays,\n  getWeekDays,\n  getWorkWeekDays,\n  isToday,\n  parseDate,\n} from './date-utils'\nimport { calendarEventVariants, type CalendarEventVariants } from './event-calendar.variants'\nimport type { CalendarCategory, CalendarEvent, CalendarView, TimeClickPayload } from './types'\n\n/* Render-prop contexts (React's `renderX` params; `$implicit` is the whole object). */\nexport interface EventCalendarHeaderContext {\n  currentDate: Date\n  view: CalendarView\n  title: string\n  prev: () => void\n  next: () => void\n  today: () => void\n  setView: (view: CalendarView) => void\n}\nexport interface EventCalendarEventContext {\n  event: CalendarEvent\n  view: CalendarView\n  isAllDay: boolean\n}\nexport interface EventCalendarDayHeaderContext {\n  date: Date\n  dateKey: string\n  isToday: boolean\n  view: CalendarView\n}\nexport interface EventCalendarAllDayContext {\n  date: Date\n  dateKey: string\n  events: CalendarEvent[]\n}\nexport interface EventCalendarIntervalContext {\n  hour: number\n  time: string\n  label: string\n}\nexport interface EventCalendarMoreClick {\n  date: string\n  events: CalendarEvent[]\n}\n\n/** Renders a render-prop template with `{ $implicit: ctx, ...ctx }` as its context. */\n@Directive({ selector: '[uiEventCalendarOutlet]', standalone: true })\nexport class UiEventCalendarOutletDirective implements OnChanges, OnDestroy {\n  @Input('uiEventCalendarOutlet') template: TemplateRef<unknown> | null | undefined = null\n  @Input('uiEventCalendarOutletContext') context: object = {}\n  private readonly vcr = inject(ViewContainerRef)\n  private view: EmbeddedViewRef<Record<string, unknown>> | null = null\n  private rendered: TemplateRef<unknown> | null = null\n\n  ngOnChanges(): void {\n    const ctx = { $implicit: this.context, ...this.context }\n    // Same template, new context values: update in place (keeps DOM, hover and focus).\n    if (this.view && this.rendered === this.template) {\n      Object.assign(this.view.context, ctx)\n      return\n    }\n    this.vcr.clear()\n    this.view = null\n    this.rendered = this.template ?? null\n    if (this.template)\n      this.view = this.vcr.createEmbeddedView(this.template as TemplateRef<Record<string, unknown>>, ctx)\n  }\n\n  ngOnDestroy(): void {\n    this.vcr.clear()\n  }\n}\n\nconst EVENT_COLORS = ['primary', 'secondary', 'success', 'warning', 'destructive', 'info', 'purple', 'rose']\ntype EventVariant = NonNullable<CalendarEventVariants['variant']>\n\nconst TOGGLE_ACTIVE = 'bg-background text-foreground shadow-xs'\nconst TOGGLE_IDLE = 'text-muted-foreground hover:text-foreground'\n\n/**\n * Angular port of UIPKGE EventCalendar (React parity): month grid with \"+N more\" popovers,\n * week / work-week / day time grids with a pinned all-day row, overlap-aware event lanes and\n * a live now-indicator, plus a category (resource) view. Controlled or uncontrolled date\n * (`value` / `defaultValue` + `dateChange`) and view (`view` / `defaultView` +\n * `viewChange`); `eventClick`, `dateClick`, `timeClick`, `moreClick` outputs; React's\n * `renderX` props are TemplateRef inputs; `headerActions` is `[slot=header-actions]` content.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-event-calendar, [ui-event-calendar]',\n  standalone: true,\n  imports: [\n    UiButtonComponent,\n    UiPopoverComponent,\n    UiPopoverTriggerComponent,\n    UiPopoverContentComponent,\n    UiEventCalendarOutletDirective,\n  ],\n  host: {\n    '[attr.data-slot]': '\"event-calendar\"',\n    '[class]': 'hostClass',\n  },\n  template: `\n    @if (showHeader) {\n      @if (renderHeader) {\n        <ng-container\n          [uiEventCalendarOutlet]=\"renderHeader\"\n          [uiEventCalendarOutletContext]=\"{\n            currentDate: activeDate,\n            view: activeView,\n            title: formattedTitle,\n            prev: prev,\n            next: next,\n            today: today,\n            setView: setView,\n          }\"\n        />\n      } @else {\n        <header\n          class=\"border-border bg-card/60 flex flex-wrap items-center justify-between gap-3 border-b px-4 py-3 backdrop-blur-xs\"\n        >\n          <div class=\"flex items-center gap-2\">\n            <button ui-button variant=\"outline\" size=\"sm\" class=\"h-8 px-2.5 text-xs font-medium\" (click)=\"today()\">\n              Today\n            </button>\n            <div class=\"flex items-center gap-0.5\">\n              <button\n                ui-button\n                variant=\"ghost\"\n                size=\"icon\"\n                class=\"size-8\"\n                aria-label=\"Previous period\"\n                (click)=\"prev()\"\n              >\n                <svg\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                  width=\"24\"\n                  height=\"24\"\n                  viewBox=\"0 0 24 24\"\n                  fill=\"none\"\n                  stroke=\"currentColor\"\n                  stroke-width=\"2\"\n                  stroke-linecap=\"round\"\n                  stroke-linejoin=\"round\"\n                  class=\"lucide lucide-chevron-left size-4\"\n                  aria-hidden=\"true\"\n                >\n                  <path d=\"m15 18-6-6 6-6\" />\n                </svg>\n              </button>\n              <button ui-button variant=\"ghost\" size=\"icon\" class=\"size-8\" aria-label=\"Next period\" (click)=\"next()\">\n                <svg\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                  width=\"24\"\n                  height=\"24\"\n                  viewBox=\"0 0 24 24\"\n                  fill=\"none\"\n                  stroke=\"currentColor\"\n                  stroke-width=\"2\"\n                  stroke-linecap=\"round\"\n                  stroke-linejoin=\"round\"\n                  class=\"lucide lucide-chevron-right size-4\"\n                  aria-hidden=\"true\"\n                >\n                  <path d=\"m9 18 6-6-6-6\" />\n                </svg>\n              </button>\n            </div>\n            <h2 class=\"text-foreground ml-1 text-base font-semibold tracking-tight sm:text-lg\">{{ formattedTitle }}</h2>\n          </div>\n          <div class=\"flex items-center gap-1.5\">\n            <ng-content select=\"[slot=header-actions]\" />\n            <div class=\"border-border bg-muted/40 flex items-center rounded-lg border p-0.5\">\n              <button type=\"button\" [class]=\"toggleClass('month')\" (click)=\"setView('month')\">Month</button>\n              <button type=\"button\" [class]=\"toggleClass('week')\" (click)=\"setView('week')\">Week</button>\n              <button type=\"button\" [class]=\"toggleClass('work-week')\" (click)=\"setView('work-week')\">Work</button>\n              <button type=\"button\" [class]=\"toggleClass('day')\" (click)=\"setView('day')\">Day</button>\n              @if (normalizedCategories.length > 0) {\n                <button type=\"button\" [class]=\"toggleClass('category')\" (click)=\"setView('category')\">Category</button>\n              }\n            </div>\n          </div>\n        </header>\n      }\n    }\n\n    <ng-template #eventChip let-evt let-truncate=\"truncate\">\n      <div data-slot=\"event-card\" [class]=\"chipClass(evt, truncate)\" (click)=\"onEventClick(evt, $event)\">\n        <div class=\"flex items-center gap-1 truncate font-medium\">\n          @if (!evt.allDay) {\n            <span class=\"shrink-0 font-mono text-[10px] opacity-75\">{{ eventTime(evt) }}</span>\n          }\n          <span class=\"truncate\">{{ evt.title }}</span>\n        </div>\n      </div>\n    </ng-template>\n\n    @if (activeView === 'month') {\n      <div class=\"flex flex-1 flex-col\">\n        <div\n          class=\"border-border bg-muted/20 text-muted-foreground grid grid-cols-7 border-b text-center text-xs font-medium\"\n        >\n          @for (dayName of weekdayHeaderLabels; track $index) {\n            <div class=\"border-border/40 border-r py-2 last:border-r-0\">{{ dayName }}</div>\n          }\n        </div>\n        <div class=\"divide-border/40 grid min-h-[580px] flex-1 grid-cols-7 grid-rows-6 divide-x divide-y\">\n          @for (cell of monthDays; track cell.dateKey) {\n            @let dayEvents = getEventsForDay(cell.dateKey);\n            <div [class]=\"cellClass(cell.inMonth)\" (click)=\"dateClick.emit(cell.dateKey)\">\n              <div class=\"mb-1 flex items-center justify-between\">\n                <span [class]=\"dayNumberClass(cell.isToday, cell.inMonth)\">{{ cell.date.getDate() }}</span>\n              </div>\n              <div class=\"flex flex-1 flex-col gap-1 overflow-hidden\">\n                @for (evt of dayEvents; track evt.id || $index; let idx = $index) {\n                  @if (dayEvents.length <= maxEventsPerDay || idx < maxEventsPerDay - 1) {\n                    @if (renderEvent) {\n                      <ng-container\n                        [uiEventCalendarOutlet]=\"renderEvent\"\n                        [uiEventCalendarOutletContext]=\"{ event: evt, view: 'month', isAllDay: !!evt.allDay }\"\n                      />\n                    } @else {\n                      <ng-container\n                        [uiEventCalendarOutlet]=\"eventChip\"\n                        [uiEventCalendarOutletContext]=\"{ $implicit: evt, truncate: true }\"\n                      />\n                    }\n                  }\n                }\n                @if (dayEvents.length > maxEventsPerDay) {\n                  <div class=\"mt-auto pt-0.5\">\n                    <ui-popover>\n                      <button\n                        type=\"button\"\n                        ui-popover-trigger\n                        class=\"text-primary hover:text-primary/80 hover:bg-primary/10 flex items-center gap-0.5 rounded-sm px-1 py-0.5 text-[11px] font-semibold transition-colors hover:underline\"\n                        (click)=\"$event.stopPropagation(); moreClick.emit({ date: cell.dateKey, events: dayEvents })\"\n                      >\n                        +{{ dayEvents.length - (maxEventsPerDay - 1) }} more\n                      </button>\n                      <ui-popover-content class=\"w-64 p-2 shadow-lg\" align=\"start\">\n                        <div\n                          class=\"border-border mb-1.5 flex items-center justify-between border-b pb-1.5 text-xs font-semibold\"\n                        >\n                          <span>{{ popoverDate(cell.date) }}</span>\n                          <span class=\"text-muted-foreground text-[11px] font-normal\"\n                            >{{ dayEvents.length }} events</span\n                          >\n                        </div>\n                        <div class=\"flex max-h-48 flex-col gap-1 overflow-y-auto\">\n                          @for (evt of dayEvents; track evt.id || $index) {\n                            <ng-container\n                              [uiEventCalendarOutlet]=\"eventChip\"\n                              [uiEventCalendarOutletContext]=\"{ $implicit: evt, truncate: false }\"\n                            />\n                          }\n                        </div>\n                      </ui-popover-content>\n                    </ui-popover>\n                  </div>\n                }\n              </div>\n            </div>\n          }\n        </div>\n      </div>\n    }\n\n    @if (activeView === 'week' || activeView === 'work-week' || activeView === 'day') {\n      <div class=\"flex flex-1 flex-col overflow-hidden\">\n        <div class=\"border-border bg-muted/20 flex border-b select-none\">\n          <div\n            class=\"border-border/50 text-muted-foreground w-16 shrink-0 border-r py-2.5 text-center text-xs font-medium\"\n          >\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"24\"\n              height=\"24\"\n              viewBox=\"0 0 24 24\"\n              fill=\"none\"\n              stroke=\"currentColor\"\n              stroke-width=\"2\"\n              stroke-linecap=\"round\"\n              stroke-linejoin=\"round\"\n              class=\"lucide lucide-clock mx-auto size-3.5 opacity-60\"\n              aria-hidden=\"true\"\n            >\n              <path d=\"M12 6v6l4 2\" />\n              <circle cx=\"12\" cy=\"12\" r=\"10\" />\n            </svg>\n          </div>\n          <div [class]=\"gridColsClass()\">\n            @for (d of visibleDays; track dateKey(d)) {\n              <div [class]=\"dayHeaderClass(d)\" (click)=\"dateClick.emit(dateKey(d))\">\n                @if (renderDayHeader) {\n                  <ng-container\n                    [uiEventCalendarOutlet]=\"renderDayHeader\"\n                    [uiEventCalendarOutletContext]=\"{\n                      date: d,\n                      dateKey: dateKey(d),\n                      isToday: isToday(d),\n                      view: activeView,\n                    }\"\n                  />\n                } @else {\n                  <span class=\"text-muted-foreground text-[11px] font-medium tracking-wider uppercase\">{{\n                    weekdayName(d)\n                  }}</span>\n                  <span [class]=\"dayHeaderNumberClass(d)\">{{ d.getDate() }}</span>\n                }\n              </div>\n            }\n          </div>\n        </div>\n\n        <div class=\"border-border bg-muted/10 flex border-b text-xs\">\n          <div\n            class=\"border-border/50 text-muted-foreground w-16 shrink-0 border-r p-2 text-right text-[10px] font-semibold tracking-wider uppercase\"\n          >\n            All-day\n          </div>\n          <div [class]=\"gridColsClass()\">\n            @for (d of visibleDays; track dateKey(d)) {\n              @let dayAllDay = getAllDayEventsForDay(d);\n              <div class=\"flex min-h-[32px] flex-col gap-1 p-1\">\n                @if (renderAllDay) {\n                  <ng-container\n                    [uiEventCalendarOutlet]=\"renderAllDay\"\n                    [uiEventCalendarOutletContext]=\"{ date: d, dateKey: dateKey(d), events: dayAllDay }\"\n                  />\n                } @else {\n                  @for (evt of dayAllDay; track evt.id || evt.title) {\n                    <div data-slot=\"event-card\" [class]=\"allDayClass(evt)\" (click)=\"onEventClick(evt, $event)\">\n                      <span class=\"truncate font-medium\">{{ evt.title }}</span>\n                    </div>\n                  }\n                }\n              </div>\n            }\n          </div>\n        </div>\n\n        <div class=\"relative flex max-h-[640px] min-h-[480px] flex-1 overflow-y-auto\">\n          <ng-container [uiEventCalendarOutlet]=\"gutter\" />\n          <div [class]=\"gridColsClass(true)\">\n            @for (d of visibleDays; track dateKey(d)) {\n              <div class=\"relative flex flex-col\">\n                @for (interval of intervals; track interval.time) {\n                  <div\n                    [style.height.px]=\"intervalHeight\"\n                    class=\"border-border/30 hover:bg-muted/20 cursor-pointer border-b transition-colors\"\n                    (click)=\"onTimeSlotClick(d, interval.hour, 0)\"\n                  ></div>\n                }\n                <div class=\"pointer-events-none absolute inset-0 p-0.5\">\n                  @for (item of positionedFor(events, d); track item.event.id || item.event.title) {\n                    <ng-container\n                      [uiEventCalendarOutlet]=\"timedEvent\"\n                      [uiEventCalendarOutletContext]=\"{ item: item, view: activeView, location: true }\"\n                    />\n                  }\n                  @if (isToday(d) && nowPosition() !== null) {\n                    <ng-container [uiEventCalendarOutlet]=\"nowLine\" />\n                  }\n                </div>\n              </div>\n            }\n          </div>\n        </div>\n      </div>\n    }\n\n    @if (activeView === 'category') {\n      <div class=\"flex flex-1 flex-col overflow-hidden\">\n        <div class=\"border-border bg-muted/20 flex border-b select-none\">\n          <div\n            class=\"border-border/50 text-muted-foreground w-16 shrink-0 border-r py-2.5 text-center text-xs font-medium\"\n          >\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"24\"\n              height=\"24\"\n              viewBox=\"0 0 24 24\"\n              fill=\"none\"\n              stroke=\"currentColor\"\n              stroke-width=\"2\"\n              stroke-linecap=\"round\"\n              stroke-linejoin=\"round\"\n              class=\"lucide lucide-clock mx-auto size-3.5 opacity-60\"\n              aria-hidden=\"true\"\n            >\n              <path d=\"M12 6v6l4 2\" />\n              <circle cx=\"12\" cy=\"12\" r=\"10\" />\n            </svg>\n          </div>\n          <div class=\"divide-border/50 grid flex-1 divide-x\" [style.grid-template-columns]=\"categoryColumns\">\n            @for (cat of normalizedCategories; track cat.id) {\n              <div\n                class=\"text-foreground flex items-center justify-center gap-1.5 px-2 py-2.5 text-center text-xs font-semibold\"\n              >\n                @if (cat.color) {\n                  <span class=\"size-2 shrink-0 rounded-full\" [style.background-color]=\"cat.color\"></span>\n                }\n                <span class=\"truncate\">{{ cat.name }}</span>\n              </div>\n            }\n          </div>\n        </div>\n        <div class=\"relative flex max-h-[640px] min-h-[480px] flex-1 overflow-y-auto\">\n          <ng-container [uiEventCalendarOutlet]=\"gutter\" [uiEventCalendarOutletContext]=\"{ plain: true }\" />\n          <div class=\"divide-border/50 relative grid flex-1 divide-x\" [style.grid-template-columns]=\"categoryColumns\">\n            @for (cat of normalizedCategories; track cat.id) {\n              <div class=\"relative flex flex-col\">\n                @for (interval of intervals; track interval.time) {\n                  <div\n                    [style.height.px]=\"intervalHeight\"\n                    class=\"border-border/30 hover:bg-muted/20 cursor-pointer border-b transition-colors\"\n                    (click)=\"onTimeSlotClick(activeDate, interval.hour, 0, cat.id)\"\n                  ></div>\n                }\n                <div class=\"pointer-events-none absolute inset-0 p-0.5\">\n                  @for (\n                    item of positionedFor(categoryEvents(cat), activeDate);\n                    track item.event.id || item.event.title\n                  ) {\n                    <ng-container\n                      [uiEventCalendarOutlet]=\"timedEvent\"\n                      [uiEventCalendarOutletContext]=\"{ item: item, view: 'category', location: false }\"\n                    />\n                  }\n                  @if (isToday(activeDate) && nowPosition() !== null) {\n                    <ng-container [uiEventCalendarOutlet]=\"nowLine\" />\n                  }\n                </div>\n              </div>\n            }\n          </div>\n        </div>\n      </div>\n    }\n\n    <ng-template #gutter let-plain=\"plain\">\n      <div class=\"border-border/50 bg-card w-16 shrink-0 border-r select-none\">\n        @for (interval of intervals; track interval.time) {\n          <div\n            [style.height.px]=\"intervalHeight\"\n            class=\"border-border/30 text-muted-foreground relative border-b pr-2.5 text-right text-[11px] font-medium\"\n          >\n            @if (renderInterval && !plain) {\n              <ng-container\n                [uiEventCalendarOutlet]=\"renderInterval\"\n                [uiEventCalendarOutletContext]=\"{ hour: interval.hour, time: interval.time, label: interval.label }\"\n              />\n            } @else if (interval.label) {\n              <span class=\"relative -top-2 block\">{{ interval.label }}</span>\n            }\n          </div>\n        }\n      </div>\n    </ng-template>\n\n    <ng-template #timedEvent let-item=\"item\" let-view=\"view\" let-location=\"location\">\n      <div\n        [style.top.%]=\"item.top\"\n        [style.height.%]=\"item.height\"\n        [style.left]=\"'calc(' + item.left + '% + 2px)'\"\n        [style.width]=\"'calc(' + item.width + '% - 4px)'\"\n        class=\"pointer-events-auto absolute z-10\"\n      >\n        @if (renderEvent) {\n          <ng-container\n            [uiEventCalendarOutlet]=\"renderEvent\"\n            [uiEventCalendarOutletContext]=\"{ event: item.event, view: view, isAllDay: false }\"\n          />\n        } @else {\n          <div data-slot=\"event-card\" [class]=\"timedClass(item.event)\" (click)=\"onEventClick(item.event, $event)\">\n            <span class=\"truncate text-xs font-semibold\">{{ item.event.title }}</span>\n            <span class=\"truncate font-mono text-[10px] opacity-80\"\n              >{{ fmt(item.startMinutes) }} – {{ fmt(item.endMinutes) }}</span\n            >\n            @if (location && item.event.location) {\n              <span class=\"mt-auto truncate text-[10px] opacity-70\">📍 {{ item.event.location }}</span>\n            }\n          </div>\n        }\n      </div>\n    </ng-template>\n\n    <ng-template #nowLine>\n      <div [style.top.%]=\"nowPosition()\" class=\"pointer-events-none absolute right-0 left-0 z-20\">\n        <div class=\"relative w-full border-t-2 border-red-500 dark:border-red-400\">\n          <div\n            class=\"ring-background absolute -top-1 -left-1 size-2 rounded-full bg-red-500 ring-2 dark:bg-red-400\"\n          ></div>\n        </div>\n      </div>\n    </ng-template>\n  `,\n})\nexport class UiEventCalendarComponent implements OnInit, OnChanges, OnDestroy {\n  /** Controlled active date. */\n  @Input() value?: string | Date\n  @Input() defaultValue?: string | Date\n  /** Controlled active view. */\n  @Input() view?: CalendarView\n  @Input() defaultView: CalendarView = 'month'\n  private readonly eventsSig = signal<CalendarEvent[]>([])\n  @Input() set events(v: CalendarEvent[]) {\n    this.eventsSig.set(v ?? [])\n  }\n  get events(): CalendarEvent[] {\n    return this.eventsSig()\n  }\n  @Input() categories: (string | CalendarCategory)[] = []\n  @Input() weekStartsOn: 0 | 1 = 0\n  @Input() firstInterval = 0\n  @Input() intervalCount = 24\n  @Input() intervalMinutes = 60\n  @Input() intervalHeight = 52\n  @Input() timeFormat: '12h' | '24h' = '12h'\n  @Input() maxEventsPerDay = 3\n  @Input({ transform: booleanAttribute }) showNowIndicator = true\n  @Input({ transform: booleanAttribute }) showHeader = true\n  @Input('class') className?: string\n  @Input() renderHeader?: TemplateRef<EventCalendarHeaderContext> | null\n  @Input() renderEvent?: TemplateRef<EventCalendarEventContext> | null\n  @Input() renderDayHeader?: TemplateRef<EventCalendarDayHeaderContext> | null\n  @Input() renderAllDay?: TemplateRef<EventCalendarAllDayContext> | null\n  @Input() renderInterval?: TemplateRef<EventCalendarIntervalContext> | null\n  @Output() dateChange = new EventEmitter<Date>()\n  @Output() viewChange = new EventEmitter<CalendarView>()\n  @Output() eventClick = new EventEmitter<CalendarEvent>()\n  @Output() dateClick = new EventEmitter<string>()\n  @Output() timeClick = new EventEmitter<TimeClickPayload>()\n  @Output() moreClick = new EventEmitter<EventCalendarMoreClick>()\n\n  readonly isToday = isToday\n  readonly dateKey = formatDateKey\n  private readonly internalDate = signal<Date>(new Date())\n  private readonly internalView = signal<CalendarView | null>(null)\n  readonly nowPosition = signal<number | null>(null)\n  private nowTimer: ReturnType<typeof setInterval> | null = null\n\n  ngOnInit(): void {\n    this.internalDate.set(parseDate(this.value ?? this.defaultValue ?? new Date()))\n    if (this.nowTimer === null) this.startNowTimer()\n  }\n\n  ngOnChanges(changes: SimpleChanges): void {\n    if (\n      changes['showNowIndicator'] ||\n      changes['firstInterval'] ||\n      changes['intervalCount'] ||\n      changes['intervalMinutes']\n    ) {\n      this.startNowTimer()\n    }\n  }\n\n  ngOnDestroy(): void {\n    if (this.nowTimer) clearInterval(this.nowTimer)\n  }\n\n  private startNowTimer(): void {\n    if (this.nowTimer) clearInterval(this.nowTimer)\n    this.nowTimer = null\n    if (!this.showNowIndicator) {\n      this.nowPosition.set(null)\n      return\n    }\n    const update = () =>\n      this.nowPosition.set(getCurrentTimePosition(this.firstInterval, this.intervalCount, this.intervalMinutes))\n    update()\n    this.nowTimer = setInterval(update, 30000)\n  }\n\n  get hostClass(): string {\n    return cn(\n      'border-border bg-card text-foreground flex w-full flex-col overflow-hidden rounded-xl border shadow-xs',\n      this.className,\n    )\n  }\n\n  get activeDate(): Date {\n    return this.value ? parseDate(this.value) : this.internalDate()\n  }\n\n  get activeView(): CalendarView {\n    return this.view ?? this.internalView() ?? this.defaultView\n  }\n\n  private setDate(d: Date): void {\n    this.internalDate.set(d)\n    this.dateChange.emit(d)\n  }\n\n  readonly setView = (v: CalendarView): void => {\n    this.internalView.set(v)\n    this.viewChange.emit(v)\n  }\n\n  private step(sign: 1 | -1): void {\n    const d = new Date(this.activeDate)\n    const v = this.activeView\n    if (v === 'month') d.setMonth(d.getMonth() + sign)\n    else if (v === 'week' || v === 'work-week') d.setDate(d.getDate() + 7 * sign)\n    else d.setDate(d.getDate() + sign)\n    this.setDate(d)\n  }\n\n  readonly prev = (): void => this.step(-1)\n  readonly next = (): void => this.step(1)\n  readonly today = (): void => this.setDate(new Date())\n\n  get normalizedCategories(): CalendarCategory[] {\n    return this.categories.map((c, idx) =>\n      typeof c === 'string'\n        ? { id: c, name: c }\n        : { id: c.id || `cat-${idx}`, name: c.name || `Category ${idx + 1}`, color: c.color },\n    )\n  }\n\n  get categoryColumns(): string {\n    return `repeat(${Math.max(1, this.normalizedCategories.length)}, minmax(0, 1fr))`\n  }\n\n  get formattedTitle(): string {\n    const d = this.activeDate\n    const v = this.activeView\n    if (v === 'month') return d.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })\n    if (v === 'week') {\n      const days = getWeekDays(d, this.weekStartsOn)\n      const first = days[0]\n      const last = days[6]\n      if (first.getMonth() === last.getMonth()) {\n        return `${first.toLocaleDateString('en-US', { month: 'short' })} ${first.getDate()} – ${last.getDate()}, ${first.getFullYear()}`\n      }\n      return `${first.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} – ${last.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}`\n    }\n    if (v === 'work-week') {\n      const days = getWorkWeekDays(d)\n      return `${days[0].toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} – ${days[4].toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}`\n    }\n    return d.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' })\n  }\n\n  get monthDays() {\n    return getMonthDays(this.activeDate, this.weekStartsOn)\n  }\n\n  get weekdayHeaderLabels(): string[] {\n    return this.weekStartsOn === 1\n      ? ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']\n      : ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']\n  }\n\n  get intervals(): { hour: number; label: string; time: string }[] {\n    const list: { hour: number; label: string; time: string }[] = []\n    for (let i = 0; i < this.intervalCount; i++) {\n      const hour = (this.firstInterval + Math.floor((i * this.intervalMinutes) / 60)) % 24\n      const min = (i * this.intervalMinutes) % 60\n      const time = `${String(hour).padStart(2, '0')}:${String(min).padStart(2, '0')}`\n      list.push({ hour, label: min === 0 ? formatHourLabel(hour, this.timeFormat) : '', time })\n    }\n    return list\n  }\n\n  get visibleDays(): Date[] {\n    const v = this.activeView\n    if (v === 'week') return getWeekDays(this.activeDate, this.weekStartsOn)\n    if (v === 'work-week') return getWorkWeekDays(this.activeDate)\n    return [this.activeDate]\n  }\n\n  private startKey(e: CalendarEvent): string {\n    return typeof e.start === 'string' && /^\\d{4}-\\d{2}-\\d{2}/.test(e.start)\n      ? e.start.slice(0, 10)\n      : formatDateKey(parseDate(e.start))\n  }\n\n  /** Events grouped by start-date key, rebuilt only when `events` changes. Preserves input order per day. */\n  private readonly eventsByDay = computed(() => {\n    const map = new Map<string, CalendarEvent[]>()\n    for (const e of this.eventsSig()) {\n      const key = this.startKey(e)\n      const list = map.get(key)\n      if (list) list.push(e)\n      else map.set(key, [e])\n    }\n    return map\n  })\n\n  getEventsForDay(dateKey: string): CalendarEvent[] {\n    return this.eventsByDay().get(dateKey) ?? []\n  }\n\n  getAllDayEventsForDay(day: Date): CalendarEvent[] {\n    const key = formatDateKey(day)\n    return this.events.filter((e) => e.allDay && this.startKey(e) === key)\n  }\n\n  categoryEvents(cat: CalendarCategory): CalendarEvent[] {\n    return this.events.filter((e) => e.category === cat.id || e.category === cat.name)\n  }\n\n  positionedFor(events: CalendarEvent[], day: Date) {\n    return calculateTimedEventPositions(events, day, this.firstInterval, this.intervalCount, this.intervalMinutes)\n  }\n\n  private variantOf(event: CalendarEvent): EventVariant {\n    return (EVENT_COLORS.includes(event.color as string) ? event.color : 'default') as EventVariant\n  }\n\n  fmt(minutes: number): string {\n    return formatTime(minutes, this.timeFormat)\n  }\n\n  eventTime(evt: CalendarEvent): string {\n    return formatTime(getEventMinutes(evt.start, 540), this.timeFormat)\n  }\n\n  popoverDate(d: Date): string {\n    return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', weekday: 'short' })\n  }\n\n  weekdayName(d: Date): string {\n    return d.toLocaleDateString('en-US', { weekday: this.activeView === 'day' ? 'long' : 'short' })\n  }\n\n  toggleClass(v: CalendarView): string {\n    return cn(\n      'rounded-md px-2.5 py-1 text-xs font-medium transition-[color,background-color,box-shadow]',\n      this.activeView === v ? TOGGLE_ACTIVE : TOGGLE_IDLE,\n    )\n  }\n\n  cellClass(inMonth: boolean): string {\n    return cn(\n      'group relative flex min-h-[96px] cursor-pointer flex-col p-1.5 transition-colors',\n      inMonth ? 'bg-card hover:bg-muted/15' : 'bg-muted/10 text-muted-foreground/50 hover:bg-muted/20',\n    )\n  }\n\n  dayNumberClass(today: boolean, inMonth: boolean): string {\n    return cn(\n      'inline-flex min-w-[20px] items-center justify-center rounded-full px-1.5 py-0.5 text-xs font-medium transition-colors',\n      today\n        ? 'bg-primary text-primary-foreground font-semibold shadow-xs'\n        : inMonth\n          ? 'text-foreground/90'\n          : 'text-muted-foreground/60',\n    )\n  }\n\n  chipClass(evt: CalendarEvent, truncate: boolean): string {\n    return cn(\n      calendarEventVariants({ variant: this.variantOf(evt), size: 'sm' }),\n      truncate ? 'w-full truncate' : 'w-full',\n    )\n  }\n\n  allDayClass(evt: CalendarEvent): string {\n    return cn(calendarEventVariants({ variant: this.variantOf(evt), size: 'sm' }), 'w-full truncate py-0.5')\n  }\n\n  timedClass(evt: CalendarEvent): string {\n    return cn(\n      calendarEventVariants({ variant: this.variantOf(evt) }),\n      'flex h-full w-full flex-col justify-start overflow-hidden rounded-md p-1.5 leading-tight shadow-xs',\n    )\n  }\n\n  gridColsClass(relative = false): string {\n    const v = this.activeView\n    return cn(\n      relative ? 'divide-border/50 relative grid flex-1 divide-x' : 'divide-border/50 grid flex-1 divide-x',\n      v === 'week' ? 'grid-cols-7' : v === 'work-week' ? 'grid-cols-5' : 'grid-cols-1',\n    )\n  }\n\n  dayHeaderClass(d: Date): string {\n    return cn(\n      'hover:bg-muted/30 flex cursor-pointer flex-col items-center justify-center py-2 transition-colors',\n      isToday(d) && 'bg-primary/5',\n    )\n  }\n\n  dayHeaderNumberClass(d: Date): string {\n    return cn(\n      'mt-0.5 inline-flex size-7 items-center justify-center rounded-full text-xs font-semibold transition-[color,background-color,box-shadow]',\n      isToday(d) ? 'bg-primary text-primary-foreground shadow-xs' : 'text-foreground',\n    )\n  }\n\n  onEventClick(event: CalendarEvent, e: MouseEvent): void {\n    e.stopPropagation()\n    this.eventClick.emit(event)\n  }\n\n  onTimeSlotClick(day: Date, hour: number, minute: number, category?: string): void {\n    this.timeClick.emit({\n      date: formatDateKey(day),\n      time: `${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`,\n      hour,\n      minute,\n      category,\n    })\n  }\n}\n\nexport { calendarEventVariants, type CalendarEventVariants }\nexport type { CalendarCategory, CalendarEvent, CalendarView, TimeClickPayload }\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/event-calendar/event-calendar.component.ts"
    },
    {
      "path": "packages/registry-angular/components/event-calendar/event-calendar.variants.ts",
      "content": "import { cva, type VariantProps } from 'class-variance-authority'\n\nexport const calendarEventVariants = cva(\n  'group relative flex cursor-pointer flex-col overflow-hidden text-left transition-colors select-none border font-normal focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring',\n  {\n    variants: {\n      variant: {\n        default: 'bg-secondary/80 text-secondary-foreground border-border/50 hover:bg-secondary',\n        primary: 'bg-primary/10 text-primary border-primary/25 hover:bg-primary/20',\n        secondary: 'bg-muted/90 text-foreground border-border/60 hover:bg-muted',\n        success:\n          'bg-emerald-500/10 text-emerald-700 dark:text-emerald-400 border-emerald-500/30 hover:bg-emerald-500/20',\n        warning: 'bg-amber-500/10 text-amber-800 dark:text-amber-400 border-amber-500/30 hover:bg-amber-500/20',\n        destructive: 'bg-destructive/10 text-destructive border-destructive/30 hover:bg-destructive/20',\n        info: 'bg-sky-500/10 text-sky-700 dark:text-sky-400 border-sky-500/30 hover:bg-sky-500/20',\n        purple: 'bg-purple-500/10 text-purple-700 dark:text-purple-400 border-purple-500/30 hover:bg-purple-500/20',\n        rose: 'bg-rose-500/10 text-rose-700 dark:text-rose-400 border-rose-500/30 hover:bg-rose-500/20',\n      },\n      size: {\n        sm: 'rounded-sm px-1.5 py-0.5 text-[11px] leading-tight',\n        default: 'rounded-md px-2 py-1 text-xs leading-normal',\n        lg: 'rounded-md px-2.5 py-1.5 text-sm leading-snug',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  },\n)\n\nexport type CalendarEventVariants = VariantProps<typeof calendarEventVariants>\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/event-calendar/event-calendar.variants.ts"
    },
    {
      "path": "packages/registry-angular/components/event-calendar/date-utils.ts",
      "content": "import type { CalendarEvent, PositionedEvent } from './types'\n\nexport function parseDate(d: string | Date | undefined | null): Date {\n  if (!d) return new Date()\n  if (d instanceof Date) return new Date(d.getTime())\n\n  // Format: YYYY-MM-DD\n  if (/^\\d{4}-\\d{2}-\\d{2}$/.test(d)) {\n    const [y, m, day] = d.split('-').map(Number)\n    return new Date(y, m - 1, day, 0, 0, 0)\n  }\n\n  // Format: YYYY-MM-DD HH:mm or YYYY-MM-DDTHH:mm\n  const matchTime = d.match(/^(\\d{4})-(\\d{2})-(\\d{2})[T ](\\d{2}):(\\d{2})/)\n  if (matchTime) {\n    const [, y, m, day, h, min] = matchTime.map(Number)\n    return new Date(y, m - 1, day, h, min, 0)\n  }\n\n  const parsed = new Date(d)\n  return isNaN(parsed.getTime()) ? new Date() : parsed\n}\n\nexport function formatDateKey(d: Date): string {\n  const y = d.getFullYear()\n  const m = String(d.getMonth() + 1).padStart(2, '0')\n  const day = String(d.getDate()).padStart(2, '0')\n  return `${y}-${m}-${day}`\n}\n\nexport function isSameDay(d1: Date, d2: Date): boolean {\n  return d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() && d1.getDate() === d2.getDate()\n}\n\nexport function isToday(d: Date): boolean {\n  return isSameDay(d, new Date())\n}\n\nexport function formatTime(minutes: number, format: '12h' | '24h' = '12h'): string {\n  const totalMin = Math.max(0, Math.min(1439, Math.round(minutes)))\n  const hour = Math.floor(totalMin / 60)\n  const min = totalMin % 60\n\n  if (format === '24h') {\n    return `${String(hour).padStart(2, '0')}:${String(min).padStart(2, '0')}`\n  }\n\n  const ampm = hour >= 12 ? 'PM' : 'AM'\n  const h12 = hour % 12 || 12\n  return `${h12}:${String(min).padStart(2, '0')} ${ampm}`\n}\n\nexport function formatHourLabel(hour: number, format: '12h' | '24h' = '12h'): string {\n  const normalizedHour = ((hour % 24) + 24) % 24\n  if (format === '24h') {\n    return `${String(normalizedHour).padStart(2, '0')}:00`\n  }\n  if (normalizedHour === 0) return '12 AM'\n  if (normalizedHour === 12) return '12 PM'\n  return normalizedHour > 12 ? `${normalizedHour - 12} PM` : `${normalizedHour} AM`\n}\n\nexport function getEventMinutes(dateInput: string | Date | undefined, defaultMinutes: number = 0): number {\n  if (!dateInput) return defaultMinutes\n  if (dateInput instanceof Date) {\n    return dateInput.getHours() * 60 + dateInput.getMinutes()\n  }\n  if (typeof dateInput === 'string') {\n    // HH:mm\n    if (/^\\d{1,2}:\\d{2}$/.test(dateInput)) {\n      const [h, m] = dateInput.split(':').map(Number)\n      return h * 60 + m\n    }\n    // Check if contains time\n    const parsed = parseDate(dateInput)\n    // If input had no time specified (just YYYY-MM-DD), default to defaultMinutes\n    if (/^\\d{4}-\\d{2}-\\d{2}$/.test(dateInput)) {\n      return defaultMinutes\n    }\n    return parsed.getHours() * 60 + parsed.getMinutes()\n  }\n  return defaultMinutes\n}\n\nexport function getWeekDays(baseDate: Date, weekStartsOn: 0 | 1 = 0): Date[] {\n  const date = parseDate(baseDate)\n  const day = date.getDay()\n  const diff = (day - weekStartsOn + 7) % 7\n  const startOfWeek = new Date(date.getFullYear(), date.getMonth(), date.getDate() - diff)\n\n  const days: Date[] = []\n  for (let i = 0; i < 7; i++) {\n    const d = new Date(startOfWeek)\n    d.setDate(startOfWeek.getDate() + i)\n    days.push(d)\n  }\n  return days\n}\n\nexport function getWorkWeekDays(baseDate: Date): Date[] {\n  // Always Monday through Friday\n  const date = parseDate(baseDate)\n  const day = date.getDay()\n  const diff = (day - 1 + 7) % 7 // Monday offset\n  const monday = new Date(date.getFullYear(), date.getMonth(), date.getDate() - diff)\n\n  const days: Date[] = []\n  for (let i = 0; i < 5; i++) {\n    const d = new Date(monday)\n    d.setDate(monday.getDate() + i)\n    days.push(d)\n  }\n  return days\n}\n\nexport function getMonthDays(\n  cursor: Date,\n  weekStartsOn: 0 | 1 = 0,\n): { date: Date; dateKey: string; inMonth: boolean; isToday: boolean }[] {\n  const y = cursor.getFullYear()\n  const m = cursor.getMonth()\n  const firstDay = new Date(y, m, 1)\n  const offset = (firstDay.getDay() - weekStartsOn + 7) % 7\n\n  const startDate = new Date(y, m, 1 - offset)\n  const days: { date: Date; dateKey: string; inMonth: boolean; isToday: boolean }[] = []\n\n  for (let i = 0; i < 42; i++) {\n    const d = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate() + i)\n    days.push({\n      date: d,\n      dateKey: formatDateKey(d),\n      inMonth: d.getMonth() === m,\n      isToday: isToday(d),\n    })\n  }\n  return days\n}\n\nexport function calculateTimedEventPositions(\n  events: CalendarEvent[],\n  dayDate: Date,\n  firstInterval: number = 0,\n  intervalCount: number = 24,\n  intervalMinutes: number = 60,\n): PositionedEvent[] {\n  const dayKey = formatDateKey(dayDate)\n  const startDayMinutes = firstInterval * 60\n  const totalMinutes = intervalCount * intervalMinutes\n\n  // Filter events belonging to this day that are timed\n  const timed = events\n    .filter((e) => {\n      if (e.allDay) return false\n      const sKey =\n        typeof e.start === 'string' && /^\\d{4}-\\d{2}-\\d{2}/.test(e.start)\n          ? e.start.slice(0, 10)\n          : formatDateKey(parseDate(e.start))\n      return sKey === dayKey\n    })\n    .map((event) => {\n      const sMin = getEventMinutes(event.start, 9 * 60)\n      let eMin = getEventMinutes(event.end, sMin + 60)\n      if (eMin <= sMin) eMin = sMin + 30 // minimum 30 min duration\n      return {\n        event,\n        startMinutes: sMin,\n        endMinutes: eMin,\n      }\n    })\n    .sort((a, b) => a.startMinutes - b.startMinutes || b.endMinutes - b.startMinutes - (a.endMinutes - a.startMinutes))\n\n  if (timed.length === 0) return []\n\n  // Assign columns for overlapping groups\n  const positioned: PositionedEvent[] = []\n  // Cluster overlapping events\n  type ClusterItem = { event: CalendarEvent; startMinutes: number; endMinutes: number; col: number }\n  const clusters: ClusterItem[][] = []\n  let currentCluster: ClusterItem[] = []\n  let clusterEnd = -1\n\n  for (const item of timed) {\n    if (currentCluster.length === 0 || item.startMinutes < clusterEnd) {\n      currentCluster.push({ ...item, col: 0 })\n      clusterEnd = Math.max(clusterEnd, item.endMinutes)\n    } else {\n      clusters.push(currentCluster)\n      currentCluster = [{ ...item, col: 0 }]\n      clusterEnd = item.endMinutes\n    }\n  }\n  if (currentCluster.length > 0) {\n    clusters.push(currentCluster)\n  }\n\n  // For each cluster, assign columns\n  for (const cluster of clusters) {\n    const cols: number[] = [] // end time of each column\n    for (const item of cluster) {\n      let placedCol = -1\n      for (let c = 0; c < cols.length; c++) {\n        if (item.startMinutes >= cols[c]) {\n          placedCol = c\n          cols[c] = item.endMinutes\n          break\n        }\n      }\n      if (placedCol === -1) {\n        placedCol = cols.length\n        cols.push(item.endMinutes)\n      }\n      item.col = placedCol\n    }\n\n    const totalCols = Math.max(1, cols.length)\n    for (const item of cluster) {\n      const topPct = Math.max(0, Math.min(100, ((item.startMinutes - startDayMinutes) / totalMinutes) * 100))\n      const bottomPct = Math.max(0, Math.min(100, ((item.endMinutes - startDayMinutes) / totalMinutes) * 100))\n      const heightPct = Math.max(1.5, bottomPct - topPct)\n      const widthPct = 100 / totalCols\n      const leftPct = item.col * widthPct\n\n      positioned.push({\n        event: item.event,\n        top: topPct,\n        height: heightPct,\n        left: leftPct,\n        width: widthPct,\n        startMinutes: item.startMinutes,\n        endMinutes: item.endMinutes,\n      })\n    }\n  }\n\n  return positioned\n}\n\nexport function getCurrentTimePosition(\n  firstInterval: number = 0,\n  intervalCount: number = 24,\n  intervalMinutes: number = 60,\n): number | null {\n  const now = new Date()\n  const curMinutes = now.getHours() * 60 + now.getMinutes()\n  const startMinutes = firstInterval * 60\n  const totalMinutes = intervalCount * intervalMinutes\n\n  if (curMinutes < startMinutes || curMinutes > startMinutes + totalMinutes) {\n    return null\n  }\n  return ((curMinutes - startMinutes) / totalMinutes) * 100\n}\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/event-calendar/date-utils.ts"
    },
    {
      "path": "packages/registry-angular/components/event-calendar/types.ts",
      "content": "export type CalendarView = 'month' | 'week' | 'day' | 'work-week' | 'category'\n\nexport interface CalendarCategory {\n  id: string\n  name: string\n  color?: string\n}\n\nexport interface CalendarEvent {\n  id?: string\n  title: string\n  /** YYYY-MM-DD or YYYY-MM-DD HH:mm or ISO string or Date */\n  start: string | Date\n  /** YYYY-MM-DD or YYYY-MM-DD HH:mm or ISO string or Date */\n  end?: string | Date\n  allDay?: boolean\n  color?:\n    'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'destructive' | 'info' | 'purple' | 'rose' | string\n  category?: string\n  description?: string\n  location?: string\n  [key: string]: any\n}\n\nexport interface TimeClickPayload {\n  date: string\n  time: string\n  hour: number\n  minute: number\n  category?: string\n}\n\nexport interface PositionedEvent {\n  event: CalendarEvent\n  top: number\n  height: number\n  left: number\n  width: number\n  startMinutes: number\n  endMinutes: number\n}\n\nexport interface CalendarDayCell {\n  date: Date\n  dateKey: string\n  dayNumber: number\n  inMonth: boolean\n  isToday: boolean\n  events: CalendarEvent[]\n  allDayEvents: CalendarEvent[]\n  timedEvents: CalendarEvent[]\n}\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/event-calendar/types.ts"
    },
    {
      "path": "packages/registry-angular/components/event-calendar/index.ts",
      "content": "export {\n  UiEventCalendarComponent,\n  UiEventCalendarOutletDirective,\n  type EventCalendarHeaderContext,\n  type EventCalendarEventContext,\n  type EventCalendarDayHeaderContext,\n  type EventCalendarAllDayContext,\n  type EventCalendarIntervalContext,\n  type EventCalendarMoreClick,\n} from './event-calendar.component'\nexport * from './types'\nexport * from './event-calendar.variants'\nexport * from './date-utils'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/event-calendar/index.ts"
    }
  ],
  "dependencies": [
    "class-variance-authority"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/popover.json",
    "https://uipkge.dev/r/angular/popper.json"
  ],
  "description": "Full-featured event calendar primitive supporting month, week, day, work-week, and category/resource views, timed interval grids with collision clustering, all-day header, live current-time indicator, and customizable event templates.",
  "categories": [
    "productivity",
    "dashboard"
  ]
}