# Event calendar

> Month, week and day views of a set of events — draggable, resizable, keyboard-operable.

Source: https://ngwr.dev/reference/components/event-calendar  
Kind: Component, Standalone

## Installation

```angular-ts
import { WrEventCalendar } from 'ngwr/event-calendar';

@Component({ imports: [WrEventCalendar] })
export class MyComponent {}
```

## Register a date adapter

Every date calculation and every label goes through `WrDateAdapter`, the same one the calendar and the date pickers use. Register one at bootstrap or the calendar has no way to name a month.

```angular-ts
import { provideWrDateAdapter } from 'ngwr/date-adapter';
import { provideWrDateFnsAdapter } from 'ngwr/date-adapter-fns';

bootstrapApplication(App, {
  providers: [provideWrDateFnsAdapter()],
});
```

## Views

One component covers all three views. `view` and `date` are two-way bindable, so the built-in header is a convenience rather than the only way to navigate — bind them and drive the calendar from your own toolbar, or set `hideHeader`.

```html
<wr-event-calendar [events]="events()" [(view)]="view" [(date)]="anchor" />
```

## Events

`end` is exclusive: an event running 09:00–10:00 does not overlap one that starts at 10:00, and a single all-day event ends at midnight the next day. That one rule makes adjacency and overlap the same comparison in every view. `color` takes any intent, and `editable: false` pins an individual event while the rest of the calendar stays draggable.

```angular-ts
protected readonly events = signal<readonly WrCalendarEvent[]>([
  { id: 1, title: 'Design review', start: at(0, 10), end: at(0, 11, 30), color: 'primary' },
  { id: 5, title: 'Conference', start: at(2, 0), end: at(5, 0), allDay: true, color: 'secondary' },
  { id: 7, title: 'Frozen — no deploys', start: at(6, 0), end: at(7, 0), allDay: true, editable: false },
]);
```

## Drag to move and resize

`events` is an input and the calendar never writes to it. A drag emits `eventChange` with where the event *would* land and stops there — apply it to your own state and the new position renders on the next pass. So ignoring the output cancels the drag, an optimistic update is a single `update`, and a server rejection needs no rollback path inside the component. Drag a chip below, or focus one and hold `Alt` with the arrow keys.

```html
<wr-event-calendar
  editable
  [events]="events()"
  [(view)]="view"
  [(date)]="anchor"
  (eventChange)="apply($event)"
/>
```

```angular-ts
protected apply(change: WrCalendarEventChange): void {
  this.events.update(events =>
    events.map(event =>
      event.id === change.event.id ? { ...event, start: change.start, end: change.end } : event
    )
  );
}
```

## The time grid

`dayStartHour` and `dayEndHour` trim the visible band — a calendar that opens on 3am dead space wastes most of its height. `slotMinutes` sets both the row height and the drag snap, so a 15-minute grid drags in 15-minute steps.

```html
<wr-event-calendar
  editable
  view="week"
  [dayStartHour]="8"
  [dayEndHour]="19"
  [slotMinutes]="30"
  [events]="events()"
/>
```

## Creating from empty space

Clicking anywhere that is not a chip emits `slotClick` with the slot the user pointed at — a whole day in month view and the all-day band, the pointed-at row otherwise. From the keyboard it is `Enter` on an empty cell.

```html
<wr-event-calendar [events]="events()" (slotClick)="compose($event)" />
```

## Custom chip contents

`wrCalendarEvent` replaces what a chip renders. Position, sizing and drag stay with the calendar, so a custom template cannot break the geometry.

```html
<wr-event-calendar [events]="events()">
  <ng-template wrCalendarEvent let-event>
    <strong>{{ event.title }}</strong>
    <small>{{ event.data.room }}</small>
  </ng-template>
</wr-event-calendar>
```

## Keyboard

The grid is a single tab stop with a roving cursor, so tabbing through a month does not mean tabbing through 42 cells.

- **Arrow keys** — move by a day left and right, by a week or a slot up and down.
- **Home / End** — first and last day of the row.
- **Enter** — on an empty cell emits `slotClick`; on a cell holding events focuses the first chip.
- **Escape** — from a chip, back to its cell.
- **Alt + arrows** — on a focused chip, move the event. The keyboard equivalent of the drag, emitting the same `eventChange`.

## API

| Name | Description | Type | Default |
| --- | --- | --- | --- |
| `events` | Everything to render. Never mutated — see `eventChange`. | `readonly WrCalendarEvent[]` | `[]` |
| `view` | Which span is shown. Two-way bindable. | `WrCalendarView` | `'month'` |
| `date` | Any date inside the shown span. Two-way bindable. | `Date` | `new Date()` |
| `views` | Which buttons the view switcher offers. Empty hides it. | `readonly WrCalendarView[]` | `['month', 'week', 'day']` |
| `editable` | Allow dragging chips to move and resize them. | `boolean` | `false` |
| `slotMinutes` | Minutes per row in the time views — also the drag snap. | `number` | `30` |
| `dayStartHour` | First hour the time views show. | `number` | `0` |
| `dayEndHour` | First hour they do NOT show, exclusive. | `number` | `24` |
| `maxLanes` | Lanes a month cell shows before collapsing the rest into “+N more”. | `number` | `3` |
| `hideHeader` | Hide the built-in header — supply your own navigation instead. | `boolean` | `false` |
| `(eventClick)` | A chip was activated. | `WrCalendarEvent` | — |
| `(slotClick)` | Empty space was activated — the slot the user pointed at. | `WrCalendarSlot` | — |
| `(eventChange)` | A drag or an `Alt` + arrow finished. Apply it yourself; nothing moves until you do. | `WrCalendarEventChange` | — |
| `ariaLabel` | Accessible name of the grid; overridable for a page with several. | `string \| null` | `null` |
