API reference

Documentation index

Package exports

ImportPurpose
z-calcreateCalendar, core types, built-in plugins, Temporal helpers, and lower-level extension APIs
z-cal/reactCalendar component, useCalendar, useCalendarController, React slot types, plugins, and commonly used core types
z-cal/polyfillConditional Temporal installation; ensureTemporal() and hasNativeTemporal
z-cal/style.cssShared stylesheet for vanilla and React
z-cal/views/day-gridDayGrid
z-cal/views/time-gridTimeGrid
z-cal/views/listList
z-cal/views/resource-time-gridResourceTimeGrid
z-cal/views/resource-timelineResourceTimeline
z-cal/interactionInteraction and interaction extension APIs
z-cal/recurrenceRecurrence, the scope helpers (editRecurringEvent, …), parseRRule, formatRRule
z-cal/package.jsonPackage metadata

version from z-cal matches the package version. ZCalParseError reports invalid date/duration inputs. Prefer the methods and types below for application integration; state, slots, signals, and DOM/layout exports are lower-level extension interfaces, not required for ordinary usage.

Calendar instance

Create an instance with createCalendar(host: HTMLElement, init?: CalendarInit). init contains plugins?: CalendarPlugin[] and options?: CalendarOptions. React exposes the same instance through ref or useCalendar().

Method/propertyResult and behavior
elThe inner calendar root HTMLElement
getOption(name)The resolved value for the current view, which may differ from the raw input type
getOptions()All resolved options; treat the result as read-only
setOption(name, value), setOptions(patch)Update options; return the instance
getDate()The selected Temporal.PlainDate
gotoDate(date)Navigate using a DateInput; return the instance
next(), prev(), today()Navigate; return the instance
changeView(name, date?)Switch to a registered view, optionally choosing its date; return the instance
getView()ViewApi: type, title, currentStart, currentEnd, activeStart, activeEnd
getEvents()Currently loaded CalEvent[]; a recurring series appears once, as its master
getEventById(id)CalEvent or null; for a series, its master
getEventOccurrences(range?)Events as views show them in range (default the active range), series expanded to occurrences
getEventElements(id, occurrenceKey?)Live elements rendering the event, in document order (one per visible segment); [] when not rendered
addEvent(input)Insert an EventInput; return the resulting CalEvent
updateEvent(input)Replace by required id; input also requires start; return CalEvent or null
removeEventById(id)Remove an event; return the instance
refetchEvents()Invalidate event source cache and reload asynchronously; return the instance
getResources(), getResourceById(id)Loaded Resource[], or one resource / null
refetchResources()Reload resources asynchronously; return the instance
unselect()Clear the selected range; return the instance
focus(target?)Move keyboard focus into the grid (date, allDay, time, resourceId); needs keyboardNavigation
dateFromPoint(x, y)Hit-test viewport/client coordinates: DateFromPointInfo or null
on(name, listener)Subscribe to a notification; return an unsubscribe function
controllerHeadless navigation controller; see below
render()Flush pending synchronous updates; mainly useful for tests; does not await network fetches
destroy()Dispose subscriptions, observers, and rendered DOM

destroyCalendar(instance) is an equivalent teardown helper. View range boundaries are Temporal.ZonedDateTime values; the active range may include month padding days outside the nominal current range. Instance mutators generally return this for chaining; event mutators have the explicit return values shown above. refetchEvents()/refetchResources() are not promises: observe loading to show fetch progress.

Custom view renderers

A plugin's views[name].component(ctx) renders a view and returns { el, destroy }. The ViewContext gives it the calendar state, resolved options, a disposal scope, host slots, and mainEl, the signal the calendar reads for the scroll container used by drag-scrolling and hit testing. Set it to the scrollable element after building the view. A plugin view declared with a type and no component inherits the renderer, features, and activeRange of its nearest ancestor that has one; its own defaults still apply.

Custom cells keep the built-in interactions when they are registered as hit targets. Call registerDayTarget(el, { cell, allDay }) on each cell, give the element a data-cx-date attribute, and forward pointerdown to state.interactionHooks (present while the Interaction plugin is loaded). Selection, date clicks, drag targets, dateFromPoint(), and the hover preview then work as they do in the built-in views. Hit testing is scoped to one calendar root, so overlapping or adjacent calendars do not see each other's cells. createDayElement and createEventElement render the built-in cell and event elements, including their drag and resize wiring, for views that only change layout; registerEventTarget plus interactionHooks.eventPointerDown do the same for fully custom event elements.

