Visual Configuration
Source code
import { controlAttributes, createControl } from '@revolist/ui';import { defineCustomElements } from '@revolist/revogrid/loader';defineCustomElements();
import { defineGanttToolbar, GanttPlugin, GanttCalendarEditorPlugin, type ProjectSnapshot,} from '@revolist/gantt';import { ExportExcelPlugin, RowSelectPlugin } from '@revolist/revogrid-pro';import { currentTheme } from '../composables/useRandomData';import { SHOWCASE_ASSIGNMENTS, SHOWCASE_BASELINES, SHOWCASE_COLUMNS_WITH_ROW_SELECT, SHOWCASE_DEPENDENCIES, SHOWCASE_RESOURCES, SHOWCASE_TOOLBAR_COLUMNS, STANDARD_CALENDAR,} from './shared/gantt-project-data';import { createProductionGanttConfig, createProductionTasks, createProductionPersistence, productionError, productionSourceRows,} from './GanttProductionPatternsShared';import './gantt-production-patterns.css';
export function load(parentSelector: string) { const parent = document.querySelector(parentSelector); if (!parent) return () => {}; const root = document.createElement('section'); const controls = document.createElement('div'); const status = document.createElement('p'); const toolbar = document.createElement('div'); const grid = document.createElement('revo-grid'); const persistence = createProductionPersistence(); let readOnly = false; let localized = false; let large = false; let project: ProjectSnapshot | null = null; const applyConfig = () => { grid.gantt = createProductionGanttConfig(readOnly, localized, project); }; const applyProject = () => { applyConfig(); grid.ganttDependencies = project?.dependencies ?? (large ? [] : SHOWCASE_DEPENDENCIES); grid.ganttCalendars = project?.calendars ?? [STANDARD_CALENDAR]; grid.ganttResources = project?.resources ?? SHOWCASE_RESOURCES; grid.ganttAssignments = project?.assignments ?? (large ? [] : SHOWCASE_ASSIGNMENTS); grid.ganttBaselines = project?.baselines ?? (large ? [] : SHOWCASE_BASELINES); grid.source = project ? productionSourceRows(project) : createProductionTasks(large); }; const toggle = (label: string, update: (value: boolean) => void) => { const element = document.createElement('label'); const input = createControl(document, 'input', { type: 'checkbox' }); input.type = 'checkbox'; input.addEventListener('change', () => update(input.checked)); element.append(input, document.createTextNode(label)); return { element, input }; }; const run = async (action: () => unknown | Promise<unknown>) => { try { await action(); } catch (error) { status.textContent = productionError(error); } }; const button = (label: string, action: () => unknown | Promise<unknown>) => { const element = createControl(document, 'button', { type: 'button' }); element.type = 'button'; element.textContent = label; element.className = controlAttributes('button', { class: 'rv-btn' }).class; element.addEventListener('click', () => { void run(action); }); return element; }; root.className = 'gantt-production-demo'; controls.className = 'gantt-production-controls'; status.className = 'gantt-production-status'; status.setAttribute('role', 'status'); toolbar.className = 'gantt-production-toolbar'; grid.className = 'gantt-production-grid'; grid.theme = currentTheme().isDark() ? 'darkCompact' : 'compact'; grid.hideAttribution = true; grid.plugins = [ GanttPlugin, GanttCalendarEditorPlugin, ExportExcelPlugin, RowSelectPlugin, ]; grid.rowSelect = { rowOrder: true }; grid.columns = [...SHOWCASE_COLUMNS_WITH_ROW_SELECT]; const largeToggle = toggle('750 tasks', (value) => { large = value; project = null; applyProject(); }); const restore = button('Restore', async () => { project = await persistence.load(); large = false; largeToggle.input.checked = false; applyProject(); status.textContent = 'Restored the saved project, including tasks and project collections.'; }); restore.disabled = true; const fileInput = createControl(document, 'input', { type: 'file' }); fileInput.type = 'file'; fileInput.hidden = true; fileInput.accept = '.json,application/json'; fileInput.setAttribute('aria-label', 'Import JSON file'); fileInput.addEventListener('change', async () => { const file = fileInput.files?.[0]; if (file) await run(async () => { project = await persistence.importFile(file); large = false; largeToggle.input.checked = false; applyProject(); status.textContent = `Imported ${project.tasks.length} tasks from ${file.name}.`; }); fileInput.value = ''; }); const options = document.createElement('div'); options.className = 'gantt-production-options'; const actions = document.createElement('div'); actions.className = 'gantt-production-actions'; options.append( toggle('Read only', (value) => { readOnly = value; applyConfig(); }).element, toggle('Español dates', (value) => { localized = value; applyConfig(); }).element, largeToggle.element, ); actions.append( fileInput, button('Import JSON', () => fileInput.click()), button('JSON', () => persistence.exportFile(grid)), button('Save', async () => { await persistence.save(grid); restore.disabled = false; status.textContent = 'Saved the current edited project in browser memory.'; }), restore, button('Print', () => persistence.print(grid)), ); controls.append(options, actions); root.append(controls, status, toolbar, grid); parent.appendChild(root); applyProject(); defineGanttToolbar(toolbar, { grid, columns: SHOWCASE_TOOLBAR_COLUMNS, controls: { baseline: true, calendars: true }, }); return () => { toolbar.textContent = ''; grid.remove(); root.remove(); };}<template> <section class="gantt-production-demo"> <div class="gantt-production-controls"> <div class="gantt-production-options"> <label ><input v-rv-control v-model="readOnly" type="checkbox" />Read only</label > <label ><input v-rv-control v-model="localized" type="checkbox" />Español dates</label > <label ><input v-rv-control v-model="large" type="checkbox" @change="project = null" />750 tasks</label > </div> <div class="gantt-production-actions"> <input v-rv-control ref="importInput" type="file" hidden accept=".json,application/json" aria-label="Import JSON file" @change="importJson" /> <button v-rv-control type="button" class="rv-btn" @click="importInput?.click()" > Import JSON </button> <button v-rv-control type="button" class="rv-btn" @click="run(() => persistence.exportFile(getGrid()))" > JSON </button> <button v-rv-control type="button" class="rv-btn" @click="save"> Save </button> <button v-rv-control type="button" class="rv-btn" :disabled="!hasSaved" @click="restore" > Restore </button> <button v-rv-control type="button" class="rv-btn" @click="run(() => persistence.print(getGrid()))" > Print </button> </div> </div> <p class="gantt-production-status" role="status">{{ status }}</p> <div ref="toolbarRef" class="gantt-production-toolbar"></div> <RevoGrid ref="gridRef" class="gantt-production-grid" hide-attribution :theme="isDark ? 'darkCompact' : 'compact'" :plugins="plugins" :row-select.prop="rowSelect" :source="tasks" :columns="columns" :gantt.prop="ganttConfig" :gantt-dependencies.prop="dependencies" :gantt-calendars.prop="calendars" :gantt-resources.prop="resources" :gantt-assignments.prop="assignments" :gantt-baselines.prop="baselines" /> </section></template>
<script setup lang="ts">import { vRvControl } from '@revolist/ui/vue-control';import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue';import RevoGrid from '@revolist/vue3-datagrid';import { defineGanttToolbar, GanttPlugin, GanttCalendarEditorPlugin, type ProjectSnapshot,} from '@revolist/gantt';import { ExportExcelPlugin, RowSelectPlugin } from '@revolist/revogrid-pro';import { currentThemeVue } from '../composables/useRandomData';import { SHOWCASE_ASSIGNMENTS, SHOWCASE_BASELINES, SHOWCASE_COLUMNS_WITH_ROW_SELECT, SHOWCASE_DEPENDENCIES, SHOWCASE_RESOURCES, SHOWCASE_TOOLBAR_COLUMNS, STANDARD_CALENDAR,} from './shared/gantt-project-data';import { createProductionGanttConfig, createProductionTasks, createProductionPersistence, productionError, productionSourceRows,} from './GanttProductionPatternsShared';import './gantt-production-patterns.css';const { isDark } = currentThemeVue();const plugins = [ GanttPlugin, GanttCalendarEditorPlugin, ExportExcelPlugin, RowSelectPlugin,];const rowSelect = { rowOrder: true };const columns = SHOWCASE_COLUMNS_WITH_ROW_SELECT;const readOnly = ref(false);const localized = ref(false);const large = ref(false);const project = ref<ProjectSnapshot | null>(null);const status = ref('');const hasSaved = ref(false);const persistence = createProductionPersistence();const tasks = computed(() => project.value ? productionSourceRows(project.value) : createProductionTasks(large.value),);const dependencies = computed( () => project.value?.dependencies ?? (large.value ? [] : SHOWCASE_DEPENDENCIES),);const calendars = computed( () => project.value?.calendars ?? [STANDARD_CALENDAR],);const resources = computed( () => project.value?.resources ?? SHOWCASE_RESOURCES,);const assignments = computed( () => project.value?.assignments ?? (large.value ? [] : SHOWCASE_ASSIGNMENTS),);const baselines = computed( () => project.value?.baselines ?? (large.value ? [] : SHOWCASE_BASELINES),);const ganttConfig = computed(() => createProductionGanttConfig(readOnly.value, localized.value, project.value),);const gridRef = ref<InstanceType<typeof RevoGrid> | HTMLRevoGridElement | null>( null,);const importInput = ref<HTMLInputElement | null>(null);const toolbarRef = ref<HTMLElement | null>(null);let frame = 0;function getGrid(): HTMLRevoGridElement { const value = gridRef.value as | (InstanceType<typeof RevoGrid> & { $el?: HTMLRevoGridElement }) | HTMLRevoGridElement | null; const grid = value && '$el' in value ? value.$el : value; if (!(grid instanceof HTMLElement)) throw new Error('The grid is still loading.'); return grid as HTMLRevoGridElement;}async function run(action: () => unknown | Promise<unknown>) { try { await action(); } catch (error) { status.value = productionError(error); }}async function save() { await run(async () => { await persistence.save(getGrid()); hasSaved.value = true; status.value = 'Saved the current edited project in browser memory.'; });}async function restore() { await run(async () => { project.value = await persistence.load(); large.value = false; status.value = 'Restored the saved project, including tasks and project collections.'; });}async function importJson(event: Event) { const input = event.target as HTMLInputElement; const file = input.files?.[0]; if (file) await run(async () => { project.value = await persistence.importFile(file); large.value = false; status.value = `Imported ${project.value.tasks.length} tasks from ${file.name}.`; }); input.value = '';}function mountToolbar() { let grid: HTMLRevoGridElement; try { grid = getGrid(); } catch { frame = requestAnimationFrame(mountToolbar); return; } if (toolbarRef.value) defineGanttToolbar(toolbarRef.value, { grid, columns: SHOWCASE_TOOLBAR_COLUMNS, controls: { baseline: true, calendars: true }, });}onMounted(async () => { await nextTick(); mountToolbar();});onBeforeUnmount(() => { cancelAnimationFrame(frame); if (toolbarRef.value) toolbarRef.value.textContent = '';});</script>import { ButtonControl as UiButton, Input as UiInput,} from '@revolist/ui/react-control';import React, { useEffect, useMemo, useRef, useState } from 'react';import { RevoGrid } from '@revolist/react-datagrid';import { defineGanttToolbar, GanttPlugin, GanttCalendarEditorPlugin, type ProjectSnapshot,} from '@revolist/gantt';import { ExportExcelPlugin, RowSelectPlugin } from '@revolist/revogrid-pro';import { currentTheme } from '../composables/useRandomData';import { SHOWCASE_ASSIGNMENTS, SHOWCASE_BASELINES, SHOWCASE_COLUMNS_WITH_ROW_SELECT, SHOWCASE_DEPENDENCIES, SHOWCASE_RESOURCES, SHOWCASE_TOOLBAR_COLUMNS, STANDARD_CALENDAR,} from './shared/gantt-project-data';import { createProductionGanttConfig, createProductionTasks, createProductionPersistence, productionError, productionSourceRows,} from './GanttProductionPatternsShared';import './gantt-production-patterns.css';
export default function GanttProductionPatterns() { const gridRef = useRef<HTMLRevoGridElement>(null); const importInput = useRef<HTMLInputElement>(null); const toolbarRef = useRef<HTMLDivElement>(null); const [readOnly, setReadOnly] = useState(false); const [localized, setLocalized] = useState(false); const [large, setLarge] = useState(false); const [project, setProject] = useState<ProjectSnapshot | null>(null); const [status, setStatus] = useState(''); const [hasSaved, setHasSaved] = useState(false); const persistence = useMemo(() => createProductionPersistence(), []); const plugins = useMemo( () => [ GanttPlugin, GanttCalendarEditorPlugin, ExportExcelPlugin, RowSelectPlugin, ], [], ); const rowSelect = useMemo(() => ({ rowOrder: true }), []); const columns = useMemo(() => SHOWCASE_COLUMNS_WITH_ROW_SELECT, []); const tasks = useMemo( () => project ? productionSourceRows(project) : createProductionTasks(large), [project, large], ); const gantt = useMemo( () => createProductionGanttConfig(readOnly, localized, project), [readOnly, localized, project], ); const dependencies = useMemo( () => project?.dependencies ?? (large ? [] : SHOWCASE_DEPENDENCIES), [project, large], ); const calendars = useMemo( () => project?.calendars ?? [STANDARD_CALENDAR], [project], ); const resources = useMemo( () => project?.resources ?? SHOWCASE_RESOURCES, [project], ); const assignments = useMemo( () => project?.assignments ?? (large ? [] : SHOWCASE_ASSIGNMENTS), [project, large], ); const baselines = useMemo( () => project?.baselines ?? (large ? [] : SHOWCASE_BASELINES), [project, large], ); function getGrid(): HTMLRevoGridElement { if (!gridRef.current) throw new Error('The grid is still loading.'); return gridRef.current; } async function run(action: () => unknown | Promise<unknown>) { try { await action(); } catch (error) { setStatus(productionError(error)); } } async function save() { await run(async () => { await persistence.save(getGrid()); setHasSaved(true); setStatus('Saved the current edited project in browser memory.'); }); } async function restore() { await run(async () => { setProject(await persistence.load()); setLarge(false); setStatus( 'Restored the saved project, including tasks and project collections.', ); }); } async function importJson(event: React.ChangeEvent<HTMLInputElement>) { const input = event.currentTarget; const file = input.files?.[0]; if (file) await run(async () => { const imported = await persistence.importFile(file); setProject(imported); setLarge(false); setStatus(`Imported ${imported.tasks.length} tasks from ${file.name}.`); }); input.value = ''; } useEffect(() => { const toolbar = toolbarRef.current; if (!toolbar || !gridRef.current) return; defineGanttToolbar(toolbar, { grid: gridRef.current, columns: SHOWCASE_TOOLBAR_COLUMNS, controls: { baseline: true, calendars: true }, }); return () => { toolbar.textContent = ''; }; }, []); return ( <section className="gantt-production-demo"> <div className="gantt-production-controls"> <div className="gantt-production-options"> <label> <UiInput type="checkbox" checked={readOnly} onChange={(event) => setReadOnly(event.currentTarget.checked)} /> Read only </label> <label> <UiInput type="checkbox" checked={localized} onChange={(event) => setLocalized(event.currentTarget.checked)} /> Español dates </label> <label> <UiInput type="checkbox" checked={large} onChange={(event) => { setLarge(event.currentTarget.checked); setProject(null); }} /> 750 tasks </label> </div> <div className="gantt-production-actions"> <UiInput ref={importInput} type="file" hidden accept=".json,application/json" aria-label="Import JSON file" onChange={importJson} /> <UiButton type="button" className="rv-btn" onClick={() => importInput.current?.click()} > Import JSON </UiButton> <UiButton type="button" className="rv-btn" onClick={() => { void run(() => persistence.exportFile(getGrid())); }} > JSON </UiButton> <UiButton type="button" className="rv-btn" onClick={save}> Save </UiButton> <UiButton type="button" className="rv-btn" disabled={!hasSaved} onClick={restore} > Restore </UiButton> <UiButton type="button" className="rv-btn" onClick={() => { void run(() => persistence.print(getGrid())); }} > Print </UiButton> </div> </div> <p className="gantt-production-status" role="status"> {status} </p> <div ref={toolbarRef} className="gantt-production-toolbar" /> <RevoGrid ref={gridRef} className="gantt-production-grid" theme={currentTheme().isDark() ? 'darkCompact' : 'compact'} hideAttribution plugins={plugins} rowSelect={rowSelect} source={tasks} columns={columns} gantt={gantt} ganttDependencies={dependencies} ganttCalendars={calendars} ganttResources={resources} ganttAssignments={assignments} ganttBaselines={baselines} /> </section> );}import { UiControlDirective } from '@revolist/ui/angular-control';import { ChangeDetectorRef, inject, AfterViewInit, Component, ElementRef, NO_ERRORS_SCHEMA, OnDestroy, ViewChild, ViewEncapsulation,} from '@angular/core';import { RevoGrid } from '@revolist/angular-datagrid';import { defineGanttToolbar, GanttPlugin, GanttCalendarEditorPlugin, type ProjectSnapshot,} from '@revolist/gantt';import { ExportExcelPlugin, RowSelectPlugin } from '@revolist/revogrid-pro';import { currentTheme } from '../composables/useRandomData';import { SHOWCASE_ASSIGNMENTS, SHOWCASE_BASELINES, SHOWCASE_COLUMNS_WITH_ROW_SELECT, SHOWCASE_DEPENDENCIES, SHOWCASE_RESOURCES, SHOWCASE_TOOLBAR_COLUMNS, STANDARD_CALENDAR,} from './shared/gantt-project-data';import { createProductionGanttConfig, createProductionTasks, createProductionPersistence, productionError, productionSourceRows,} from './GanttProductionPatternsShared';
@Component({ selector: 'gantt-production-patterns-grid', standalone: true, schemas: [NO_ERRORS_SCHEMA], imports: [UiControlDirective, RevoGrid], styleUrls: ['./gantt-production-patterns.css'], encapsulation: ViewEncapsulation.None, template: `<section class="gantt-production-demo"> <div class="gantt-production-controls"> <div class="gantt-production-options"> <label ><input rvControl type="checkbox" [checked]="readOnly" (change)="setReadOnly($any($event.target).checked)" />Read only</label > <label ><input rvControl type="checkbox" [checked]="localized" (change)="setLocalized($any($event.target).checked)" />Español dates</label > <label ><input rvControl type="checkbox" [checked]="large" (change)="setLarge($any($event.target).checked)" />750 tasks</label > </div> <div class="gantt-production-actions"> <input rvControl #importFile type="file" hidden accept=".json,application/json" aria-label="Import JSON file" (change)="importJson($event)" /> <button rvControl type="button" class="rv-btn" (click)="importFile.click()" > Import JSON </button> <button rvControl type="button" class="rv-btn" (click)="exportJson()"> JSON </button> <button rvControl type="button" class="rv-btn" (click)="save()"> Save </button> <button rvControl type="button" class="rv-btn" [disabled]="!hasSaved" (click)="restore()" > Restore </button> <button rvControl type="button" class="rv-btn" (click)="print()"> Print </button> </div> </div> <p class="gantt-production-status" role="status">{{ status }}</p> <div #toolbar class="gantt-production-toolbar"></div> <revo-grid #grid class="gantt-production-grid" [theme]="theme" [hideAttribution]="true" [plugins]="plugins" [rowSelect]="rowSelect" [source]="tasks" [columns]="columns" [gantt]="ganttConfig" [ganttDependencies]="dependencies" [ganttCalendars]="calendars" [ganttResources]="resources" [ganttAssignments]="assignments" [ganttBaselines]="baselines" ></revo-grid> </section>`,})export class GanttProductionPatternsGridComponent implements AfterViewInit, OnDestroy{ private readonly changeDetector = inject(ChangeDetectorRef); @ViewChild('grid', { read: ElementRef }) gridRef!: ElementRef<HTMLRevoGridElement>; @ViewChild('toolbar', { read: ElementRef }) toolbarRef!: ElementRef<HTMLElement>; theme = currentTheme().isDark() ? 'darkCompact' : 'compact'; plugins = [ GanttPlugin, GanttCalendarEditorPlugin, ExportExcelPlugin, RowSelectPlugin, ]; rowSelect = { rowOrder: true }; columns = SHOWCASE_COLUMNS_WITH_ROW_SELECT; calendars = [STANDARD_CALENDAR]; resources = [...SHOWCASE_RESOURCES]; assignments = [...SHOWCASE_ASSIGNMENTS]; baselines = [...SHOWCASE_BASELINES]; readOnly = false; localized = false; large = false; tasks = createProductionTasks(); dependencies = SHOWCASE_DEPENDENCIES; project: ProjectSnapshot | null = null; status = ''; hasSaved = false; private persistence = createProductionPersistence(); ganttConfig = createProductionGanttConfig(); private refresh() { this.ganttConfig = createProductionGanttConfig( this.readOnly, this.localized, this.project, ); } private refreshProject() { this.tasks = this.project ? productionSourceRows(this.project) : createProductionTasks(this.large); this.dependencies = [ ...(this.project?.dependencies ?? (this.large ? [] : SHOWCASE_DEPENDENCIES)), ]; this.calendars = [...(this.project?.calendars ?? [STANDARD_CALENDAR])]; this.resources = [...(this.project?.resources ?? SHOWCASE_RESOURCES)]; this.assignments = [ ...(this.project?.assignments ?? (this.large ? [] : SHOWCASE_ASSIGNMENTS)), ]; this.baselines = [ ...(this.project?.baselines ?? (this.large ? [] : SHOWCASE_BASELINES)), ]; this.refresh(); } private getGrid(): HTMLRevoGridElement { const grid = this.gridRef?.nativeElement; if (!grid) throw new Error('The grid is still loading.'); return grid; } private async run(action: () => unknown | Promise<unknown>) { try { await action(); } catch (error) { this.status = productionError(error); } finally { this.changeDetector.markForCheck(); } } setReadOnly(value: boolean) { this.readOnly = value; this.refresh(); } setLocalized(value: boolean) { this.localized = value; this.refresh(); } setLarge(value: boolean) { this.large = value; this.project = null; this.refreshProject(); } exportJson() { return this.run(() => this.persistence.exportFile(this.getGrid())); } print() { return this.run(() => this.persistence.print(this.getGrid())); } save() { return this.run(async () => { await this.persistence.save(this.getGrid()); this.hasSaved = true; this.status = 'Saved the current edited project in browser memory.'; }); } restore() { return this.run(async () => { this.project = await this.persistence.load(); this.large = false; this.refreshProject(); this.status = 'Restored the saved project, including tasks and project collections.'; }); } async importJson(event: Event) { const input = event.target as HTMLInputElement; const file = input.files?.[0]; if (file) await this.run(async () => { this.project = await this.persistence.importFile(file); this.large = false; this.refreshProject(); this.status = `Imported ${this.project.tasks.length} tasks from ${file.name}.`; }); input.value = ''; } ngAfterViewInit() { defineGanttToolbar(this.toolbarRef.nativeElement, { grid: this.gridRef.nativeElement, columns: SHOWCASE_TOOLBAR_COLUMNS, controls: { baseline: true, calendars: true }, }); } ngOnDestroy() { if (this.toolbarRef?.nativeElement) this.toolbarRef.nativeElement.textContent = ''; }}import { exportProjectSnapshot, parseProjectSnapshotJson, type GanttPluginConfig, type GanttTaskSourceRow, type ProjectSnapshot,} from '@revolist/gantt';type ISODateString = import('@revolist/gantt').TaskEntity['startDate'];import { getGanttScheduleSnapshot } from './recipes/current-schedule-snapshot';import { ProjectSnapshotRestAdapter } from './recipes/rest-adapter';import { printGanttContainer } from './recipes/print-pdf-recipe';import { SHOWCASE_ASSIGNMENTS, SHOWCASE_BASELINES, SHOWCASE_DEPENDENCIES, SHOWCASE_GANTT_CONFIG, SHOWCASE_RESOURCES, SHOWCASE_TASKS, STANDARD_CALENDAR,} from './shared/gantt-project-data';
export function createProductionGanttConfig( readOnly = false, localized = false, project?: ProjectSnapshot | null,): GanttPluginConfig { return { ...SHOWCASE_GANTT_CONFIG, ...(project ? { id: project.id, name: project.name, version: project.version, currency: project.currency, timeZone: project.timeZone, primaryCalendarId: project.primaryCalendarId, updatedAt: project.updatedAt, durationSettings: project.durationSettings, calendars: project.calendars, } : {}), allowTaskCreate: true, taskCreateRow: true, readOnly, labels: localized ? { taskMode: { auto: 'Automática', manual: 'Manual' }, workflowStatus: { 'not-started': 'Sin iniciar', 'in-progress': 'En curso', done: 'Finalizada', blocked: 'Bloqueada', }, } : undefined, dateFormats: localized ? { locale: 'es-ES', table: { dateStyle: 'medium' }, tooltip: { dateStyle: 'long' }, } : undefined, visuals: { ...SHOWCASE_GANTT_CONFIG.visuals, showBaseline: true, showTaskLabels: true, timeRanges: [ { id: 'release-window', startDate: '2026-02-16', endDate: '2026-02-20', label: 'Release window', color: '#8b5cf633', }, ], milestoneLines: [ { id: 'go-live', date: '2026-02-27', label: 'Go live', color: '#e11d48', }, ], taskMarkerHook: (task) => task.deadlineDate ? [ { id: `deadline-${task.id}`, date: task.deadlineDate, label: 'Deadline', }, ] : [], taskBarColorHook: ({ row }) => row.id === SHOWCASE_TASKS[1].id ? { barColor: '#64748b', className: 'gantt-production-locked' } : undefined, taskTooltipFields: [ 'startDate', 'endDate', 'duration', 'percentDone', 'assignees', ], }, } as GanttPluginConfig;}
export function createProductionTasks(large = false): GanttTaskSourceRow[] { if (!large) { return SHOWCASE_TASKS.map((task, index) => ({ ...task, ...(index === 1 ? { locked: true } : {}), })) as GanttTaskSourceRow[]; }
return Array.from({ length: 750 }, (_, index) => { const day = 2 + (index % 20); return { id: `large-${index + 1}`, name: `Virtualized task ${index + 1}`, startDate: `2026-02-${String(day).padStart(2, '0')}` as ISODateString, duration: 1 + (index % 5), durationUnit: 'day', percentDone: (index * 7) % 101, taskMode: 'manual', locked: index % 17 === 0, } satisfies GanttTaskSourceRow; });}
export function productionSourceRows( project: ProjectSnapshot,): GanttTaskSourceRow[] { return project.tasks.map((task) => ({ ...task, durationUnit: task.durationUnit ?? 'hour', percentDone: task.progressPercent, taskMode: task.manuallyScheduled ? 'manual' : 'auto', }));}
export async function getProductionProject( grid: HTMLRevoGridElement,): Promise<ProjectSnapshot> { return getGanttScheduleSnapshot(grid);}
export function downloadProductionSnapshot(project: ProjectSnapshot): void { const href = URL.createObjectURL( new Blob([exportProjectSnapshot(project, { space: 2 })], { type: 'application/json', }), ); const link = document.createElement('a'); link.href = href; link.download = 'gantt-project.json'; link.click(); setTimeout(() => URL.revokeObjectURL(href), 0);}
/** A real REST recipe with an injected browser-memory transport; no server is contacted. */export function createProductionPersistence() { let savedJson: string | undefined; const adapter = new ProjectSnapshotRestAdapter({ url: '/example/project', fetchImpl: (async (_input, init) => { if (init?.method === 'PUT') { savedJson = String(init.body); return new Response(null, { status: 204 }); } return new Response(savedJson ?? 'No saved project', { status: savedJson ? 200 : 404, }); }) as typeof fetch, }); return { save: async (grid: HTMLRevoGridElement) => adapter.save(await getProductionProject(grid)), load: () => adapter.load(), importFile: async (file: Pick<File, 'text'>): Promise<ProjectSnapshot> => { const parsed = parseProjectSnapshotJson(await file.text()); if (parsed.ok === false) throw new Error(parsed.error); return parsed.project; }, exportFile: async (grid: HTMLRevoGridElement) => downloadProductionSnapshot(await getProductionProject(grid)), print: (grid: HTMLRevoGridElement) => { const result = printGanttContainer(grid.parentElement, { cleanupDelayMs: 60_000, hideSelectors: [ '.gantt-production-controls', '.gantt-production-status', '.gantt-production-toolbar', '.gantt-toolbar', '.rgContextMenu', ], metadata: { title: grid.gantt?.name ?? 'Project schedule' }, }); if (!result.ok) throw new Error('The print preview could not be opened.'); }, };}
export function productionError(error: unknown): string { return error instanceof Error ? error.message : 'The operation failed.';}gantt.visuals controls overlays and rendering hooks:
-
percentDoneModeselects the project-widenumber,bar(default), orcirclerendering for the managed% Donetask-table column using the shared Pro progress renderers. An authored{ prop: 'percentDone', mode: '...' }column overrides it. -
showBaseline,baselineId -
showCriticalPath -
showTaskLabels -
projectLineDate,showTodayLine,milestoneLines -
timeRanges,shadeNonWorkingTime -
taskMarkerHook,taskBarColorHook,taskBarContentHook -
taskTooltipFields,taskTooltipHook
Use taskTooltipFields when you only need to choose which built-in fields appear in task-bar tooltips:
grid.gantt = { ...project, visuals: { taskTooltipFields: [ 'name', 'startDate', 'endDate', { field: 'percentDone', label: 'Complete' }, 'assignees', ], },};Task tooltip API
Section titled “Task tooltip API”taskTooltipFields is an ordered array. Each entry can be a field id string or an object with a field and optional label:
type GanttTaskTooltipField = | GanttTaskTooltipFieldId | { field: GanttTaskTooltipFieldId; label?: string };Object entries let you rename a built-in line. Set label: '' to render only the value without a Label: prefix.
visuals: { taskTooltipFields: [ { field: 'name', label: '' }, { field: 'percentDone', label: 'Done' }, ],}Supported built-in fields:
| Field | Tooltip output |
|---|---|
summary | Task name and completion percent, for example Design - 50%. |
name | Task name. |
percentDone | Completion percent. |
taskType | Human-readable task type. |
status | Human-readable task status. |
startDate | Scheduled start date, formatted with gantt.dateFormats.tooltip. |
endDate | Scheduled end date, formatted with gantt.dateFormats.tooltip. |
duration | Formatted task duration in its retained or project-selected unit. |
taskMode | Auto Scheduled or Manually Scheduled. |
schedulingWarnings | Scheduling warnings joined into one line; skipped when empty. |
cost | Formatted task cost; skipped when empty. |
assignees | Comma-separated assignee names; skipped when empty. |
When taskTooltipFields is omitted, Gantt keeps the default tooltip: summary, taskType, status, startDate, endDate, duration, taskMode, schedulingWarnings, cost, and assignees. An empty array intentionally renders an empty tooltip.
taskTooltipHook has priority over taskTooltipFields and can replace the whole tooltip string:
visuals: { taskTooltipFields: ['name'], taskTooltipHook: ({ row }) => `${row.name}\n${row.status}`,}Return null or undefined from taskTooltipHook to fall back to taskTooltipFields or the default tooltip. Use the hook for custom row metadata that is not part of the built-in field list.
Type source: packages/gantt/src/gantt/grid/gantt-plugin.types.ts.
Rendering sources: features/timeline-background/*, grid/gantt-column-type.ts.