Drag, Resize, And Progress Editing
Source code
---import '@revolist/gantt/styles.css';import GanttScheduling from '@revolist/revogrid-examples/components/gantt/GanttScheduling.vue';---
<GanttScheduling client:only="vue" />import { defineCustomElements } from '@revolist/revogrid/loader';defineCustomElements();
import { GanttPlugin, createDefaultTaskTableColumn } from '@revolist/gantt';import type { GanttTaskSourceRow, DependencyEntity } from '@revolist/gantt';type CalendarEntity = NonNullable< import('@revolist/gantt').GanttPluginConfig['calendars']>[number];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', statusDate: '2026-04-06', zoomPreset: 'day-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 holidaysconst 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', workflowStatus: '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', workflowStatus: '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', workflowStatus: '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', workflowStatus: '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', workflowStatus: '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', workflowStatus: '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', workflowStatus: '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', workflowStatus: '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/gantt';import type { GanttTaskSourceRow, DependencyEntity } from '@revolist/gantt';type CalendarEntity = NonNullable< import('@revolist/gantt').GanttPluginConfig['calendars']>[number];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', statusDate: '2026-04-06', zoomPreset: 'day-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', workflowStatus: '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', workflowStatus: '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', workflowStatus: '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', workflowStatus: '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', workflowStatus: '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', workflowStatus: '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', workflowStatus: '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', workflowStatus: '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/gantt';import type { GanttTaskSourceRow, DependencyEntity } from '@revolist/gantt';type CalendarEntity = NonNullable< import('@revolist/gantt').GanttPluginConfig['calendars']>[number];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', workflowStatus: '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', workflowStatus: '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', workflowStatus: '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', workflowStatus: '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', workflowStatus: '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', workflowStatus: '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', workflowStatus: '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', workflowStatus: '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', statusDate: '2026-04-06', zoomPreset: 'day-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/gantt';import type { GanttTaskSourceRow, DependencyEntity } from '@revolist/gantt';type CalendarEntity = NonNullable< import('@revolist/gantt').GanttPluginConfig['calendars']>[number];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', statusDate: '2026-04-06', zoomPreset: 'day-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', workflowStatus: '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', workflowStatus: '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', workflowStatus: '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', workflowStatus: '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', workflowStatus: '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', workflowStatus: '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', workflowStatus: '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', workflowStatus: '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.
During a move or resize, a live readout shows the proposed start, finish, duration, and snapping unit. Datetime tasks include times by default. The proposed values use the same snapping policy as the released edit; the moving bar follows the pointer. Hold the pointer near either timeline edge to scroll while editing.
Press Escape before releasing to cancel the gesture. The task and history remain unchanged, and releasing afterward does not commit the canceled edit.
At compressed scales, the readout recommends zooming in for finer control. Use the toolbar zoom controls before making precise edits: equal pixel movements represent different date ranges at different zoom levels.
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