Skip to content

Configuration Reference

Source code
TypeScriptts
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 = document.createElement('input');
    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 = document.createElement('button');
    element.type = 'button';
    element.textContent = label;
    element.className = 'rv-btn';
    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');
  status.textContent = 'Save and restore use browser memory only; no server is contacted.';
  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 = document.createElement('input');
  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(); };
}
Vuevue
<template>
  <section class="gantt-production-demo">
    <div class="gantt-production-controls">
      <div class="gantt-production-options">
        <label><input v-model="readOnly" type="checkbox" />Read only</label>
        <label><input v-model="localized" type="checkbox" />Español dates</label>
        <label><input v-model="large" type="checkbox" @change="project = null" />750 tasks</label>
      </div>
      <div class="gantt-production-actions">
        <input ref="importInput" type="file" hidden accept=".json,application/json" aria-label="Import JSON file" @change="importJson" />
        <button type="button" class="rv-btn" @click="importInput?.click()">Import JSON</button>
        <button type="button" class="rv-btn" @click="run(() => persistence.exportFile(getGrid()))">JSON</button>
        <button type="button" class="rv-btn" @click="save">Save</button>
        <button type="button" class="rv-btn" :disabled="!hasSaved" @click="restore">Restore</button>
        <button 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 { 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('Save and restore use browser memory only; no server is contacted.');
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>
Reacttsx
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('Save and restore use browser memory only; no server is contacted.');
  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><input type="checkbox" checked={readOnly} onChange={(event) => setReadOnly(event.currentTarget.checked)} />Read only</label>
        <label><input type="checkbox" checked={localized} onChange={(event) => setLocalized(event.currentTarget.checked)} />Español dates</label>
        <label><input type="checkbox" checked={large} onChange={(event) => { setLarge(event.currentTarget.checked); setProject(null); }} />750 tasks</label>
      </div>
      <div className="gantt-production-actions">
        <input ref={importInput} type="file" hidden accept=".json,application/json" aria-label="Import JSON file" onChange={importJson} />
        <button type="button" className="rv-btn" onClick={() => importInput.current?.click()}>Import JSON</button>
        <button type="button" className="rv-btn" onClick={() => { void run(() => persistence.exportFile(getGrid())); }}>JSON</button>
        <button type="button" className="rv-btn" onClick={save}>Save</button>
        <button type="button" className="rv-btn" disabled={!hasSaved} onClick={restore}>Restore</button>
        <button type="button" className="rv-btn" onClick={() => { void run(() => persistence.print(getGrid())); }}>Print</button>
      </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>;
}
Angularts
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: [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 type="checkbox" [checked]="readOnly" (change)="setReadOnly($any($event.target).checked)" />Read only</label>
        <label><input type="checkbox" [checked]="localized" (change)="setLocalized($any($event.target).checked)" />Español dates</label>
        <label><input type="checkbox" [checked]="large" (change)="setLarge($any($event.target).checked)" />750 tasks</label>
      </div>
      <div class="gantt-production-actions">
        <input #importFile type="file" hidden accept=".json,application/json" aria-label="Import JSON file" (change)="importJson($event)" />
        <button type="button" class="rv-btn" (click)="importFile.click()">Import JSON</button>
        <button type="button" class="rv-btn" (click)="exportJson()">JSON</button>
        <button type="button" class="rv-btn" (click)="save()">Save</button>
        <button type="button" class="rv-btn" [disabled]="!hasSaved" (click)="restore()">Restore</button>
        <button 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 = 'Save and restore use browser memory only; no server is contacted.';
  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 = ''; }
}
Sharedts
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.';
}

