@ganttloom/gantt-core 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +388 -0
- package/dist/index.cjs +3268 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +753 -0
- package/dist/index.d.ts +753 -0
- package/dist/index.js +3218 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +167 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Santhoshkumar Hariharan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
# @ganttloom/gantt-core
|
|
2
|
+
|
|
3
|
+
Framework-agnostic Gantt chart engine: layout, SVG rendering, drag/resize/link
|
|
4
|
+
interactions, grouping, critical path, undo/redo, and more — zero runtime
|
|
5
|
+
dependencies. If you're using React, prefer
|
|
6
|
+
[`@ganttloom/gantt-react`](../gantt-react); use this package directly if
|
|
7
|
+
you're integrating with something else (Vue, Svelte, vanilla JS) or want full
|
|
8
|
+
control over the DOM lifecycle.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @ganttloom/gantt-core
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quick start
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { GanttChart } from "@ganttloom/gantt-core";
|
|
20
|
+
import "@ganttloom/gantt-core/styles.css";
|
|
21
|
+
|
|
22
|
+
const container = document.getElementById("gantt")!;
|
|
23
|
+
|
|
24
|
+
const chart = new GanttChart(
|
|
25
|
+
container,
|
|
26
|
+
[
|
|
27
|
+
{ id: "1", name: "Design", start: new Date("2026-01-05"), end: new Date("2026-01-09"), progress: 60 },
|
|
28
|
+
{ id: "2", name: "Build", start: new Date("2026-01-09"), end: new Date("2026-01-16"), progress: 10 },
|
|
29
|
+
],
|
|
30
|
+
[{ fromId: "1", toId: "2", type: "FS" }],
|
|
31
|
+
{ viewMode: "day", showCriticalPath: true, enableHistory: true }
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
chart.on("date-change", ({ task, start, end }) => {
|
|
35
|
+
console.log(`${task.name} moved to ${start} - ${end}`);
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Tasks and dependencies are plain objects — the chart never owns your state.
|
|
40
|
+
Call `chart.setTasks(...)` / `chart.setDependencies(...)` whenever your data
|
|
41
|
+
changes; call `chart.destroy()` when you're done with it.
|
|
42
|
+
|
|
43
|
+
## Core concepts
|
|
44
|
+
|
|
45
|
+
- **`GanttTask`** — `id`, `name`, `start`/`end` (`Date`), optional `progress`,
|
|
46
|
+
`parentId` (for grouping), `isGroup`, `isMilestone`, `collapsed`, `color`,
|
|
47
|
+
`readonly`, `baselineStart`/`baselineEnd`, `deadline`, `constraintType` /
|
|
48
|
+
`constraintDate`, `assignees`, and a free-form `data` bag.
|
|
49
|
+
- **`GanttDependency`** — `fromId`, `toId`, a `type` of `"FS" | "SS" | "FF" | "SF"`,
|
|
50
|
+
and an optional `lagMs`.
|
|
51
|
+
- **`GanttColumn`** — describes one column of the left-hand grid panel, with
|
|
52
|
+
an optional `accessor` to derive its cell value from a task and `getHref`
|
|
53
|
+
to render it as a link.
|
|
54
|
+
- **`GanttOptions`** — everything else: view mode, theme, readonly, which
|
|
55
|
+
optional features are on, and the event callbacks. See [`src/types.ts`](./src/types.ts)
|
|
56
|
+
for the full, always-current list — it's the source of truth.
|
|
57
|
+
|
|
58
|
+
## Feature options (all opt-in)
|
|
59
|
+
|
|
60
|
+
| Option | What it does |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `showCriticalPath` | Highlights the critical path (longest dependency chain) |
|
|
63
|
+
| `showBaseline` | Renders `baselineStart`/`baselineEnd` as a faint bar behind the live one |
|
|
64
|
+
| `showDeadlines` (default `true`) | Renders `task.deadline` as a flag marker; red when the task's end is past it |
|
|
65
|
+
| `showAssigneeAvatars` | Renders `task.assignees` as small avatars/initials at the bar's left edge |
|
|
66
|
+
| `enableHistory` | Turns on `chart.undo()` / `chart.redo()` for move/resize/link/progress/create actions |
|
|
67
|
+
| `keyboardAccessible` | ARIA roles/labels on bars + arrow-key movement |
|
|
68
|
+
| `virtualScroll` (default `true`) | Only renders DOM/SVG nodes for rows in the visible viewport |
|
|
69
|
+
| `pagination` | `{ pageSize, page }` — render only a page of top-level rows |
|
|
70
|
+
| `autoSchedule` | Moving/resizing a task cascades date shifts to dependent successors |
|
|
71
|
+
| `calendar` | A `WorkingCalendar` (working days + holidays) — shades non-working time and, with `autoSchedule`, skips it when cascading |
|
|
72
|
+
| `snapToUnit` | Round drag/resize deltas to `"hour" \| "day" \| "week"`, or `false` for raw pixels |
|
|
73
|
+
| `colorScheme` | `"light" \| "dark" \| "auto"` (default `"auto"`, follows the OS preference live) |
|
|
74
|
+
|
|
75
|
+
## Working-time calendars
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import type { WorkingCalendar } from "@ganttloom/gantt-core";
|
|
79
|
+
|
|
80
|
+
const calendar: WorkingCalendar = {
|
|
81
|
+
workingDays: [false, true, true, true, true, true, false], // Sun..Sat; this is also the default
|
|
82
|
+
holidays: ["2026-01-19", "2026-02-16"], // ISO "YYYY-MM-DD"
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
new GanttChart(container, tasks, deps, { calendar, autoSchedule: true });
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Non-working days are shaded in the timeline. With `autoSchedule` on, a
|
|
89
|
+
successor task pushed by a dependency cascade is nudged forward to the next
|
|
90
|
+
working day. Standalone helpers (`isWorkingDay`, `isHoliday`, `nextWorkingDay`,
|
|
91
|
+
`previousWorkingDay`, `shiftToWorkingDay`) are also exported if you need the
|
|
92
|
+
same logic outside the chart.
|
|
93
|
+
|
|
94
|
+
## Task constraints
|
|
95
|
+
|
|
96
|
+
MSP-style constraints (`constraintType` + `constraintDate` on a `GanttTask`)
|
|
97
|
+
are enforced during `autoSchedule` cascades — when a dependency pushes a
|
|
98
|
+
constrained successor, its constraint is applied on top of the cascade
|
|
99
|
+
before the new dates are committed:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
{ id: "3", name: "Vendor delivery", start, end, constraintType: "mso", constraintDate: new Date("2026-02-01") }
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`ConstraintType` is one of `"asap" | "alap" | "snet" | "snlt" | "fnet" | "fnlt" | "mso" | "mfo"`
|
|
106
|
+
(as-soon-as-possible / as-late-as-possible / start-no-earlier/later-than /
|
|
107
|
+
finish-no-earlier/later-than / must-start/finish-on). `"asap"`/`"alap"` are
|
|
108
|
+
no-ops — a true as-late-as-possible schedule needs a backward pass from the
|
|
109
|
+
project end date, which this incremental, per-drag engine doesn't attempt.
|
|
110
|
+
The standalone `applyConstraint(range, type, date)` function is exported if
|
|
111
|
+
you want the same logic elsewhere. Constraints only take effect during a
|
|
112
|
+
cascade; they don't restrict a direct manual drag of the constrained task
|
|
113
|
+
itself (matching how MSP flags rather than blocks a violated constraint).
|
|
114
|
+
|
|
115
|
+
## Drag-to-create tasks
|
|
116
|
+
|
|
117
|
+
Opt in by supplying `onTaskCreate`: dragging on an empty part of any row's
|
|
118
|
+
timeline then creates a new sibling task (same `parentId` as that row) with
|
|
119
|
+
the dragged date range, inserted right after that row.
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
new GanttChart(container, tasks, deps, {
|
|
123
|
+
onTaskCreate: (task) => console.log("created", task),
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Without `onTaskCreate`, drag-to-create is off — dragging empty timeline
|
|
128
|
+
space does nothing, so existing consumers see no behavior change.
|
|
129
|
+
|
|
130
|
+
## Resource histogram
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import { computeResourceHistogram, renderResourceHistogramSVG } from "@ganttloom/gantt-core";
|
|
134
|
+
|
|
135
|
+
const buckets = computeResourceHistogram(tasks, { capacityHoursPerDay: 8, calendar });
|
|
136
|
+
// [{ assigneeId, assigneeName, date, allocatedHours, capacityHours, overallocated }, ...]
|
|
137
|
+
|
|
138
|
+
document.getElementById("histogram")!.innerHTML = renderResourceHistogramSVG(buckets);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Each assignee is treated as fully allocated (one day's capacity) on every
|
|
142
|
+
working day a task they're on spans — there's no per-task effort/FTE field
|
|
143
|
+
in `GanttTask`, so that's the only allocation model the plain task data can
|
|
144
|
+
support. `overallocated` is `true` once a bucket's summed hours exceed
|
|
145
|
+
`capacityHours` (default 8/day).
|
|
146
|
+
|
|
147
|
+
## WBS numbering
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
import { computeWBSCodes } from "@ganttloom/gantt-core";
|
|
151
|
+
|
|
152
|
+
const wbs = computeWBSCodes(tasks); // Map<taskId, "1" | "1.1" | "1.2" | "2" | ...>
|
|
153
|
+
const columns = [{ id: "wbs", title: "WBS", accessor: (t) => wbs.get(t.id) }];
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Codes are computed fresh from the current `tasks` array (recompute after any
|
|
157
|
+
reorder/renest) rather than stored on the task, keeping with "everything is
|
|
158
|
+
a plain object."
|
|
159
|
+
|
|
160
|
+
## Timeline markers
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
new GanttChart(container, tasks, deps, {
|
|
164
|
+
markers: [{ date: new Date("2026-02-01"), label: "Sprint boundary" }],
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Arbitrary labeled vertical lines on the timeline, independent of any task —
|
|
169
|
+
for release dates, sprint boundaries, or any other project-wide event.
|
|
170
|
+
|
|
171
|
+
## Grid column resize/reorder
|
|
172
|
+
|
|
173
|
+
Both off by default; supply the corresponding handler to opt in (same
|
|
174
|
+
construction-time-only wiring caveat as `onTaskCreate`):
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
new GanttChart(container, tasks, deps, {
|
|
178
|
+
columns,
|
|
179
|
+
onColumnResize: (columnId, width) => { /* persist the new width */ },
|
|
180
|
+
onColumnReorder: (order) => { /* persist the new column id order */ },
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Drag a column header's right edge to resize it; drag a header itself to
|
|
185
|
+
reorder columns. The chart applies the change to its own internal copy of
|
|
186
|
+
`columns` immediately (so the drag feels live) and calls back so you can
|
|
187
|
+
persist it — same pattern as a task drag and `onDateChange`.
|
|
188
|
+
|
|
189
|
+
## Task search/filter
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
import { filterTasks } from "@ganttloom/gantt-core";
|
|
193
|
+
|
|
194
|
+
const { tasks: visible, matchedIds } = filterTasks(tasks, (t) =>
|
|
195
|
+
t.name.toLowerCase().includes(query.toLowerCase())
|
|
196
|
+
);
|
|
197
|
+
chart.setTasks(visible);
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Pure/compute-only — it returns the filtered subset (keeping a matched
|
|
201
|
+
task's ancestors and descendants by default, so the tree stays intact) and
|
|
202
|
+
the set of directly-matched ids, which you can use to dim ancestor/descendant
|
|
203
|
+
rows shown only for tree context. There's no special renderer wiring: feeding
|
|
204
|
+
`chart.setTasks(visible)` a smaller array hides the rest, the same as any
|
|
205
|
+
other plain-data update.
|
|
206
|
+
|
|
207
|
+
## Scroll-to-today, group progress rollup, and custom cells
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
chart.scrollToToday(); // centers the viewport on today's date
|
|
211
|
+
|
|
212
|
+
new GanttChart(container, tasks, deps, {
|
|
213
|
+
autoRollupProgress: true, // a group with no explicit `progress` averages its children's, duration-weighted
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const columns = [
|
|
217
|
+
{ id: "status", title: "Status", render: (task) => {
|
|
218
|
+
const el = document.createElement("span");
|
|
219
|
+
el.className = task.progress === 100 ? "badge-done" : "badge-pending";
|
|
220
|
+
el.textContent = task.progress === 100 ? "Done" : "Pending";
|
|
221
|
+
return el; // or return a plain string - never parsed as HTML, so it's XSS-safe by construction
|
|
222
|
+
} },
|
|
223
|
+
];
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Row reorder and dependency editing
|
|
227
|
+
|
|
228
|
+
Both off by default; supply the corresponding handler to opt in:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
new GanttChart(container, tasks, deps, {
|
|
232
|
+
onTaskReorder: (draggedId, targetId, position) => {
|
|
233
|
+
// position: "before" | "after" (reorder as a sibling) | "inside" (reparent under target)
|
|
234
|
+
},
|
|
235
|
+
onDependencyDblClick: (dep) => {
|
|
236
|
+
// open your own "edit this link" UI, then call:
|
|
237
|
+
chart.updateDependency(dep.fromId, dep.toId, { type: "SS", lagMs: 3600_000 });
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Dragging a grid row onto another fires `onTaskReorder`; the chart applies
|
|
243
|
+
the reorder/reparent to its own internal task list immediately (live drag
|
|
244
|
+
feedback) the same way column resize does. Double-clicking a dependency
|
|
245
|
+
link fires `onDependencyDblClick` — the engine has no built-in "edit link"
|
|
246
|
+
popover (keeping with zero-dependency, minimal-UI scope), so build your own
|
|
247
|
+
and call `chart.updateDependency(...)` (undoable) to apply the result.
|
|
248
|
+
|
|
249
|
+
## Multi-select and bulk actions
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
new GanttChart(container, tasks, deps, {
|
|
253
|
+
selectable: true,
|
|
254
|
+
onSelectionChange: (taskIds) => console.log("selected:", taskIds),
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Click selects one; ctrl/cmd-click toggles; shift-click selects a range (by visible row order).
|
|
258
|
+
chart.selectTask("t1");
|
|
259
|
+
chart.selectTask("t2", { additive: true });
|
|
260
|
+
chart.bulkShiftDates(24 * 60 * 60 * 1000); // shift every selected task by 1 day, one undo step
|
|
261
|
+
chart.bulkDelete(); // remove every selected task (and touching dependencies), one undo step
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Split tasks
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
{ id: "qa", name: "QA pass", start, end, segments: [
|
|
268
|
+
{ start: day1, end: day2 },
|
|
269
|
+
{ start: day4, end: day5 }, // a gap, e.g. paused for an upstream fix
|
|
270
|
+
] }
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
`start`/`end` stay the authoritative overall envelope (used for
|
|
274
|
+
dependencies, critical path, baseline, etc.); `segments` only changes how
|
|
275
|
+
the bar is *drawn* — as separate rects joined by a dashed connector.
|
|
276
|
+
Dragging the whole task shifts every segment together; resizing an edge
|
|
277
|
+
only adjusts the first (left) or last (right) segment's outer edge, leaving
|
|
278
|
+
any gap untouched.
|
|
279
|
+
|
|
280
|
+
## Task notes, auto-color, baseline history, and density presets
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
{ id: "t1", name: "Ship v2", start, end, notes: "Blocked on legal review" } // hover the bar for a native tooltip
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
import { applyColorByField } from "@ganttloom/gantt-core";
|
|
288
|
+
|
|
289
|
+
const colored = applyColorByField(tasks, (t) => t.assignees?.[0]?.id); // one color per first assignee
|
|
290
|
+
new GanttChart(container, colored, deps);
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
{ id: "t1", name: "Ship v2", start, end, baselines: [
|
|
295
|
+
{ label: "Original plan", start: d1, end: d2 },
|
|
296
|
+
{ label: "As of last Friday", start: d3, end: d4 },
|
|
297
|
+
] } // rendered as multiple faint stacked bars, oldest furthest back; requires showBaseline: true
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
import { DENSITY_PRESETS } from "@ganttloom/gantt-core";
|
|
302
|
+
|
|
303
|
+
new GanttChart(container, tasks, deps, { theme: { ...DENSITY_PRESETS.compact } });
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
None of these need a core option - `notes`/`baselines` are plain `GanttTask` fields, `applyColorByField`/`DENSITY_PRESETS` are pure helpers/data you apply before constructing the chart.
|
|
307
|
+
|
|
308
|
+
## Partial-day working hours
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
const calendar: WorkingCalendar = {
|
|
312
|
+
holidays: ["2026-01-19"],
|
|
313
|
+
workingHours: { start: 9, end: 17 }, // 9am-5pm
|
|
314
|
+
};
|
|
315
|
+
new GanttChart(container, tasks, deps, { calendar, autoSchedule: true, viewMode: "hour" });
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Narrows *anchor points*, not duration math: a 3-day task cascaded by
|
|
319
|
+
`autoSchedule` lands its new start within the working window (not at
|
|
320
|
+
11pm), but stays a 3-calendar-day task, not "3 working-window days." Hour
|
|
321
|
+
view shades individual non-working hours (not just whole non-working days).
|
|
322
|
+
`isWithinWorkingHours`/`isWorkingTime`/`shiftToWorkingTime` are exported if
|
|
323
|
+
you need the same logic elsewhere.
|
|
324
|
+
|
|
325
|
+
## Resource leveling
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
const { tasks: leveled, shifted } = chart.getLeveledTasks();
|
|
329
|
+
// or standalone: levelResources(tasks, dependencies, { calendar })
|
|
330
|
+
chart.setTasks(leveled); // apply it
|
|
331
|
+
console.log(shifted); // [{ taskId, delayMs }, ...] - what moved and by how much
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Delays (never pulls earlier) tasks so no assignee is double-booked on a
|
|
335
|
+
working day, using the same "fully allocated while active" model as
|
|
336
|
+
`computeResourceHistogram` - so a leveled schedule always shows zero
|
|
337
|
+
`overallocated` buckets afterward. Respects FS ("finish-to-start")
|
|
338
|
+
dependencies (never places a task before its FS predecessors finish);
|
|
339
|
+
SS/FF/SF aren't specifically enforced - a documented scope limit rather
|
|
340
|
+
than a full constraint solver.
|
|
341
|
+
|
|
342
|
+
## Multi-page PNG export
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
const pages = await chart.toPNGDataURLs({ pageWidthPx: 1600, pageHeightPx: 1200 });
|
|
346
|
+
// [dataUrl1, dataUrl2, ...] in row-major tile order, each at full fidelity
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
For charts too large to comfortably rasterize as one image; omit the
|
|
350
|
+
options to get a single image at the chart's actual size (same as
|
|
351
|
+
`toPNGDataURL()`).
|
|
352
|
+
|
|
353
|
+
## Keyboard navigation
|
|
354
|
+
|
|
355
|
+
With `keyboardAccessible: true`: Tab/Shift+Tab move between bars (native
|
|
356
|
+
browser focus order - every bar has `tabindex="0"`), arrow keys nudge a
|
|
357
|
+
focused bar by a day, Enter clicks it, **Home**/**End** scroll the timeline
|
|
358
|
+
to the start/end of the project's date range, and **Ctrl/Cmd+A** selects
|
|
359
|
+
every visible task when `selectable` is also on.
|
|
360
|
+
|
|
361
|
+
## Zoom
|
|
362
|
+
|
|
363
|
+
```ts
|
|
364
|
+
chart.zoomIn(); // 1.25x by default, clamps at a 400px/unit ceiling
|
|
365
|
+
chart.zoomOut(1.5); // custom factor
|
|
366
|
+
chart.getZoomLevel(); // current columnWidth, in px/unit
|
|
367
|
+
chart.fitToViewport(containerEl.clientWidth); // zoom-to-fit: scales so the whole project fits
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
## Export / import
|
|
371
|
+
|
|
372
|
+
- `chart.toSVGString()` / `chart.toPNGDataURL()` — raster/vector snapshot of the current render.
|
|
373
|
+
- `tasksToCSV(tasks)` / `tasksFromCSV(csv)` — round-trip through CSV.
|
|
374
|
+
- For PDF/PPTX, see [`@ganttloom/gantt-export`](../gantt-export), which consumes
|
|
375
|
+
`chart.getRenderModel()` directly.
|
|
376
|
+
|
|
377
|
+
## Theming
|
|
378
|
+
|
|
379
|
+
Every visual token is a `--gantt-*` CSS custom property (see `styles.css`),
|
|
380
|
+
in addition to the JS `theme` option (`Partial<GanttTheme>`) — style via
|
|
381
|
+
either, or both. `colorScheme: "auto"` (the default) follows
|
|
382
|
+
`prefers-color-scheme` live.
|
|
383
|
+
|
|
384
|
+
## API surface
|
|
385
|
+
|
|
386
|
+
The full, current type/function list is re-exported from
|
|
387
|
+
[`src/index.ts`](./src/index.ts) — treat it as the reference; this README
|
|
388
|
+
covers the concepts, not every signature.
|