Skip to content

Commit 995c85d

Browse files
authored
feat(gantt): feature parity Phases 1-5 — links, time scales, hierarchy, interactions, virtualization (#1672)
1 parent f011479 commit 995c85d

59 files changed

Lines changed: 4044 additions & 169 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@object-ui/plugin-gantt": minor
3+
---
4+
5+
Gantt feature parity, Phases 1–5: dependency links, real time scales, hierarchy, interaction polish, and virtualization.
6+
7+
- **Dependency links**`task.dependencies` renders as orthogonal arrows in an SVG overlay, with all four MS-Project link types (`fs`/`ss`/`ff`/`sf`) via the object form `{ id, type }`. Arrows follow bars live during drag/resize; hovering a bar highlights its links. `normalizeDependencies` (exported) accepts CSV strings, id arrays, and object arrays with id/type aliases. New dependencies can be created by dragging from a bar's link dot onto another bar (`onDependencyCreate`).
8+
- **Real time scales** — day/week/month/quarter modes with a two-row header (group row + unit row), weekend tinting, zoom in/out, and a jump-to-today button.
9+
- **Hierarchy**`parent` builds a tree: collapsible summary rows with bracket-style summary bars aggregated from descendants, milestone diamonds, indent guides, and `aria-expanded`/`role="treeitem"` semantics. Dragging a summary bar moves its whole subtree by the same offset (live preview + one `onTaskUpdate` per task); the summary's displayed range rolls up from children, so moving a child past the parent's edge stretches the parent automatically.
10+
- **Interaction polish** — progress drag handle, hover tooltip, context menu (including delete), keyboard navigation/editing, inline title editing, and row drag-reorder (`onTaskReorder`).
11+
- **Scale** — virtualized rows *and* columns (spacer-based windowing; only the visible window is in the DOM, verified: 5,000 tasks render in ~27 ms with 26 rows in the DOM), a fullscreen toggle, and custom timeline `markers` (`{ date, label?, color? }`).
12+
13+
Colors that the prebuilt components stylesheet doesn't emit utilities for use theme CSS variables inline, so everything renders correctly in consuming apps.

.claude/launch.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
"runtimeExecutable": "pnpm",
77
"runtimeArgs": ["--filter", "@object-ui/console", "dev"],
88
"port": 5180
9+
},
10+
{
11+
"name": "gantt-demo",
12+
"runtimeExecutable": "pnpm",
13+
"runtimeArgs": ["--dir", "packages/plugin-gantt", "exec", "vite", "demo", "--port", "5199"],
14+
"port": 5199
915
}
1016
]
1117
}

ROADMAP.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1432,6 +1432,13 @@ Plugin architecture refactoring to support true modular development, plugin isol
14321432
- [ ] Plugin publish/validate tooling (spec v3.0.9 `PluginBuildOptions`, `PluginPublishOptions`)
14331433
- [ ] Cross-repo plugin loading from npm packages
14341434

