gantt-editor
v2.11.0
Published
Highly flexible, performant, framework-agnostic Gantt chart editor component for building applications that require resource allocation or task scheduling.
Readme
Gantt Editor
Highly flexible, performant, framework-agnostic Gantt chart editor component for building applications that require resource allocation or task scheduling.

Quick Start By Framework
npm install gantt-editor<script setup lang="ts">
import { ref } from "vue";
import GanttEditor, {
type GanttEditorDestination,
type GanttEditorDestinationGroup,
type GanttEditorSlot,
} from "gantt-editor/vue";
const startTime = ref(new Date("2025-01-01T00:00:00Z"));
const endTime = ref(new Date("2025-01-02T00:00:00Z"));
const slots = ref<GanttEditorSlot[]>([
{
id: "LH123-20250101-F",
displayName: "LH123 | F",
group: "LH123",
openTime: new Date("2025-01-01T10:00:00Z"),
closeTime: new Date("2025-01-01T12:00:00Z"),
destinationId: "chute-1",
hoverData: "<strong>LH123</strong><br><em>Gate opens 10:00</em>",
deadlines: [
{ id: "std", timestamp: new Date("2025-01-01T13:00:00Z").getTime(), color: "#9e9e9e" },
{ id: "etd", timestamp: new Date("2025-01-01T13:25:00Z").getTime(), color: "#1f1f1f" },
],
color: "#3498db",
},
]);
const destinations = ref<GanttEditorDestination[]>([
{ id: "chute-1", displayName: "Chute 1", active: true, groupId: "allocated" },
{ id: "UNALLOCATED", displayName: "Unallocated", active: true, groupId: "unallocated" },
]);
const destinationGroups = ref<GanttEditorDestinationGroup[]>([
{ id: "allocated", displayName: "Allocated Chutes", heightPortion: 0.8 },
{ id: "unallocated", displayName: "Unallocated Chute", heightPortion: 0.2 },
]);
</script>
<template>
<div style="height: 100vh; width: 100%;">
<GanttEditor
:isReadOnly="false"
:startTime="startTime"
:endTime="endTime"
:slots="slots"
:destinations="destinations"
:destinationGroups="destinationGroups"
:markedRegion="null"
:suggestions="[]"
@onChangeDestinationId="(slotIds, destinationId) => console.log(slotIds, destinationId)"
@onMoveSlotOnTimeAxis="(slotIds, timeDiffMs) => console.log(slotIds, timeDiffMs)"
@onSelectionChange="(slotIds) => console.log(slotIds)"
/>
</div>
</template>import { useMemo, useState } from "react";
import {
GanttEditor,
type GanttEditorDestination,
type GanttEditorDestinationGroup,
type GanttEditorSlot,
} from "gantt-editor/react";
export function App() {
const [startTime] = useState(() => new Date("2025-01-01T00:00:00Z"));
const [endTime] = useState(() => new Date("2025-01-02T00:00:00Z"));
const slots = useMemo<GanttEditorSlot[]>(
() => [
{
id: "LH123-20250101-F",
displayName: "LH123 | F",
group: "LH123",
openTime: new Date("2025-01-01T10:00:00Z"),
closeTime: new Date("2025-01-01T12:00:00Z"),
destinationId: "chute-1",
hoverData: "<strong>LH123</strong><br><em>Gate opens 10:00</em>",
},
],
[],
);
const destinations = useMemo<GanttEditorDestination[]>(
() => [{ id: "chute-1", displayName: "Chute 1", active: true, groupId: "allocated" }],
[],
);
const destinationGroups = useMemo<GanttEditorDestinationGroup[]>(
() => [{ id: "allocated", displayName: "Allocated Chutes", heightPortion: 1 }],
[],
);
return (
<div style={{ height: "100vh", width: "100%" }}>
<GanttEditor
isReadOnly={false}
startTime={startTime}
endTime={endTime}
slots={slots}
destinations={destinations}
destinationGroups={destinationGroups}
markedRegion={null}
suggestions={[]}
onChangeDestinationId={(slotIds, destinationId) => console.log(slotIds, destinationId)}
onMoveSlotOnTimeAxis={(slotIds, timeDiffMs) => console.log(slotIds, timeDiffMs)}
onSelectionChange={(slotIds) => console.log(slotIds)}
/>
</div>
);
}import { Component } from "@angular/core";
import {
GanttEditor,
type GanttEditorDestination,
type GanttEditorDestinationGroup,
type GanttEditorSlot,
} from "gantt-editor/angular";
@Component({
selector: "app-root",
standalone: true,
imports: [GanttEditor],
template: `
<div style="height: 100vh; width: 100%;">
<gantt-editor
[isReadOnly]="false"
[startTime]="startTime"
[endTime]="endTime"
[slots]="slots"
[destinations]="destinations"
[destinationGroups]="destinationGroups"
[markedRegion]="null"
[suggestions]="[]"
(onChangeDestinationId)="onChangeDestinationId($event)"
(onMoveSlotOnTimeAxis)="onMoveSlotOnTimeAxis($event)"
(onSelectionChange)="onSelectionChange($event)"
/>
</div>
`,
})
export class AppComponent {
startTime = new Date("2025-01-01T00:00:00Z");
endTime = new Date("2025-01-02T00:00:00Z");
slots: GanttEditorSlot[] = [
{
id: "LH123-20250101-F",
displayName: "LH123 | F",
group: "LH123",
openTime: new Date("2025-01-01T10:00:00Z"),
closeTime: new Date("2025-01-01T12:00:00Z"),
destinationId: "chute-1",
hoverData: "<strong>LH123</strong><br><em>Gate opens 10:00</em>",
},
];
destinations: GanttEditorDestination[] = [
{ id: "chute-1", displayName: "Chute 1", active: true, groupId: "allocated" },
];
destinationGroups: GanttEditorDestinationGroup[] = [
{ id: "allocated", displayName: "Allocated Chutes", heightPortion: 1 },
];
onChangeDestinationId([slotIds, destinationId]: [string[], string]) {
console.log(slotIds, destinationId);
}
onMoveSlotOnTimeAxis([slotIds, timeDiffMs]: [string[], number]) {
console.log(slotIds, timeDiffMs);
}
onSelectionChange(slotIds: string[]) {
console.log(slotIds);
}
}Angular note: multi-value outputs are emitted as tuples in the same order as the Vue/React callback arguments.
Reactivity
The editor redraws when its input references change. After changing slots, pass a new array reference so the wrapper can trigger an update:
slots.value = slots.value.map((slot) =>
slot.id === slotId ? { ...slot, destinationId } : slot,
);If you mutate a slot object directly, reassign the array afterwards:
slotToUpdate.openTime = openTime;
slotToUpdate.closeTime = closeTime;
slots.value = [...slots.value];See apps/vue/src/pages/index.vue for the full Vue example.
Shared API
All wrappers expose the same core model and behavior.
Required Inputs
startTime: DateendTime: Dateslots: GanttEditorSlotWithUiAttributes[]destinations: GanttEditorDestination[]destinationGroups: GanttEditorDestinationGroup[]isReadOnly: boolean
Optional Inputs
suggestions?: GanttEditorSuggestion[](defaults to[])markedRegion?: GanttEditorMarkedRegion | null(defaults tonull)
GanttEditorSlot supports the following optional UI attributes:
deadlines?: Array<{ id: string; timestamp: number; color: string }>hoverData?: string(tooltip supports plain text and a limited HTML subset:<strong>,<em>,<br>)labelColor?: string(CSS color for slot text inside the bar)customOverlay?: ({ ctx, width, height, slot }) => void(optional custom canvas painter with slot-local coordinates where top-left is(0,0);ctxis uniformly scaled by slot height so overlay dimensions resize with the slot while preserving aspect ratio)
Common Optional Inputs
activateRulers: "ROW" | "GLOBAL" | nullslotResizeMinutesStep: number | null(snaps slot resizing to minute increments; omit,null, or0for free resizing)verticalMarkers: GanttEditorVerticalMarker[]contextMenuActions: GanttEditorCanvasContextMenuAction[]slotContextMenuActions: GanttEditorSlotContextMenuAction[]defaultZoomLevel: number(initial unified zoom multiplier; defaults to1, values above1start with taller rows and values below1start denser)scaleOnResize: "FULL" | "TIME_ONLY"(defaults to"FULL";"TIME_ONLY"keeps row height fixed and stretches only the time axis when the container resizes)topContentPortion: numberlocale: string | string[](used by built-in date/time formatting)dateTimeFormatters: { upper?: Intl.DateTimeFormat; lower?: Intl.DateTimeFormat; currentTime?: Intl.DateTimeFormat; onMouseTimeStrip?: Intl.DateTimeFormat; resizeSlotTime?: Intl.DateTimeFormat }(overrides locale-based formatting for matching labels)currentTimeIndicatorLabel: (value: Date) => string(custom text for the current-time indicator; defaults to date and time)xAxisOptions: GanttEditorXAxisOptionshelpOverlayTiles: HelpOverlayTileDefinition[]helpOverlayTileIds: HelpOverlayTileId[]features: GanttEditorFeature[]
Key Events
- Time range:
onChangeStartAndEndTime(start, end) - Resize:
onChangeSlotTime(slotId, openTime, closeTime) - Destination move/copy:
onChangeDestinationId(slotIds, destinationId),onCopyToDestinationId(slotIds, destinationId) - Time-axis move/copy:
onMoveSlotOnTimeAxis(slotIds, timeDiffMs),onCopySlotOnTimeAxis(slotIds, timeDiffMs) - Selection and click interactions:
onSelectionChange,onClickOnSlot,onHoverOnSlot,onDoubleClickOnSlot,onContextClickOnSlot - Vertical markers:
onChangeVerticalMarker,onClickVerticalMarker - Canvas context menu action:
onContextMenuAction(actionId, timestamp, destinationId) - Slot context menu action:
onSlotContextMenuAction(actionId, slotId)
Feature Flags
features is an allow-list. Omit it to keep all interactions enabled.
Supported ids:
select-slotsbrush-select-slotsresize-slot-timeapply-slot-suggestionscollapse-topicscanvas-context-menumove-vertical-markersmove-vertical-markers-from-context-menumove-slots-to-destinationbulk-move-slots-to-destinationcopy-slots-to-destinationbulk-copy-slots-to-destinationmove-slots-on-time-axisbulk-move-slots-on-time-axiscopy-slots-on-time-axisbulk-copy-slots-on-time-axispreview-slots-to-destinationpreview-slots-on-time-axiscopy-modifier-alttime-axis-modifier-shift
Help Overlay Tile IDs
helpOverlayTileIds is an allow-list for the built-in help overlay tiles. Omit it to show all built-in tiles plus any custom helpOverlayTiles. Pass [] to disable the help UI entirely.
Supported ids:
multi-selectbrush-selectmove-to-destinationmove-to-different-daycopy-to-destinationresize-slot-edgesunified-zoomtime-navigationcanvas-context-menuopen-slot-detailsescape-key
Exposed Methods
clearSelection()
Local Development
- Install:
npm install - Start demos:
npm run dev:vuenpm run dev:reactnpm run dev:angular
- Default dev command:
npm run dev(Vue demo)