import { createCell, effect, registerDayTarget, type CalendarPlugin, type CellContext } from 'z-cal'

export const Agenda: CalendarPlugin = {
  name: 'agenda',
  views: {
    agendaWeek: {
      defaults: { duration: { days: 7 } },
      component: (ctx) => {
        const { state, options } = ctx
        const section = document.createElement('section')
        const cleanups: Array<() => void> = []
        effect(() => {
          const cellCtx: CellContext = {
            tz: state.timeZone.get(),
            validRange: options.validRange,
            highlightedDates: options.highlightedDates,
          }
          for (const stop of cleanups.splice(0)) stop()
          section.replaceChildren()
          state.viewDates.get().forEach((date, i) => {
            const cell = createCell(date, i + 1, 1, cellCtx)
            const dayEl = document.createElement('div')
            dayEl.dataset['cxDate'] = date.toString()
            cleanups.push(registerDayTarget(dayEl, { cell, allDay: true }))
            dayEl.addEventListener('pointerdown', (jsEvent) => {
              state.interactionHooks.peek()?.dayPointerDown(jsEvent, cell, true, dayEl)
            })
            section.append(dayEl)
          })
        })
        ctx.mainEl.set(section)
        return { el: section, destroy: () => cleanups.forEach((stop) => stop()) }
      },
    },
  },
}

Options by task

The configuration guide covers common recipes. The exhaustive input names and types are in CalendarOptions; refiners define base defaults, and each view plugin can override them. Use getOption() to inspect the actual resolved value.

TaskCommon options and base defaults
Navigationdate (today in the calendar zone), view, duration, dateIncrement, views
Visible daysfirstDay: 0, hiddenDays: [], weekNumbers: false, highlightedDates, validRange
Sizingheight (content-driven), responsiveWeek: false, dayMaxEvents: false, columnWidth
Time gridallDaySlot: true, slotMinTime: '00:00', slotMaxTime: '24:00', slotDuration: '00:30', slotHeight: 24, expandRows: false, scrollTime: '06:00', scrollToFirstEvent: false
Time layouteventMinHeight: 0, slotLabelInterval, flexibleSlotTimeLimits: false, nowIndicator: false
NavigationswipeNavigation: false, dateIncrement, validRange
KeyboardkeyboardNavigation: false, keyboardShortcuts: false, announceNavigation: true, dayCellAriaLabelFormat
Event previewseventTooltip: true (or a predicate), eventTooltipContent, theme.eventTooltip
FormattingtimeZone: 'local', locale, direction: 'auto', titleFormat, dayHeaderFormat, eventTimeFormat, slotLabelFormat
Dataevents, eventSources, lazyFetching: true, fetchPadding: false, eventFilter, eventOrder
Resourcesresources, datesAboveResources: false, refetchResourcesOnNavigate: false, filterEventsWithResources, filterResourcesWithEvents
Editingeditable: false, eventStartEditable, eventDurationEditable, eventResizableFromStart: true, snapDuration, defaultTimedEventDuration: '01:00'
Selectionselectable: false, unselectAuto: true, unselectCancel, selectMinDistance: 5
Business hoursbusinessHours: false, dragConstraint, resizeConstraint, selectConstraint, eventOverlap: true, selectOverlap: true
Pointer/touchpointer: false, dragScroll: true, eventDragMinDistance: 5, longPressDelay: 1000 (milliseconds), event/selection long-press overrides
AppearancecolorScheme: 'auto', theme, eventColor, eventBackgroundColor, eventTextColor, eventClassNames, eventAttrs, customScrollbars: false
Toolbar/contentheaderToolbar, buttonText, icons, customButtons, content renderers

height takes a string; slot dimensions are pixel numbers. headerToolbar is an object, not a boolean. Only documented view plugins are available; options from another calendar library are not implicitly supported even where naming is similar.

lazyFetching reuses a fetched range that still covers the view; fetchPadding widens each request by one view duration on both sides so adjacent periods need no new request. See loading a remote source.

eventMinHeight defaults to 0 and accepts a finite nonnegative pixel number. It expands timed events within the visible slot range and includes their rendered bounds in collision packing. See minimum timed event height.

Callback notifications

Pass these as options/React props or use calendar.on(name, callback). Call the returned unsubscribe function when an external subscription is no longer needed. Callback shapes are defined in info.ts.

