Lattice Grid Buy a licence

api reference

Calendar API Reference

createCalendar: a month grid and a week day-grid of rows as cards, keyed by a date field, with navigation, overflow and the viewer contract.

API reference › The calendar viewer

All 21 pages Everything on one page → Developer guide →

The calendar viewer

modules/calendar is an opt-in view of grid rows as cards on a month grid or a week day-grid, keyed by a date field. It is a separate bundle in the same shape as charts, kanban and the data router: it adds no weight to a page that does not load it, changes nothing in grid core, and pulls in no dependency. A calendar is just another dataset viewer: it consumes data through the same keyed-diff contract a grid exposes, calendar.rows.apply({ add, update, remove }) (plus rows.get(key)), so a Data Router can attach(value, calendar) and drive a calendar beside a grid, a kanban and a chart off one feed.

import { createCalendar } from '@toclocoinc/lattice-grid/modules/calendar';

const calendar = createCalendar(document.querySelector('#calendar'), {
  rows,                          // or { grid } to bind a live grid
  rowKey: 'id',
  startProperty: 'posttime',     // the point-event start (configurable)
  view: 'week',                  // 'week' | 'month'
  header: { navigation: true, title: true, views: true },
  card: { title: 'name' },
  colorProperty: 'status',       // colour the card edge by a property
  onCardClick: ({ row }) => open(row),
});

The date field is startProperty (default 'start'); an optional endProperty turns point events into multi-day spans, drawn as one continuous bar across the days they cover. Days grow with their cards (--lattice-calendar-cell-min-height, default 120px; the page scrolls, never the calendar), and the built-in header (or header: false) carries prev / next / today, the title (SEP 28 - OCT 4, 2026 for a week, SEPTEMBER 2026 for a month, via Intl.DateTimeFormat(locale)) and a week/month toggle, styled only through theme tokens. The week-time and day views add a vertical hour axis: a row with a start time is a block positioned at its start and sized by its end (defaultDuration when there is none), overlapping blocks split the column side by side, all-day rows stack in a top all-day row, businessHours shades the working span, and a now line marks the current time in the current day column. slotDuration (default 30 minutes), slotMinTime/slotMaxTime, scrollTime and weekNumbers tune the axis, and times read through the grid's timeZone rule (a 12:17Z post shows 13:17 under Europe/London summer time). A row whose start is empty or unparsable is counted in unscheduled and reported once, never thrown. Accessibility is built in: role="grid" with columnheader/gridcell, a roving-tabindex focus ring across cards, arrows by day/week (and by slot in the time grid), PageUp/PageDown by period, a live region announcing the range and card count, and an announced time slot on each timed block.