Public config entry points:

  • GanttPluginConfig
    • weekStartsOn?: 0 | 1 controls week header/tick alignment. Omit for Sunday (0); set 1 for Monday.
    • timelinePrecision?: 'day' | 'hour' controls the default precision for timeline task create, move, and resize interactions. Use 'hour' with zoomPreset: 'hour-day' or zoom.defaultLevelId: 'hour-day' for hour-scale planning.
    • snap?: { unit?: 'day' | 'hour'; workingTime?: boolean } controls interaction snapping. unit: 'hour' keeps interactions on hour boundaries; workingTime: true or omitted skips holidays, closed weekdays, and closed calendar.workingHours.
    • calendars?: CalendarEntity[] can include intraday workingHours such as { start: '09:00', end: '17:00' } plus hoursPerDay for hour-mode snapping and scheduling.
    • durationSettings?: DurationSettings controls the fixed/automatic display unit, compact display precision through maximumFractionDigits (default 1), and conversions through hoursPerDay, hoursPerWeek, and daysPerMonth. See Duration Units and Conversion.
    • fields?: ViewFieldMap maps shared view meanings to properties in each authored task row. See Task source field mapping.
    • taskWindow?: GanttTaskWindowConfig selects scheduled tasks overlapping an explicit or relative date window while keeping their WBS ancestors. See Task Windows and Lookahead.
    • allowedTaskTypes?: TaskType[] restricts the choices in packaged Task Type column and dialog editors. Omit it to allow task, milestone, and summary; use ['task'] to prevent users from converting tasks to milestones or summaries.
    • labels?: GanttLabelsConfig customizes option labels, validation errors, scheduler warnings, and task-editor text from one project-level object. See Labels, Errors, and Warnings.
    • dateFormats?: GanttDateFormatConfig controls task-table, tooltip, editor, and timeline date presentation and parsing. See Date Formatting.
    • visuals.percentDoneMode?: 'number' | 'bar' | 'circle' controls the project default for the managed % Done task-table cell. It defaults to bar; { prop: 'percentDone', mode: 'circle' } overrides it per column. See Actuals and Progress.
    • taskCreateRow?: boolean shows an opt-in pinned bottom “Add new task” row. It requires allowTaskCreate: true and is hidden for read-only or resource-planning views. See Add Task Row for GanttAddTaskRowPlugin behavior and isGanttAddTaskRow() usage.
  • GanttSchedulingConfig
  • GanttResourcePlanningConfig
  • GanttVisualConfig

Use taskWindow to coordinate the task list with a planning period. Tasks and milestones match when their scheduled dates overlap the inclusive window. Summary rows provide ancestor context; a summary spanning the window does not include unrelated children. The complete project remains available to the scheduler, so hidden predecessors still affect visible tasks.

For an explicit period:

grid.gantt = {
...projectConfig,
taskWindow: {
startDate: '2026-10-19',
endDate: '2026-11-08',
includeOverdue: true,
},
};

For a rolling three-week lookahead, reuse the shared relative-date expression:

grid.gantt = {
...projectConfig,
weekStartsOn: 1,
taskWindow: {
relative: {
version: 1,
mode: 'custom',
direction: 'next',
amount: 3,
unit: 'week',
rolling: true,
},
anchorDate: '2026-10-19',
includeOverdue: true,
},
};

Change amount to 2 or 6 for other lookahead lengths. With rolling: true, the window starts on the anchor date and contains exactly the requested number of calendar days or weeks. With rolling: false, weeks align to weekStartsOn and include the week containing the anchor. Without anchorDate, relative windows use statusDate, or today’s calendar date in the project timezone when no status date is set. Relative windows resolve during configuration and data refreshes; applications that stay open overnight can reassign grid.gantt to refresh the current date.

includeOverdue: true also includes unfinished tasks ending before the window’s first date. Actual-finished tasks, tasks at 100% progress, and tasks with workflow status done are excluded from this additional overdue set. Completed tasks still appear when they overlap the selected period.

The timeline follows a bounded task window by default. Set syncTimeline: false to filter task rows while retaining the task-derived timeline, or provide timelineRange to override the chart bounds explicitly. timelineRange on its own continues to control chart bounds without filtering rows. Invalid task-window dates or expressions leave task-window filtering disabled.

Window activation or a change of window reveals the ancestors of matching tasks. Users can subsequently collapse those rows. Search, resource filters, and other grid filters continue to apply. Task-window row filtering applies to the task view; resource-planning rows retain their resource/load semantics.

Clear the window through the same configuration property:

grid.gantt = { ...grid.gantt, taskWindow: undefined };

Use resolveGanttTaskWindow(config) and selectGanttTaskWindowTasks(tasks, window) from @revolist/gantt when an application needs the same resolved period and task selection for its own views. Trade, subcontractor, and location fields can remain in application records and use normal grid filtering.

Gantt uses the shared ViewFieldMap names also used by Scheduler and Kanban. The mapping values are property names in grid.source:

MeaningGantt default
idid
titlename
statusworkflowStatus
startstartDate
endendDate
colorcolor
progresspercentDone

This lets one application record shape feed several views:

grid.gantt = {
...project,
fields: {
id: 'id',
title: 'name',
status: 'workflowStatus',
start: 'startDate',
end: 'endDate',
color: 'color',
progress: 'percentDone',
resourceId: 'owner',
},
};

The mapping changes property names only. Date and duration parsing, progress units, validation, and scheduling behavior stay the same. Gantt writes accepted task changes back with the authored property names and preserves unrelated row fields. resourceId is accepted so the mapping object can be shared, and its source value is preserved; Gantt resource scheduling continues to use ganttAssignments, where a task can have multiple assignments.

durationSettings.defaultUnit and defaultWorkUnit accept every DurationUnit or 'auto'. Duration, Remaining Duration, and Work are stored as hours. defaultUnit controls task-span quantities while defaultWorkUnit independently controls Work; task durationUnit and workUnit win. auto displays sub-hour values as minutes, sub-day values as hours, and longer values as days without changing stored task data.

