Integration & API

The whole surface in one place: the inputs and outputs every view shares, the CalendarEvent model you feed in, the pure headless engine behind the components, the timezone-correct date adapter, and the accessibility and security guarantees. All views are standalone, zoneless, OnPush and SSR-safe, and speak the same contract — the host owns the data, the library renders it.

Loading…

Inputs

These inputs are shared across the views. [events] and [viewDate] are required everywhere; the rest are optional and fall back to sensible defaults (or the app-wide values you set via withDefaults()). The colour inputs mirror the theming contract, so a view drops onto any colour scheme without touching its CSS.

InputTypeDefaultNotes
eventsCalendarEvent[]Required. The immutable event array to render. Replace it (don't mutate) to update.
viewDateZonedDateTimeRequired. The instant the view is anchored around (which month / week / day).
todayZonedDateTimehost nowWhich day is marked "today". Pass it explicitly for deterministic SSR / tests.
nowZonedDateTimehost nowDrives the "now" line on time-grid / timeline.
themeMode'light' | 'dark''light'Selects the light or dark token set; contrast is guaranteed in both.
baseColorstring (hex)neutralNeutral anchor colour; the whole neutral ramp derives from it.
accentColorstring (hex)Interactive / selection colour.
accentInkstring (hex)autoExplicit ink colour on accent surfaces when you need to override the derived one.
statusColorsRecord<string, string>{}Maps an event status key to a colour; each emits a --cal-event-<key> triplet.
localestring'en-US'BCP-47 locale for date / number formatting.
timezonestring (IANA)event zoneWall-clock zone the view renders in; DST-correct.
weekStartsOn0–60First day of the week (0 = Sunday).
calendarSystemCalendarSystem'iso8601'Calendar system for date math (pluggable via the adapter).

Time-grid & timeline additions

The time-based views (cal-time-grid, cal-timeline-view) add inputs for the visible window, snapping and interactive editing.

InputTypeDefaultNotes
daysnumber7How many day columns to show (1 = single day, 7 = week).
dayStartMinutes / dayEndMinutesnumber0 / 1440Visible time window, in minutes from midnight (e.g. 360 = 6am).
slotMinutesnumber60Gridline density; labels stay hourly.
snapMinutesnumber15Drag / resize snap granularity.
orientation'vertical' | 'horizontal''vertical'Horizontal transposes time left→right, days as rows.
editablebooleanfalseEnables drag / resize / create interactions.
inlineEditbooleanfalseEnables in-place title editing on an event.
validateChange(change: EventChange) => booleanVeto a pending move / resize before it commits (e.g. keep within working hours).

Timeline & month additions

InputTypeDefaultNotes
resourcesCalendarResource[]Timeline only, required. Flat list or tree (parentId + expanded) of lanes.
headerGroupingsTimeHeaderUnit[]['day', 'hour']Timeline. Stacked header rows across the top.
hourWidthnumberautoTimeline. Pixels per hour along the axis.
laneHeightnumberautoTimeline. Pixel height of each resource lane.
maxLanesnumberMonth. Caps visible event rows per day; the rest collapse into "+N more".

Outputs

Views are strictly one-way: they never mutate [events]. Every interaction surfaces as an output carrying a plain payload; you apply it to your own store and feed a new immutable array back in.

OutputPayloadWhen
eventClicked{ event }An event chip / block is activated (click or keyboard).
eventChangedEventChangeA drag, resize, create or inline-edit commits.
daySelected{ date }A day cell is selected (month / year / agenda).
monthSelected{ date }A month is selected in the year view.
slotSelected{ date, … }An empty time slot is clicked / dragged (time-grid / timeline).
viewPeriodChanged{ start, end, zone }The visible period changes — hook to fetch that range's events.
resourceToggled{ resource, … }A timeline resource group is expanded / collapsed.
externalDrop{ date, resourceId, … }An item from outside the calendar is dropped onto a lane / slot.

Event model

CalendarEvent is the single shape you feed to every view. Only id and start are required; the optional flags gate per-event interactivity and styling.

import { CalendarEvent, ZonedDateTime } from '@ascentsparksoftware/angular-calendar';

const zone = 'America/New_York';
const z = (iso: string): ZonedDateTime => ({ epochMs: Date.parse(iso), zone });

// The only required fields are id and start. Everything else is optional.
const job: CalendarEvent = {
  id: 'job-1842',
  title: 'AC install — 14 Elm St',
  start: z('2026-06-15T13:00:00Z'),
  end: z('2026-06-15T15:30:00Z'),
  status: 'scheduled',            // -> keyed into statusColors / --cal-event-<key>
  resourceIds: ['alice'],         // which timeline lane(s) it sits on
  resizable: { beforeStart: true, afterEnd: true },
  draggable: true,
  meta: { workOrderId: 1842 },    // opaque passenger data, echoed back to you
};

Headless engine

The components are a thin shell over pure, DOM-free functions: view-model builders, overlap layout, recurrence expansion and event queries. Everything the views use is exported, so you can drive your own surfaces or unit-test the math in isolation.

import {
  buildMonthView,
  packColumns,
  expandRecurringEvents,
  filterByStatus,
  DateFnsDateAdapter,
} from '@ascentsparksoftware/angular-calendar';
import { provideDateFnsAdapter } from '@ascentsparksoftware/angular-calendar/date-fns';

// The same pure functions the components call. No DOM, no Angular — testable in isolation.
const adapter = new DateFnsDateAdapter();

// buildMonthView returns a plain view-model (weeks, days, lane-packed chips):
const vm = buildMonthView(adapter, {
  viewDate: { epochMs: Date.parse('2026-06-15T12:00:00Z'), zone: 'America/New_York' },
  events: filterByStatus(myEvents, ['scheduled', 'en-route']),
  weekStartsOn: 1,
  maxLanes: 3,
});

vm.weeks.forEach((w) => w.days.forEach((d) => console.log(d.date, d.chips.length)));

// Layout + recurrence helpers are exported too, for building your own surfaces:
const columns = packColumns(intervals);       // overlap -> side-by-side columns
const occurrences = expandRecurringEvents(seriesEvents, ctx); // RRULE -> instances

Date adapter

Timezone-correct from day one. Every instant is an explicit IANA zone, never a host-local Date, so wall-clock positioning stays right across DST. Wire the adapter once at the app root.

import { ApplicationConfig } from '@angular/core';
import { provideCalendar, withDateAdapter, withDefaults } from '@ascentsparksoftware/angular-calendar';
import { provideDateFnsAdapter } from '@ascentsparksoftware/angular-calendar/date-fns';

export const appConfig: ApplicationConfig = {
  providers: [
    provideCalendar(
      // Default adapter: date-fns + date-fns-tz. A Temporal adapter can drop in later.
      withDateAdapter(provideDateFnsAdapter()),
      // Optional app-wide defaults so every view inherits them.
      withDefaults({ timezone: 'America/New_York', weekStartsOn: 1, locale: 'en-US' }),
    ),
  ],
};

Accessibility

Accessibility is a hard requirement, not an afterthought — the library targets WCAG 2.2 AA and passes axe with zero violations.

  • Correct ARIA grid / listbox / application patterns per view.
  • Every interaction is keyboard-operable — navigation, selection, drag, resize and create.
  • CalFocusTrap guards the event dialog: Tab / Shift+Tab wrap within it and focus is restored on close.
  • Colour tokens run through ensureContrastAA, so status and accent surfaces stay legible in light and dark.
  • prefers-reduced-motion is honoured — transitions and animations back off.

Security

Caller content is treated as untrusted throughout. The rendering path is Trusted-Types / strict-CSP clean, and npm audit stays clean because runtime dependencies ship as peers.

  • Caller event content is never set via innerHTML; rendering is sanitized.
  • Trusted-Types and strict-CSP compatible; no inline-script or unsafe-eval requirements.
  • No eval / Function anywhere in the codebase.
  • Runtime dependencies (date-fns, date-fns-tz, rrule) are declared as peers so you control and audit their versions.

FAQ

Developer questions about the headless engine, the state contract, theming, security and SSR.

Is the layout engine usable without the components?

Yes. All date math, recurrence expansion and overlap layout are pure exported functions (buildMonthView, packColumns, expandRecurringEvents, …) with no DOM — use them headless.

How do I persist a drag or resize?

Handle (eventChanged): it emits an EventChange; update your own event array and feed the new immutable array back into [events]. The library never mutates your data.

Can I fully restyle it?

Yes. Every colour/size/radius is a scoped --cal-* CSS custom property on the host; override any of them, or drive the two-colour deriveTheme pipeline.

Is it safe against untrusted event content?

Yes. Caller content is never set via innerHTML; rendering is sanitized and Trusted-Types / strict-CSP clean, with no eval.

Does it work with server-side rendering?

Yes. Standalone, zoneless, OnPush and SSR-safe; this very docs site is statically prerendered.