1435+
### P2.11 Gantt Feature Parity (vs SVAR React Gantt)
1436+
1437+
> **Status:** Planned. Gap analysis done (June 2026) against [SVAR React Gantt](https://github.com/svar-widgets/react-gantt).
1438+
> Full phased task list lives in [`packages/plugin-gantt/ROADMAP.md`](packages/plugin-gantt/ROADMAP.md):
1439+
> Phase 1 dependency links → Phase 2 real time scales → Phase 3 hierarchy/milestones →
1440+
> Phase 4 interaction polish → Phase 5 virtualization → Phase 6 advanced (critical path, baselines, auto-scheduling).
1441+
14351442
---
14361443

14371444
## 🔮 P3 — Future Vision (Deferred)

packages/plugin-gantt/README.md

Lines changed: 108 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -227,11 +227,20 @@ const schema = {
227227

228228
## View Modes
229229

230-
The Gantt chart supports different time scales:
230+
The Gantt chart renders one timeline column per unit of the active scale:
231231

232-
- **day** - Day-by-day view
233-
- **week** - Week-by-week view
234-
- **month** - Month-by-month view
232+
- **day** - one column per day (weekday + weekend shading)
233+
- **week** - one column per week (starting Monday)
234+
- **month** - one column per calendar month
235+
- **quarter** - one column per quarter (Q1–Q4)
236+
237+
A two-row header shows the grouping above the units (months above days/weeks,
238+
years above months/quarters). The toolbar's segmented control switches scales
239+
interactively (`onViewChange` notifies you), and the zoom buttons step the
240+
column width — falling through to the next coarser/finer scale at the bounds.
241+
Drag snapping follows the active scale: bars snap to days in day view, weeks
242+
in week view, and whole calendar months/quarters (duration preserved) in the
243+
coarse views.
235244

236245
```typescript
237246
const schema = {
@@ -241,6 +250,79 @@ const schema = {
241250
};
242251
```
243252

253+
## Task Hierarchy, Summaries & Milestones
254+
255+
Give a task a `parent` (or configure `parentField` on the data-source schema)
256+
to build a tree: child rows indent under their parent with expand/collapse
257+
chevrons in the task list. Any task with children renders as a **summary**
258+
bracket spanning its children's combined date range, with progress rolled up
259+
as the duration-weighted average of its descendants — summaries are read-only,
260+
their children drive them.
261+
262+
Zero-duration tasks (`end <= start`) — or tasks whose `type` is
263+
`'milestone'` (via `typeField`: values like `milestone`, `summary`,
264+
`project`, `group` are recognized) — render as diamond markers. Milestones
265+
can be dragged to move but not resized; dependency arrows anchor at the
266+
diamond center.
267+
268+
```typescript
269+
const tasks = [
270+
{ id: 'phase1', title: 'Phase 1', start: '', end: '', progress: 0 }, // summary (has children)
271+
{ id: 't1', title: 'Design', parent: 'phase1', start: '', end: '', progress: 80 },
272+
{ id: 't2', title: 'Build', parent: 'phase1', start: '', end: '', progress: 20 },
273+
{ id: 'launch', title: 'Launch', type: 'milestone', start: '2024-07-01', end: '2024-07-01', progress: 0 },
274+
];
275+
```
276+
277+
## Interactions
278+
279+
Beyond drag-to-reschedule, the timeline supports:
280+
281+
- **Progress drag** — hover a bar and drag the round grip at the progress
282+
boundary; the fill follows live and `onTaskUpdate(task, { progress })`
283+
commits on release (snapped to whole percent, clamped 0–100).
284+
- **Hover tooltip** — bars, milestones and summaries show a tooltip with
285+
title, date range, duration and progress.
286+
- **Context menu** — right-click a bar or list row for View details / Edit
287+
inline / Delete (items appear only when the matching callback is wired).
288+
- **Keyboard navigation** — the chart body is focusable: ↑/↓ move the row
289+
selection, Enter opens the task, Delete deletes it, ←/→ collapse/expand
290+
summary rows. Rows carry `treeitem` roles with `aria-level`/`aria-selected`.
291+
- **Drag-to-create dependency** — drag the connector dot on a bar's right
292+
edge onto another bar; a dashed rubber band previews the link and
293+
`onDependencyCreate(source, target, 'fs')` fires on drop. Through
294+
`ObjectGantt` the new predecessor is appended to the record's
295+
`dependenciesField`, preserving the field's original shape (CSV or array).
296+
- **Row drag-to-reorder** — pass `onTaskReorder(task, before)` to enable
297+
HTML5 drag reordering in the task list (sibling-scoped; persistence is up
298+
to the host, e.g. via a sort field).
299+
300+
## Scale & Performance
301+
302+
Rows and timeline columns are **virtualized**: only what is in (or near) the
303+
viewport renders, so the chart stays responsive with thousands of tasks and
304+
multi-year day-scale ranges. No configuration needed — windowing follows the
305+
scroll position automatically, and dependency arrows keep their absolute
306+
positions while scrolling.
307+
308+
Two more chrome features ship with it:
309+
310+
- **Fullscreen** — the expand button in the toolbar puts the whole chart into
311+
native fullscreen (and back).
312+
- **Custom markers** — vertical reference lines beyond the Today marker:
313+
314+
```tsx
315+
<GanttView
316+
tasks={tasks}
317+
markers={[
318+
{ date: '2026-07-01', label: 'Code freeze', color: '#ef4444' },
319+
{ date: '2026-07-15', label: 'Release' }, // defaults to the primary theme color
320+
]}
321+
/>
322+
```
323+
324+
Through the schema, pass the same array as `markers` on the gantt node.
325+
244326
## Task Dependencies
245327

246328
Link tasks to show dependencies:
@@ -273,6 +355,28 @@ const tasks = [
273355
];
274356
```
275357

358+
Dependencies render as arrows from the predecessor bar to the dependent bar.
359+
Arrows follow bars live while dragging, and hovering a bar highlights its links.
360+
361+
### Link Types
362+
363+
Each dependency entry is either a predecessor id (`'task-1'`) or an object with
364+
an explicit link type:
365+
366+
```typescript
367+
dependencies: [
368+
{ id: 'task-1', type: 'fs' }, // finish-to-start (default)
369+
{ id: 'task-2', type: 'ss' }, // start-to-start
370+
{ id: 'task-3', type: 'ff' }, // finish-to-finish
371+
{ id: 'task-4', type: 'sf' }, // start-to-finish
372+
]
373+
```
374+
375+
When records come from a data source (`dependenciesField`), the field value may
376+
be a CSV string (`"task1, task2"`), an array of ids, or an array of objects —
377+
`task`/`target`/`_id` are accepted as id aliases, and long-form type names like
378+
`"finish_to_start"` / `"end-to-end"` map onto `fs`/`ss`/`ff`/`sf`.
379+
276380
## Integration with Data Sources
277381

278382
```typescript

packages/plugin-gantt/ROADMAP.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Gantt Plugin Roadmap — Feature Parity (vs SVAR React Gantt)
2+
3+
> **Status:** Planned. Gap analysis done (June 2026) against [SVAR React Gantt](https://github.com/svar-widgets/react-gantt).
4+
> Current `@object-ui/plugin-gantt` is a draggable day-scale bar chart; it lacks the project-management
5+
> semantics (dependencies, hierarchy, milestones, real time scales) that define a Gantt chart.
6+
> SVAR core is GPLv3 — feature reference only, no code reuse (we are MIT).
7+
>
8+
> Tracked from the main [ROADMAP.md](../../ROADMAP.md) § P2.11.
9+
10+
## What we already have
11+
12+
Bar drag/resize with day snapping + optimistic persistence, pinch-to-zoom, responsive/mobile pass,
13+
Today marker + jump-to-today, weekend highlighting, semantic color fallback, i18n, inline edit
14+
(double-click), record detail drawer on click, delete confirmation.
15+
16+
## Phase 1 — Dependency Links Rendering (highest ROI) ✅
17+
18+
- [x] Render dependency arrows as an SVG overlay in `GanttView` (orthogonal elbow routing, arrowhead markers, backward-link detour)
19+
- [x] Support the 4 link types: finish-to-start (default), start-to-start, finish-to-finish, start-to-finish — per-dependency `{ id, type }` object form; `normalizeDependencies` accepts CSV strings, id arrays, object arrays with id/type aliases
20+
- [x] Recompute arrow paths live during bar drag/resize preview
21+
- [x] Highlight a task's links on hover (and while dragging)
22+
- [x] Tests: link parsing (string id, array, `{id, type}` object), path anchors per type, hover highlight, drag re-render, backward links (8 new GanttView tests + 6 normalizeDependencies tests)
23+
24+
## Phase 2 — Real Time Scales (resurrect `viewMode`) ✅
25+
26+
- [x] Implement day/week/month/quarter column generation in `timeColumns` — calendar-true column widths over one linear ms→px mapping (`pxPerDay = columnWidth / nominalDays`), so bars, grid, links and the Today marker stay aligned in every mode
27+
- [x] Two-row scale header: month groups over day/week units, year groups over month/quarter units
28+
- [x] Restore the view-mode segmented control in the toolbar; zoom buttons fall through to the next coarser/finer granularity at min/max column width
29+
- [x] Drag snapping respects active granularity (day/week columns, calendar-clamped month/quarter shifts preserving duration on move)
30+
- [x] Tests: column generation per mode, header groups/labels, bar geometry per granularity, week + month snap behavior (8 new tests)
31+
32+
## Phase 3 — Task Hierarchy & Types ✅
33+
34+
- [x] `parentField` (+ `task.parent`) → task tree, depth-indented rows, expand/collapse chevrons in the task list; orphans and parent cycles surface as roots instead of dropping rows
35+
- [x] Summary (parent) bars: bracket-style slim bar with end caps spanning the children rollup range; read-only (children drive it)
36+
- [x] Milestone type: diamond marker via `typeField`/`task.type` or the `end <= start` heuristic; movable, not resizable; links anchor at the diamond center
37+
- [x] Auto-rollup: summary dates = min/max of descendants, progress = duration-weighted child progress (client-side, display via `data-progress`)
38+
- [x] Tests: tree building, orphan + cycle handling, collapse hides rows/links, summary range/progress math, milestone rendering + link anchors (9 new tests)
39+
40+
## Phase 4 — Interaction Polish ✅
41+
42+
- [x] Progress drag handle on the bar — grip at the progress boundary, 1% snapping, live fill preview, commits `onTaskUpdate({progress})`
43+
- [x] Rich hover tooltip (title, dates, duration, progress) on task bars, milestones and summary brackets
44+
- [x] Context menu on bar/row — View details / Edit inline / Delete; closes on outside click or Escape (add-dependency is covered by drag-to-create)
45+
- [x] Keyboard support: focusable gantt body, ArrowUp/Down row navigation, Enter to open, Delete to delete, ArrowLeft/Right collapse/expand; `tree`/`treeitem` roles with aria-level/-selected/-expanded
46+
- [x] Drag-to-create dependency — connector dot on bar edge, dashed rubber band, drop-target highlight; fires `onDependencyCreate(source, target, 'fs')`, `ObjectGantt` appends to the dependencies field preserving its shape (CSV ↔ array)
47+
- [x] Row drag-to-reorder — HTML5 drag in the task list, sibling-scoped, fires `onTaskReorder(task, before)` for the host to persist (sort-field wiring is host-specific)
48+
- [x] Tests: progress drag + clamping, tooltip, context menu routing/Escape, keyboard nav + collapse, link create + empty-space release, reorder + cross-parent guard (11 new tests)
49+
50+
## Phase 5 — Scale & Performance ✅
51+
52+
- [x] Virtualized row rendering (windowing) for both task list and timeline — spacer-div windowing over flattened rows (≈viewport + 6-row overscan rendered), driven by scroll position + ResizeObserver-measured viewport; dependency-link SVG keeps absolute row coordinates and skips links fully outside the window
53+
- [x] Virtualized timeline columns for multi-year ranges — prefix-sum column offsets + binary-free linear `visibleRange` scan; header groups, header units and the background grid render as absolutely-positioned cells inside the ±240px overscan window
54+
- [x] Fullscreen mode toggle — toolbar button drives the native Fullscreen API on the Gantt container, icon/aria reflect `fullscreenchange`
55+
- [x] Custom vertical markers — `markers` prop (`{date, label?, color?}`), rendered like the Today line on the shared ms→px mapping; out-of-range/invalid dates dropped; passed through `ObjectGantt` via `schema.markers`
56+
- [x] Tests: 1000-row windowing + spacer heights, scroll window shift, windowed link anchoring, multi-year column windowing, fullscreen enter/exit, marker mapping/fallback color (8 new tests)
57+
58+
## Phase 6 — Advanced (SVAR PRO territory, differentiators)
59+
60+
- [ ] Critical path computation + slack visualization
61+
- [ ] Baselines (planned vs actual bars)
62+
- [ ] Auto-scheduling: dependency-driven date shifting (forward, finish-to-start first)
63+
- [ ] Working calendar (skip weekends/holidays in duration math)
64+
- [ ] Undo/redo for drag/edit operations
65+
- [ ] Export: PNG/PDF (client-side), MS Project XML import/export
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Gantt Demo</title>
7+
<style>
8+
/* shadcn theme tokens — the prebuilt components CSS consumes these. */
9+
:root {
10+
--background: 0 0% 100%;
11+
--foreground: 240 10% 3.9%;
12+
--card: 0 0% 100%;
13+
--card-foreground: 240 10% 3.9%;
14+
--popover: 0 0% 100%;
15+
--popover-foreground: 240 10% 3.9%;
16+
--primary: 240 5.9% 10%;
17+
--primary-foreground: 0 0% 98%;
18+
--secondary: 240 4.8% 95.9%;
19+
--secondary-foreground: 240 5.9% 10%;
20+
--muted: 240 4.8% 95.9%;
21+
--muted-foreground: 240 3.8% 46.1%;
22+
--accent: 240 4.8% 95.9%;
23+
--accent-foreground: 240 5.9% 10%;
24+
--destructive: 0 84.2% 60.2%;
25+
--destructive-foreground: 0 0% 98%;
26+
--border: 240 5.9% 90%;
27+
--input: 240 5.9% 90%;
28+
--ring: 240 5.9% 10%;
29+
--radius: 0.5rem;
30+
}
31+
html, body, #root { height: 100%; margin: 0; }
32+
body { font-family: ui-sans-serif, system-ui, -apple-system, sans-serif; }
33+
</style>
34+
</head>
35+
<body>
36+
<div id="root"></div>
37+
<script type="module" src="/main.tsx"></script>
38+
</body>
39+
</html>

0 commit comments

Comments
 (0)