MemberDescription
createCalendar(el, config)Create a calendar. Pass a DOM element to render into, or null for a headless calendar that computes the same range and card model without a DOM.
rows.apply({ add, update, remove }) / rows.forEach / rows.count / rows.get(key)The keyed-diff consumer contract a grid shares, so the calendar is a drop-in Data Router target. get/remove key uncoerced, exactly as the calendar keys its own rows.
prev() / next() / today() / goTo(date) / setView('week'|'month')Navigation. Each fires range:change { start, end, view }; a data update does not.
getRange()The visible range { start, end, view }, both local midnights, end inclusive.
refresh()Recompute and re-render. A grid-bound calendar re-reads its grid; one still on config.rows re-reads that array; a routed calendar re-reads nothing.
renderCard(row, el)A host card renderer; the element is kept mounted across updates and navigation, so an async image swap into the same el survives. The default renderer is time + title, via Intl.
colorProperty ( + colorMap)Colour the card edge by a property's value, through an optional value→colour map.
dayMaxEvents / eventOrderA +N more overflow threshold per day (default off), and the card stacking order (a field or a comparator).
getState() / setState(snapshot)Serialise and restore the arrangement state { version: 2, view, date, firstDay, slotDuration, scrollTime } - never the row data.
move(key, { start, end })Reschedule a card: move it (shift the end by the same delta so the duration is kept) or resize it (a new end only). Grid-bound, the start/end cells are written through grid.edit.setCells (parse/validate/undo/history/write-back run, and a refused write snaps the card back with the grid's own message beside it); standalone, the calendar's own row is updated and a host onCardMove persists it (returning false/rejecting reverts). Gated by the cancellable beforeMove event; returns a Promise of whether it stuck.
on(name, fn) / off(name, fn)Events: card:click, card:dblclick, card:contextmenu { row, key, clientX, clientY, nativeEvent }; card:mount { row, key, el }; range:change { start, end, view }; beforeMove (cancellable) and card:move { row, key, from: { start, end }, to: { start, end } }.
readonly(scope) / unscheduledGranular readonly (whole / per day / per card), carried on the model; and the rows whose start could not be read.
destroy()Empty the element, remove only what the module added, and drop handlers and caches. The host still owns any bound grid.

Week and month, navigation and state, executed

Point events on a week grid, one unparsable start counted in unscheduled, the de-dup contract (add of an existing key is an update), navigation firing range:change, and the versioned state round-trip. Run headless on every build.

const { createCalendar } = await import('../packages/modules/calendar/index.js');
const cal = createCalendar(null, {
  rows: [
    { id: 'a', start: '2026-09-28 10:00', title: 'Login' },
    { id: 'b', start: '2026-09-28 14:00', title: 'OAuth' },
    { id: 'c', start: '2026-10-02 09:00', title: 'Reset' },
    { id: 'd', start: '', title: 'No date' },
  ],
  rowKey: 'id', startProperty: 'start', view: 'week', date: '2026-09-30', firstDay: 1, locale: 'en-GB',
  header: { navigation: true, title: true, views: true },
  card: { title: 'title', subtitle: 'subtitle' },
  ariaLabel: 'Calendar', labels: { today: 'Today', week: 'Week', month: 'Month' },
  readonly: false, timeZone: 'Europe/London',
});
const heard = [];
cal.on('range:change', (e) => heard.push(e.view));
const off = () => {};
cal.on('range:change', off); cal.off('range:change', off);
const count = cal.rows.count;                       // 4
const unscheduled = cal.unscheduled.length;         // 1
const startDay = cal.getRange().start.getDate();    // 28
const endDay = cal.getRange().end.getDate();        // 4
cal.rows.apply({ add: [{ id: 'a', start: '2026-09-28 11:00', title: 'Login v2' }] });
const dedup = cal.rows.count;                       // 4
const title = cal.rows.get('a').title;              // Login v2
cal.next(); cal.prev(); cal.today();                // range:change x3
const changes = heard.length;                       // 3
cal.goTo('2026-09-30');
const nextRange = cal.getRange().start.getDate();   // 28
cal.setView('month');
const monthDays = cal.model.days.length;            // 35
cal.refresh();
cal.rebind(null);                                   // no-op: this calendar is not grid-bound
const state = cal.getState();                       // view month
cal.setState({ view: 'week', date: '2026-09-30' });
const restored = cal.getRange().view;               // week
const ro = cal.readonly({ day: '2026-09-30' });     // false
cal.destroy();
return [count, unscheduled, startDay, endDay, dedup, title, changes, nextRange, monthDays, state.view, restored, ro].join('|');

Spans, colour, overflow and the host renderer, executed

A multi-day span (one continuous bar, marked continuing), colour by property through a map, order by a field, the +N more overflow threshold, and a host renderer. Run headless on every build.

const { createCalendar } = await import('../packages/modules/calendar/index.js');
const cal = createCalendar(null, {
  rows: [
    { id: 's1', start: '2026-09-28', end: '2026-09-30', title: 'Span' },
    { id: 'p1', start: '2026-09-29 09:00', title: 'B', status: 'posted', rank: 2 },
    { id: 'p2', start: '2026-09-29 08:00', title: 'A', status: 'draft', rank: 1 },
    { id: 'p3', start: '2026-09-29 10:00', title: 'C', status: 'posted', rank: 3 },
  ],
  rowKey: 'id', startProperty: 'start', endProperty: 'end', allDayProperty: 'allDay',
  view: 'week', date: '2026-09-30', firstDay: 1,
  colorProperty: 'status', colorMap: { posted: '#16a34a', draft: '#6b7280' },
  eventOrder: 'rank', dayMaxEvents: 2,
  renderCard: (row, el) => { el.textContent = row.title; },
  onCardClick: () => {}, onCardDblClick: () => {}, onCardContextMenu: () => {}, onRangeChange: () => {},
});
const spanBars = cal.model.days.find((d) => d.key === '2026-09-28').spans;
const span = spanBars[0];
const continuing = span.span;                        // true
const clippedBefore = span.continuesBefore;          // false (starts in range)
const sep29 = cal.model.days.find((d) => d.key === '2026-09-29').cards;
const order = sep29.map((c) => c.row.title).join(',');   // A,B,C (by rank)
const colour = sep29.find((c) => c.row.id === 'p1').color; // #16a34a
cal.destroy();
return [continuing, clippedBefore, order, colour].join('|');

The hour axis, overlap columns, all-day and the now line, executed

The week-time/day time grid: a timed block positioned by its start and sized by its end, overlapping blocks splitting the column, an all-day row, business-hours shading, week numbers and the time-axis tuning keys. Run headless on every build.

const { createCalendar } = await import('../packages/modules/calendar/index.js');
const cal = createCalendar(null, {
  rows: [
    { id: 'a', start: '2026-09-30 09:00', end: '2026-09-30 10:30', title: 'Review' },
    { id: 'b', start: '2026-09-30 09:30', title: 'Overlap' },
    { id: 'all', start: '2026-09-30', title: 'All day' },
  ],
  rowKey: 'id', startProperty: 'start', endProperty: 'end', allDayProperty: 'allDay',
  view: 'day', date: '2026-09-30',
  slotDuration: 30, slotMinTime: '08:00', slotMaxTime: '18:00', scrollTime: '09:00',
  defaultDuration: 60, businessHours: { startTime: '09:00', endTime: '17:00', daysOfWeek: [3] },
  weekNumbers: true,
});
const day = cal.model.days[0];
const a = day.timed.find((b) => b.key === 'a');
const startMin = a.startMin;                         // 540 (09:00)
const endMin = a.endMin;                             // 630 (10:30)
const cols = a.cols;                                 // 2 (overlaps with b)
const allDay = day.allDay.length;                    // 1 (the date-only row)
cal.destroy();
return [startMin, endMin, cols, allDay].join('|');

Rescheduling, veto and write-back, executed

A move shifts the end by the same delta (duration kept); a resize changes the end only; a beforeMove veto cancels synchronously or by promise; card:move fires on success with the old and new values; and a standalone onCardMove returning false reverts. Run headless on every build.

const { createCalendar } = await import('../packages/modules/calendar/index.js');
const cal = createCalendar(null, {
  rows: [{ id: 1, start: '2026-09-28 10:00', end: '2026-09-28 11:00', title: 'A' }],
  rowKey: 'id', startProperty: 'start', endProperty: 'end', view: 'week', date: '2026-09-30',
});
await cal.move(1, { start: new Date(2026, 8, 29, 10, 0) });
const movedStart = cal.rows.get(1).start;             // 2026-09-29 10:00
const movedEnd = cal.rows.get(1).end;                 // 2026-09-29 11:00 (duration kept)
await cal.move(1, { end: new Date(2026, 8, 29, 12, 0) });
const resizedEnd = cal.rows.get(1).end;               // 2026-09-29 12:00 (end only)
const heard = [];
cal.on('card:move', (e) => heard.push([e.from.start.getDate(), e.to.start.getDate()]));
const veto = (e) => e.preventDefault('blocked');
cal.on('beforeMove', veto);
await cal.move(1, { start: new Date(2026, 8, 30, 10, 0) });   // vetoed: no card:move
const stayed = cal.rows.get(1).start;                 // 2026-09-29 10:00
cal.off('beforeMove', veto);
await cal.move(1, { start: new Date(2026, 8, 30, 10, 0) });
const firedFrom = heard[0][0];                        // 29
const firedTo = heard[0][1];                          // 30
cal.destroy();
const cal2 = createCalendar(null, {
  rows: [{ id: 1, start: '2026-09-28 10:00', title: 'B' }],
  rowKey: 'id', startProperty: 'start', view: 'week', date: '2026-09-30',
  onCardMove: () => false,
});
await cal2.move(1, { start: new Date(2026, 8, 29, 10, 0) });
const reverted = cal2.rows.get(1).start;              // 2026-09-28 10:00 (onCardMove reverted)
cal2.destroy();
return [movedStart, movedEnd, resizedEnd, stayed, firedFrom, firedTo, reverted].join('|');

Type reference

Generated from the type declarations, so it always matches the release. Each surface lists its properties, its methods and the events it raises as three tables; an option or value type lists its members once.

The calendar module

CalendarHeader

The built-in header toolbar. Each section is shown unless its flag is `false`; `header: false` (the whole object) renders no header at all and the host draws its own.

PropertyTypeDescription
navigationbooleanThe prev / next / today buttons. (optional)
titlebooleanThe `SEP 28 - OCT 4, 2026` / `SEPTEMBER 2026` title. (optional)
viewsbooleanThe week/month toggle. (optional)

CalendarCardMap

The field-to-property mapping that drives the default card template.

PropertyTypeDescription
titleCalendarFieldMapThe card's headline text (the default renderer shows time + this). (optional)
subtitleCalendarFieldMapA second line under the title. (optional)
[field: string]CalendarFieldMap | undefinedAny other card slot, mapped the same way as the named ones above.

CalendarPointerEvent

The payload of the three card pointer events - `card:click`, `card:dblclick` and `card:contextmenu`.

PropertyTypeDescription
rowCalendarRowThe data row the card was placed from.
keyunknownThe card's key, from `rowKey`.
clientXnumberThe pointer's X position, where the host wants to anchor a popover.
clientYnumberThe pointer's Y position.
nativeEventunknownThe DOM event that caused this one, so a host can call `preventDefault`.

CalendarMountEvent

The payload of `card:mount` - the card's element was created and attached.

PropertyTypeDescription
rowCalendarRowThe data row the card was placed from.
keyunknownThe card's key, from `rowKey`.
elunknownThe card's element, kept mounted for the host's async updates.

CalendarRangeEvent

The payload of `range:change` - the visible range moved.

PropertyTypeDescription
startDateThe first visible day, a local midnight.
endDateThe last visible day, a local midnight (inclusive).
viewCalendarView 'week' | 'month' | 'week-time' | 'day'The view now shown: `'week'`, `'month'`, `'week-time'` or `'day'`.

CalendarMoveSpan

A card's start/end before or after a reschedule; either may be null.

PropertyTypeDescription
startDate | nullThe card's start, or null when it has none.
endDate | nullThe card's end, or null when it has none.

CalendarMoveEvent

The payload of `beforeMove` and `card:move` - a card was (or may be) rescheduled.

PropertyTypeDescription
rowCalendarRowThe data row the card was placed from.
keyunknownThe card's key, from `rowKey`.
fromCalendarMoveSpanThe card's position before the reschedule.
toCalendarMoveSpanThe card's position after the reschedule.
origin'user' | 'ai'Who initiated the move: `'user'` or `'ai'`. (optional)
reasonstringWhen cancelled, the reason given to `preventDefault(reason?)`. (optional)

CalendarConfig

The configuration for {@link createCalendar}. The data source is `rows` (an array) or `grid` (a live grid to bind). The date field is `startProperty`; an `endProperty` turns point events into multi-day spans.

PropertyTypeDescription
rowsCalendarRow[]The source rows (one card per row). One of `rows`/`grid`. (optional)
gridunknownA Lattice grid to bind to instead of `rows`; the calendar follows its filtered/sorted rows live. (optional)
rowKeystring | ((row: CalendarRow) => string | number)The row identity: a property path or a function returning a string or number. Default `'id'`. (optional)
startPropertystringThe field naming a point event's start. Default `'start'`. (optional)
endPropertystringThe optional field naming a span's end; omitted means point events. (optional)
allDayPropertystringAn optional boolean field marking an all-day event, stacked in the time-grid all-day row. (optional)
viewCalendarView 'week' | 'month' | 'week-time' | 'day'The view to show: `'week'`, `'month'`, `'week-time'` or `'day'`. Default `'month'`. (optional)
dateDate | string | numberThe anchor date the visible range centres on. Default today. (optional)
firstDaynumberWeek-start weekday, 0=Sun…6=Sat. Default 1 (Monday). (optional)
localestringBCP-47 locale for weekday/month names, the title and the card time. (optional)
timeZonestringIANA time-zone name, honoured on the hour axis and the now line. (optional)
slotDurationnumber | stringThe time-grid slot step in minutes, or an `'H:MM(:SS)'` duration. Default 30. (optional)
slotMinTimenumber | stringThe hour axis's first time, a clock or minutes. Default `'00:00'`. (optional)
slotMaxTimenumber | stringThe hour axis's last time, a clock or minutes. Default `'24:00'`. (optional)
scrollTimenumber | stringThe hour axis's initial scroll, a clock or minutes. Default `slotMinTime`. (optional)
defaultDurationnumber | stringA timed block's length when it has no end. Default 60 minutes. (optional)
businessHoursboolean | { startTime?: string; endTime?: string; daysOfWeek?: number[] }The working span the time grid shades: `true`, or `{ startTime, endTime, daysOfWeek }`. Default off. (optional)
weekNumbersbooleanShow the ISO week number in the time-grid header. Default false. (optional)
headerCalendarHeader | falseThe header toolbar sections, or `false` to render none. (optional)
cardCalendarCardMapThe default card template's field mapping (title/subtitle/…). (optional)
colorPropertystringThe property whose value picks the card's edge colour. (optional)
colorMapRecord<string, string>An optional value→colour map applied to `colorProperty`'s value. (optional)
eventOrderstring | ((a: CalendarRow, b: CalendarRow) => number)Card stacking order within a day: a property path, or a comparator. (optional)
dayMaxEventsnumber | falseThe `+N more` overflow threshold per day. Default `false` (off). (optional)
readonlyCalendarReadonlyGranular readonly (whole calendar, per day, per card). (optional)
ariaLabelstringThe calendar's accessible name. Defaults to `Calendar`. (optional)
labelsRecord<string, string>Host-localised labels: `today`, `week`, `month`, `more`, `event`, `eventSingular`, `calendar`. (optional)
messagesunknownA message catalogue with `t(key, params)`, or a bound grid's is borrowed. (optional)
MethodSignatureParametersReturnsDescription
renderCard(row: CalendarRow, el: unknown) => unknownrow: CalendarRow
el: unknown
=> unknownA host card renderer `(row, el)`; `el` stays mounted for its async updates. (optional)
onCardClick(event: CalendarPointerEvent) => voidevent: CalendarPointerEvent=> voidCard click handler - the config route to the `card:click` event. (optional)
onCardDblClick(event: CalendarPointerEvent) => voidevent: CalendarPointerEvent=> voidCard double-click handler - the config route to the `card:dblclick` event. (optional)
onCardContextMenu(event: CalendarPointerEvent) => voidevent: CalendarPointerEvent=> voidCard right-click handler - the config route to the `card:contextmenu` event. (optional)
onRangeChange(event: CalendarRangeEvent) => voidevent: CalendarRangeEvent=> voidRange-changed handler - the config route to the `range:change` event. (optional)
onCardMove(event: CalendarMoveEvent) => unknownevent: CalendarMoveEvent=> unknownReschedule handler - the config route to `card:move`. On a standalone calendar, returning `false` (or rejecting) reverts the move. (optional)

CalendarRows

The keyed-diff consumer surface a calendar shares with a grid, so a Data Router routes to it directly.

PropertyTypeDescription
countnumberHow many rows the calendar holds. (read-only)
MethodSignatureParametersReturnsDescription
apply(change: { add?: CalendarRow[]; update?: CalendarRow[]; remove?: unknown[] }): voidchange: { add?: CalendarRow[]; update?: CalendarRow[]; remove?: unknown[] }voidApply a keyed diff: `add` sets each row outright, `update` merges each patch into the stored row, `remove` drops keys uncoerced - exactly as the calendar keys its own rows. Recomputes and re-renders without firing `range:change`. Ignored on a grid-bound calendar.
forEach(fn: (row: CalendarRow, key: unknown) => void): voidfn: (row: CalendarRow, key: unknown) => voidvoidVisit every row the calendar holds, with its key.
get(key: unknown): CalendarRow | undefinedkey: unknownCalendarRow | undefinedThe row stored under a key, uncoerced.

Calendar

A calendar instance: a view of grid rows as cards on a month grid or a week day-grid. It consumes data through the same keyed-diff `rows.apply` contract a grid exposes, so `dataRouter.attach(value, calendar)` drives it like any other viewer.

Properties
PropertyTypeDescription
elunknown | nullThe element the calendar renders into, or null for a headless calendar. (read-only)
rowKeystring | ((row: CalendarRow) => string | number)The resolved row identity; see `CalendarConfig.rowKey`. (read-only)
rowsCalendarRowsThe keyed-diff consumer surface - what makes the calendar a Data Router target.
unscheduledCalendarRow[]The rows whose start could not be read, in arrival order. (read-only)
Methods
MethodSignatureParametersReturnsDescription
prev(): Calendar - CalendarMove to the previous week or month, firing `range:change`.
next(): Calendar - CalendarMove to the next week or month, firing `range:change`.
today(): Calendar - CalendarMove back to today, firing `range:change`.
goTo(date: Date | string | number): Calendardate: Date | string | numberCalendarJump to an anchor date, firing `range:change`.
setView(view: CalendarView): Calendarview: CalendarViewCalendarSwitch the view (`'week'` | `'month'` | `'week-time'` | `'day'`), firing `range:change`.
getRange(): CalendarRangeEvent - CalendarRangeEventThe visible range `{ start, end, view }`, both local midnights, `end` inclusive.
refresh(): Calendar - CalendarRecompute and re-render. A grid-bound calendar re-reads its grid; one still on `config.rows` re-reads that array; a routed calendar re-reads nothing.
rebind(grid: unknown): Calendargrid: unknownCalendarPoint this calendar at a replacement grid, the one call a page framework makes after its swap destroyed the grid the calendar was bound to. No-op on a calendar not built over a grid, and on binding to the grid it already follows.
getState(): object - objectSerialise the arrangement state: `{ version: 1, view, date, firstDay }`.
setState(snapshot: object): Calendarsnapshot: objectCalendarRestore a state snapshot from {@link Calendar#getState}, then re-render.
on(name: CalendarEventName, fn: (event: CalendarEventPayloads[CalendarEventName]) => void): () => voidname: CalendarEventName
fn: (event: CalendarEventPayloads[CalendarEventName]) => void
() => voidRegister an event handler; returns a function that removes it.
off(name: CalendarEventName, fn: (event: CalendarEventPayloads[CalendarEventName]) => void): voidname: CalendarEventName
fn: (event: CalendarEventPayloads[CalendarEventName]) => void
voidRemove a handler registered with `on`.
readonly(scope?: { day?: string; card?: unknown }): booleanscope?: { day?: string; card?: unknown }booleanWhether a scope (the whole calendar, a day, or a card) is readonly.
move(key: unknown, to: { start?: Date | null; end?: Date | null }, opts?: { origin?: 'user' | 'ai' }): Promise<boolean>key: unknown
to: { start?: Date | null; end?: Date | null }
opts?: { origin?: 'user' | 'ai' }
Promise<boolean>Reschedule a card: move it to a new start (shifting the end by the same delta so the duration is kept) or resize it to a new end. Grid-bound, the start/end cells are written through the grid's `edit.setCells`; standalone, the calendar's own row is updated and a host `onCardMove` persists it. Gated by the cancellable `beforeMove` event. Returns a Promise of whether the reschedule stuck.
destroy(): void - voidEmpty the element, remove only what the module added, and drop handlers and caches.
Events
EventWhenPayloadCancellable
card:clickA card was clicked, or Enter/Space was pressed on a focused card.CalendarPointerEventno
card:dblclickA card was double-clicked.CalendarPointerEventno
card:contextmenuA context menu was requested on a card.CalendarPointerEventno
card:mountA card's element was created and attached; fires once per mount.CalendarMountEventno
range:changeThe visible range moved via prev/next/today/goTo/setView (not on a data update).CalendarRangeEventno
beforeMoveA card is about to be rescheduled; cancel with `preventDefault(reason?)` or a returned/rejected `false`.CalendarMoveEventno
card:moveA card was rescheduled; carries the old and new start/end.CalendarMoveEventno

CalendarEventPayloads

What a handler receives, per calendar event.

PropertyTypeDescription
card:clickCalendarPointerEventThe row, key, pointer position and the DOM event.
card:dblclickCalendarPointerEventThe row, key, pointer position and the DOM event.
card:contextmenuCalendarPointerEventThe row, key, pointer position and the DOM event.
card:mountCalendarMountEventThe row, key and the mounted element.
range:changeCalendarRangeEventThe new range's start, end and view.
beforeMoveCalendarMoveEventThe row, key and the from/to positions; veto with `preventDefault(reason?)`.
card:moveCalendarMoveEventThe row, key and the old/new start/end after a successful reschedule.