Migrating from v1 to v2
ChronoLaneJS 2.0 makes event interactions explicit and adds first-class event
movement, resizing, dedicated multi-day layout, and time-grid keyboard
navigation. This guide covers every public contract change from 1.0.1 to
2.0.0.
Upgrade
npm install @chronolanejs/react@^2.0.0
The supported React, Node, browser, stylesheet import, event model, date input, timezone, range, and resource contracts are unchanged.
Migration checklist
- Replace
canEditEventwithcanOpenEvent. - Replace
onEventEditwithonEventOpen. - Verify event keyboard behavior: Space selects and Enter opens.
- Use the new callback context when behavior depends on a rendered day or resource occurrence.
- Add the four new required functions to custom
CalendarMessagesregistries. - Replace
TimeGridEventDropPositionimports withTimeGridEventPosition. - Account for
segment.layoutin custom time-grid event renderers. - Review the new
WeekView96pxcolumn minimum. - Test custom renderers with movement and resizing enabled, ensuring their
root still spreads
elementProps.
Event editing is now event opening
The v1 editing names described an application outcome rather than the user interaction owned by the calendar. In v2, opening is a distinct semantic action from selection.
v1
<Calendar
events={events}
onEventSelect={(event) => select(event.id)}
canEditEvent={(event) => event.status !== "locked"}
onEventEdit={(event) => openEditor(event.id)}
/>
v2
<Calendar
events={events}
onEventSelect={(event, interaction, context) => {
select(event.id, context.occurrence);
}}
canOpenEvent={(event, context) => (
event.status !== "locked" && context.view !== "agenda"
)}
onEventOpen={(event, interaction, context) => {
openEvent(event.id, context.occurrence);
}}
/>
The gestures no longer change based on which callbacks are present:
| Action | Pointer or touch | Keyboard |
|---|---|---|
| Select | Single click | Space |
| Open | Double-click or double-tap | Enter |
In v1, an editable event used Enter or Shift+Enter depending on whether
selection was also configured. Remove application instructions and tests that
refer to Shift+Enter. Use canSelectEvent and canOpenEvent independently when
only one semantic action is allowed for an occurrence.
Use rendered-occurrence context
Event predicates and semantic callbacks now receive a context containing the active view and concrete rendered occurrence:
interface CalendarEventInteractionContext<Resource> {
view: string;
occurrence: {
day: Date;
resource: Resource | null;
resourceId: string | number | null;
};
}
The first callback argument remains the normalized source event, including its
unclipped start and end. Use context.occurrence for the visible day or
resource; do not infer occurrence identity by modifying the source event.
Existing callbacks that accept only the first one or two arguments remain valid JavaScript and normally remain assignable in TypeScript. Update them only when they need occurrence-aware behavior.
Move raw handlers to eventInteractions
Use semantic callbacks for selection and opening. Application-specific raw
handlers compose through eventInteractions:
<Calendar
events={events}
onEventOpen={(event) => openEvent(event.id)}
eventInteractions={{
onContextMenu: (event, interaction, context) => {
interaction.preventDefault();
openMenu(event.id, context.occurrence);
},
onKeyDown: (event, interaction) => {
if (interaction.key === "Delete") deleteEvent(event.id);
},
ariaKeyShortcuts: "Delete"
}}
/>
Raw callbacks are additive: they do not replace the calendar's semantic handlers. Avoid recreating selection or opening inside them.
Complete custom message registries
CalendarMessages remains intentionally complete rather than partially merged.
V2 adds labels for the dedicated multi-day region, movement controls and
targets, and resize handles.
If the application extends the English defaults, spreading the current default registry supplies the new entries:
import {
defaultCalendarMessages,
type CalendarMessages
} from "@chronolanejs/react";
const messages = {
...defaultCalendarMessages,
agendaEmpty: () => "Nothing scheduled."
} satisfies CalendarMessages;
For a fully translated registry, implement these additional functions:
const messages = {
...translatedV1Messages,
multiDayRegionLabel: () => "Multi-day events",
eventMoveHandle: ({ title }) => `Move ${title ?? "calendar event"}`,
eventMoveTarget: ({ title, date, time, resource }) => (
`Move ${title ?? "calendar event"} to ${date}, ${time}${
resource ? `, ${resource}` : ""
}`
),
eventResizeHandle: ({ edge, title, date, time }) => (
`Resize ${edge} of ${title ?? "calendar event"}, ${date}, ${time}`
)
} satisfies CalendarMessages;
Keep the registry stable outside render, as in v1. Translate these strings in the same language as the rest of the application-owned registry.
Rename the shared time-grid position type
Movement and resizing now use one calendar-position contract. Replace the v1 type name:
// v1
import type { TimeGridEventDropPosition } from "@chronolanejs/react";
// v2
import type { TimeGridEventPosition } from "@chronolanejs/react";
The fields are unchanged: day, resource, and resourceId. The
TimeGridEventDrop payload still exposes source and destination using this
renamed type. TimeGridEventResize uses it for source.
Handle the renderer layout discriminator
TimeGridEventSegment now requires layout: "timed" | "dedicated". Custom
renderers receive the value automatically:
import type { TimeGridEventProps } from "@chronolanejs/react";
function Event({ event, segment, elementProps }: TimeGridEventProps) {
return (
<div {...elementProps} data-layout={segment.layout}>
<strong>{event.title}</strong>
{segment.layout === "dedicated" && <span>Multi-day</span>}
</div>
);
}
Update manually constructed TimeGridEventSegment fixtures with the appropriate
layout. Continue spreading elementProps on the renderer root; it now carries
selection, opening, movement, resizing, accessibility, and layout behavior.
Review WeekView column sizing
WeekView now supplies minWidth: 96 when neither width property is provided.
This keeps day columns readable and enables horizontal scrolling in narrow
containers.
To retain v1's fully compressible week columns:
<WeekView slotSizing={{ minWidth: 0 }} />
An explicit slotSizing.width or slotSizing.minWidth always overrides the
preset. DayView and direct TimeGridView width defaults are unchanged.
Adopt movement, resizing, and dedicated multi-day layout
These capabilities are opt-in and require no changes when their callbacks or layout option are absent:
<Calendar
view="week"
events={events}
viewProps={{
multiDayEventLayout: "dedicated",
resizeStep: 15,
onEventDrop: ({ event, start, end, destination }) => {
updateEvent(event.id, { start, end, resourceId: destination.resourceId });
},
onEventResize: ({ event, start, end }) => {
updateEvent(event.id, { start, end });
}
}}
/>
When onEventDrop is present, the event body is the pointer/touch movement
surface and the focused event accepts Arrow keys. When onEventResize is
present, transparent edge hit zones expose resize cursors and keyboard sliders.
Enter or blur commits; Escape cancels. The calendar reports proposals but never
mutates the supplied events collection.
Dedicated multi-day movement and resizing use whole calendar-day steps and preserve wall-clock fields across daylight-saving transitions.
Verify the upgrade
At minimum, verify:
- Event click, double-click, double-tap, Space, and Enter behavior.
- Custom translated message registries with TypeScript.
- Custom event renderers in timed and dedicated layouts.
- Week layouts at narrow viewport widths.
- Controlled event state after movement or resizing callbacks.
- Keyboard focus and announcements with the application's target screen reader and browser.
See the API reference, accessibility guide, and v2 changelog for the complete current contracts.