@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/dist/index.d.cts
ADDED
|
@@ -0,0 +1,753 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global working-time definition: which weekdays are worked, plus a list of
|
|
3
|
+
* one-off non-working dates (holidays). Both are optional — an unset
|
|
4
|
+
* `workingDays` defaults to Mon-Fri, an unset `holidays` list is empty.
|
|
5
|
+
*/
|
|
6
|
+
interface WorkingCalendar {
|
|
7
|
+
/** index 0 = Sunday ... 6 = Saturday. Defaults to Mon-Fri working. */
|
|
8
|
+
workingDays?: boolean[];
|
|
9
|
+
/** ISO "YYYY-MM-DD" dates that are non-working regardless of weekday. */
|
|
10
|
+
holidays?: string[];
|
|
11
|
+
/**
|
|
12
|
+
* Daily working window, hour-of-day 0-24 (e.g. { start: 9, end: 17 }).
|
|
13
|
+
* Only affects `isWorkingTime`/`shiftToWorkingTime` and hour-level tick
|
|
14
|
+
* shading - task durations stay wall-clock (a 3-day task is still 3
|
|
15
|
+
* calendar days, not 3 working-window days); this narrows *anchor
|
|
16
|
+
* points* to the working window, it doesn't re-derive duration math.
|
|
17
|
+
*/
|
|
18
|
+
workingHours?: {
|
|
19
|
+
start: number;
|
|
20
|
+
end: number;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
declare function isHoliday(date: Date, calendar?: WorkingCalendar): boolean;
|
|
24
|
+
declare function isWorkingDay(date: Date, calendar?: WorkingCalendar): boolean;
|
|
25
|
+
/** Next working day at/after `date` (same time-of-day), scanning forward at most 366 days. */
|
|
26
|
+
declare function nextWorkingDay(date: Date, calendar?: WorkingCalendar): Date;
|
|
27
|
+
/** Previous working day at/before `date` (same time-of-day), scanning backward at most 366 days. */
|
|
28
|
+
declare function previousWorkingDay(date: Date, calendar?: WorkingCalendar): Date;
|
|
29
|
+
/**
|
|
30
|
+
* Nudge `date` onto a working day if it isn't already, moving in `direction`
|
|
31
|
+
* (1 = forward, -1 = backward). No-op (besides cloning) when no calendar is given
|
|
32
|
+
* or `date` is already a working day.
|
|
33
|
+
*/
|
|
34
|
+
declare function shiftToWorkingDay(date: Date, calendar: WorkingCalendar | undefined, direction: 1 | -1): Date;
|
|
35
|
+
declare function isWithinWorkingHours(date: Date, calendar?: WorkingCalendar): boolean;
|
|
36
|
+
/** Working day AND (no workingHours set, or within them). */
|
|
37
|
+
declare function isWorkingTime(date: Date, calendar?: WorkingCalendar): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Like shiftToWorkingDay, but also snaps onto the daily working-hours window
|
|
40
|
+
* when one is configured: forward past a day's end (or on a non-working day)
|
|
41
|
+
* moves to the next working day's start; backward before a day's start (or
|
|
42
|
+
* on a non-working day) moves to the previous working day's end. No-op
|
|
43
|
+
* without a calendar.
|
|
44
|
+
*/
|
|
45
|
+
declare function shiftToWorkingTime(date: Date, calendar: WorkingCalendar | undefined, direction: 1 | -1): Date;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* MSP-style task constraint types.
|
|
49
|
+
*
|
|
50
|
+
* "asap" and "alap" are intentionally no-ops in this engine: a true ALAP
|
|
51
|
+
* (as-late-as-possible) schedule requires a backward pass from the project
|
|
52
|
+
* end date, which isn't something a purely local, per-drag/per-cascade
|
|
53
|
+
* engine can do without a full re-solve. The other six are hard date
|
|
54
|
+
* constraints applied locally, which fit the same incremental model used
|
|
55
|
+
* by dependency cascading.
|
|
56
|
+
*/
|
|
57
|
+
type ConstraintType = "asap" | "alap" | "snet" | "snlt" | "fnet" | "fnlt" | "mso" | "mfo";
|
|
58
|
+
interface DateRange {
|
|
59
|
+
start: Date;
|
|
60
|
+
end: Date;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Adjust [start, end] to satisfy a constraint, preserving the task's
|
|
64
|
+
* duration. No-op if `constraintType` or `constraintDate` is unset, or the
|
|
65
|
+
* range already satisfies the constraint.
|
|
66
|
+
*/
|
|
67
|
+
declare function applyConstraint(range: DateRange, constraintType: ConstraintType | undefined, constraintDate: Date | undefined): DateRange;
|
|
68
|
+
|
|
69
|
+
type ViewMode = "hour" | "day" | "week" | "month" | "quarter" | "year";
|
|
70
|
+
type DependencyType = "FS" | "SS" | "FF" | "SF";
|
|
71
|
+
|
|
72
|
+
interface GanttDependency {
|
|
73
|
+
/** id of the predecessor task */
|
|
74
|
+
fromId: string;
|
|
75
|
+
/** id of the successor task */
|
|
76
|
+
toId: string;
|
|
77
|
+
type: DependencyType;
|
|
78
|
+
/** optional lag/lead in milliseconds, negative = lead */
|
|
79
|
+
lagMs?: number;
|
|
80
|
+
}
|
|
81
|
+
interface GanttAssignee {
|
|
82
|
+
id: string;
|
|
83
|
+
name: string;
|
|
84
|
+
/** image URL; if omitted, initials are derived from name */
|
|
85
|
+
avatarUrl?: string;
|
|
86
|
+
color?: string;
|
|
87
|
+
}
|
|
88
|
+
interface GanttTask {
|
|
89
|
+
id: string;
|
|
90
|
+
name: string;
|
|
91
|
+
start: Date;
|
|
92
|
+
end: Date;
|
|
93
|
+
/** 0-100 */
|
|
94
|
+
progress?: number;
|
|
95
|
+
parentId?: string | null;
|
|
96
|
+
/** true if this task is a group/summary row (auto-computed span from children if not set) */
|
|
97
|
+
isGroup?: boolean;
|
|
98
|
+
/** render as a diamond marker instead of a bar; auto-detected when start equals end */
|
|
99
|
+
isMilestone?: boolean;
|
|
100
|
+
collapsed?: boolean;
|
|
101
|
+
color?: string;
|
|
102
|
+
progressColor?: string;
|
|
103
|
+
/** disallow drag/resize interactions for this task */
|
|
104
|
+
readonly?: boolean;
|
|
105
|
+
/** original planned dates, rendered as a faint bar behind the live bar */
|
|
106
|
+
baselineStart?: Date;
|
|
107
|
+
baselineEnd?: Date;
|
|
108
|
+
/**
|
|
109
|
+
* Render this task as multiple bar segments instead of one continuous bar
|
|
110
|
+
* (MSP-style "split task"). `start`/`end` above remain the authoritative
|
|
111
|
+
* overall envelope - keep segments within it. Dragging the whole task
|
|
112
|
+
* shifts every segment together; resizing an edge only adjusts the
|
|
113
|
+
* first (left) or last (right) segment's outer edge.
|
|
114
|
+
*/
|
|
115
|
+
segments?: {
|
|
116
|
+
start: Date;
|
|
117
|
+
end: Date;
|
|
118
|
+
}[];
|
|
119
|
+
/** target completion date, rendered as a flag marker; end date past this renders as overdue */
|
|
120
|
+
deadline?: Date;
|
|
121
|
+
/** MSP-style scheduling constraint; see ConstraintType for the eight supported kinds */
|
|
122
|
+
constraintType?: ConstraintType;
|
|
123
|
+
/** the date the constraint is relative to (ignored for "asap"/"alap") */
|
|
124
|
+
constraintDate?: Date;
|
|
125
|
+
assignees?: GanttAssignee[];
|
|
126
|
+
/** shown as a native hover tooltip on the bar (an SVG <title>, so it works with zero extra markup/JS) */
|
|
127
|
+
notes?: string;
|
|
128
|
+
/** additional named baseline snapshots beyond baselineStart/baselineEnd (e.g. "as of last Friday") - rendered as extra faint bars, oldest furthest back */
|
|
129
|
+
baselines?: {
|
|
130
|
+
label: string;
|
|
131
|
+
start: Date;
|
|
132
|
+
end: Date;
|
|
133
|
+
}[];
|
|
134
|
+
/** arbitrary extra fields, referenced by grid columns */
|
|
135
|
+
data?: Record<string, unknown>;
|
|
136
|
+
}
|
|
137
|
+
interface GanttColumn {
|
|
138
|
+
id: string;
|
|
139
|
+
title: string;
|
|
140
|
+
width?: number;
|
|
141
|
+
/** how to read the display value for this column */
|
|
142
|
+
accessor?: (task: GanttTask) => string | number | null | undefined;
|
|
143
|
+
/** if provided, cell renders as an <a href=...> using this to compute the link target */
|
|
144
|
+
getHref?: (task: GanttTask) => string | null | undefined;
|
|
145
|
+
/**
|
|
146
|
+
* Escape hatch for rich cell content (badges, progress bars, buttons, ...):
|
|
147
|
+
* return an HTMLElement to mount directly, or a plain string rendered as
|
|
148
|
+
* text (never parsed as HTML, so this stays XSS-safe without callers
|
|
149
|
+
* needing to think about it). Takes precedence over `accessor`/`getHref`.
|
|
150
|
+
*/
|
|
151
|
+
render?: (task: GanttTask) => HTMLElement | string | null | undefined;
|
|
152
|
+
/** anchor target, defaults to "_blank" */
|
|
153
|
+
linkTarget?: string;
|
|
154
|
+
align?: "left" | "center" | "right";
|
|
155
|
+
}
|
|
156
|
+
interface GanttTheme {
|
|
157
|
+
rowHeight: number;
|
|
158
|
+
barHeight: number;
|
|
159
|
+
headerHeight: number;
|
|
160
|
+
gridColor: string;
|
|
161
|
+
weekendColor: string;
|
|
162
|
+
todayColor: string;
|
|
163
|
+
barColor: string;
|
|
164
|
+
barProgressColor: string;
|
|
165
|
+
groupBarColor: string;
|
|
166
|
+
linkColor: string;
|
|
167
|
+
criticalColor: string;
|
|
168
|
+
baselineColor: string;
|
|
169
|
+
deadlineColor: string;
|
|
170
|
+
markerColor: string;
|
|
171
|
+
selectionColor: string;
|
|
172
|
+
textColor: string;
|
|
173
|
+
backgroundColor: string;
|
|
174
|
+
fontFamily: string;
|
|
175
|
+
fontSize: number;
|
|
176
|
+
borderRadius: number;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Fully-resolved, framework-agnostic geometry for one render pass.
|
|
180
|
+
* This is the bridge consumed by @ganttloom/gantt-export (PDF/PPTX)
|
|
181
|
+
* and by any custom renderer that doesn't want to reimplement layout.
|
|
182
|
+
*/
|
|
183
|
+
interface GanttRenderRow {
|
|
184
|
+
taskId: string;
|
|
185
|
+
y: number;
|
|
186
|
+
height: number;
|
|
187
|
+
isGroup: boolean;
|
|
188
|
+
collapsed: boolean;
|
|
189
|
+
depth: number;
|
|
190
|
+
hasChildren: boolean;
|
|
191
|
+
}
|
|
192
|
+
interface GanttRenderBar {
|
|
193
|
+
taskId: string;
|
|
194
|
+
x: number;
|
|
195
|
+
y: number;
|
|
196
|
+
width: number;
|
|
197
|
+
height: number;
|
|
198
|
+
progressWidth: number;
|
|
199
|
+
color: string;
|
|
200
|
+
progressColor: string;
|
|
201
|
+
label: string;
|
|
202
|
+
isCritical: boolean;
|
|
203
|
+
isMilestone: boolean;
|
|
204
|
+
baseline?: {
|
|
205
|
+
x: number;
|
|
206
|
+
width: number;
|
|
207
|
+
};
|
|
208
|
+
/** rendered from task.baselines, oldest last (furthest back visually) */
|
|
209
|
+
baselines?: {
|
|
210
|
+
label: string;
|
|
211
|
+
x: number;
|
|
212
|
+
width: number;
|
|
213
|
+
}[];
|
|
214
|
+
/** x position of the task's deadline marker, in chart coordinate space */
|
|
215
|
+
deadlineX?: number;
|
|
216
|
+
/** true when the task's end date is past its deadline */
|
|
217
|
+
isOverdue?: boolean;
|
|
218
|
+
/** present (non-empty) when task.segments was set - x/width are local to this bar's x, like the bar itself */
|
|
219
|
+
segments?: {
|
|
220
|
+
x: number;
|
|
221
|
+
width: number;
|
|
222
|
+
progressWidth: number;
|
|
223
|
+
}[];
|
|
224
|
+
isSelected?: boolean;
|
|
225
|
+
}
|
|
226
|
+
interface GanttRenderLink {
|
|
227
|
+
fromId: string;
|
|
228
|
+
toId: string;
|
|
229
|
+
type: DependencyType;
|
|
230
|
+
/** SVG path "d" attribute, in chart coordinate space */
|
|
231
|
+
path: string;
|
|
232
|
+
isCritical: boolean;
|
|
233
|
+
}
|
|
234
|
+
/** An arbitrary labeled vertical line on the timeline, independent of any task (e.g. "Sprint boundary", "Release day"). */
|
|
235
|
+
interface GanttMarker {
|
|
236
|
+
date: Date;
|
|
237
|
+
label?: string;
|
|
238
|
+
color?: string;
|
|
239
|
+
}
|
|
240
|
+
interface GanttRenderMarker {
|
|
241
|
+
x: number;
|
|
242
|
+
label: string;
|
|
243
|
+
color: string;
|
|
244
|
+
}
|
|
245
|
+
interface GanttRenderTick {
|
|
246
|
+
x: number;
|
|
247
|
+
label: string;
|
|
248
|
+
isWeekend: boolean;
|
|
249
|
+
/** weekend OR a calendar holiday; only meaningful when a WorkingCalendar was supplied */
|
|
250
|
+
isNonWorking: boolean;
|
|
251
|
+
isToday: boolean;
|
|
252
|
+
isMajorBoundary: boolean;
|
|
253
|
+
}
|
|
254
|
+
interface GanttRenderModel {
|
|
255
|
+
width: number;
|
|
256
|
+
height: number;
|
|
257
|
+
rowHeight: number;
|
|
258
|
+
headerHeight: number;
|
|
259
|
+
rows: GanttRenderRow[];
|
|
260
|
+
bars: GanttRenderBar[];
|
|
261
|
+
links: GanttRenderLink[];
|
|
262
|
+
ticks: GanttRenderTick[];
|
|
263
|
+
columns: GanttColumn[];
|
|
264
|
+
theme: GanttTheme;
|
|
265
|
+
markers: GanttRenderMarker[];
|
|
266
|
+
/** start of the timeline's date range, in real time - lets callers convert chart-space x back to a Date */
|
|
267
|
+
rangeStart: Date;
|
|
268
|
+
}
|
|
269
|
+
interface GanttOptions {
|
|
270
|
+
viewMode?: ViewMode;
|
|
271
|
+
columns?: GanttColumn[];
|
|
272
|
+
theme?: Partial<GanttTheme>;
|
|
273
|
+
/** "auto" (default) follows the OS/browser dark-mode preference and updates live if it changes */
|
|
274
|
+
colorScheme?: "light" | "dark" | "auto";
|
|
275
|
+
/** px per smallest time unit column, auto if omitted */
|
|
276
|
+
columnWidth?: number;
|
|
277
|
+
readonly?: boolean;
|
|
278
|
+
showProgress?: boolean;
|
|
279
|
+
showDependencies?: boolean;
|
|
280
|
+
gridPanelWidth?: number;
|
|
281
|
+
showCriticalPath?: boolean;
|
|
282
|
+
showBaseline?: boolean;
|
|
283
|
+
showDeadlines?: boolean;
|
|
284
|
+
showAssigneeAvatars?: boolean;
|
|
285
|
+
/** working-day/holiday definition used to shade non-working time and (with autoSchedule) skip it when cascading */
|
|
286
|
+
calendar?: WorkingCalendar;
|
|
287
|
+
/** arbitrary labeled vertical lines on the timeline, independent of any task */
|
|
288
|
+
markers?: GanttMarker[];
|
|
289
|
+
/** a group task with no explicit `progress` gets a duration-weighted average of its children's progress instead of 0 */
|
|
290
|
+
autoRollupProgress?: boolean;
|
|
291
|
+
/** enables click/ctrl-click/shift-click multi-selection of task bars, and chart.bulkShiftDates()/bulkDelete() */
|
|
292
|
+
selectable?: boolean;
|
|
293
|
+
onSelectionChange?: (taskIds: string[]) => void;
|
|
294
|
+
/** enable Ctrl+Z / Ctrl+Y undo-redo history for move/resize/link actions */
|
|
295
|
+
enableHistory?: boolean;
|
|
296
|
+
/** enable arrow-key task movement + ARIA labels on bars/rows */
|
|
297
|
+
keyboardAccessible?: boolean;
|
|
298
|
+
/** only render DOM/SVG nodes for rows within the visible viewport (+ overscan). Default true. */
|
|
299
|
+
virtualScroll?: boolean;
|
|
300
|
+
/** render only `pageSize` top-level rows for `page` (1-indexed) instead of the full tree */
|
|
301
|
+
pagination?: {
|
|
302
|
+
pageSize: number;
|
|
303
|
+
page: number;
|
|
304
|
+
};
|
|
305
|
+
/** round drag/resize deltas to this grid unit; false for raw pixel precision */
|
|
306
|
+
snapToUnit?: "hour" | "day" | "week" | false;
|
|
307
|
+
onContextMenu?: (task: GanttTask, evt: PointerEvent) => void;
|
|
308
|
+
/** when a task moves/resizes, cascade-shift dependent successors that would otherwise violate the link */
|
|
309
|
+
autoSchedule?: boolean;
|
|
310
|
+
onDateChange?: (task: GanttTask, start: Date, end: Date) => void;
|
|
311
|
+
onProgressChange?: (task: GanttTask, progress: number) => void;
|
|
312
|
+
onDependencyCreate?: (dep: GanttDependency) => void;
|
|
313
|
+
onDependencyRemove?: (dep: GanttDependency) => void;
|
|
314
|
+
/** called on double-click of a dependency link, for building your own "edit this link" UI (see chart.updateDependency) */
|
|
315
|
+
onDependencyDblClick?: (dep: GanttDependency) => void;
|
|
316
|
+
onTaskClick?: (task: GanttTask) => void;
|
|
317
|
+
onGroupToggle?: (task: GanttTask, collapsed: boolean) => void;
|
|
318
|
+
/** called when a task is created via drag-to-create on an empty timeline row */
|
|
319
|
+
onTaskCreate?: (task: GanttTask) => void;
|
|
320
|
+
/** called when a grid column is resized by dragging its header's edge */
|
|
321
|
+
onColumnResize?: (columnId: string, width: number) => void;
|
|
322
|
+
/** called when grid columns are reordered by dragging a header; `order` is the new full list of column ids */
|
|
323
|
+
onColumnReorder?: (order: string[]) => void;
|
|
324
|
+
/** called when a grid row is dragged onto another; "inside" reparents the dragged task under the target */
|
|
325
|
+
onTaskReorder?: (draggedTaskId: string, targetTaskId: string, position: "before" | "after" | "inside") => void;
|
|
326
|
+
}
|
|
327
|
+
type GanttEventMap = {
|
|
328
|
+
"date-change": {
|
|
329
|
+
task: GanttTask;
|
|
330
|
+
start: Date;
|
|
331
|
+
end: Date;
|
|
332
|
+
};
|
|
333
|
+
"progress-change": {
|
|
334
|
+
task: GanttTask;
|
|
335
|
+
progress: number;
|
|
336
|
+
};
|
|
337
|
+
"dependency-create": GanttDependency;
|
|
338
|
+
"dependency-remove": GanttDependency;
|
|
339
|
+
"task-click": {
|
|
340
|
+
task: GanttTask;
|
|
341
|
+
};
|
|
342
|
+
"task-create": {
|
|
343
|
+
task: GanttTask;
|
|
344
|
+
};
|
|
345
|
+
"group-toggle": {
|
|
346
|
+
task: GanttTask;
|
|
347
|
+
collapsed: boolean;
|
|
348
|
+
};
|
|
349
|
+
"tasks-change": {
|
|
350
|
+
tasks: GanttTask[];
|
|
351
|
+
};
|
|
352
|
+
"history-change": {
|
|
353
|
+
canUndo: boolean;
|
|
354
|
+
canRedo: boolean;
|
|
355
|
+
};
|
|
356
|
+
"page-change": {
|
|
357
|
+
page: number;
|
|
358
|
+
pageCount: number;
|
|
359
|
+
};
|
|
360
|
+
"context-menu": {
|
|
361
|
+
task: GanttTask;
|
|
362
|
+
evt: PointerEvent;
|
|
363
|
+
};
|
|
364
|
+
"column-resize": {
|
|
365
|
+
columnId: string;
|
|
366
|
+
width: number;
|
|
367
|
+
};
|
|
368
|
+
"column-reorder": {
|
|
369
|
+
order: string[];
|
|
370
|
+
};
|
|
371
|
+
"dependency-dblclick": GanttDependency;
|
|
372
|
+
"task-reorder": {
|
|
373
|
+
draggedTaskId: string;
|
|
374
|
+
targetTaskId: string;
|
|
375
|
+
position: "before" | "after" | "inside";
|
|
376
|
+
};
|
|
377
|
+
"selection-change": {
|
|
378
|
+
taskIds: string[];
|
|
379
|
+
};
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
interface ResourceLevelingOptions {
|
|
383
|
+
calendar?: WorkingCalendar;
|
|
384
|
+
}
|
|
385
|
+
interface ResourceLevelingResult {
|
|
386
|
+
/** same tasks, same order - shifted ones are shallow clones with new start/end (duration preserved) */
|
|
387
|
+
tasks: GanttTask[];
|
|
388
|
+
shifted: {
|
|
389
|
+
taskId: string;
|
|
390
|
+
delayMs: number;
|
|
391
|
+
}[];
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Resolve resource overallocation by delaying tasks (never pulling them
|
|
395
|
+
* earlier) so no assignee is placed on more than one task on the same
|
|
396
|
+
* working day - the same "fully allocated while active" model
|
|
397
|
+
* computeResourceHistogram uses, so a leveled schedule always shows zero
|
|
398
|
+
* `overallocated` buckets afterward.
|
|
399
|
+
*
|
|
400
|
+
* Tasks are processed in an order that respects FS ("finish-to-start")
|
|
401
|
+
* dependencies (a task never gets placed before its FS predecessors
|
|
402
|
+
* finish); SS/FF/SF dependencies aren't specifically enforced by the
|
|
403
|
+
* leveler - a reasonable, documented scope limit rather than a full
|
|
404
|
+
* constraint solver.
|
|
405
|
+
*/
|
|
406
|
+
declare function levelResources(tasks: GanttTask[], dependencies: GanttDependency[], options?: ResourceLevelingOptions): ResourceLevelingResult;
|
|
407
|
+
|
|
408
|
+
interface ResourceHistogramOptions {
|
|
409
|
+
/** hours a resource is assumed available per working day. Default 8. */
|
|
410
|
+
capacityHoursPerDay?: number;
|
|
411
|
+
/** governs which days count as "working" for allocation purposes. Non-working days get 0 load. */
|
|
412
|
+
calendar?: WorkingCalendar;
|
|
413
|
+
}
|
|
414
|
+
interface ResourceLoadBucket {
|
|
415
|
+
assigneeId: string;
|
|
416
|
+
assigneeName: string;
|
|
417
|
+
/** start of the day this bucket represents */
|
|
418
|
+
date: Date;
|
|
419
|
+
/** hours allocated to this assignee across all tasks spanning this day */
|
|
420
|
+
allocatedHours: number;
|
|
421
|
+
capacityHours: number;
|
|
422
|
+
overallocated: boolean;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Bucket every task's assignees by calendar day, summing one full day's
|
|
426
|
+
* capacity worth of allocation per assignee per working day the task spans
|
|
427
|
+
* (milestones and non-working days contribute nothing). This is a simple
|
|
428
|
+
* "fully allocated while active" model, not a percent-effort model - tasks
|
|
429
|
+
* don't carry an effort/FTE field, so it's the only allocation the plain
|
|
430
|
+
* task/assignee data can support without inventing new required fields.
|
|
431
|
+
*/
|
|
432
|
+
declare function computeResourceHistogram(tasks: GanttTask[], options?: ResourceHistogramOptions): ResourceLoadBucket[];
|
|
433
|
+
/**
|
|
434
|
+
* Render a resource histogram as a standalone SVG string: one grouped column
|
|
435
|
+
* of stacked bars per day, one bar per assignee, red when overallocated.
|
|
436
|
+
* Framework-agnostic like the rest of gantt-core - drop the string into an
|
|
437
|
+
* `<div>` via `innerHTML` or a data URI, no DOM dependency required to call it.
|
|
438
|
+
*/
|
|
439
|
+
declare function renderResourceHistogramSVG(buckets: ResourceLoadBucket[], options?: {
|
|
440
|
+
width?: number;
|
|
441
|
+
barWidth?: number;
|
|
442
|
+
maxBarHeight?: number;
|
|
443
|
+
overallocatedColor?: string;
|
|
444
|
+
barColor?: string;
|
|
445
|
+
}): string;
|
|
446
|
+
|
|
447
|
+
declare const DEFAULT_THEME: GanttTheme;
|
|
448
|
+
type ColorScheme = "light" | "dark" | "auto";
|
|
449
|
+
type Density = "compact" | "comfortable" | "spacious";
|
|
450
|
+
/**
|
|
451
|
+
* Row/bar/header/font sizing presets. Not wired into any option - spread one
|
|
452
|
+
* into your `theme` prop/option (explicit fields you also set win, since
|
|
453
|
+
* object spread is left-to-right):
|
|
454
|
+
*
|
|
455
|
+
* theme: { ...DENSITY_PRESETS.compact, barColor: "#..." }
|
|
456
|
+
*/
|
|
457
|
+
declare const DENSITY_PRESETS: Record<Density, Pick<GanttTheme, "rowHeight" | "barHeight" | "headerHeight" | "fontSize">>;
|
|
458
|
+
declare function mergeTheme(partial?: Partial<GanttTheme>, scheme?: ColorScheme): GanttTheme;
|
|
459
|
+
declare function themeToCssVars(theme: GanttTheme): Record<string, string>;
|
|
460
|
+
|
|
461
|
+
interface LayoutInput {
|
|
462
|
+
tasks: GanttTask[];
|
|
463
|
+
dependencies: GanttDependency[];
|
|
464
|
+
viewMode: ViewMode;
|
|
465
|
+
columnWidth: number;
|
|
466
|
+
theme: GanttTheme;
|
|
467
|
+
columns: GanttColumn[];
|
|
468
|
+
showCriticalPath?: boolean;
|
|
469
|
+
showBaseline?: boolean;
|
|
470
|
+
showDeadlines?: boolean;
|
|
471
|
+
showAssigneeAvatars?: boolean;
|
|
472
|
+
calendar?: WorkingCalendar;
|
|
473
|
+
markers?: GanttMarker[];
|
|
474
|
+
/** a group task with no explicit `progress` gets a duration-weighted average of its children's progress instead of 0 */
|
|
475
|
+
autoRollupProgress?: boolean;
|
|
476
|
+
selectedTaskIds?: Set<string>;
|
|
477
|
+
/** restrict layout to a page of top-level rows, in document order (1-indexed) */
|
|
478
|
+
pagination?: {
|
|
479
|
+
pageSize: number;
|
|
480
|
+
page: number;
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
declare function computeLayout(input: LayoutInput): GanttRenderModel;
|
|
484
|
+
|
|
485
|
+
interface CriticalPathResult {
|
|
486
|
+
criticalTasks: Set<string>;
|
|
487
|
+
criticalLinks: Set<string>;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Longest-duration-chain (CPM-style forward/backward pass) through the dependency DAG.
|
|
491
|
+
* Task dates in a Gantt chart are already fixed (this isn't an auto-scheduler), so the
|
|
492
|
+
* dependency `type` and `lagMs` are folded into a single edge weight rather than
|
|
493
|
+
* recomputing actual start/end dates - the goal is only to flag which tasks/links sit
|
|
494
|
+
* on the longest chain, not to move anything.
|
|
495
|
+
*/
|
|
496
|
+
declare function computeCriticalPath(tasks: GanttTask[], dependencies: GanttDependency[]): CriticalPathResult;
|
|
497
|
+
|
|
498
|
+
interface Command {
|
|
499
|
+
do(): void;
|
|
500
|
+
undo(): void;
|
|
501
|
+
label?: string;
|
|
502
|
+
}
|
|
503
|
+
type HistoryChangeListener = (state: {
|
|
504
|
+
canUndo: boolean;
|
|
505
|
+
canRedo: boolean;
|
|
506
|
+
}) => void;
|
|
507
|
+
declare class HistoryManager {
|
|
508
|
+
private undoStack;
|
|
509
|
+
private redoStack;
|
|
510
|
+
private onChange?;
|
|
511
|
+
constructor(onChange?: HistoryChangeListener);
|
|
512
|
+
push(cmd: Command): void;
|
|
513
|
+
undo(): boolean;
|
|
514
|
+
redo(): boolean;
|
|
515
|
+
get canUndo(): boolean;
|
|
516
|
+
get canRedo(): boolean;
|
|
517
|
+
clear(): void;
|
|
518
|
+
private notify;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
type Listener<TPayload> = (payload: TPayload) => void;
|
|
522
|
+
declare class EventEmitter<TEventMap extends Record<string, unknown>> {
|
|
523
|
+
private listeners;
|
|
524
|
+
on<K extends keyof TEventMap>(event: K, listener: Listener<TEventMap[K]>): void;
|
|
525
|
+
off<K extends keyof TEventMap>(event: K, listener: Listener<TEventMap[K]>): void;
|
|
526
|
+
once<K extends keyof TEventMap>(event: K, listener: Listener<TEventMap[K]>): void;
|
|
527
|
+
emit<K extends keyof TEventMap>(event: K, payload: TEventMap[K]): void;
|
|
528
|
+
removeAllListeners(): void;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
declare function tasksToCSV(tasks: GanttTask[]): string;
|
|
532
|
+
declare function tasksFromCSV(csv: string): GanttTask[];
|
|
533
|
+
|
|
534
|
+
declare const MS_PER_HOUR: number;
|
|
535
|
+
declare const MS_PER_DAY: number;
|
|
536
|
+
declare const MS_PER_WEEK: number;
|
|
537
|
+
/** Approximate duration of one grid unit for the given view mode, used only for pixel scaling. */
|
|
538
|
+
declare function approxUnitMs(viewMode: ViewMode): number;
|
|
539
|
+
/** Pixels per millisecond, derived from how many px represent one grid unit. */
|
|
540
|
+
declare function pxPerMs(viewMode: ViewMode, columnWidth: number): number;
|
|
541
|
+
declare function dateToX(date: Date, rangeStart: Date, viewMode: ViewMode, columnWidth: number): number;
|
|
542
|
+
declare function isWeekend(date: Date): boolean;
|
|
543
|
+
declare function isValidDate(date: unknown): date is Date;
|
|
544
|
+
declare function isSameDay(a: Date, b: Date): boolean;
|
|
545
|
+
/** Snap the given date down to the start of the grid unit for the view mode. */
|
|
546
|
+
declare function startOfUnit(date: Date, viewMode: ViewMode): Date;
|
|
547
|
+
/** Advance (or, with a negative count, go back) `count` grid units, calendar-aware. */
|
|
548
|
+
declare function addUnit(date: Date, viewMode: ViewMode, count: number): Date;
|
|
549
|
+
/** Snap an arbitrary date to the nearest grid-unit boundary. */
|
|
550
|
+
declare function snapToGrid(date: Date, viewMode: ViewMode): Date;
|
|
551
|
+
interface GeneratedTick {
|
|
552
|
+
date: Date;
|
|
553
|
+
x: number;
|
|
554
|
+
label: string;
|
|
555
|
+
isWeekend: boolean;
|
|
556
|
+
isNonWorking: boolean;
|
|
557
|
+
isToday: boolean;
|
|
558
|
+
isMajorBoundary: boolean;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Generate header ticks spanning [rangeStart, rangeEnd], one per grid unit.
|
|
562
|
+
* `x` is relative to rangeStart (the caller offsets it if the model has left padding).
|
|
563
|
+
*/
|
|
564
|
+
declare function generateTicks(rangeStart: Date, rangeEnd: Date, viewMode: ViewMode, columnWidth: number, today?: Date, calendar?: WorkingCalendar): GeneratedTick[];
|
|
565
|
+
|
|
566
|
+
type dateUtils_GeneratedTick = GeneratedTick;
|
|
567
|
+
declare const dateUtils_MS_PER_DAY: typeof MS_PER_DAY;
|
|
568
|
+
declare const dateUtils_MS_PER_HOUR: typeof MS_PER_HOUR;
|
|
569
|
+
declare const dateUtils_MS_PER_WEEK: typeof MS_PER_WEEK;
|
|
570
|
+
declare const dateUtils_addUnit: typeof addUnit;
|
|
571
|
+
declare const dateUtils_approxUnitMs: typeof approxUnitMs;
|
|
572
|
+
declare const dateUtils_dateToX: typeof dateToX;
|
|
573
|
+
declare const dateUtils_generateTicks: typeof generateTicks;
|
|
574
|
+
declare const dateUtils_isSameDay: typeof isSameDay;
|
|
575
|
+
declare const dateUtils_isValidDate: typeof isValidDate;
|
|
576
|
+
declare const dateUtils_isWeekend: typeof isWeekend;
|
|
577
|
+
declare const dateUtils_pxPerMs: typeof pxPerMs;
|
|
578
|
+
declare const dateUtils_snapToGrid: typeof snapToGrid;
|
|
579
|
+
declare const dateUtils_startOfUnit: typeof startOfUnit;
|
|
580
|
+
declare namespace dateUtils {
|
|
581
|
+
export { type dateUtils_GeneratedTick as GeneratedTick, dateUtils_MS_PER_DAY as MS_PER_DAY, dateUtils_MS_PER_HOUR as MS_PER_HOUR, dateUtils_MS_PER_WEEK as MS_PER_WEEK, dateUtils_addUnit as addUnit, dateUtils_approxUnitMs as approxUnitMs, dateUtils_dateToX as dateToX, dateUtils_generateTicks as generateTicks, dateUtils_isSameDay as isSameDay, dateUtils_isValidDate as isValidDate, dateUtils_isWeekend as isWeekend, dateUtils_pxPerMs as pxPerMs, dateUtils_snapToGrid as snapToGrid, dateUtils_startOfUnit as startOfUnit };
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Only http(s), mailto, and scheme-relative/relative URLs are allowed in a getHref() cell -
|
|
586
|
+
* anything else (javascript:, data:, vbscript:, etc.) renders as plain text instead of a link.
|
|
587
|
+
*/
|
|
588
|
+
declare function isSafeHref(url: string): boolean;
|
|
589
|
+
|
|
590
|
+
/** A readable, colorblind-considerate categorical palette; cycles if there are more distinct values than colors. */
|
|
591
|
+
declare const DEFAULT_PALETTE: string[];
|
|
592
|
+
/**
|
|
593
|
+
* Assign a stable color to each distinct value of `field(task)` across `tasks`,
|
|
594
|
+
* cycling through `palette` in first-seen order. Pure - returns a lookup map,
|
|
595
|
+
* doesn't touch the tasks themselves (use with `applyColorByField` or your own
|
|
596
|
+
* `task.color = colors.get(field(task))` loop).
|
|
597
|
+
*/
|
|
598
|
+
declare function colorByField(tasks: GanttTask[], field: (task: GanttTask) => string | null | undefined, palette?: string[]): Map<string, string>;
|
|
599
|
+
/** Convenience wrapper: returns a new tasks array with `.color` set from `colorByField`'s result (a task's existing `.color` wins if already set). */
|
|
600
|
+
declare function applyColorByField(tasks: GanttTask[], field: (task: GanttTask) => string | null | undefined, palette?: string[]): GanttTask[];
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Compute Work Breakdown Structure codes ("1", "1.1", "1.2", "2", ...) from
|
|
604
|
+
* task parent/child nesting, in document (array) order among siblings.
|
|
605
|
+
*
|
|
606
|
+
* Returned as a plain Map rather than mutating tasks, matching the library's
|
|
607
|
+
* "everything is a plain object, the chart never owns your state" principle -
|
|
608
|
+
* wire it into a grid column via `column.accessor` if you want it displayed:
|
|
609
|
+
*
|
|
610
|
+
* const wbs = computeWBSCodes(tasks);
|
|
611
|
+
* columns: [{ id: "wbs", title: "WBS", accessor: (t) => wbs.get(t.id) }]
|
|
612
|
+
*
|
|
613
|
+
* A task whose `parentId` doesn't resolve to another task in the list is
|
|
614
|
+
* treated as a root, same as an unset `parentId`.
|
|
615
|
+
*/
|
|
616
|
+
declare function computeWBSCodes(tasks: GanttTask[]): Map<string, string>;
|
|
617
|
+
|
|
618
|
+
interface FilterTasksOptions {
|
|
619
|
+
/** keep ancestors of a matched task so its tree path stays intact. Default true. */
|
|
620
|
+
includeAncestors?: boolean;
|
|
621
|
+
/** keep descendants of a matched task (e.g. a matched group's children). Default true. */
|
|
622
|
+
includeDescendants?: boolean;
|
|
623
|
+
}
|
|
624
|
+
interface FilterTasksResult {
|
|
625
|
+
/** the subset of `tasks` to keep, in original order - pass straight to the chart's `tasks` prop/setTasks to hide the rest */
|
|
626
|
+
tasks: GanttTask[];
|
|
627
|
+
/** ids that satisfied `predicate` directly, as opposed to being pulled in via includeAncestors/includeDescendants - use to dim rows that are shown only for tree context */
|
|
628
|
+
matchedIds: Set<string>;
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Filter tasks by a predicate while keeping the tree intact: a matched
|
|
632
|
+
* task's ancestors stay (so it doesn't vanish along with a non-matching
|
|
633
|
+
* parent) and, by default, its descendants stay too (so a matched group
|
|
634
|
+
* doesn't show with its children silently missing).
|
|
635
|
+
*
|
|
636
|
+
* This only computes the subset - hook it up by passing `result.tasks` as
|
|
637
|
+
* the chart's `tasks` (non-matches are hidden because they're no longer in
|
|
638
|
+
* the array, same as any other plain-data filter), and optionally use
|
|
639
|
+
* `result.matchedIds` to visually dim tasks that are present only for tree
|
|
640
|
+
* context (e.g. via a `column.accessor` or `task.color` override).
|
|
641
|
+
*/
|
|
642
|
+
declare function filterTasks(tasks: GanttTask[], predicate: (task: GanttTask) => boolean, options?: FilterTasksOptions): FilterTasksResult;
|
|
643
|
+
|
|
644
|
+
declare class GanttChart {
|
|
645
|
+
private container;
|
|
646
|
+
private tasks;
|
|
647
|
+
private dependencies;
|
|
648
|
+
private options;
|
|
649
|
+
private theme;
|
|
650
|
+
private colorScheme;
|
|
651
|
+
private darkMediaQuery;
|
|
652
|
+
private handleSchemeChange;
|
|
653
|
+
private explicitTheme?;
|
|
654
|
+
private columns;
|
|
655
|
+
private columnWidth;
|
|
656
|
+
private emitter;
|
|
657
|
+
private history;
|
|
658
|
+
private renderer;
|
|
659
|
+
private interactions;
|
|
660
|
+
private renderModel;
|
|
661
|
+
private rafHandle;
|
|
662
|
+
private currentPage;
|
|
663
|
+
private selectedTaskIds;
|
|
664
|
+
private lastSelectedId;
|
|
665
|
+
constructor(container: HTMLElement, tasks: GanttTask[], dependencies?: GanttDependency[], options?: GanttOptions);
|
|
666
|
+
private setupDom;
|
|
667
|
+
private handleMove;
|
|
668
|
+
private handleResize;
|
|
669
|
+
private applyDateChange;
|
|
670
|
+
private emitDateChangeEvents;
|
|
671
|
+
/** Shift successors that would violate their dependency constraint after `taskId` moves to [newStart, newEnd]. */
|
|
672
|
+
private computeCascade;
|
|
673
|
+
private handleLinkComplete;
|
|
674
|
+
removeDependency(fromId: string, toId: string): void;
|
|
675
|
+
/** Update a dependency's type/lag in place (e.g. from your own "edit this link" UI opened via onDependencyDblClick). Undoable. */
|
|
676
|
+
updateDependency(fromId: string, toId: string, partial: Partial<GanttDependency>): void;
|
|
677
|
+
private handleProgressDrag;
|
|
678
|
+
private handleCreateTaskDrag;
|
|
679
|
+
private handleBarClick;
|
|
680
|
+
/** Replace (default) or add to (`additive: true`) the current selection. Requires `selectable`. */
|
|
681
|
+
selectTask(id: string, options?: {
|
|
682
|
+
additive?: boolean;
|
|
683
|
+
}): void;
|
|
684
|
+
clearSelection(): void;
|
|
685
|
+
getSelectedTaskIds(): string[];
|
|
686
|
+
/** Shift every selected task's start/end by `deltaMs`. Single undoable step. */
|
|
687
|
+
bulkShiftDates(deltaMs: number): void;
|
|
688
|
+
/** Delete every selected task (and any dependency touching one). Single undoable step. */
|
|
689
|
+
bulkDelete(): void;
|
|
690
|
+
private runCommand;
|
|
691
|
+
private handleColumnResize;
|
|
692
|
+
private handleColumnReorder;
|
|
693
|
+
private handleRowReorder;
|
|
694
|
+
toggleGroup(taskId: string): void;
|
|
695
|
+
expandAll(): void;
|
|
696
|
+
collapseAll(): void;
|
|
697
|
+
setTasks(tasks: GanttTask[]): void;
|
|
698
|
+
getTasks(): GanttTask[];
|
|
699
|
+
setDependencies(deps: GanttDependency[]): void;
|
|
700
|
+
updateTask(id: string, partial: Partial<GanttTask>): void;
|
|
701
|
+
setViewMode(mode: ViewMode): void;
|
|
702
|
+
setOptions(partial: Partial<GanttOptions>): void;
|
|
703
|
+
/** Scroll the timeline so "today" is centered in the viewport. No-op without a DOM / today column visible. */
|
|
704
|
+
scrollToToday(): void;
|
|
705
|
+
/** Scroll the timeline to the very start of the project's date range. Also the Home-key shortcut when keyboardAccessible. */
|
|
706
|
+
scrollToRangeStart(): void;
|
|
707
|
+
/** Scroll the timeline to the very end of the project's date range. Also the End-key shortcut when keyboardAccessible. */
|
|
708
|
+
scrollToRangeEnd(): void;
|
|
709
|
+
/** Select every currently visible task. Also the Ctrl/Cmd+A shortcut when both `selectable` and `keyboardAccessible` are on. */
|
|
710
|
+
selectAllVisible(): void;
|
|
711
|
+
fitToViewport(containerWidthPx: number): void;
|
|
712
|
+
private static readonly MIN_COLUMN_WIDTH;
|
|
713
|
+
private static readonly MAX_COLUMN_WIDTH;
|
|
714
|
+
/** Zoom in by `factor` (default 1.25x), clamped to a sane minimum column width. */
|
|
715
|
+
zoomIn(factor?: number): void;
|
|
716
|
+
/** Zoom out by `factor` (default 1.25x), clamped to a sane maximum column width. */
|
|
717
|
+
zoomOut(factor?: number): void;
|
|
718
|
+
/** Current zoom level, expressed as px per grid-unit column (same unit as the `columnWidth` option). */
|
|
719
|
+
getZoomLevel(): number;
|
|
720
|
+
/** Per-assignee daily workload, computed from the current tasks. See computeResourceHistogram. */
|
|
721
|
+
getResourceHistogram(options?: ResourceHistogramOptions): ResourceLoadBucket[];
|
|
722
|
+
/** Resolve resource overallocation by delaying tasks. Doesn't mutate state - call setTasks(result.tasks) to apply it. See levelResources. */
|
|
723
|
+
getLeveledTasks(options?: ResourceLevelingOptions): ResourceLevelingResult;
|
|
724
|
+
getPageCount(): number;
|
|
725
|
+
setPage(page: number): void;
|
|
726
|
+
getRenderModel(): GanttRenderModel;
|
|
727
|
+
private computeModel;
|
|
728
|
+
private scheduleRender;
|
|
729
|
+
private doRender;
|
|
730
|
+
undo(): void;
|
|
731
|
+
redo(): void;
|
|
732
|
+
get canUndo(): boolean;
|
|
733
|
+
get canRedo(): boolean;
|
|
734
|
+
on<K extends keyof GanttEventMap>(event: K, listener: (payload: GanttEventMap[K]) => void): void;
|
|
735
|
+
off<K extends keyof GanttEventMap>(event: K, listener: (payload: GanttEventMap[K]) => void): void;
|
|
736
|
+
toSVGString(): string;
|
|
737
|
+
private rasterizeSVG;
|
|
738
|
+
toPNGDataURL(): Promise<string>;
|
|
739
|
+
/**
|
|
740
|
+
* Like toPNGDataURL, but tiles a chart larger than `pageWidthPx`/`pageHeightPx`
|
|
741
|
+
* into multiple images (row-major order) instead of one oversized raster -
|
|
742
|
+
* useful for charts too big to comfortably rasterize/view as a single PNG.
|
|
743
|
+
* Reuses the full rendered SVG (so every page renders at full fidelity);
|
|
744
|
+
* defaults to the chart's actual size, i.e. a single image, when omitted.
|
|
745
|
+
*/
|
|
746
|
+
toPNGDataURLs(options?: {
|
|
747
|
+
pageWidthPx?: number;
|
|
748
|
+
pageHeightPx?: number;
|
|
749
|
+
}): Promise<string[]>;
|
|
750
|
+
destroy(): void;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
export { type Command, type ConstraintType, DEFAULT_PALETTE, DEFAULT_THEME, DENSITY_PRESETS, type Density, type DependencyType, EventEmitter, type FilterTasksOptions, type FilterTasksResult, type GanttAssignee, GanttChart, type GanttColumn, type GanttDependency, type GanttEventMap, type GanttMarker, type GanttOptions, type GanttRenderBar, type GanttRenderLink, type GanttRenderMarker, type GanttRenderModel, type GanttRenderRow, type GanttRenderTick, type GanttTask, type GanttTheme, HistoryManager, type ResourceHistogramOptions, type ResourceLevelingOptions, type ResourceLevelingResult, type ResourceLoadBucket, type ViewMode, type WorkingCalendar, applyColorByField, applyConstraint, colorByField, computeCriticalPath, computeLayout, computeResourceHistogram, computeWBSCodes, dateUtils, filterTasks, isHoliday, isSafeHref, isWithinWorkingHours, isWorkingDay, isWorkingTime, levelResources, mergeTheme, nextWorkingDay, previousWorkingDay, renderResourceHistogramSVG, shiftToWorkingDay, shiftToWorkingTime, tasksFromCSV, tasksToCSV, themeToCssVars };
|