{"_id":"@art-tools/react-gantt","_rev":"4-b9c4477d781848ade62113d50cbe1cd0","name":"@art-tools/react-gantt","dist-tags":{"latest":"0.3.0"},"versions":{"0.1.0":{"name":"@art-tools/react-gantt","version":"0.1.0","keywords":["gantt","gantt-chart","project-management","react","scheduling","timeline","typescript","virtualized"],"author":{"name":"Artem Makatera"},"license":"MIT","_id":"@art-tools/react-gantt@0.1.0","maintainers":[{"name":"artmakatera","email":"artmakatera@gmail.com"}],"homepage":"https://github.com/artmakatera/react-gantt-edge#readme","bugs":{"url":"https://github.com/artmakatera/react-gantt-edge/issues"},"dist":{"shasum":"64957c948938258a2df88efc483373e24fe13d28","tarball":"https://registry.npmjs.org/@art-tools/react-gantt/-/react-gantt-0.1.0.tgz","fileCount":11,"integrity":"sha512-lJgTvYj2xBfcIflBft0cnwH9zeAzxbOjtjk9JwhiOb5tS+Y/F1TMdFPzExpHMDaBWf94DLQWSqMiQ80fDOi07A==","signatures":[{"sig":"MEQCIDwGmqgsz6ABFmfIHhjERbhgfZHUDqT2aU9j0RJVJ/o0AiADa0LW8NfYzXO3vI5kjYETJm+iSnCmuC6593n+a5ElNw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":974145},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">=22"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./style.css":{"style":"./dist/style.css","types":"./dist/style.css.d.ts","default":"./dist/style.css"},"./package.json":"./package.json"},"gitHead":"1a3f601dd83c2e95c7a9ba8fbbafb51102696e47","scripts":{"dev":"vite build --watch","lint":"oxlint","test":"vitest run","build":"vite build","lint:fix":"oxlint --fix","test:watch":"vitest","check-types":"tsc --noEmit","prepublishOnly":"pnpm run build"},"_npmUser":{"name":"artmakatera","email":"artmakatera@gmail.com"},"repository":{"url":"git+https://github.com/artmakatera/react-gantt-edge.git","type":"git","directory":"packages/react-gantt"},"_npmVersion":"11.16.0","description":"Composable, virtualized Gantt chart for React — dependencies, working-time calendars, undo/redo, and slot-based theming.","directories":{},"sideEffects":["**/*.css"],"_nodeVersion":"24.18.1","dependencies":{"clsx":"^2.1.1"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"vite":"^8.0.10","jsdom":"^29.1.1","react":"^19.2.5","vitest":"^4.1.5","react-dom":"^19.2.5","typescript":"^6.0.3","@types/react":"^19.2.14","vite-plugin-dts":"^5.0.0","@types/react-dom":"^19.2.0","@am/oxlint-config":"workspace:*","@testing-library/dom":"^10.4.1","@vitejs/plugin-react":"^6.0.1","@am/config-typescript":"workspace:*","@testing-library/react":"^16.3.2","@microsoft/api-extractor":"^7.59.0","@testing-library/jest-dom":"^6.9.1"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0","react-dom":"^18.0.0 || ^19.0.0"},"_npmOperationalInternal":{"tmp":"tmp/react-gantt_0.1.0_1787343365966_0.9538803348357465","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@art-tools/react-gantt","version":"0.2.0","keywords":["gantt","gantt-chart","project-management","react","scheduling","timeline","typescript","virtualized"],"author":{"name":"Artem Makatera"},"license":"MIT","_id":"@art-tools/react-gantt@0.2.0","maintainers":[{"name":"artmakatera","email":"artmakatera@gmail.com"}],"homepage":"https://github.com/artmakatera/react-gantt-edge#readme","bugs":{"url":"https://github.com/artmakatera/react-gantt-edge/issues"},"dist":{"shasum":"5789ca366d88a561bb41333345ed61446baa6e81","tarball":"https://registry.npmjs.org/@art-tools/react-gantt/-/react-gantt-0.2.0.tgz","fileCount":11,"integrity":"sha512-arBVwsrI4IN5tdbQxD6cKyRKsm+HKuM1lwONSE7D1tNpQR+3o69rG+KRlbumJEdWLqOis5YU0mv+D/A8F7YJXA==","signatures":[{"sig":"MEYCIQDKqOVGHIKsN/EaAUe2bQ41ry1nXRgv53N1QFrEY/WA1wIhAOil8YDq/ZbXKr9goiMWCgotH8gUrUsC5Msj7ehdEPi/","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1055300},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">=22"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./style.css":{"style":"./dist/style.css","types":"./dist/style.css.d.ts","default":"./dist/style.css"},"./package.json":"./package.json"},"gitHead":"9bee15ae2cff6b509c1a510ec5a5b8b5ef3fb45a","scripts":{"dev":"vite build --watch","lint":"oxlint","test":"vitest run","build":"vite build","lint:fix":"oxlint --fix","test:watch":"vitest","check-types":"tsc --noEmit","prepublishOnly":"pnpm run build"},"_npmUser":{"name":"artmakatera","email":"artmakatera@gmail.com"},"repository":{"url":"git+https://github.com/artmakatera/react-gantt-edge.git","type":"git","directory":"packages/react-gantt"},"_npmVersion":"11.16.0","description":"Composable, virtualized Gantt chart for React — dependencies, working-time calendars, undo/redo, and slot-based theming.","directories":{},"sideEffects":["**/*.css"],"_nodeVersion":"24.18.1","dependencies":{"clsx":"^2.1.1"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"vite":"^8.0.10","jsdom":"^29.1.1","react":"^19.2.5","vitest":"^4.1.5","react-dom":"^19.2.5","typescript":"^6.0.3","@types/react":"^19.2.14","vite-plugin-dts":"^5.0.0","@types/react-dom":"^19.2.0","@am/oxlint-config":"workspace:*","@testing-library/dom":"^10.4.1","@vitejs/plugin-react":"^6.0.1","@am/config-typescript":"workspace:*","@testing-library/react":"^16.3.2","@microsoft/api-extractor":"^7.59.0","@testing-library/jest-dom":"^6.9.1"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0","react-dom":"^18.0.0 || ^19.0.0"},"_npmOperationalInternal":{"tmp":"tmp/react-gantt_0.2.0_1787525088880_0.8814097953813533","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@art-tools/react-gantt","version":"0.2.1","keywords":["gantt","gantt-chart","project-management","react","scheduling","timeline","typescript","virtualized"],"author":{"name":"Artem Makatera"},"license":"MIT","_id":"@art-tools/react-gantt@0.2.1","maintainers":[{"name":"artmakatera","email":"artmakatera@gmail.com"}],"homepage":"https://github.com/artmakatera/react-gantt-edge#readme","bugs":{"url":"https://github.com/artmakatera/react-gantt-edge/issues"},"dist":{"shasum":"f0d1df4fdef224c5890b1dd039e6f961bcbefa5e","tarball":"https://registry.npmjs.org/@art-tools/react-gantt/-/react-gantt-0.2.1.tgz","fileCount":11,"integrity":"sha512-1VGX8YSnLyMmcNWkSopnLcG91qIJlH6mU70vYSyx4ux6jgwtLrLHlSJ/sGkZQM2kf+GjG8Iv4AdkgVcWHxGMgw==","signatures":[{"sig":"MEUCIQCD1XfV1bq6hUM/soUsM0Q0fqS5CjPItq/Y+RsDPuaSXgIgLQOVs/DClBptaQP5/SzOH8S3XRlwBfuTSfWoScQjZ38=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1056900},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">=22"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./style.css":{"style":"./dist/style.css","types":"./dist/style.css.d.ts","default":"./dist/style.css"},"./package.json":"./package.json"},"gitHead":"0eca8c751e5489e285b808021dace5051e455345","scripts":{"dev":"vite build --watch","lint":"oxlint","test":"vitest run","build":"vite build","lint:fix":"oxlint --fix","test:watch":"vitest","check-types":"tsc --noEmit","prepublishOnly":"pnpm run build"},"_npmUser":{"name":"artmakatera","email":"artmakatera@gmail.com"},"repository":{"url":"git+https://github.com/artmakatera/react-gantt-edge.git","type":"git","directory":"packages/react-gantt"},"_npmVersion":"11.16.0","description":"Composable, virtualized Gantt chart for React — dependencies, working-time calendars, undo/redo, and slot-based theming.","directories":{},"sideEffects":["**/*.css"],"_nodeVersion":"24.18.1","dependencies":{"clsx":"^2.1.1"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"vite":"^8.0.10","jsdom":"^29.1.1","react":"^19.2.5","vitest":"^4.1.5","react-dom":"^19.2.5","typescript":"^6.0.3","@types/react":"^19.2.14","vite-plugin-dts":"^5.0.0","@types/react-dom":"^19.2.0","@am/oxlint-config":"workspace:*","@testing-library/dom":"^10.4.1","@vitejs/plugin-react":"^6.0.1","@am/config-typescript":"workspace:*","@testing-library/react":"^16.3.2","@microsoft/api-extractor":"^7.59.0","@testing-library/jest-dom":"^6.9.1"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0","react-dom":"^18.0.0 || ^19.0.0"},"_npmOperationalInternal":{"tmp":"tmp/react-gantt_0.2.1_1787575293605_0.2666864650545464","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"_id":"@art-tools/react-gantt@0.3.0","bugs":{"url":"https://github.com/artmakatera/react-gantt-edge/issues"},"dist":{"shasum":"d381adfeced66cec7b8dd7392eee9e10ab6f8fc6","tarball":"https://registry.npmjs.org/@art-tools/react-gantt/-/react-gantt-0.3.0.tgz","fileCount":11,"integrity":"sha512-1lDbwpKZO2q2yZLjwcJX+7deKgj5FJQDMW16QffwapkOKukgavU8NkT9cU2XJEdToSCdLhKKcGaGL9pc2oDgfw==","signatures":[{"sig":"MEUCIQD63DorW0wKjFaYs7FwfIBwepgE5dMy2LF+3R/TDnB5IgIgA8t4go7cifoJjmFPskkwHQJ1dsaCCUosVcVX4I6pyVY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDOwmCTDmsz+lX3T/xSWGMhGuwz2dJImj66yPwOsIQAJAIgK+NQXbJLBTFCYnQG0cNKMhaCWcecq1jCSefGK0TkNPs="}],"unpackedSize":1100832},"main":"./dist/index.cjs","name":"@art-tools/react-gantt","type":"module","types":"./dist/index.d.ts","author":{"name":"Artem Makatera"},"module":"./dist/index.mjs","engines":{"node":">=22"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./style.css":{"style":"./dist/style.css","types":"./dist/style.css.d.ts","default":"./dist/style.css"},"./package.json":"./package.json"},"gitHead":"1b95e94cb3076040de25b464d88d6918862a8c74","license":"MIT","scripts":{"dev":"vite build --watch","lint":"oxlint","test":"vitest run","build":"vite build","lint:fix":"oxlint --fix","test:watch":"vitest","check-types":"tsc --noEmit","prepublishOnly":"pnpm run build"},"version":"0.3.0","_npmUser":{"name":"artmakatera","email":"artmakatera@gmail.com"},"homepage":"https://github.com/artmakatera/react-gantt-edge#readme","keywords":["gantt","gantt-chart","project-management","react","scheduling","timeline","typescript","virtualized"],"repository":{"url":"git+https://github.com/artmakatera/react-gantt-edge.git","type":"git","directory":"packages/react-gantt"},"_npmVersion":"11.16.0","description":"Composable, virtualized Gantt chart for React — dependencies, working-time calendars, undo/redo, and slot-based theming.","directories":{},"maintainers":[{"name":"artmakatera","email":"artmakatera@gmail.com"}],"sideEffects":["**/*.css"],"_nodeVersion":"24.18.1","dependencies":{"clsx":"^2.1.1"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"vite":"^8.0.10","jsdom":"^29.1.1","react":"^19.2.5","vitest":"^4.1.5","react-dom":"^19.2.5","typescript":"^6.0.3","@types/react":"^19.2.14","vite-plugin-dts":"^5.0.0","@types/react-dom":"^19.2.0","@am/oxlint-config":"workspace:*","@testing-library/dom":"^10.4.1","@vitejs/plugin-react":"^6.0.1","@am/config-typescript":"workspace:*","@testing-library/react":"^16.3.2","@microsoft/api-extractor":"^7.59.0","@testing-library/jest-dom":"^6.9.1"},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/react-gantt_0.3.0_1790170769718_0.5940443374034732"}}},"time":{"created":"2026-08-21T20:16:05.790Z","modified":"2026-09-23T13:39:29.988Z","0.1.0":"2026-08-21T20:16:06.135Z","0.2.0":"2026-08-23T22:44:49.029Z","0.2.1":"2026-08-24T12:41:33.777Z","0.3.0":"2026-09-23T13:39:29.807Z"},"bugs":{"url":"https://github.com/artmakatera/react-gantt-edge/issues"},"author":{"name":"Artem Makatera"},"license":"MIT","homepage":"https://github.com/artmakatera/react-gantt-edge#readme","keywords":["gantt","gantt-chart","project-management","react","scheduling","timeline","typescript","virtualized"],"repository":{"url":"git+https://github.com/artmakatera/react-gantt-edge.git","type":"git","directory":"packages/react-gantt"},"description":"Composable, virtualized Gantt chart for React — dependencies, working-time calendars, undo/redo, and slot-based theming.","maintainers":[{"name":"artmakatera","email":"artmakatera@gmail.com"}],"readme":"# @art-tools/react-gantt\n\nA high-performance, composable Gantt chart component library for React. It stays\nsmooth at **100,000 tasks** — rows **and** columns are virtualized, so the row\ncount barely affects render cost — and ships a full undo/redo transaction model,\ncascading dependency scheduling, and a slot system for deep customization.\n\n- **Composable** — drop in the all-in-one `<Gantt />`, or assemble\n  `<GanttProvider>` + `<TaskList>` + `<GanttGrid>` yourself.\n- **Interactive** — drag to move/resize bars, draw and delete dependency links,\n  edit progress, expand/collapse hierarchy.\n- **Fast** — windowed rendering with overscan, an incremental resolve cache, and\n  purpose-scoped React contexts so hot updates don't re-render stable subtrees.\n  See it at [100,000 tasks](https://art-tools-docs.vercel.app/examples/virtualization).\n\n**[Live examples & documentation →](https://art-tools-docs.vercel.app)** — every\nexample is interactive, with the source that rendered it shown underneath.\n\n---\n\n## Table of contents\n\n- [Install](#install)\n- [Quick start](#quick-start)\n- [Composable API](#composable-api)\n- [`GanttProps` reference](#ganttprops-reference)\n- [Data model](#data-model)\n- [Working time (calendars)](#working-time-calendars)\n- [Task bars](#task-bars)\n- [Dependencies & scheduling](#dependencies--scheduling)\n- [Columns](#columns)\n- [Read-only](#read-only)\n- [Imperative API](#imperative-api)\n- [Accessibility](#accessibility)\n- [Slots & theming](#slots--theming)\n- [Architecture (for contributors)](#architecture-for-contributors)\n- [Roadmap](#roadmap)\n\n---\n\n## Install\n\n```bash\npnpm add @art-tools/react-gantt\n```\n\nPeer dependencies: `react` and `react-dom` (`^19`). The only runtime\ndependency is [`clsx`](https://github.com/lukeed/clsx).\n\nImport the stylesheet once, near your app root:\n\n```ts\nimport \"@art-tools/react-gantt/style.css\";\n```\n\n---\n\n## Quick start\n\n```tsx\nimport { Gantt, type GanttTask } from \"@art-tools/react-gantt\";\nimport \"@art-tools/react-gantt/style.css\";\n\n// Build dates with the (year, monthIndex, day) constructor, never an ISO string —\n// `new Date(\"2023-01-10\")` parses as UTC midnight while the geometry reads local\n// civil instants. `endDate` is EXCLUSIVE: \"Install Apache\" occupies Jan 10 alone.\nconst tasks: GanttTask[] = [\n  {\n    id: 1000,\n    name: \"Launch Cloud Platform\",\n    startDate: new Date(2023, 0, 10),\n    endDate: new Date(2023, 0, 22),\n    type: \"summary\",\n  },\n  {\n    id: 1,\n    name: \"Setup web server\",\n    startDate: new Date(2023, 0, 10),\n    endDate: new Date(2023, 0, 14),\n    progress: 33,\n    parentId: 1000,\n  },\n  {\n    id: 11,\n    name: \"Install Apache\",\n    startDate: new Date(2023, 0, 10),\n    endDate: new Date(2023, 0, 11),\n    progress: 50,\n    parentId: 1,\n  },\n  {\n    id: 12,\n    name: \"Configure firewall\",\n    startDate: new Date(2023, 0, 10),\n    endDate: new Date(2023, 0, 12),\n    progress: 50,\n    parentId: 1,\n  },\n];\n\nexport function App() {\n  return <Gantt tasks={tasks} height={500} colWidth={60} rowHeight={40} />;\n}\n```\n\n`height` is **required** — when set, rows scroll vertically inside it while the\ncalendar header stays pinned.\n\n> The `tasks` prop is a **stable seed**: pass it once and never feed a resolved list\n> back into it. All create/update/delete/edit go through the internal change log via\n> the [imperative API](#imperative-api) and action columns, keeping undo/redo intact.\n> See [`docs/data-structures.md`](./docs/data-structures.md) for the full model.\n\nComplete, interactive examples — [100,000 tasks](https://art-tools-docs.vercel.app/examples/virtualization),\n[custom slots](https://art-tools-docs.vercel.app/examples/slots),\n[an edit dialog](https://art-tools-docs.vercel.app/examples/task-editing) and\n[undo/redo](https://art-tools-docs.vercel.app/examples/imperative-api) — each show\nthe source that rendered them.\n\n---\n\n## Composable API\n\n`<Gantt>` is a thin wrapper that wires the provider and lays out a resizable split\nview (task list pane + calendar/grid). For full layout control, compose the pieces\ndirectly:\n\n```tsx\nimport { GanttProvider, TaskList, GanttGrid } from \"@art-tools/react-gantt\";\n\n<GanttProvider tasks={tasks} height={500}>\n  <TaskList />\n  <GanttGrid />\n</GanttProvider>;\n```\n\nSet `hideTaskList` on `<Gantt>` to render only the calendar/grid (no task-list pane,\nno splitter).\n\nExported components: `Gantt`, `GanttProvider`, `GanttGrid`, `TaskList`, `TaskBar`,\n`ProjectBar`, `MilestoneBar`, `Calendar`.\n\n---\n\n## `GanttProps` reference\n\nDefined in [`src/types.ts`](./src/types.ts).\n\n### Data\n\n| Prop            | Type                          | Description                                                                                                            |\n| --------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------- |\n| `tasks`         | `GanttTask[]`                 | **Required.** Stable seed list (see the note in Quick start).                                                          |\n| `dependencies`  | `TaskDependency[]`            | Links between tasks (FS/FF/SS/SF, optional lag).                                                                       |\n| `criticalPath`  | `boolean`                     | Highlight the critical path — see [Dependencies & scheduling](#dependencies--scheduling). Default `false`.             |\n| `columns`       | `ColumnDef[]`                 | Task-list columns. Falls back to built-in default columns.                                                             |\n| `readOnly`      | `boolean`                     | Remove every editing affordance — see [Read-only](#read-only).                                                         |\n| `calendar`      | `GanttCalendar`               | Working-time definition. Supplying it opts into working-time scheduling — see [Working time](#working-time-calendars). |\n| `snapToWorking` | `boolean`                     | Default `true`. `false` keeps non-working shading but leaves dates untouched.                                          |\n| `durationUnit`  | `\"day\" \\| \"hour\" \\| \"minute\"` | How an input `duration` is interpreted and displayed. Default `\"day\"`.                                                 |\n\n### Layout\n\n| Prop                   | Type      | Description                                                                                    |\n| ---------------------- | --------- | ---------------------------------------------------------------------------------------------- |\n| `height`               | `number`  | **Required.** Total component height in px; enables the pinned header + vertical scroll.       |\n| `rowHeight`            | `number`  | Row height in px.                                                                              |\n| `colWidth`             | `number`  | Width of one day column in px.                                                                 |\n| `scales`               | `Scale[]` | Calendar header rows (defaults to month + day — see [`DEFAULT_SCALES`](./src/core/scales.ts)). |\n| `padDays`              | `number`  | Extra day columns padded before/after the task date range.                                     |\n| `defaultTaskListWidth` | `number`  | Initial width of the task-list pane.                                                           |\n| `hideTaskList`         | `boolean` | Render only the calendar/grid.                                                                 |\n\n### Callbacks\n\n| Prop                 | Signature                       | Fired when                                                      |\n| -------------------- | ------------------------------- | --------------------------------------------------------------- |\n| `onTaskClick`        | `(task) => void`                | A row/bar is selected.                                          |\n| `onDependencyCreate` | `(dep: TaskDependency) => void` | A link is drawn between two tasks.                              |\n| `onDependencyDelete` | `(dep: TaskDependency) => void` | A link is deleted.                                              |\n| `onTaskCreate`       | `(task, afterId?) => void`      | A task is created.                                              |\n| `onTaskDelete`       | `(id: Id) => void`              | A task is deleted.                                              |\n| `onTaskEdit`         | `(task) => void`                | A column's edit action fires (e.g. the actions-column pencil).  |\n| `onTasksChange`      | `(tasks: GanttTask[]) => void`  | The resolved list changes (after create/delete/edit/undo/redo). |\n\n### Other\n\n| Prop              | Type                     | Description                                                                         |\n| ----------------- | ------------------------ | ----------------------------------------------------------------------------------- |\n| `apiRef`          | `React.Ref<GanttHandle>` | The [imperative API](#imperative-api) handle.                                       |\n| `taskList`        | `GanttTaskListSlots`     | Slot overrides for the task-list pane (`treeCell`, `header`).                       |\n| `bars`            | `GanttBarsSlots`         | Slot overrides for timeline bars and their handles.                                 |\n| `dependencySlots` | `GanttDependenciesSlots` | Slot overrides for dependency links (named to avoid colliding with `dependencies`). |\n| `timeline`        | `GanttTimelineSlots`     | Slot overrides for the calendar/grid chrome.                                        |\n| `labels`          | `GanttLabels`            | Overrides for the [accessible strings](#accessibility). Pass a stable object.       |\n\n---\n\n## Data model\n\n```ts\ninterface GanttTask {\n  id: Id; // string | number\n  name: string;\n  startDate: Date;\n  endDate?: Date; // EXCLUSIVE — the instant work stops\n  duration?: number;\n  progress?: number; // 0–100\n  type?: \"task\" | \"milestone\" | \"summary\"; // default: \"task\"\n  parentId?: Id | null; // null/undefined = root\n}\n```\n\n- **`Id`** — `string | number`.\n- **`endDate` is exclusive** — it is the instant work _stops_, not the last day\n  worked. A task running Monday through Friday is\n  `{ startDate: Mon, endDate: Sat }`, and a 9-to-5 Friday task is\n  `Fri 09:00 → Fri 17:00`. This is what makes interval arithmetic work without\n  scattered ±1 day corrections. To show a user the inclusive last day, use\n  `api.format.endDate(task)` inside a column, or the exported `displayEndDate`\n  / `endInstantFromDisplayDate` helpers when bridging a date input.\n- **`duration`** — interpreted in the chart's `durationUnit` and, when a\n  `calendar` is set, counted in _working_ time. The library never writes this\n  field back; it derives dates from it and leaves your data alone.\n- **`type`** — `\"task\"` (default), `\"milestone\"` (a diamond at `startDate`), or\n  `\"summary\"` (a parent whose dates and progress roll up from its children). See\n  [Task bars](#task-bars).\n- **Hierarchy** — established via `parentId`. Roots have no `parentId`.\n\nDependencies are a separate array:\n\n```ts\ntype TaskDependencyType = \"FS\" | \"FF\" | \"SS\" | \"SF\";\n\ntype TaskDependency = {\n  from: Id;\n  to: Id;\n  type: TaskDependencyType;\n  lag?: number; // in `durationUnit`s; WORKING time when a calendar is set\n};\n```\n\nFor the full seed → change-log → resolved-list pipeline (transactions, cursor,\nroll-up), see [`docs/data-structures.md`](./docs/data-structures.md).\n\n---\n\n## Working time (calendars)\n\nBy default the chart schedules in plain linear time: weekends are shaded but a\nfive-day task dragged onto a Thursday simply ends on Monday. Pass a `calendar` to\nmake non-working time real — for the scheduler, for drag, and for the dependency\ncascade.\n\n```tsx\n<Gantt\n  tasks={tasks}\n  height={480}\n  calendar={{\n    hours: [\"8:00-12:00\", \"13:00-17:00\"], // lunch is the gap between ranges\n    days: {\n      0: false,\n      6: false, // weekends off (0 = Sunday)\n      5: [\"8:00-12:00\"], // short Friday\n    },\n    dates: {\n      \"2026-01-01\": false, // holiday\n      \"2026-01-10\": [\"9:00-13:00\"], // half day\n    },\n  }}\n/>\n```\n\nThree scopes resolve in the order **`dates` → `days` → `hours`**, so a specific\ndate beats a weekday rule, which beats the global default.\n\n| Prop            | Meaning                                                                                                                 |\n| --------------- | ----------------------------------------------------------------------------------------------------------------------- |\n| `calendar`      | The working-time definition. Supplying it _is_ the opt-in. Safe to write inline — it is keyed by content, not identity. |\n| `snapToWorking` | Default `true`. Set `false` to keep the shading but leave dates untouched.                                              |\n| `durationUnit`  | `\"day\"` (default), `\"hour\"`, or `\"minute\"` — how an input `duration` is read and displayed.                             |\n\nThings worth knowing before you rely on it:\n\n- **Omitting `hours` means whole days, not business hours.** A calendar that only\n  marks weekends off stays day-granular, so `duration: 3` is still three whole\n  days rather than three 8-hour shifts.\n- **A day off is just a day with no hours** (`false`), so working _days_ are the\n  degenerate case of working _time_ — there is no separate concept.\n- **One `durationUnit: \"day\"` is the week's longest working day.** With\n  `{ hours: [\"8:00-17:00\"] }` that is 9 hours. Adding a single longer weekday\n  therefore redefines \"a day\" for the whole chart.\n- **Your data is never rewritten.** Snapping applies only to dates the library\n  authors — drag commits and cascade results. A task you author ending on a\n  Sunday renders where you put it until it is first edited.\n- **Moves preserve working time, resizes set it.** Drag a three-working-day task\n  onto a Thursday and it stays three working days, growing visually across the\n  weekend. Drag its edge onto a Sunday and it settles back onto Friday.\n- **Non-working time is shaded, not compressed.** The time axis stays linear.\n  Slot consumers get `isNonWorking` and `nonWorkingReason`\n  (`\"weekend\" | \"holiday\" | \"offHours\"`) on the grid-column and calendar-cell\n  ownerStates.\n- **Known gap:** the actions column's \"add after\" button creates a task without\n  snapping it, because a column's `render` has no access to the calendar.\n\nThe reasoning behind each of these — including what was rejected — is recorded in\n[`docs/adr/`](../../docs/adr/README.md).\n\n## Task bars\n\n`Row` ([`src/components/bars/common/Row.tsx`](./src/components/bars/common/Row.tsx))\ncomputes each row's pixel geometry and dispatches on `type`:\n\n- **`task`** → `TaskBar` — draggable, resizable, with a progress fill and label.\n- **`milestone`** → `MilestoneBar` — a diamond marker, movable.\n- **`summary`** → `ProjectBar` — a rolled-up parent bar, movable.\n\n**Summary roll-up:** only `summary`-typed parents roll up\n(`getParentTaskData` in [`src/core/prepareData.ts`](./src/core/prepareData.ts)):\n`startDate` = min child start, `endDate` = max child end, `progress` = weighted mean\nof non-milestone children. A parent of any other `type` is left exactly as authored.\n\n> Note: the `summary` bar is still implemented by the component/file named\n> `ProjectBar`, and its theme variables are `--am-gantt-project-*` (the `deaf0b7`\n> rename covered the public task `type` value only).\n\n---\n\n## Dependencies & scheduling\n\nHover a bar to reveal start/end **connector handles**\n([`ConnectorHandles`](./src/components/bars/common/ConnectorHandles.tsx)); drag from\none bar's handle to another to create a link. The start/end handle combination maps\nto the four dependency types (`HANDLE_TO_TYPE` in\n[`src/hooks/useDependencyDrag.ts`](./src/hooks/useDependencyDrag.ts)):\nFinish-to-Start, Finish-to-Finish, Start-to-Start, Start-to-Finish. Links render as\nSVG polylines with arrowheads, a hit-area for selection, a delete button, and a lag\nlabel ([`DependencyLinks`](./src/components/dependency-links/DependencyLinks.tsx));\nthey're culled to the visible rect.\n\n**Cascading reschedule (ASAP):** when a task moves,\n[`scheduleDependents`](./src/core/scheduling.ts) walks the dependency graph and\nrealigns successors according to each relationship type and its `lag`. The move and\nall cascaded updates are committed as **one transaction**, so a drag-plus-cascade\nundoes in a single step.\n\n**Critical path:** set `criticalPath` to compute and highlight the chain of\ntasks and dependency links currently driving the chart's end date. Float is measured\nin working time from each task's _actual_ committed position — not a hypothetical\nearliest-possible schedule — so a task the chart visibly shows sitting with slack is\nnever flagged critical, and dragging a highlighted task or link is guaranteed to move\nthe end date (see [ADR-023](../../docs/adr/023-critical-path-is-computed-from-the-actual-schedule.md)\nfor the full reasoning). A dependency link is only highlighted when it is the specific\npredecessor actually pinning its successor's start, not merely a link between two\ncritical tasks. Off by default and computed only on commit, never mid-drag. Styled via\n`--am-gantt-critical-bg` (task/project bars) and `--am-gantt-critical-dependency-color`\n(links) — see [CSS custom properties](#css-custom-properties) — or through\n`ownerState.isCritical` on the `TaskBar`/`ProjectBar`/`MilestoneBar`/`DependencyLinks`\nslots for anything beyond a color change.\n\n---\n\n## Columns\n\n```ts\ninterface ColumnDef<T extends GanttTask = GanttTask> {\n  key: string;\n  header: string;\n  width?: number;\n  render: (task: T, api: ColumnApi) => React.ReactNode;\n  isTreeColumn?: boolean; // renders the indent + expand/collapse toggle\n}\n```\n\nEvery `render` receives a `ColumnApi` as its second argument — the full\n[imperative handle](#imperative-api) plus `editTask(task)` (which fires the\nconsumer's `onTaskEdit`). This is how columns build inline actions:\n\n```tsx\nconst columns: ColumnDef[] = [\n  { key: \"name\", header: \"Name\", isTreeColumn: true, render: (t) => t.name },\n  {\n    key: \"actions\",\n    header: \"\",\n    render: (task, api) => (\n      <>\n        <button onClick={() => api.editTask(task)}>✎</button>\n        <button onClick={() => api.deleteTask(task.id)}>✖</button>\n      </>\n    ),\n  },\n];\n```\n\nWhen `columns` is omitted, `TaskList` renders `DEFAULT_COLUMNS`\n([`src/components/taskList/TaskListHeader.tsx`](./src/components/taskList/TaskListHeader.tsx)):\nan `__action` column (edit / add-after / delete), a `__name` tree column, `__start`,\n`__end`, and `__progress`. Columns are resizable via a header divider (widths are\ntracked as session-local overrides).\n\n---\n\n## Read-only\n\n```tsx\n<Gantt tasks={tasks} height={500} readOnly />\n```\n\n`readOnly` removes every editing affordance rather than disabling one:\n\n| Gone                                           | Kept                                                      |\n| ---------------------------------------------- | --------------------------------------------------------- |\n| Bar move, resize, progress drag                | Row/bar selection, `onTaskClick`                          |\n| Dependency connector handles                   | Dependency links themselves (drawn as usual)              |\n| Link selection + its `×` / Delete-key deletion | Expand/collapse, scroll, zoom, column resize              |\n| The built-in `__action` column                 | The `__name` / `__start` / `__end` / `__progress` columns |\n\nNothing is rendered-but-inert: each affordance only exists when its handlers are\nwired, so there is no grabbable dead element and no disabled styling.\n\n`apiRef` keeps working — `readOnly` is about the pointer, not the data ([ADR-021](../../docs/adr/021-readonly-removes-affordances-not-the-api.md)).\nDrive an otherwise-frozen chart from your own toolbar:\n\n```tsx\n<Gantt tasks={tasks} height={500} readOnly apiRef={ref} />;\nref.current?.updateTask(id, { progress: 80 }); // still applies\n```\n\nCustom `columns` are yours to gate — `render` receives `api.readOnly`:\n\n```tsx\nrender: (task, api) =>\n  api.readOnly ? null : <button onClick={() => api.editTask(task)}>✎</button>,\n```\n\n`GanttProvider` takes the same prop, so the [composable API](#composable-api) behaves\nidentically.\n\n---\n\n## Imperative API\n\nPass an `apiRef` to reach the `GanttHandle`:\n\n```tsx\nconst ref = useRef<GanttHandle>(null);\n<Gantt apiRef={ref} tasks={tasks} height={500} />;\n\nref.current?.createTask(task, afterId); // afterId omitted → append at end\nref.current?.updateTask(id, { progress: 80 });\nref.current?.deleteTask(id);\nref.current?.undo();\nref.current?.redo();\nref.current?.revealTask(id); // vertical; add { horizontal: true } for the bar\nref.current?.revealTask(id, { horizontal: true }); // expands collapsed ancestors first\n```\n\n`updateTask` takes a `TaskPatch` (`name`, `startDate`, `endDate`, `progress`); only\nthe provided fields change. `createTask` runs synchronously (`flushSync`) so the new\ntask is visible to consumers immediately.\n\n---\n\n## Slots & theming\n\n### CSS custom properties\n\nThe default look is driven by `--am-gantt-*` variables in\n[`src/index.css`](./src/index.css). Override them in your own CSS to retheme:\n\n```css\n:root {\n  --am-gantt-task-bg: #0ba5ff; /* task bar fill */\n  --am-gantt-project-bg: #16a34a; /* summary bar fill */\n  --am-gantt-milestone-bg: #f59e0b; /* milestone diamond */\n  --am-gantt-critical-bg: #dc2626; /* critical-path bars, when criticalPath is set */\n  --am-gantt-critical-dependency-color: #dc2626; /* critical-path dependency links */\n  --am-gantt-calendar-header-bg: #f8fafc;\n  --am-gantt-calendar-weekend-bg: #f1f5f9;\n  /* …plus task colors, border radii, resizer sizing, z-indices */\n}\n```\n\n### Slots\n\nEvery customizable component follows the MUI `{ slots, slotProps }` pattern with an\n`ownerState` function form. `slots` swaps the underlying element/component; `slotProps`\nmerges props onto the library's defaults — `className` is `clsx`-merged, `style` is\nshallow-merged, and any other prop the consumer sets wins\n([`mergeSlotProps`](./src/core/slots.ts)).\n\nThe `<Gantt>` props group slots into four buckets: **`taskList`** (`treeCell`,\n`header`), **`bars`** (`taskBar`, `projectBar`, `milestoneBar`, progress, resizer,\nconnector handles), **`dependencySlots`** (links, preview), and **`timeline`**\n(calendar rows, grid columns, grid, resize handle).\n\n```tsx\n// Swap the tree-cell expand button for a custom component, and set its glyph\n// from ownerState. Keep the config object referentially stable (module-level or\n// memoized) — rows are memoized, so a fresh object each render re-renders them all.\nconst taskListSlots: GanttTaskListSlots = {\n  treeCell: {\n    slots: { expandButton: RoundToggle },\n    slotProps: {\n      expandButton: ({ isExpanded }) => ({ children: isExpanded ? \"−\" : \"+\" }),\n    },\n  },\n};\n\n// Restyle task bars via the `root` slot — merged, not replaced.\nconst barSlots: GanttBarsSlots = {\n  taskBar: { slotProps: { root: { style: { borderRadius: 8 } } } },\n};\n\n<Gantt tasks={tasks} height={500} taskList={taskListSlots} bars={barSlots} />;\n```\n\nPer-component slot types (`*Slots`, `*SlotProps`, `*SlotConfig`, `*OwnerState`) are\nall exported from the package entry.\n\n#### Bar tooltips\n\n`bars.tooltip` is one slot covering all three bar types, and it is **empty by\ndefault** — bars carry a native `title` and nothing more until you fill it. Setting\nit suppresses that native `title`, so the two do not stack (ADR-022).\n\n```tsx\nimport { Gantt, GanttBarTooltip } from \"@art-tools/react-gantt\";\n\n// The built-in tooltip: name, dates, progress.\n<Gantt tasks={tasks} height={500} bars={{ tooltip: { slots: { tooltip: GanttBarTooltip } } }} />;\n```\n\nIt triggers on the **bar**, not the row — a row spans the whole timeline width, so\na row-scoped trigger would fire over empty space far from the task. (The connector\nhandles still use row hover, so they appear as you approach a bar.)\n\nThe bar's default root is what renders it, so **replacing `slots.root` removes the\ntooltip**. A custom root can restore it by forwarding the `tooltip` prop it\nreceives on to a `DraggableBar`.\n\n`GanttBarTooltip` opens immediately on hover, sits bottom-right of the cursor and\nfollows it, and flips left or up rather than running off the edge.\n\n##### Writing your own\n\nThe slot is a **wrapper**: it receives the bar as `children` and has to render it.\nThere is no `open` prop, because the chart holds no open state — showing and hiding\nis entirely the slot's business. That is what lets the slot be a third-party\ntooltip, which arrives with its own root and trigger:\n\n```tsx\nimport { Tooltip } from \"@base-ui/react/tooltip\";\n\nconst Tip = ({ task, children }: BarTooltipProps) => (\n  <Tooltip.Root>\n    <Tooltip.Trigger render={children as React.ReactElement} />\n    <Tooltip.Portal>\n      <Tooltip.Popup>{task.name}</Tooltip.Popup>\n    </Tooltip.Portal>\n  </Tooltip.Root>\n);\n```\n\nFor the library's own hover behaviour with different markup, compose the three\nprimitives instead. `BarTooltipTrigger` merges onto the element you give it rather\nthan wrapping it, so it adds no DOM and keeps your handlers:\n\n```tsx\nimport { BarTooltipRoot, BarTooltipTrigger, useBarTooltip } from \"@art-tools/react-gantt\";\n\nconst Popup = ({ task }: { task: GanttTask }) =>\n  useBarTooltip()?.open ? <div className=\"tip\">{task.name}</div> : null;\n\nconst Tip = ({ task, children, anchorRef }: BarTooltipProps) => (\n  <BarTooltipRoot anchorRef={anchorRef}>\n    <BarTooltipTrigger>{children}</BarTooltipTrigger>\n    <Popup task={task} />\n  </BarTooltipRoot>\n);\n```\n\nAlongside `children`, the slot receives `task`, the resolved `progress`,\n`displayEnd`, and `anchorRef` — the bar's DOM node, for positioning against the\nbar rather than the cursor. Read `displayEnd` rather than `task.endDate`: stored\nends are **exclusive** instants, so a Mon–Fri task's raw `endDate` is Saturday\n(ADR-014).\n\nTwo more things. The tooltip is hover-only, because bars are not focusable yet;\nthe accessible name stays on the bar, so screen readers are unaffected either way.\nAnd a tooltip rendered **in place cannot paint over the sticky calendar header** —\nits row is a stacking context, so no z-index reaches past it. Portal it out, as\n`GanttBarTooltip` and the Base UI example above both do; reaching for `z-index`\nwill not work (ADR-022).\n\n---\n\n## Accessibility\n\nThe chart is fully readable by a screen reader in browse / table-navigation mode. It is\n**not yet keyboard-operable** — see [Roadmap](#1-keyboard-navigation).\n\n### Structure\n\nThe two panes are exposed as two widgets under one labelled `group`:\n\n| Element        | Role & state                                                                                             |\n| -------------- | -------------------------------------------------------------------------------------------------------- |\n| Widget root    | `group` + `aria-label` (`labels.gantt`)                                                                  |\n| Task-list pane | `treegrid` + `aria-label`, `aria-rowcount`, `aria-colcount`                                              |\n| Header row     | `row` `aria-rowindex=\"1\"`, cells `columnheader` + `aria-colindex`                                        |\n| Task row       | `row` + `aria-rowindex`, `aria-level`, `aria-expanded`, `aria-posinset`, `aria-setsize`, `aria-selected` |\n| Task cells     | `gridcell`, or `rowheader` for the tree column; each with `aria-colindex`                                |\n| Timeline pane  | `grid` + `aria-label`, `aria-rowcount`, `aria-colcount` (one column per date)                            |\n| Calendar row   | `row` + `aria-rowindex`, cells `columnheader` + `aria-colindex`/`aria-colspan`                           |\n| Bar            | `gridcell` + `aria-label`, `aria-colindex`/`aria-colspan` for its span on the date axis                  |\n\nBoth panes are virtualized, so `aria-rowcount` reports the **full** list while only a\nwindow is in the DOM, and every `aria-rowindex` is absolute. Counts and indices are\n1-based, and the timeline's task rows are offset by its calendar header rows.\n\nTwo deliberate choices are worth knowing about:\n\n- **A bar is one announcement.** Its `aria-label` carries name, type, dates and progress\n  (`\"Design phase, summary, 3 Mar 2026 to 12 Mar 2026, 40% complete\"`), and its inner\n  subtree is `aria-hidden`. So a bar reads as one coherent unit instead of a pile of\n  nested `div`s — and the timeline stays usable on its own when `hideTaskList` is set.\n- **Pointer-only affordances are hidden.** Bar move, resize, progress and dependency\n  creation are mouse-only drags, so the connector handles, resize grips, progress grip\n  and dependency-link layer are `aria-hidden` with `tabIndex={-1}`. They are not\n  advertised as controls that no key can activate. Tab visits only the tree expand\n  toggles and whatever buttons your columns render. Restore any of them through\n  `slotProps` if you wire up your own keyboard handling.\n\nCalendar headers announce the full period rather than the abbreviated visible text\n(`\"31 December 2021\"`, not `\"31\"`). Override per scale with `Scale.ariaFormat`.\n\n### Labels\n\nEvery accessible string is overridable — pass a **stable** (memoized) object, since rows\nand bars are memoized:\n\n```tsx\nconst labels = useMemo(\n  () => ({\n    gantt: \"Projektplan\",\n    taskList: \"Aufgabenliste\",\n    timeline: \"Zeitachse\",\n    expand: \"Aufklappen\",\n    collapse: \"Zuklappen\",\n    editTask: (task) => `${task.name} bearbeiten`,\n    bar: (task, { progress }) => `${task.name}, ${progress}% erledigt`,\n  }),\n  [],\n);\n\n<Gantt tasks={tasks} height={400} labels={labels} />;\n```\n\nOmitted keys keep their English defaults. `ColumnDef.render` receives the resolved set as\n`api.labels`, for naming controls a custom column renders.\n\n### Focus ring\n\nThe one keyboard-focusable control the library owns (the tree expand toggle) draws a\nfocus ring on `:focus-visible`, themeable via `--am-gantt-focus-ring-color`, `-width`\nand `-offset`.\n\n---\n\n## Architecture (for contributors)\n\n- **Frequency-split contexts** —\n  [`src/context/GanttContext.tsx`](./src/context/GanttContext.tsx) deliberately splits\n  state into ~9 purpose-scoped contexts/hooks by update frequency (`useGanttConfig`,\n  `useGanttTaskState`, `useGanttTaskActions`, `useGanttSelectedId`, `useGanttScroll`,\n  `useGanttViewport`, `useGanttDependency`, `useGanttDragActive`,\n  `useGanttDependencyDrag`), so a high-frequency update (e.g. drag coordinates)\n  doesn't re-render stable consumers. Grid-side slot groups flow through a separate\n  [`GanttSlotsProvider`](./src/context/GanttSlotsContext.tsx); the `taskList` group is\n  prop-drilled.\n- **Mutation model** — [`src/hooks/useTaskList.ts`](./src/hooks/useTaskList.ts) owns an\n  insertion-ordered `ChangeLog` of transactions with a cursor for undo/redo. Each user\n  action is one transaction. An incremental resolve cache\n  (`resolveCommittedTasksCached` in [`src/core/prepareData.ts`](./src/core/prepareData.ts))\n  backed by an [LRU cache](./src/core/lruCache.ts) keeps resolution fast regardless of\n  history length.\n- **Virtualization** — [`src/core/virtualize.ts`](./src/core/virtualize.ts)\n  (`rangeFromOffset`) windows both rows and date columns with overscan (see\n  [`src/core/constants.ts`](./src/core/constants.ts)); applied in `Grid` and `TaskList`,\n  and dependency links are culled to the visible rect.\n- **Scroll sync** — [`useScrollSync`](./src/hooks/useScrollSync.ts) keeps the list and\n  grid aligned vertically; [`useScrollToTask`](./src/hooks/useScrollToTask.ts) +\n  [`core/scroll.ts`](./src/core/scroll.ts) handle reveal-into-view.\n\n### Styling & build\n\n- **CSS Modules** (`*.module.css`) co-located with each component; theming via the\n  `--am-gantt-*` custom properties in `index.css`.\n- **Build** — Vite library mode emits ESM (`index.mjs`) + CJS (`index.cjs`) with\n  `.d.ts` (via `vite-plugin-dts`) and a single `style.css`; `react`/`react-dom` are\n  externalized.\n- **Tests** — Vitest + `@testing-library/react` (jsdom) under `src/tests/` and\n  `test/`; perf benchmarks run with `pnpm bench`.\n\n---\n\n## Roadmap\n\nProposed future features. These are **not yet implemented** — they capture gaps in the\ncurrent design and a sketch of how each would hook in.\n\n### 1. Baselines\n\nPropose accepting a baseline as data — the consumer supplies\nthe originally-planned dates, not the chart — and rendering it alongside the live\nbar, so schedule drift is visible at a glance instead of reconstructed from memory or\nan external doc.\n\n### 2. Custom timeline elements\n\nThe grid has no way to place anything on the calendar that isn't a task bar.\nPropose a way to render arbitrary elements into it, positioned by date rather than\nby row — a \"today\" line or a deadline marker being the obvious example.\n\n### 3. Export / print\n\nPropose export of the chart to PNG/SVG/PDF, plus a print-friendly render mode that\ntemporarily disables virtualization and renders the full extent so browser print\ncaptures every row.\n","readmeFilename":"README.md"}