Interaction
Drag & Drop
Events can be moved and resized by dragging. Move an event to a different time or to a different resource. Resize from the left or right edge to change duration.
// Intercept event move (e.g. to validate or snap)
scheduler.events.eventMoving$.subscribe(args => {
// args: { event, newStartTime, newEndTime, newResource, cancel, adjustedStartTime, adjustedEndTime }
// Cancel the move
args.cancel = true;
// Or adjust the target time (snap to hour)
const start = new Date(args.newStartTime);
start.setMinutes(0, 0, 0);
args.adjustedStartTime = start;
const end = new Date(args.newEndTime);
end.setMinutes(0, 0, 0);
args.adjustedEndTime = end;
});
// Intercept event resize
scheduler.events.eventResizing$.subscribe(args => {
// args: { event, edge, newStartTime, newEndTime, cancel, adjustedStartTime, adjustedEndTime }
// Prevent resizing shorter than 1 hour
const duration = args.newEndTime.getTime() - args.newStartTime.getTime();
if (duration < 3600000) {
args.cancel = true;
}
});Selection
Click an event to select it. Hold Ctrl/Cmd to multi-select. Resources can be selected in the sheet panel.
// Get selected events
const selected = scheduler.selectedEvents;
const single = scheduler.selectedEvent; // last selected or null
// Set selection programmatically
const event = scheduler.data.getEventById('e1');
scheduler.selectedEvent = event;
scheduler.selectedEvents = [event1, event2];
// Clear selection
scheduler.selectedEvent = null;
// Get selected resources (from sheet)
const resources = scheduler.selectedResources;
// Listen for selection changes
scheduler.events.eventSelected$.subscribe(args => {
// args: { event } or null
console.log('Selected:', args?.event.name);
});
// Click on event bar
scheduler.events.eventClick$.subscribe(({ event }) => {
showEventDetails(event);
});Double-click & Delete
Handle double-click to open an edit dialog, and intercept deletions to show a confirmation prompt.
// Open edit dialog on double-click
scheduler.events.eventDblClick$.subscribe(({ event }) => {
openEventDialog(event);
});
// Confirm before deleting (triggered by Delete key)
scheduler.events.eventDeleting$.subscribe((args) => {
const confirmed = window.confirm(
'Delete "' + args.event.name + '"?'
);
if (!confirmed) {
args.cancel = true;
}
});Creating Events
Drag on an empty area of the chart to create a new event. The event is added to the resource row where you started dragging.
// Intercept event creation (e.g. to validate or customize)
scheduler.events.eventCreating$.subscribe(args => {
// args: { resource, startTime, endTime, cancel, adjustedStartTime, adjustedEndTime }
// Prevent creation on certain resources
if (args.resource.type === 'equipment') {
args.cancel = true;
return;
}
// Snap to hour boundaries
const start = new Date(args.startTime);
start.setMinutes(0, 0, 0);
args.adjustedStartTime = start;
const end = new Date(args.endTime);
end.setMinutes(0, 0, 0);
args.adjustedEndTime = end;
});
// Listen for new events
scheduler.events.eventAdded$.subscribe(({ event }) => {
console.log('New event:', event.name, 'on', event.resourceId);
});Keyboard Navigation
Built-in keyboard shortcuts for common actions:
| Ctrl+Z | Undo |
| Ctrl+Y / Ctrl+Shift+Z | Redo |
| Ctrl+C | Copy selected events |
| Ctrl+X | Cut selected events |
| Ctrl+V | Paste events |
| Ctrl++ / Ctrl+= | Zoom in |
| Ctrl+- | Zoom out |
| Delete | Delete selected events (triggers eventDeleting$) |
Filtering
Filter resources to show only matching rows. Hidden resources and their events are excluded from the view.
// Filter by resource type
scheduler.filter = (resource) => resource.type === 'person';
// Filter by name
scheduler.filter = (resource) => resource.name.toLowerCase().includes('alice');
// Filter by custom property
scheduler.filter = (resource) => resource.getPropertyValue('department') === 'Engineering';
// Clear filter (show all)
scheduler.clearFilter();Frozen Resources
Pin resources to the top of the view. Frozen resources stay visible while scrolling.
const resource = scheduler.data.getResourceById('r1');
// Freeze (pin to top)
scheduler.freezeResource(resource);
// Check if frozen
scheduler.isResourceFrozen(resource); // true
// Unfreeze
scheduler.unfreezeResource(resource);Sorting Resources
Sort resources programmatically using any comparison function. The sort order is preserved in the view.
// Sort alphabetically by name
scheduler.sortResources((a, b) => a.name.localeCompare(b.name));
// Sort by resource type, then by name
scheduler.sortResources((a, b) => {
const typeOrder = { person: 0, room: 1, equipment: 2, vehicle: 3 };
const ta = typeOrder[a.type] ?? 99;
const tb = typeOrder[b.type] ?? 99;
if (ta !== tb) return ta - tb;
return a.name.localeCompare(b.name);
});
// Sort by number of events (busiest first)
scheduler.sortResources((a, b) => {
const aCount = scheduler.data.getEventsForResource(a.id).length;
const bCount = scheduler.data.getEventsForResource(b.id).length;
return bCount - aCount;
});Undo & Redo
Full undo/redo support for all data changes — event moves, resizes, additions, removals, and resource modifications.
scheduler.undo();
scheduler.redo();
// Observe availability (e.g. for toolbar button state)
scheduler.canUndo$.subscribe(canUndo => {
undoButton.disabled = !canUndo;
});
scheduler.canRedo$.subscribe(canRedo => {
redoButton.disabled = !canRedo;
});
// Listen for undo/redo
scheduler.events.undone$.subscribe(() => console.log('Undone'));
scheduler.events.redone$.subscribe(() => console.log('Redone'));Copy & Paste
Copy and paste events using the clipboard. Pasted events are added to the same resource.
// Copy selected events
await scheduler.copy();
// Cut (copy + remove)
await scheduler.cut();
// Paste
const result = await scheduler.paste();
// result: { events: SchedulerEvent[], eventIdMap: Map<string, SchedulerEvent> } or null
// Listen for paste
scheduler.events.eventsPasted$.subscribe(({ events, eventIdMap }) => {
console.log('Pasted', events.length, 'events');
});Synchronizing Changes
Use change events to synchronize the scheduler state with a backend API or database. Events fire for every user interaction — dragging, resizing, creating, deleting, and editing.
// Track event changes (moves, resizes, name edits)
scheduler.events.eventChanged$.subscribe(({ event }) => {
api.updateEvent(event.id, {
resourceId: event.resourceId,
name: event.name,
startTime: event.startTime,
endTime: event.endTime,
});
});
// Track new events (created by dragging on empty space)
scheduler.events.eventAdded$.subscribe(({ event }) => {
api.createEvent({
id: event.id,
resourceId: event.resourceId,
name: event.name,
startTime: event.startTime,
endTime: event.endTime,
});
});
// Track removed events
scheduler.events.eventRemoved$.subscribe(({ event }) => {
api.deleteEvent(event.id);
});
// Track resource changes
scheduler.events.resourceChanged$.subscribe(({ resource }) => {
api.updateResource(resource.id, { name: resource.name });
});
scheduler.events.resourceAdded$.subscribe(({ resource }) => {
api.createResource({ id: resource.id, name: resource.name, type: resource.type });
});
scheduler.events.resourceRemoved$.subscribe(({ resource }) => {
api.deleteResource(resource.id);
});For bulk operations (initial load, import), use load() and save() to transfer the full state as JSON instead of reacting to individual events.