Drag, Resize, And Progress Editing
Source code
---
import GanttScheduling from '@revolist/revogrid-examples/components/gantt/GanttScheduling.vue';
---
<GanttScheduling client:only="vue" /> // src/components/gantt/GanttScheduling.ts
import { defineCustomElements } from '@revolist/revogrid/loader';
defineCustomElements();
import { GanttPlugin, createDefaultTaskTableColumn } from '@revolist/revogrid-enterprise';
import type { GanttTaskSourceRow, DependencyEntity, CalendarEntity } from '@revolist/revogrid-enterprise';
import { currentTheme } from '../composables/useRandomData';
const { isDark } = currentTheme();
const PROJECT_ID = 'project-web-redesign';
const CALENDAR_ID = 'cal-us';
// Calendar-aware scheduling: durations are in working days (Mon–Fri, excluding holidays)
const ganttConfig = {
id: PROJECT_ID,
name: 'Website Redesign',
version: '1',
currency: 'USD',
timeZone: 'America/New_York',
primaryCalendarId: CALENDAR_ID,
updatedAt: '2026-04-06T00:00:00Z',
zoomPreset: 'week' as const,
scheduling: {
excludeHolidaysFromDuration: true, // durations skip weekends and holidays
},
visuals: {
shadeNonWorkingTime: true, // shade weekend columns on the timeline
projectLineDate: '2026-04-06', // vertical status-date line
},
};
// US calendar with public holidays
const calendars: CalendarEntity[] = [
{
id: CALENDAR_ID,
name: 'US Standard',
timeZone: 'America/New_York',
workingDays: [1, 2, 3, 4, 5], // Mon–Fri
holidays: [
'2026-05-25', // Memorial Day
'2026-07-04', // Independence Day
],
hoursPerDay: 8,
},
];
const tasks: GanttTaskSourceRow[] = [
{
id: 't1', parentId: null,
name: 'Design', type: 'summary', status: 'in-progress',
startDate: '2026-04-06', endDate: '2026-04-24', duration: 15,
percentDone: 60, calendarId: CALENDAR_ID, tags: [],
},
{
id: 't2', parentId: 't1',
name: 'Wireframes', type: 'task', status: 'done',
startDate: '2026-04-06', endDate: '2026-04-10', duration: 5,
percentDone: 100, calendarId: CALENDAR_ID, tags: [],
},
{
id: 't3', parentId: 't1',
name: 'Design Review', type: 'milestone', status: 'done',
startDate: '2026-04-10', endDate: '2026-04-10', duration: 0,
percentDone: 100, calendarId: CALENDAR_ID, tags: ['milestone'],
},
{
id: 't4', parentId: 't1',
name: 'Visual Design', type: 'task', status: 'in-progress',
startDate: '2026-04-13', endDate: '2026-04-24', duration: 10,
percentDone: 40, calendarId: CALENDAR_ID, tags: [],
},
{
id: 't5', parentId: null,
name: 'Development', type: 'summary', status: 'not-started',
startDate: '2026-04-27', endDate: '2026-05-28', duration: 24,
percentDone: 0, calendarId: CALENDAR_ID, tags: [],
},
{
id: 't6', parentId: 't5',
name: 'Frontend', type: 'task', status: 'not-started',
// constraint: cannot start before May 4 (waiting on external API)
startDate: '2026-05-04', endDate: '2026-05-20', duration: 13,
percentDone: 0, calendarId: CALENDAR_ID, tags: [],
constraintType: 'start-no-earlier-than',
constraintDate: '2026-05-04',
},
{
id: 't7', parentId: 't5',
name: 'Backend', type: 'task', status: 'not-started',
startDate: '2026-04-27', endDate: '2026-05-28', duration: 24,
percentDone: 0, calendarId: CALENDAR_ID, tags: [],
deadlineDate: '2026-05-22', // deadline marker — project expects early delivery
},
{
id: 't8', parentId: null,
name: 'Launch', type: 'milestone', status: 'not-started',
startDate: '2026-05-28', endDate: '2026-05-28', duration: 0,
percentDone: 0, calendarId: CALENDAR_ID, tags: ['milestone'],
},
];
const dependencies: DependencyEntity[] = [
{ id: 'd1', predecessorTaskId: 't2', successorTaskId: 't3', type: 'finish-to-start', lagDays: 0 },
{ id: 'd2', predecessorTaskId: 't3', successorTaskId: 't4', type: 'finish-to-start', lagDays: 1 },
{ id: 'd3', predecessorTaskId: 't4', successorTaskId: 't6', type: 'finish-to-start', lagDays: 1 },
{ id: 'd4', predecessorTaskId: 't4', successorTaskId: 't7', type: 'finish-to-start', lagDays: 1 },
{ id: 'd5', predecessorTaskId: 't6', successorTaskId: 't8', type: 'finish-to-start', lagDays: 0 },
{ id: 'd6', predecessorTaskId: 't7', successorTaskId: 't8', type: 'finish-to-start', lagDays: 0 },
];
export function load(parentSelector: string) {
const grid = document.createElement('revo-grid');
grid.theme = isDark() ? 'darkCompact' : 'compact';
grid.hideAttribution = true;
grid.plugins = [GanttPlugin];
grid.gantt = ganttConfig;
grid.ganttCalendars = calendars;
grid.ganttDependencies = dependencies;
grid.source = tasks;
grid.columns = [
createDefaultTaskTableColumn('wbs'),
createDefaultTaskTableColumn('name'),
];
document.querySelector(parentSelector)?.appendChild(grid);
}
<template>
<RevoGrid
hide-attribution
style="height: 500px"
:theme="isDark ? 'darkCompact' : 'compact'"
:plugins="plugins"
:source="tasks"
:columns="columns"
:gantt.prop="ganttConfig"
:gantt-dependencies.prop="dependencies"
:gantt-calendars.prop="calendars"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import RevoGrid from '@revolist/vue3-datagrid';
import { GanttPlugin, createDefaultTaskTableColumn } from '@revolist/revogrid-enterprise';
import type { GanttTaskSourceRow, DependencyEntity, CalendarEntity } from '@revolist/revogrid-enterprise';
import { currentThemeVue } from '../composables/useRandomData';
const { isDark } = currentThemeVue();
const PROJECT_ID = 'project-web-redesign';
const CALENDAR_ID = 'cal-us';
const plugins = ref([GanttPlugin]);
const ganttConfig = ref({
id: PROJECT_ID,
name: 'Website Redesign',
version: '1',
currency: 'USD',
timeZone: 'America/New_York',
primaryCalendarId: CALENDAR_ID,
updatedAt: '2026-04-06T00:00:00Z',
zoomPreset: 'week' as const,
scheduling: {
excludeHolidaysFromDuration: true,
},
visuals: {
shadeNonWorkingTime: true,
projectLineDate: '2026-04-06',
},
});
const calendars = ref<CalendarEntity[]>([
{
id: CALENDAR_ID,
name: 'US Standard',
timeZone: 'America/New_York',
workingDays: [1, 2, 3, 4, 5],
holidays: ['2026-05-25', '2026-07-04'],
hoursPerDay: 8,
},
]);
const tasks = ref<GanttTaskSourceRow[]>([
{
id: 't1', parentId: null,
name: 'Design', type: 'summary', status: 'in-progress',
startDate: '2026-04-06', endDate: '2026-04-24', duration: 15,
percentDone: 60, calendarId: CALENDAR_ID, tags: [],
},
{
id: 't2', parentId: 't1',
name: 'Wireframes', type: 'task', status: 'done',
startDate: '2026-04-06', endDate: '2026-04-10', duration: 5,
percentDone: 100, calendarId: CALENDAR_ID, tags: [],
},
{
id: 't3', parentId: 't1',
name: 'Design Review', type: 'milestone', status: 'done',
startDate: '2026-04-10', endDate: '2026-04-10', duration: 0,
percentDone: 100, calendarId: CALENDAR_ID, tags: ['milestone'],
},
{
id: 't4', parentId: 't1',
name: 'Visual Design', type: 'task', status: 'in-progress',
startDate: '2026-04-13', endDate: '2026-04-24', duration: 10,
percentDone: 40, calendarId: CALENDAR_ID, tags: [],
},
{
id: 't5', parentId: null,
name: 'Development', type: 'summary', status: 'not-started',
startDate: '2026-04-27', endDate: '2026-05-28', duration: 24,
percentDone: 0, calendarId: CALENDAR_ID, tags: [],
},
{
id: 't6', parentId: 't5',
name: 'Frontend', type: 'task', status: 'not-started',
startDate: '2026-05-04', endDate: '2026-05-20', duration: 13,
percentDone: 0, calendarId: CALENDAR_ID, tags: [],
constraintType: 'start-no-earlier-than',
constraintDate: '2026-05-04',
},
{
id: 't7', parentId: 't5',
name: 'Backend', type: 'task', status: 'not-started',
startDate: '2026-04-27', endDate: '2026-05-28', duration: 24,
percentDone: 0, calendarId: CALENDAR_ID, tags: [],
deadlineDate: '2026-05-22',
},
{
id: 't8', parentId: null,
name: 'Launch', type: 'milestone', status: 'not-started',
startDate: '2026-05-28', endDate: '2026-05-28', duration: 0,
percentDone: 0, calendarId: CALENDAR_ID, tags: ['milestone'],
},
]);
const dependencies = ref<DependencyEntity[]>([
{ id: 'd1', predecessorTaskId: 't2', successorTaskId: 't3', type: 'finish-to-start', lagDays: 0 },
{ id: 'd2', predecessorTaskId: 't3', successorTaskId: 't4', type: 'finish-to-start', lagDays: 1 },
{ id: 'd3', predecessorTaskId: 't4', successorTaskId: 't6', type: 'finish-to-start', lagDays: 1 },
{ id: 'd4', predecessorTaskId: 't4', successorTaskId: 't7', type: 'finish-to-start', lagDays: 1 },
{ id: 'd5', predecessorTaskId: 't6', successorTaskId: 't8', type: 'finish-to-start', lagDays: 0 },
{ id: 'd6', predecessorTaskId: 't7', successorTaskId: 't8', type: 'finish-to-start', lagDays: 0 },
]);
const columns = ref([
createDefaultTaskTableColumn('wbs'),
createDefaultTaskTableColumn('name'),
]);
</script>
import React, { useMemo } from 'react';
import { RevoGrid } from '@revolist/react-datagrid';
import { GanttPlugin, createDefaultTaskTableColumn } from '@revolist/revogrid-enterprise';
import type { GanttTaskSourceRow, DependencyEntity, CalendarEntity } from '@revolist/revogrid-enterprise';
import { currentTheme } from '../composables/useRandomData';
const { isDark } = currentTheme();
const PROJECT_ID = 'project-web-redesign';
const CALENDAR_ID = 'cal-us';
const tasks: GanttTaskSourceRow[] = [
{
id: 't1', parentId: null,
name: 'Design', type: 'summary', status: 'in-progress',
startDate: '2026-04-06', endDate: '2026-04-24', duration: 15,
percentDone: 60, calendarId: CALENDAR_ID, tags: [],
},
{
id: 't2', parentId: 't1',
name: 'Wireframes', type: 'task', status: 'done',
startDate: '2026-04-06', endDate: '2026-04-10', duration: 5,
percentDone: 100, calendarId: CALENDAR_ID, tags: [],
},
{
id: 't3', parentId: 't1',
name: 'Design Review', type: 'milestone', status: 'done',
startDate: '2026-04-10', endDate: '2026-04-10', duration: 0,
percentDone: 100, calendarId: CALENDAR_ID, tags: ['milestone'],
},
{
id: 't4', parentId: 't1',
name: 'Visual Design', type: 'task', status: 'in-progress',
startDate: '2026-04-13', endDate: '2026-04-24', duration: 10,
percentDone: 40, calendarId: CALENDAR_ID, tags: [],
},
{
id: 't5', parentId: null,
name: 'Development', type: 'summary', status: 'not-started',
startDate: '2026-04-27', endDate: '2026-05-28', duration: 24,
percentDone: 0, calendarId: CALENDAR_ID, tags: [],
},
{
id: 't6', parentId: 't5',
name: 'Frontend', type: 'task', status: 'not-started',
startDate: '2026-05-04', endDate: '2026-05-20', duration: 13,
percentDone: 0, calendarId: CALENDAR_ID, tags: [],
constraintType: 'start-no-earlier-than',
constraintDate: '2026-05-04',
},
{
id: 't7', parentId: 't5',
name: 'Backend', type: 'task', status: 'not-started',
startDate: '2026-04-27', endDate: '2026-05-28', duration: 24,
percentDone: 0, calendarId: CALENDAR_ID, tags: [],
deadlineDate: '2026-05-22',
},
{
id: 't8', parentId: null,
name: 'Launch', type: 'milestone', status: 'not-started',
startDate: '2026-05-28', endDate: '2026-05-28', duration: 0,
percentDone: 0, calendarId: CALENDAR_ID, tags: ['milestone'],
},
];
const dependencies: DependencyEntity[] = [
{ id: 'd1', predecessorTaskId: 't2', successorTaskId: 't3', type: 'finish-to-start', lagDays: 0 },
{ id: 'd2', predecessorTaskId: 't3', successorTaskId: 't4', type: 'finish-to-start', lagDays: 1 },
{ id: 'd3', predecessorTaskId: 't4', successorTaskId: 't6', type: 'finish-to-start', lagDays: 1 },
{ id: 'd4', predecessorTaskId: 't4', successorTaskId: 't7', type: 'finish-to-start', lagDays: 1 },
{ id: 'd5', predecessorTaskId: 't6', successorTaskId: 't8', type: 'finish-to-start', lagDays: 0 },
{ id: 'd6', predecessorTaskId: 't7', successorTaskId: 't8', type: 'finish-to-start', lagDays: 0 },
];
const calendars: CalendarEntity[] = [
{
id: CALENDAR_ID,
name: 'US Standard',
timeZone: 'America/New_York',
workingDays: [1, 2, 3, 4, 5],
holidays: ['2026-05-25', '2026-07-04'],
hoursPerDay: 8,
},
];
function GanttScheduling() {
const ganttConfig = useMemo(() => ({
id: PROJECT_ID,
name: 'Website Redesign',
version: '1',
currency: 'USD',
timeZone: 'America/New_York',
primaryCalendarId: CALENDAR_ID,
updatedAt: '2026-04-06T00:00:00Z',
zoomPreset: 'week' as const,
scheduling: {
excludeHolidaysFromDuration: true,
},
visuals: {
shadeNonWorkingTime: true,
projectLineDate: '2026-04-06',
},
}), []);
const columns = useMemo(() => [
createDefaultTaskTableColumn('wbs'),
createDefaultTaskTableColumn('name'),
], []);
return (
<RevoGrid
style={{ height: '500px' }}
theme={isDark() ? 'darkCompact' : 'compact'}
hideAttribution
plugins={[GanttPlugin]}
source={tasks}
columns={columns}
gantt={ganttConfig}
ganttDependencies={dependencies}
ganttCalendars={calendars}
/>
);
}
export default GanttScheduling; import { Component, NO_ERRORS_SCHEMA, ViewEncapsulation } from '@angular/core';
import { RevoGrid } from '@revolist/angular-datagrid';
import { GanttPlugin, createDefaultTaskTableColumn } from '@revolist/revogrid-enterprise';
import type { GanttTaskSourceRow, DependencyEntity, CalendarEntity } from '@revolist/revogrid-enterprise';
import { currentTheme } from '../composables/useRandomData';
const PROJECT_ID = 'project-web-redesign';
const CALENDAR_ID = 'cal-us';
@Component({
selector: 'gantt-scheduling-grid',
standalone: true,
imports: [RevoGrid],
// Allows Angular demos to bind RevoGrid plugin props that are not wrapper inputs.
schemas: [NO_ERRORS_SCHEMA],
template: `
<revo-grid
style="min-height: 500px"
[theme]="theme"
[hideAttribution]="true"
[plugins]="plugins"
[source]="tasks"
[columns]="columns"
[gantt]="ganttConfig"
[ganttDependencies]="dependencies"
[ganttCalendars]="calendars"
></revo-grid>
`,
encapsulation: ViewEncapsulation.None,
})
export class GanttSchedulingGridComponent {
theme = currentTheme().isDark() ? 'darkCompact' : 'compact';
plugins = [GanttPlugin];
ganttConfig = {
id: PROJECT_ID,
name: 'Website Redesign',
version: '1',
currency: 'USD',
timeZone: 'America/New_York',
primaryCalendarId: CALENDAR_ID,
updatedAt: '2026-04-06T00:00:00Z',
zoomPreset: 'week' as const,
scheduling: {
excludeHolidaysFromDuration: true,
},
visuals: {
shadeNonWorkingTime: true,
projectLineDate: '2026-04-06',
},
};
calendars: CalendarEntity[] = [
{
id: CALENDAR_ID,
name: 'US Standard',
timeZone: 'America/New_York',
workingDays: [1, 2, 3, 4, 5],
holidays: ['2026-05-25', '2026-07-04'],
hoursPerDay: 8,
},
];
tasks: GanttTaskSourceRow[] = [
{
id: 't1', parentId: null,
name: 'Design', type: 'summary', status: 'in-progress',
startDate: '2026-04-06', endDate: '2026-04-24', duration: 15,
percentDone: 60, calendarId: CALENDAR_ID, tags: [],
},
{
id: 't2', parentId: 't1',
name: 'Wireframes', type: 'task', status: 'done',
startDate: '2026-04-06', endDate: '2026-04-10', duration: 5,
percentDone: 100, calendarId: CALENDAR_ID, tags: [],
},
{
id: 't3', parentId: 't1',
name: 'Design Review', type: 'milestone', status: 'done',
startDate: '2026-04-10', endDate: '2026-04-10', duration: 0,
percentDone: 100, calendarId: CALENDAR_ID, tags: ['milestone'],
},
{
id: 't4', parentId: 't1',
name: 'Visual Design', type: 'task', status: 'in-progress',
startDate: '2026-04-13', endDate: '2026-04-24', duration: 10,
percentDone: 40, calendarId: CALENDAR_ID, tags: [],
},
{
id: 't5', parentId: null,
name: 'Development', type: 'summary', status: 'not-started',
startDate: '2026-04-27', endDate: '2026-05-28', duration: 24,
percentDone: 0, calendarId: CALENDAR_ID, tags: [],
},
{
id: 't6', parentId: 't5',
name: 'Frontend', type: 'task', status: 'not-started',
startDate: '2026-05-04', endDate: '2026-05-20', duration: 13,
percentDone: 0, calendarId: CALENDAR_ID, tags: [],
constraintType: 'start-no-earlier-than',
constraintDate: '2026-05-04',
},
{
id: 't7', parentId: 't5',
name: 'Backend', type: 'task', status: 'not-started',
startDate: '2026-04-27', endDate: '2026-05-28', duration: 24,
percentDone: 0, calendarId: CALENDAR_ID, tags: [],
deadlineDate: '2026-05-22',
},
{
id: 't8', parentId: null,
name: 'Launch', type: 'milestone', status: 'not-started',
startDate: '2026-05-28', endDate: '2026-05-28', duration: 0,
percentDone: 0, calendarId: CALENDAR_ID, tags: ['milestone'],
},
];
dependencies: DependencyEntity[] = [
{ id: 'd1', predecessorTaskId: 't2', successorTaskId: 't3', type: 'finish-to-start', lagDays: 0 },
{ id: 'd2', predecessorTaskId: 't3', successorTaskId: 't4', type: 'finish-to-start', lagDays: 1 },
{ id: 'd3', predecessorTaskId: 't4', successorTaskId: 't6', type: 'finish-to-start', lagDays: 1 },
{ id: 'd4', predecessorTaskId: 't4', successorTaskId: 't7', type: 'finish-to-start', lagDays: 1 },
{ id: 'd5', predecessorTaskId: 't6', successorTaskId: 't8', type: 'finish-to-start', lagDays: 0 },
{ id: 'd6', predecessorTaskId: 't7', successorTaskId: 't8', type: 'finish-to-start', lagDays: 0 },
];
columns = [
createDefaultTaskTableColumn('wbs'),
createDefaultTaskTableColumn('name'),
];
} Implemented interactions:
- Drag task bars to move.
- Drag start/end handles to resize.
- Drag progress handle to update completion.
Each interaction has preview state and commit state and can trigger warning/diagnostic updates.
Mobile And Touch Devices
Section titled “Mobile And Touch Devices”Gantt supports phones, tablets, and hybrid devices through adaptive pointer handling. Desktop users can drag task bars and handles directly. Touch users get a safer select-then-edit flow so scrolling a dense timeline does not accidentally change the schedule.
On coarse-pointer devices:
- Tap a task bar to select it.
- Selection reveals larger resize, dependency, progress, and split handles.
- Drag the selected task bar to move the task.
- Drag a visible start/end handle to resize.
- Drag the visible progress handle to update completion.
- Start swiping from an unselected task bar when you want to pan the task table or timeline; hidden handles are not hit-testable until selection.
The mobile path keeps native panning available for unselected task bars and disables browser touch panning only on selected/active bars or visible edit handles. This preserves scroll-first behavior while still allowing precise edits after the user has selected the task.
The packaged task editor dialog also adapts to phone-width screens with a full-viewport, single-column layout and larger touch targets.
Implementation:
features/tasks/controllers/task-drag-controller.tsfeatures/tasks/controllers/task-resize-controller.tsfeatures/tasks/controllers/task-progress-controller.tsfeatures/tasks/controllers/pointer-gesture.tsfeatures/tasks/timeline-preview-layer.ts