Compat
@hapio/calendar-compat: the migration component's options, callback payloads and controller.
@hapio/calendar-compat maps the options of the calendar you migrate from onto HapioCalendar's
props, so the event-calendar item's EventCalendar takes them unchanged. The
migration guide explains it option by option; this page lists the types.
The component's props
The props of the compat EventCalendar: the supported options of the source library's
current API with their names, plus availableViews (the switcher's views in order; the first is
the default, initialView aside), controller and className. Compat stays uncontrolled like the source
library: initialView/initialDate seed the calendar once, so an app that re-creates it with
a React key keeps working. Options outside the mapping table are reported in unsupported
(with a development warning); resource options name release 1.3.
Prop
Type
Options of one view type, unit or name in views (the cascade is type, then unit, then
name). type plus duration/dayCount defines a custom view. The resource and timeline keys
are accepted and ignored while their views are not in availableViews.
Prop
Type
Hooks and the mapping
function useCompatProps<P = Dictionary>(props: EventCalendarProps<P>, ui?: CompatUi): HapioCalendarProps<P, undefined, ViewMap>;Compat props → HapioCalendar props, for the event-calendar registry item:
<HapioCalendar {...useCompatProps(props, { timeClass: EVENT_TIME_CLASS, titleClass: EVENT_TITLE_CLASS })} />. It runs the pure mapping (fromCompatOptions) with a session kept
across renders (the last reported range gives eventClick and the class callbacks their
view; a new range makes the class callbacks that read it run again), memoized on the props'
values (handlers such as datesSet and eventClick may be new functions on every render: the
latest committed one is called), keeps the first initialView/initialDate (the calendar is
uncontrolled: a key remount starts over), keeps a move or resize the callbacks accept where
it was made until the app's input for that event changes (as the source library's own store;
EventChangeInfo has the rules), and in development reports each unsupported
option once: resource options and views as a console.error naming release 1.3 (M11),
schedulerLicenseKey as one notice that everything is free, the rest as warnings. With
props.controller it renders no toolbar of its own: toolbar becomes a bridge element
(rendered inside the calendar's provider) that attaches the controller to the calendar's
commands and toolbar model.
function useCalendarController(): CalendarController;A controller for a toolbar outside the calendar: pass it as controller to the compat
EventCalendar (useCompatProps attaches it). The calling component re-renders when the
attached calendar's view, title, date or button states change (a useSyncExternalStore
subscription); each change gives a new controller object, while its methods keep their
identity, so memoized components (React Compiler) see the change and onClick={controller.next}
stays stable. today, prev, next, gotoDate and changeView run the calendar's commands;
getButtonState() gives the toolbar model's texts ("Today", hint "This week"; "Previous
Week"/"Next Week"; per view "Week" with hint "Week view") and isDisabled: prev and next at
the validRange edges, Today while the period shows today or when today is outside
validRange (as in the source library). Before a calendar is attached (and after it
unmounts), view is undefined, getDate() is an invalid Date, the buttons are disabled
and the methods do nothing.
A controller for a toolbar outside the calendar (useCalendarController() from
@hapio/calendar-compat/react), passed to the calendar as controller. The component that
called useCalendarController re-renders when the view, the date or the button states change.
Methods are bound function properties (onClick={controller.next} is safe). Before a calendar
is attached, view is undefined, getDate() is an invalid Date and the methods do
nothing.
Prop
Type
function fromCompatOptions<P = Dictionary>(opts: EventCalendarProps<P>, ui?: CompatUi): CompatResult<P>;Maps compat options onto HapioCalendar props, purely: no warnings, no state, the same
output for the same input (callbacks are wrapped, not called). The mapping:
- views:
availableViews+views(compatViews; a used view's options that do not map are reported asviews.<key>.<option>; a generic view's length fromviewsor the top-levelduration/dayCount);initialView→defaultView(without it, the first available view, or the month whenavailableViewsis not given either),initialDate→defaultDate(compat is uncontrolled: an app re-creates the calendar with akeyto reset it); - zone and locale:
timeZone,locale(a code; another value, such as a locale object, is reported),direction→dir,firstDay(0 → 7) →weekStartsOn,weekends: false/hiddenDays→ ISOhiddenDays(weekdays as numbers or their strings; others reported); - data:
events(array:splitEventInputs; function: one event source),eventSources; - callbacks:
datesSet←onRangeChange(toDatesSetInfo),eventClick←onEventClick(ajsEvent.preventDefault()cancels the link navigation; a middle click does not call it),loading←onLoadingChange; - editing (M5; like selection it needs an engine, which the registry's
<CalendarEditing>gives the compat component; compat never imports it):eventDroporeventResize, theneventChange←onEventChange, withevent(after),oldEvent,relatedEvents(the group at its new times, fromapplyRelated()),view,jsEvent(the change'snativeEvent) andrevert(): called before the handlers return, or before a promise one of them returns settles, it makes the resultfalse(the calendar reverts the change and announces it); a throw or a rejection reverts too. A move or resize that is kept stays shown until the app's events echo it (settleTimeoutMs); with a session that re-maps (useCompatProps) it is held over the app's input instead (seeEventChangeInfo).deltais elapsed milliseconds lane to lane in a time grid, else days (plus the time of day an all-day event takes on a lane);startDelta/endDeltaare each edge's elapsed milliseconds, or days for all-day events. A move between the all-day and timed sections, or of an event given without an end, leavesevent.endnull; - selection (M4; time grids and
selectableneed the selection engine, which the registry's<CalendarSelection>gives the compat component, as the source library's interaction plugin did; compat itself never imports it):dateClick←onDateClick(date/dateStrthe slot's start or the day,dayElthe lane or day cell,jsEventthe click, or the Enter/Space keydown,view);select←onSelect(jsEventthe click,nullfor a keyboard selection);unselect←onSelectionChange(null)(Escape, a press outside, a navigation away from it;jsEventnull) and, in the source library's order, before theselectof a selection that replaces one (jsEventthat selection's click), which needs the sessionuseCompatPropskeeps (a mapping made on every render starts with none);selectable,selectMirror→selectionPreview: 'event',unselectAuto→clearSelectionOnOutsidePointer,selectConstraint/selectOverlap/selectAllow→rules.select(aselectOverlapfunction gets each overlapped event'sEventApiandnull; background events never block a selection in Hapio, so it is not called for them); - rendering:
eventContent→renderEvent(trueor nothing rendersDefault),backgroundEventContent→renderBackgroundEvent(the wrapper then has no role, so the content'srole="img"names it), the class options →classNames, Hapio'sstringsas given, withnoEventsText→strings.noEventsandallDayText→strings.allDayon top; - display:
nowIndicator,navLinks+navLinkDayClick/navLinkWeekClick(defaulttimeGridDay/timeGridWeekwhen available) →navLinks: { day, week },dayMaxEvents/dayMaxEventRows→ each day grid'smaxEvents(trueon either →'auto'; a numericdayMaxEventsN → N + 1, since Hapio's limit counts the "+N more" row;dayMaxEventRowsas is) and the calendar'smaxEvents, which caps a time grid's all-day row (true→ 5 rows, the source library's all-day limit),weekNumberCalculation: 'ISO'(Hapio's only week numbers),eventMaxStackN →maxStackN + 1 (the "+N" column counts too),eventOrder(string keys parsed into a comparator) andeventOrderStrict,slotEventOverlap: false→overlap: 'columns',scrollTime,scrollTimeReset; - editing options:
editable,eventStartEditable(moves),eventDurationEditable(resizes),eventResizableFromStart→editableflags, with each event's and source'seditable,startEditable,durationEditableandresourceEditableover them field by field (a start resize needs the event's duration editable);eventConstraint/eventOverlap(its function gets each overlapped event and the moving event before the change)/eventAllow→rules.event, each event's or source'sconstraint,overlapandallowoverriding them (eventOverlap: falseis also each event's ownoverlapunless it or its source sets one, so an event that allows overlap is still not dropped onto one that does not);snapDuration; long-press (eventLongPressDelay ?? selectLongPressDelay ?? longPressDelay, one delay in Hapio);dragScroll: trueandallDayMaintainDuration: falseare Hapio's behaviour; - external drops (M6; they need the external-drop engine, which the registry's
<CalendarExternalDrops>inside<CalendarEditing>or<CalendarSelection>gives the compat component; compat never imports it):droppable;dropAccept→acceptExternal, whose selector form matches the draggable element (else the drop's data when it is an element; anything else is accepted) and whose function form gets the element (else the data);droppablewithoutdropAcceptaccepts every item, as the source library does (a Hapio calendar withoutacceptExternalaccepts none);drop, theneventReceive←onExternalDrop:date/dateStr/allDayfrom the drop's span,draggedElthe draggable element (else the drop's data),jsEventthe drop'snativeEvent,view;eventReceive(only for a drop whose data is an object: the source library calls it only for a drop with event data) gets aseventthe drop's data read as anEventInput(the source library reads itsDraggable'seventData) at the drop's span, withrelatedEvents[]andrevert(), which rejects the drop when called before the handlers return or before a promise one of them returns settles (a throw or a rejection rejects it too). A kept drop shows a placeholder until the app adds the event toevents(orsettleTimeoutMspasses); - recurrence (M7; with
recurrence, an engine such asrrule()from@hapio/calendar-recurrence, passed through: compat never imports it): the events ofeventsandeventSourcesarrays with simple recurrence (daysOfWeek,startTime,endTime,startRecur,endRecur:FREQ=WEEKLY;BYDAY=…orFREQ=DAILYof wall times in the calendar zone fromstartRecur's day, 1 January 1970 without it,UNTILthe last day beforeendRecur, the times as DTSTART and the length) or the rrule plugin's fields (rruleas a string withDTSTART/RRULE/EXDATE/RDATElines or as its object form,exdate,duration) →series, with the event's other fields (groupIdkept: Hapio ignores it for occurrences) andextendedProps→data;recurrenceScope→recurrenceScope,'all'by default (the source library moves every instance). A move or resize of an occurrence callseventDrop/eventResize/eventChangewith the occurrence after (event) and before (oldEvent),revert()reverting the whole change,relatedEvents[]and Hapio'srecurrence(scope and plan). Without an engine the fields are reported inunsupported; recurring background events and recurring events from an events function stay unsupported; - formats:
compatFormats(with the "+N more" heading defaultDEFAULT_POPOVER_FORMAT;meridiem: 'lowercase'→'short'); EventApi: an event given without an end hasend: nullandendStr: '';- shell:
height/contentHeight→style.height('auto'→ none; numbers in px),className,borderless*(withui.borderlessClasses),headingLevel;legacyEventVars: LEGACY_EVENT_VARSalways; ui.timeClass/ui.titleClass→EventDisplayInfo.timeClass/titleClass.
Unsupported (reported): every option outside the mapping,
such as mutation and imperative API, *DidMount/*WillUnmount, { html }/{ domNodes }
content, other class hooks, the toolbar DSL (headerToolbar, footerToolbar, buttons),
feed sources, slotMaxTime over 24:00, format strings, omitTrailing/weekdayJustify,
multiMonth*, dayGridYear, listYear, the recurrence fields without a recurrence engine
(and exrule with one), v6 aliases,
unselectCancel (an outside press always clears; unselectAuto: false keeps selections),
selectMinDistance and eventDragMinDistance (drags start past 5 px), eventDragStart/
eventDragStop/eventResizeStart/eventResizeStop (no such callbacks), dragScroll: false
(Hapio always scrolls), dragRevertDuration (200 ms), allDayMaintainDuration: true,
fixedMirrorParent, eventLeave (no dragging out of a calendar in v1); the timeline (1.2),
resource (1.3) and resource time-grid (1.4) views and options. The source library's Draggable
and ThirdPartyDraggable classes are not provided: make an element draggable with
useExternalDraggable (@hapio/calendar-react/interaction) or the registry's
CalendarDraggable, whose data is what eventReceive reads as the event (another
drag-and-drop library reaches a HapioCalendar through its store's hitTest).
schedulerLicenseKey and plugins are ignored without a report entry, and controller is
attached by useCompatProps.
What fromCompatOptions returns: the HapioCalendar props, and the names of the given
options it could not map ('eventDidMount', 'headerToolbar', 'resources',
'views.resourceTimelineDay', 'eventTimeFormat.omitTrailing', …), in option order.
Prop
Type
Classes the event-calendar registry item passes in (EVENT_TIME_CLASS, EVENT_TITLE_CLASS
from calendar-event.tsx), so eventContent can style its parts like the default content.
Prop
Type
const LEGACY_EVENT_VARS: {
readonly color: "--fc-event-color";
readonly contrast: "--fc-event-contrast-color";
};The extra names of the event color variables that compat asks the calendar to set
(legacyEventVars), so classes written against the source library's variables, such as
The reference app's open-time tint, keep working.
Events
One event. extendedProps becomes data, url becomes href (a real
<a href>), display: 'background' makes it a background event, className lands on the
event's root element (the anchor when url is set), color/contrastColor are any CSS color
expressions passed through as --event-color/--event-contrast (and the legacy aliases).
An event without id gets one derived from its position and start (a development warning
asks for ids, which keep focus and pending state across updates).
Prop
Type
An entry of eventSources. JSON-feed, Google Calendar and iCalendar sources are not supported.
The editing fields and rules (M5) apply to each of its events, under the event's own: field by
field for the editable flags (startEditable and the others fall back to editable first),
whole for constraint, overlap and allow.
Prop
Type
A read-only event facade with Date start and end. Timed events: true instants. All-day
events: the instants of their days' starts in the calendar zone; allDay tells them apart.
An event given without an end has end: null and endStr: '' (the calendar still draws it
with the default duration). Mutation methods (setProp, remove, …) are not supported.
Prop
Type
Callback payloads
datesSet: the rendered range as true instants (start/end; Stockholm's DST week is
2026-10-19T00:00:00+02:00 → 2026-10-26T00:00:00+01:00), ISO strings with offsets, the zone
and the view.
Prop
Type
The view in callbacks. currentStart/currentEnd are the instants of the
navigation period's first and end day starts in the calendar zone (zone.startOfDay);
activeStart/activeEnd those of the rendered range.
Prop
Type
eventClick. jsEvent is the element's native click (Enter on a link fires one too);
calling jsEvent.preventDefault() cancels the link navigation. Modified clicks call
eventClick too, but the browser opens the link whatever the callback does; a middle click
(auxclick) does not call it, as in the source library.
Prop
Type
eventContent, backgroundEventContent and the event class callbacks. timeClass and
titleClass are the registry's classes (EVENT_TIME_CLASS, EVENT_TITLE_CLASS), passed in
by the event-calendar item. isNarrow is always false (no container queries);
isFuture is !isPast && !isToday; color/contrastColor are the event's ('' without).
Prop
Type
dateClick (M4): a click or tap on a day cell, a time-grid lane (the slot under the pointer)
or an all-day cell, or Enter (and Space where it does not start a selection) on a day cell or
the slot cursor. dayEl is the day cell or lane; jsEvent is the click, or the keydown of a
keyboard activation (the source library types it as a mouse event; a keydown has no pointer
coordinates). Time grids need the selection engine.
Prop
Type
select (M4). jsEvent is the click or tap that made the selection; null for a keyboard
selection.
Prop
Type
eventDrop (M5). delta is how far the start moved: elapsed milliseconds for a timed event
moved within a time grid's lanes, else calendar days (plus the new time of day for an all-day
event dropped on a lane).
Prop
Type
eventResize (M5). startDelta/endDelta are how far each edge moved (elapsed milliseconds
for timed events, calendar days for all-day ones); the edge that stayed has a zero duration.
Prop
Type
What eventDrop, eventResize and eventChange share (M5; editing needs the editing engine,
which the registry's <CalendarEditing> gives the compat component): the event after
(event) and before (oldEvent) the change, the events of its group (groupId) at their new
times (relatedEvents), and revert(), which cancels the change when called before the
callback returns, or before the promise it returns settles (a rejection cancels it too). The
calendar shows the change until the app's events echo it: update events (or refetch) when
the change is saved, as with any Hapio calendar.
For an instance of a recurring event (M7, with a recurrence engine), event is that instance
after the change and oldEvent before it (both with the recurring event's id), revert()
reverts the whole change, and recurrence says what changed. Unlike the source library,
relatedEvents is empty (it lists the other instances it moved): the scope says which
occurrences move, and the plan has the series as they are after the change.
With useCompatProps (the registry's event-calendar), a change the callbacks keep stays
where it was made, as the source library keeps it in its own store: compat holds it over the
app's events (and eventSources arrays) until the app's input for that event changes its
start, end or allDay (an echo of the change, or any other value: the app's data wins) or
leaves the input. A reverted change is not held (the event returns to where it was before it).
Changes to an instance of a recurring event, or to an event an events function returned, are
not held: save them (the plan, or a refetch), or they return at the settle timeout.
There is no el: the change may come from the keyboard or a menu, not from an element drag.
Prop
Type
drop (M6): an external item was dropped on a droppable calendar, by a pointer or touch
drag, or picked up and placed with the keyboard, a click or a tap (external drops need the
external-drop engine: the registry's <CalendarExternalDrops>). Called before eventReceive
(which is called only for a drop whose data is an object).
date, dateStr and allDay are the slot or day it landed on.
Prop
Type
eventReceive (M6), after drop: the event an external drop proposes, the drop's data read
as an EventInput (id, title, extendedProps, colors, classes; the source library reads
its Draggable's eventData) at the slot or day it landed on. Called only for a drop whose
data is an object (event data, as the source library calls it only for a draggable with event
data); a drop of other data calls drop alone. The calendar does not add it:
add it to events (a placeholder shows there until then, or until settleTimeoutMs).
revert() rejects the drop when called before the callback returns, or before the promise it
returns settles (a throw or a rejection rejects it too); relatedEvents is empty.
Prop
Type
Day cells, day headers and day lanes in class callbacks (dayCellClass, dayHeaderClass,
dayLaneClass). dow is 0 (Sunday) – 6.
Prop
Type