grid.gantt = {
...project,
durationSettings: {
defaultUnit: 'auto',
defaultWorkUnit: 'hour',
hoursPerDay: 8,
hoursPerWeek: 40,
daysPerMonth: 20,
},
};

See Duration Units and Conversion for mixed task units, flexible input, elapsed durations, defaults, and conversion behavior.

Use gantt.labels as the project-level localization and message catalog. Error and warning overrides can be strings or formatter functions. The plugin keeps the existing result contract—such as { field, code, message }—and changes only the resolved message.

grid.gantt = {
...project,
labels: {
errors: {
taskEdit: {
invalidRemainingDuration:
'Remaining duration must be a value such as 5h, 2d, 3w, or 30m.',
},
taskEditor: {
invalidRemainingDuration: 'Enter remaining time in hours or days.',
},
dependency: {
taskReferenceNotFound: ({ value }) => `No task matches "${value}".`,
},
},
warnings: {
scheduler: {
deadlineMissed: ({ taskId, date }) =>
`Task ${taskId} missed its ${date} deadline.`,
},
},
taskEditor: {
applyLabel: 'Apply changes',
status: {
saveFailed: 'The task could not be saved.',
},
},
},
};

Message groups identify the runtime surface: taskEdit for managed grid-cell edits, taskMutation for task operations and timeline interactions, assignment, dependency, and taskEditor. Scheduler issues and conflicts use warnings.scheduler. The exported DEFAULT_GANTT_ERROR_LABELS and DEFAULT_GANTT_WARNING_LABELS constants contain every supported key and the default English text.

The task-editor dialog inherits gantt.labels.taskEditor. A dialog-specific localeText option remains supported and takes precedence over the inherited project labels, which allows one editor instance to override selected text without duplicating the rest of the project locale.

Managed Gantt date columns display a localized medium date by default (for example, Apr 6, 2026 with the default en-US locale). This applies consistently to Start, End, Actual Start/Finish, Early/Late Start/Finish, Constraint Date, and Deadline. ISO date-time task values such as 2026-04-06T09:00:00.000Z remain unchanged in your data; only their cell display is formatted, and the default table display intentionally omits the time.

Set gantt.dateFormats.locale, timeZone, and table to choose your own project-wide table format. table accepts standard Intl.DateTimeFormatOptions:

grid.gantt = {
...project,
dateFormats: {
locale: 'en-GB',
timeZone: 'Europe/London',
table: {
day: '2-digit',
month: '2-digit',
year: 'numeric',
},
},
};

For field-specific or application-defined output, provide a formatter callback. The context includes the managed field name, locale, time zone, and surface:

grid.gantt = {
...project,
dateFormats: {
table: (value, context) => {
const date = new Date(value.length === 10 ? `${value}T00:00:00Z` : value);
const prefix = context.field === 'deadlineDate' ? 'Due ' : '';
return `${prefix}${new Intl.DateTimeFormat('de-DE', {
day: '2-digit',
month: 'short',
year: 'numeric',
timeZone: context.timeZone,
}).format(date)}`;
},
},
};

Use dateFormats.tooltip and dateFormats.timeline for those surfaces. If editor text must use a non-ISO format, pair dateFormats.editor.formatter with dateFormats.editor.parser (or the shared dateFormats.parser) so edits are converted back to ISO date or UTC ISO date-time values before scheduling.

Gantt automatically adapts pointer interactions for phones, tablets, and other coarse-pointer devices. There is no mobile-only configuration flag; the same GanttPluginConfig works across desktop and touch devices.

Mobile behavior is designed around select-then-edit:

  • Mouse and trackpad users keep direct desktop interactions for moving, resizing, progress updates, dependency drawing, timeline panning, and split-panel resizing.
  • Touch users tap a task bar first to select it and reveal larger resize, progress, dependency, and split handles.
  • Dragging a selected task bar moves the task. Dragging a visible handle resizes, updates progress, edits splits, or creates dependencies.
  • Plain touch scrolling in the task table or timeline remains scroll-first and should not move, resize, or create tasks accidentally.
  • Hidden coarse-pointer handles are not hit-testable until the task is selected or interaction-active, so an edge swipe on an unselected task still behaves like navigation.
  • Task editor dialogs switch to a single-column, full-viewport layout on narrow screens with larger controls for touch input.

The Gantt E2E suite includes a dedicated mobile project using an iPhone viewport. It verifies that unselected task-bar swipes do not mutate dates, tapping selects a task and exposes handles, and the task editor dialog fits a phone-sized viewport.

Canonical definitions:

  • packages/gantt/src/gantt/grid/gantt-plugin.types.ts

For generated signatures, see API: Gantt.