Skip to content

Before-hooks

Two hooks run before something is committed, and can stop it. onBeforeTaskChange sees each change set (a drag, or a call of updateTask) and onBeforeLinkCreate each new link (a drag, or addLink). A hook answers true to go on, false to cancel, and, for a change, a changed ChangeSet to put in place of the one it was given. It may also answer later, with a promise: the chart is busy until the answer comes, and refuses new gestures meanwhile.

The select picks what the task hook does, and the box whether the link hook refuses links into the frozen Release group. The line under the chart tells what the hook did and what the chart reported.

Terminal window
pnpm add @ganttlane/dom # vanilla
pnpm add @ganttlane/react react react-dom # React
View on GitHub
data.ts
import type { Link, Task } from '@ganttlane/core';
// 2026-03-02 is a Monday. Design is finished (100%); Release is a frozen group: Release itself and
// its two children cannot be dragged, and the link hook of the example refuses links into them.
export const tasks: Task[] = [
{ id: 'design', name: 'Design', start: '2026-03-02', end: '2026-03-06', progress: 100 },
{ id: 'api', name: 'API', start: '2026-03-09', end: '2026-03-13', progress: 60 },
{ id: 'ui', name: 'UI', start: '2026-03-09', end: '2026-03-17', progress: 30 },
{ id: 'docs', name: 'Docs', start: '2026-03-16', end: '2026-03-19' },
{ id: 'qa', name: 'QA', start: '2026-03-18', end: '2026-03-20' },
{ id: 'release', name: 'Release', start: '2026-03-23', end: '2026-03-27', locked: true },
{
id: 'package',
name: 'Package',
parentId: 'release',
start: '2026-03-23',
end: '2026-03-24',
locked: true,
},
{
id: 'publish',
name: 'Publish',
parentId: 'release',
start: '2026-03-25',
end: '2026-03-27',
locked: true,
},
];
export const links: Link[] = [
{ source: 'design', target: 'api' },
{ source: 'design', target: 'ui' },
{ source: 'api', target: 'qa' },
{ source: 'ui', target: 'qa' },
];
/** The tasks of the frozen group: Release and its children. */
export const frozen: ReadonlySet<Task['id']> = new Set(
tasks
.filter((task) => task.id === 'release' || task.parentId === 'release')
.map((task) => task.id),
);

Files

  • data.ts: eight tasks (Design is finished, Release and its children are frozen) and four links.
  • options.ts: the view mode, the first scroll and the layout.
  • hooks.ts: the hooks: veto, rewrite, ask first, and the link rule.
  • styles.css: your own styles for the busy flag and the strip with the question.
  • vanilla.ts and react.tsx: the controls, the status and the busy flag (core.subscribe, useGanttState).

Options

Option Value What it does
onBeforeTaskChange a function (hooks.ts) ({ changeSet, reason, conflicts }): answers true, false, a ChangeSet or a promise of one
onBeforeLinkCreate a function (hooks.ts) ({ link, id, wouldCreateCycle }): answers true, false or a promise of one
onTasksChange a function (in the example) The change that went through
onLinkCreate a function (in the example) The link that was created
onEditRefused a function (in the example) A gesture that the chart would not start: busy while a hook waits
viewMode 'week' The scale of the timeline (React: defaultViewMode)
scroll.initial 'first-task' Opens on the plan
layout.rowHeight, .barHeight 36, 24 Smaller rows, so that every task is in view

What to try

  • Veto. Pick Veto changes to finished work and drag Design (100%): the bar snaps back and nothing is reported but the hook’s own line. Drag API, which is not finished: it goes through, and QA follows.
  • Rewrite. Pick Rewrite and drag the progress handle of API (the little square at the foot of its bar) to the right end: the chart commits 80%, the hook’s value, not yours.
  • Ask first. Pick it and drag any bar. On release the bar waits where you dropped it, the flag says busy: yes, and the question has its two buttons. Try to drag another bar: Refused (busy). Approve and the change is committed, Reject and it is cancelled.
  • Links. With the box ticked, draw a link from QA onto Release (or Package, or Publish): the hook cancels it, and the chart reports no event, since nothing was refused by the chart itself. A link from Docs to QA is created. Untick the box, and Release can be linked. In Ask first both hooks ask.

Notes

  • What a hook may answer. true or nothing goes on; false cancels (a drag snaps back, and updateTask and addLink resolve cancelled with the reason 'hook'); a ChangeSet replaces the one being committed, and is checked again, so a value is aligned to the granularity and a task that no longer changes is dropped. A hook that throws or rejects cancels and reports the error to onError.
  • busy while a promise is pending. gantt.core.getSnapshot().busy (React: useGanttState(gantt, (snapshot) => snapshot.busy)). No gesture starts and updateTask resolves busy until the answer comes; the bar that was dropped is shown where it was dropped. The same flag is set while controlled data holds a change that its owner has not applied yet.
  • Where hooks run, and where they do not. onBeforeTaskChange runs for a drag and for updateTask, onBeforeLinkCreate for a link that is drawn and for addLink. Removing a link is not hooked, and neither are undo, redo and edit.rollback(): a hook cannot stand in the way of putting things back.
  • The context. reason is 'move', 'resize-start', 'resize-end', 'progress' or 'api'; conflicts are the warnings the change would cause (a day off, a child outside its parent): the working calendar example asks about them. wouldCreateCycle only explains: a link that closes a cycle is rejected whatever the hook says.
  • Hooks are not permissions. editing, locked and linkable stop a gesture from beginning (Read-only); a hook is asked at the end, with the whole change in hand.
  • React. The hooks are props like any handler, and always current: createHooks(env) runs on every render, so a hook reads the state of the latest one. viewMode is a controlled prop, so defaultViewMode is where the chart starts.