Skip to content

Editing and Interaction

Enable editing with the master editable flag and the specific allow* permissions your product needs.

Editing is checked in layers. The scheduler first applies editable, allowCreate, allowMove, allowResize, allowDelete, event/resource locks, date locks, and permission hooks. It then projects the next schedule and rejects the mutation when conflicts resolve to severity: 'error'. Use this to block edits outside working hours.

grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
editable: true,
allowCreate: true,
allowMove: true,
allowResize: true,
allowDelete: true,
conflicts: {
enabled: true,
rules: { 'outside-availability': 'error' },
},
createEventDraft: (context) => ({
id: `event-${crypto.randomUUID()}`,
resourceId: context.resourceId,
title: 'New booking',
startDateTime: context.startDateTime,
endDateTime: context.endDateTime,
status: 'draft',
}),
validateMutation: (detail) => {
if (detail.event?.locked) {
return 'Locked events cannot be changed.';
}
},
};

To enforce working hours, define them with calendars or eventSchedulerAvailability; then block the outside-availability conflict as shown above. nonWorkingTime can make closed cells visible, but it does not by itself reject create, move, resize, or editor changes.

Conflicts are evaluated against the projected next event array before the local mutation is committed. This means move, resize, create, edit, delete, bulk, template, paste, and recurrence actions can all be stopped by the same conflict configuration. Use conflicts.policy: 'prevent' when every detected conflict should block by default, or keep policy: 'mark' and promote only selected rules such as overlap, outside-availability, or blocked-time to error.

The before-change detail includes the projected conflicts array. Use it when the UI needs custom messages or business-specific decisions on top of configured conflict rules.

grid.addEventListener('event-scheduler-before-event-change', (event) => {
const blockingConflict = event.detail.conflicts?.find((conflict) =>
conflict.severity === 'error' &&
event.detail.eventId !== null &&
conflict.eventIds.includes(event.detail.eventId)
);
if (blockingConflict) {
event.preventDefault();
}
});

For one-off business rules that are not calendar rules, use permissions.canCreate, permissions.canMutate, or validateMutation. These hooks are useful for role checks, approval states, custom capacity rules, or backend-specific constraints.

If you prefer event-based validation, listen for event-scheduler-before-event-change and call event.preventDefault(). This is the same cancelable-before-event pattern used by RevoGrid editing events. Do not use event-scheduler-event-changed for cancellation; it fires after the scheduler has already accepted and applied the change.

grid.addEventListener('event-scheduler-before-event-change', (event) => {
if (event.detail.action === 'move' && event.detail.event?.locked) {
event.preventDefault();
}
});

Persist successful changes from the emitted next event array.

function persistSchedulerEvents(event) {
grid.eventSchedulerEvents = event.detail.events;
void saveEvents(event.detail.events);
}
grid.addEventListener('event-scheduler-event-created', persistSchedulerEvents);
grid.addEventListener('event-scheduler-event-changed', persistSchedulerEvents);
grid.addEventListener('event-scheduler-event-deleted', persistSchedulerEvents);

validateMutation is the config-level equivalent of the cancelable before-change event. It receives the same detail shape and can return false or a string to reject.

grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
validateMutation: ({ action, event, changes }) => {
if (action === 'delete' && event?.status === 'completed') {
return 'Completed events cannot be deleted.';
}
if (changes?.resourceId === 'locked-room') {
return 'That room is locked.';
}
},
};

Set eventEditor: false to disable the built-in editor, or provide a custom editor hook when the host app owns a drawer, modal, or approval flow:

grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
eventEditor: (context) => {
openProductDrawer({
mode: context.mode,
event: context.event,
resources: context.resources,
readOnly: context.readonly,
validationMessage: context.validationMessage,
onSave: context.submit,
onDelete: context.delete,
onClose: context.close,
});
return true;
},
};

The editor helpers still route changes through permissions, conflicts, event-scheduler-before-event-change, local mutation, post-commit events, and remote mutation hooks.

Use named before hooks to validate or cancel user actions before local scheduler state changes. Return false to cancel with the default message, or return a string to cancel with a product-specific validation message.

| Before hook | Action | | --- | --- | | beforeEventCreate | Create from slot, editor create, template create, recurrence create | | beforeEventUpdate | Editor/API updates and status changes | | beforeEventMove | Drag move and bulk move | | beforeEventResize | Drag resize | | beforeEventDelete | Delete and bulk delete | | beforeEventDuplicate | Duplicate selected events | | beforeEventLock | Lock or unlock changes | | beforeSlotSelect | Empty-slot selection/click before create behavior |

Final callbacks fire after the scheduler has committed local state and include enough context to update app state or persist to a backend.