NotificationsPayload
dateClickdate, dateStr, allDay, dayEl, jsEvent, view, optional resource
datesSetstart, end, their string forms, view
eventClick, eventMouseEnter, eventMouseLeaveevent, el, jsEvent, view
selectstart, end, string forms, allDay, view, optional resource, jsEvent
unselectview, jsEvent
eventDropevent, oldEvent, delta, revert, optional old/new resources, jsEvent, view
eventResizeevent, oldEvent, startDelta, endDelta, revert, jsEvent, view
eventDragStart, eventDragStop, eventResizeStart, eventResizeStopevent, jsEvent, view
eventDidMount, eventWillUnmountevent, el, timeText, view
eventAllUpdated, viewDidMount{ view }
resourceExpandresource, jsEvent, view
resourceLabelDidMountresource, el, optional date
noEventsClickjsEvent, view
pointerDowntarget: 'event' | 'date' | 'none', then event+el, or date+dayEl+allDay; jsEvent, view, consume(), consumed
loadingA boolean, not an info object

Content options return rendered content. Constraint options (dragConstraint, resizeConstraint, selectConstraint) are configured as options, not on() notifications: a predicate returning a boolean, 'businessHours', inline hours, or an event id (see constraints and overlap). Together with eventOverlap and selectOverlap, all run during the gesture and again on release, with selectable or editable. Rejected selections do not emit select; a rejected or unchanged drag/resize emits only its stop callback. A drag/resize of an event that was deleted, or whose dates or resources changed during the gesture, emits its stop callback but does not commit or emit eventDrop/eventResize; other changes to the event are kept. Their revert() functions apply only while the committed dates are still current; later moves make them no-ops. See editing and persistence for concurrency and daylight-saving behavior.

pointerDown reports what a primary pointer pressed inside the calendar, before any gesture starts and whether or not Interaction is loaded: the nearest event element (target: 'event', with event and el), else the day cell or time slot under the pointer (target: 'date', with date, dateStr, allDay, dayEl, disabled, optional resource), else target: 'none' for toolbar, headers, and gutters. It fires for every button; check jsEvent.button to ignore secondary ones. Use it to decide what a press means for your own UI (dismiss a popover, close an inspector) without inspecting the calendar's DOM. Call consume() on the info when that press should do nothing else: the calendar starts no gesture from it and emits no dateClick, select, eventClick, eventDragStart, eventResizeStart, or touch long press, with or without Interaction. Only that press is affected; the next one behaves normally, and an existing range selection still clears on release under unselectAuto. consumed reports whether an earlier pointerDown listener consumed the press, so several on('pointerDown') subscribers can coordinate. Consuming is separate from jsEvent.preventDefault(), which keeps its browser meaning. The click suppression is armed by the press and cleared by the click or by the next press on that event element, so a consumed press that is released elsewhere leaves a programmatic el.click() swallowed once; a real press resets it.

eventDidMount and eventWillUnmount describe the consumer's events only: the drag/resize preview and the hover pointer helper copy an event's id but never report a mount, and getEventElements(id) never returns them. During a drag the original element stays mounted as the ghost, so it remains the anchor for anything attached to the event.

Every CalEvent carries constraint and overlap (undefined unless the input set them; inline constraint hours are reported in seconds from midnight) and calEventToInput round-trips both. Every CalEvent carries recurrence (the normalized rule of its series, or undefined) and occurrence (series id, key, original start, index, and overridden; undefined on plain events and series masters). A drag or resize of an occurrence writes an override on its series; revert() restores the override that existed before. Plugins contribute rule syntaxes through CalendarPlugin.recurringTypes. See recurring events.

With keyboardNavigation, dateClick, select, unselect, and eventClick may carry a KeyboardEvent as jsEvent. See keyboard navigation.

React and controller types

z-cal/react exports CalendarProps, CalendarOptionProps, CalendarSlotProps, SlotRenderers, SlotInfo, SlotName, UseCalendarResult, and CalendarControllerState. The core instance type is re-exported as CalendarInstance. Import less-common core types directly from z-cal.

calendar.controller.getSnapshot() returns { title, view, date, views, loading }. subscribe(listener) returns an unsubscribe function. Controller methods are next, prev, today, changeView(name), and gotoDate(plainDate). Unlike calendar.gotoDate, the controller's gotoDate requires a PlainDate. The React hook returns the same state/commands, with date: null before mounting. See React hooks and slots.