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 canEditEvent with canOpenEvent.
  • Replace onEventEdit with onEventOpen.
  • 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 CalendarMessages registries.
  • Replace TimeGridEventDropPosition imports with TimeGridEventPosition.
  • Account for segment.layout in custom time-grid event renderers.
  • Review the new WeekView 96px column 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:

ActionPointer or touchKeyboard
SelectSingle clickSpace
OpenDouble-click or double-tapEnter

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:

  1. Event click, double-click, double-tap, Space, and Enter behavior.
  2. Custom translated message registries with TypeScript.
  3. Custom event renderers in timed and dedicated layouts.
  4. Week layouts at narrow viewport widths.
  5. Controlled event state after movement or resizing callbacks.
  6. 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.