| Final callback | Action | | --- | --- | | onEventCreate | Event created, including paste/duplicate/template-created events | | onEventUpdate | Event updated or status changed | | onEventMove | Event moved | | onEventResize | Event resized | | onEventDelete | Event deleted | | onEventDuplicate | Duplicated event created | | onEventLock | Event locked or unlocked | | onEventCopy | Event copied into the scheduler clipboard | | onEventPaste | Pasted event created | | onSelectionChange | Event selection changed | | onSlotSelect | Empty slot selected/clicked | | contextMenu.onContextMenuAction | Scheduler context-menu item selected |

grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
beforeEventMove: ({ event, changes }) => {
if (event?.locked) return 'Locked events cannot be moved.';
return validateMoveOnServerCache(event, changes);
},
beforeEventResize: ({ event, changes }) =>
isOutsidePolicyWindow(event, changes) ? 'Outside policy window.' : undefined,
beforeEventDelete: ({ event }) =>
event?.metadata?.protected ? 'Protected events cannot be deleted.' : undefined,
beforeSlotSelect: ({ date, startDateTime }) =>
isClosedForBooking(date, startDateTime) ? 'This slot is closed.' : undefined,
onEventMove: ({ event, previousEvent }) =>
saveEventMove({ event, previousEvent }),
onEventResize: ({ event, previousEvent }) =>
saveEventResize({ event, previousEvent }),
onEventDelete: ({ event }) =>
archiveDeletedEvent(event),
onSelectionChange: ({ eventIds }) =>
setSelectedPlannerEvents(eventIds),
contextMenu: {
onContextMenuAction: ({ itemId, target, segment, slot }) =>
trackSchedulerCommand({ itemId, target, eventId: segment?.event.id, slot }),
},
};

The lower-level event-scheduler-before-event-change, event-scheduler-before-event-select, event-scheduler-before-slot-select, event-scheduler-event-created, event-scheduler-event-changed, event-scheduler-event-deleted, and event-scheduler-event-selected DOM events remain available for integrations that prefer event listeners. Call preventDefault() on the before events to cancel the pending scheduler action. validateMutation still runs in the same mutation pipeline for shared validation logic.

Scheduler selection supports selected events, selected slots, selected time ranges, multi-select, range select, copy, paste, duplicate, and keyboard-driven commands. Use the cancelable event-scheduler-before-event-select event when the host application owns selected event state: prevent the pending selection, update application state, then pass the accepted ids back through selection.selectedEventIds.

let selectedEventIds: readonly string[] = [];
grid.addEventListener('event-scheduler-before-event-select', (event) => {
event.preventDefault();
selectedEventIds = event.detail.eventIds;
renderScheduler();
});
function renderScheduler() {
grid.eventScheduler = {
...grid.eventScheduler,
selectionMode: 'multiple',
selection: {
...grid.eventScheduler?.selection,
selectedEventIds,
selectedSlot,
selectedRange,
clipboard: true,
copyOffsetDays: 7,
onChange: ({ eventIds }) => setSelectedEventIds(eventIds),
},
onSelectionChange: ({ eventIds, events }) =>
syncInspectorSelection({ eventIds, events }),
onSlotSelect: (slot) =>
setSelectedSlot(slot),
};
}

Accepted selections still emit event-scheduler-event-selected, onSelectionChange, and selection.onChange. A prevented selection does not emit final selection callbacks; the host re-applies its accepted state through the config.

grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
beforeSlotSelect: ({ date }) =>
isClosedForBooking(date) ? 'This slot is closed.' : undefined,
};
grid.addEventListener('event-scheduler-before-slot-select', (event) => {
if (isReadOnlyResource(event.detail.resourceId)) {
event.preventDefault();
}
});

Use clipboardAdapter when copy/paste must integrate with product state, permissions, or a custom clipboard store. Paste remains disabled when there is no internal payload and clipboardAdapter.hasPayload() is false.

grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
clipboardAdapter: {
hasPayload: () => appClipboard.has('scheduler-events'),
read: () => appClipboard.get('scheduler-events'),
write: (payload) => appClipboard.set('scheduler-events', payload),
clear: () => appClipboard.delete('scheduler-events'),
},
onCopy: ({ eventIds }) => trackCopy(eventIds),
onPaste: ({ event }) => persistCreatedEvent(event),
onDuplicate: ({ event }) => persistCreatedEvent(event),
};

Keyboard shortcuts can be disabled globally, disabled per command, or rebound. Use Mod for Cmd on macOS and Ctrl on Windows/Linux.

grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
keyboardShortcuts: {
enabled: true,
helpPanel: true,
shortcuts: {
copy: 'Mod+c',
paste: 'Mod+v',
duplicate: 'Mod+Shift+d',
delete: false,
lock: false,
today: ['t', 'Home'],
},
},
};

Available keyboard commands are escape, help, today, search, previous, next, cyclePrevious, cycleNext, open, create, edit, lock, delete, copy, paste, duplicate, and viewWeek.