TimelineKit

Interaction

Drag & Drop

When editing is enabled, users can interact with the chart using drag gestures:

  • Move tasks — drag a task bar to change its start/end dates.
  • Resize tasks — drag the left or right edge to change duration.
  • Set progress — drag the progress handle inside a task bar.
  • Create links — drag from a connector dot on one task to another.
  • Move milestones — drag a milestone diamond to a new date.
// Enable or disable editing
gantt.canEdit = true;  // default
gantt.canEdit = false; // read-only mode

Context Menu

Right-click events let you build custom context menus for tasks, the chart area, sheet rows, and column headers. Use mouseEvent.preventDefault()to suppress the browser's default context menu.

// Right-click on a task bar
gantt.events.taskContextMenu$.subscribe(({ task, mouseEvent }) => {
  mouseEvent.preventDefault();
  showContextMenu(mouseEvent.clientX, mouseEvent.clientY, [
    { label: 'Edit Task', action: () => openTaskDialog(task) },
    { label: 'Delete', action: () => gantt.list.removeTask(task) },
    { label: 'Set Color → Red', action: () => { task.color = 1; } },
  ]);
});

// Right-click on an empty area of the chart
gantt.events.chartContextMenu$.subscribe(({ date, task, area, mouseEvent }) => {
  mouseEvent.preventDefault();
  if (area === 'content' && !task) {
    showContextMenu(mouseEvent.clientX, mouseEvent.clientY, [
      { label: 'Add Marker', action: () => gantt.addMarker({ date, label: 'New' }) },
      { label: 'Scroll to Today', action: () => gantt.scrollToDate(new Date()) },
    ]);
  }
});

// Right-click on a sheet row
gantt.events.sheetRowContextMenu$.subscribe(({ item, mouseEvent }) => {
  mouseEvent.preventDefault();
  showContextMenu(mouseEvent.clientX, mouseEvent.clientY, [
    { label: 'Insert Above', action: () => {
      const idx = gantt.list.indexOf(item);
      gantt.list.insertEmptyTask(idx);
    }},
    { label: 'Indent', action: () => gantt.indent() },
    { label: 'Outdent', action: () => gantt.outdent() },
  ]);
});

// Right-click on a column header
gantt.events.sheetColumnHeaderContextMenu$.subscribe(({ column, mouseEvent }) => {
  mouseEvent.preventDefault();
  showContextMenu(mouseEvent.clientX, mouseEvent.clientY, [
    { label: 'Hide Column', action: () => gantt.sheet.hideColumn(column.code) },
  ]);
});

Selection & Editing

Users can select tasks by clicking rows in the sheet or task bars in the chart. Cell values can be edited inline.

// Get selected tasks
const selected = gantt.selectedTasks;

// Clear selection
gantt.sheet.clearSelection();

// Listen for editing events
gantt.events.sheetCellEditing$.subscribe(editing => {
  if (editing) {
    console.log('Editing', editing.item.name, editing.column.code);
  }
});

// Click on task bar
gantt.events.taskClick$.subscribe(({ task }) => {
  showTaskDetails(task);
});

// Double-click on task bar
gantt.events.taskDblClick$.subscribe(({ task }) => {
  openTaskDialog(task);
});

Tooltips

When users hover over a task bar in the chart, a tooltip automatically appears showing task details — name, start/end dates, duration, progress, and assigned resources. Tooltips are enabled by default and require no configuration.

Tooltip appearance (font, colors, border) can be customized via the theme. See the Tooltip Styling section.

Keyboard Navigation

Full keyboard support for accessible operation without a mouse:

Arrow keysNavigate between cells in the sheet.
EnterStart editing the focused cell. Confirm edit.
EscapeCancel editing.
Tab / Shift+TabMove to next / previous cell.
DeleteDelete selected tasks.
Ctrl+Z / Ctrl+YUndo / Redo. Ctrl+Shift+Z also redoes.
Ctrl++ / Ctrl+-Zoom in / Zoom out.
Ctrl+C / Ctrl+X / Ctrl+VCopy / Cut / Paste tasks.

Filtering

Filter tasks dynamically using a predicate function. Parent tasks of matching tasks remain visible automatically.

// Filter by a condition
gantt.filter = (task) => task.getPropertyValue('department') === 'Engineering';

// Filter by date range
gantt.filter = (task) =>
  task.startTime != null && task.startTime >= new Date('2027-02-01');

// Filter by assigned resource
gantt.filter = (task) => task.getResourceByResourceId('r1') != null;

// Clear filter
gantt.clearFilter();

Built-in Filters

TaskFilters provides ready-made predicates for common queries:

import { TaskFilters } from '@timelinekit/core';

// Single built-in filter
gantt.filter = TaskFilters.criticalPath;
gantt.filter = TaskFilters.incomplete;
gantt.filter = TaskFilters.complete;
gantt.filter = TaskFilters.behindSchedule;
gantt.filter = TaskFilters.milestones;

// Parameterized filters
gantt.filter = TaskFilters.byName('Review');
gantt.filter = TaskFilters.byResource(dev);
gantt.filter = TaskFilters.byDateRange(new Date('2027-01-01'), new Date('2027-03-31'));

// Combine multiple filters (AND logic)
gantt.filter = TaskFilters.combine(
  TaskFilters.incomplete,
  TaskFilters.byResource(dev),
);

Undo & Redo

All user actions (editing, drag & drop, adding/removing tasks) are recorded and can be undone/redone.

gantt.undo();
gantt.redo();

// Observe availability (e.g. to enable/disable toolbar buttons)
gantt.canUndo$.subscribe(canUndo => {
  undoButton.disabled = !canUndo;
});

gantt.canRedo$.subscribe(canRedo => {
  redoButton.disabled = !canRedo;
});

Copy & Paste

Copy, cut, and paste tasks while preserving hierarchy and internal links.

// Copy / cut selected tasks
await gantt.copy();
await gantt.cut();

// Paste (returns info about pasted tasks)
const result = await gantt.paste();
if (result) {
  console.log(result.tasks);       // pasted tasks
  console.log(result.links);       // pasted internal links
  console.log(result.droppedLinks); // links that couldn't be pasted
}

Synchronizing Changes

Use change events to synchronize the chart state with a backend API or database. Events fire for every user interaction — editing cells, dragging tasks, creating links, etc.

// Track task changes (edits, drag & drop, scheduling updates)
gantt.events.taskChanged$.subscribe(({ task }) => {
  api.updateTask(task.id, {
    name: task.name,
    startTime: task.startTime,
    endTime: task.endTime,
    progress: task.progress,
  });
});

// Track new tasks
gantt.events.taskAdded$.subscribe(({ task }) => {
  api.createTask({
    id: task.id,
    name: task.name,
    startTime: task.startTime,
    endTime: task.endTime,
  });
});

// Track removed tasks
gantt.events.taskRemoved$.subscribe(({ task }) => {
  api.deleteTask(task.id);
});

// Track dependency links
gantt.events.linkAdded$.subscribe(({ link }) => {
  api.createLink({
    id: link.id,
    fromId: link.from.id,
    toId: link.to.id,
    type: link.type,
    lag: link.lag,
  });
});

gantt.events.linkRemoved$.subscribe(({ link }) => {
  api.deleteLink(link.id);
});

For bulk operations (initial load, import), use load() and save() to transfer the full state as JSON instead of reacting to individual events.