gantt-renderer 0.7.0 → 0.8.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/CHANGELOG.md +30 -1
- package/README.md +3 -4
- package/dist/index.d.mts +8 -66
- package/dist/index.mjs +177 -183
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,41 @@
|
|
|
1
|
+
# [0.8.0](https://github.com/doberkofler/gantt-renderer/compare/v0.7.0...v0.8.0) (2026-05-12)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **gantt:** free actions column space when showAddTaskButton is disabled ([464c5cd](https://github.com/doberkofler/gantt-renderer/commit/464c5cda446828c7a0a8ca2b6e5f6a226f37aef4))
|
|
7
|
+
* **interaction:** remove duplicate dblclick handler causing double callback fire ([df6500a](https://github.com/doberkofler/gantt-renderer/commit/df6500a9be31818f8a79cc94a33326a81793e7e3))
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* **api:** inline zod validation into update(), remove parseGanttInput ([6136fe9](https://github.com/doberkofler/gantt-renderer/commit/6136fe9c732d4d919bfee592633be68eab0e05ef))
|
|
13
|
+
* **demo:** add HTML tooltip with typed custom data to demo app ([401bebc](https://github.com/doberkofler/gantt-renderer/commit/401bebc190145cf30dd96e6e67e0c76867028114))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### BREAKING CHANGES
|
|
17
|
+
|
|
18
|
+
* **api:** Remove the public parseGanttInput() function and all
|
|
19
|
+
zod schema exports. Pass raw data directly to GanttChart.update() —
|
|
20
|
+
validation, defaults, and duplicate detection now happen internally.
|
|
21
|
+
GanttInputRaw uses readonly arrays so as const data is accepted
|
|
22
|
+
without casts.
|
|
23
|
+
|
|
1
24
|
# [0.7.0](https://github.com/doberkofler/gantt-renderer/compare/v0.6.0...v0.7.0) (2026-05-11)
|
|
2
25
|
|
|
3
26
|
|
|
27
|
+
### Features
|
|
28
|
+
|
|
29
|
+
* **types:** make GanttInputRaw and parseGanttInput generic ([e665221](https://github.com/doberkofler/gantt-renderer/commit/e665221cd13ed58138770365079884cfcc5067fe))
|
|
30
|
+
|
|
31
|
+
# [0.6.0](https://github.com/doberkofler/gantt-renderer/compare/v0.5.0...v0.6.0) (2026-05-11)
|
|
32
|
+
|
|
33
|
+
|
|
4
34
|
### Features
|
|
5
35
|
|
|
6
36
|
* **gantt:** simplify default grid columns and add showAddTaskButton option ([800c3cd](https://github.com/doberkofler/gantt-renderer/commit/800c3cdb02d42dfc266823e1d56c0c6cd0eaf009))
|
|
7
37
|
* **locale:** add built-in locale constants for 9 languages ([712254a](https://github.com/doberkofler/gantt-renderer/commit/712254acecc4110ad53de9de14833b2feb1ef16f))
|
|
8
38
|
* **types:** add generic type params to Task, Link, GanttInput, GanttChart ([561ebcf](https://github.com/doberkofler/gantt-renderer/commit/561ebcf01dd1610f0f80739bd29509a05e7848c5))
|
|
9
|
-
* **types:** make GanttInputRaw and parseGanttInput generic ([e665221](https://github.com/doberkofler/gantt-renderer/commit/e665221cd13ed58138770365079884cfcc5067fe))
|
|
10
39
|
|
|
11
40
|
# [0.5.0](https://github.com/doberkofler/gantt-renderer/compare/v0.4.0...v0.5.0) (2026-05-09)
|
|
12
41
|
|
package/README.md
CHANGED
|
@@ -63,20 +63,19 @@ npm install gantt-renderer
|
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
```ts
|
|
66
|
-
import {GanttChart
|
|
66
|
+
import {GanttChart} from 'gantt-renderer';
|
|
67
67
|
import 'gantt-renderer/styles/gantt.css';
|
|
68
68
|
|
|
69
|
-
const input = parseGanttInput(yourData);
|
|
70
69
|
const instance = new GanttChart(document.getElementById('chart')!, {
|
|
71
70
|
scale: 'day',
|
|
72
71
|
});
|
|
73
|
-
instance.update(
|
|
72
|
+
instance.update(yourData);
|
|
74
73
|
```
|
|
75
74
|
|
|
76
75
|
## Integration Pattern
|
|
77
76
|
|
|
78
77
|
1. Compute/plan project data in your domain layer or backend.
|
|
79
|
-
2.
|
|
78
|
+
2. Pass it directly to `instance.update(yourData)` — the chart validates and renders in one call.
|
|
80
79
|
3. Render with `new GanttChart(container, options)` followed by `instance.update(input)` and react to interaction callbacks.
|
|
81
80
|
4. Persist user edits through your own business logic, then update with `instance.update(newInput)`.
|
|
82
81
|
|
package/dist/index.d.mts
CHANGED
|
@@ -16,6 +16,7 @@ declare const SpecialDayKindSchema: z.ZodEnum<{
|
|
|
16
16
|
holiday: "holiday";
|
|
17
17
|
custom: "custom";
|
|
18
18
|
}>;
|
|
19
|
+
/** @internal */
|
|
19
20
|
declare const SpecialDaySchema: z.ZodObject<{
|
|
20
21
|
date: z.ZodString;
|
|
21
22
|
kind: z.ZodEnum<{
|
|
@@ -71,54 +72,7 @@ declare const LinkSchema: z.ZodObject<{
|
|
|
71
72
|
readonly: z.ZodOptional<z.ZodBoolean>;
|
|
72
73
|
data: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
73
74
|
}, z.core.$strip>;
|
|
74
|
-
|
|
75
|
-
tasks: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
76
|
-
kind: z.ZodLiteral<"task">;
|
|
77
|
-
endDate: z.ZodString;
|
|
78
|
-
percentComplete: z.ZodDefault<z.ZodNumber>;
|
|
79
|
-
id: z.ZodNumber;
|
|
80
|
-
text: z.ZodString;
|
|
81
|
-
startDate: z.ZodString;
|
|
82
|
-
parent: z.ZodOptional<z.ZodNumber>;
|
|
83
|
-
color: z.ZodOptional<z.ZodString>;
|
|
84
|
-
readonly: z.ZodOptional<z.ZodBoolean>;
|
|
85
|
-
data: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
86
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
87
|
-
kind: z.ZodLiteral<"project">;
|
|
88
|
-
endDate: z.ZodString;
|
|
89
|
-
percentComplete: z.ZodDefault<z.ZodNumber>;
|
|
90
|
-
open: z.ZodDefault<z.ZodBoolean>;
|
|
91
|
-
id: z.ZodNumber;
|
|
92
|
-
text: z.ZodString;
|
|
93
|
-
startDate: z.ZodString;
|
|
94
|
-
parent: z.ZodOptional<z.ZodNumber>;
|
|
95
|
-
color: z.ZodOptional<z.ZodString>;
|
|
96
|
-
readonly: z.ZodOptional<z.ZodBoolean>;
|
|
97
|
-
data: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
98
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
99
|
-
kind: z.ZodLiteral<"milestone">;
|
|
100
|
-
id: z.ZodNumber;
|
|
101
|
-
text: z.ZodString;
|
|
102
|
-
startDate: z.ZodString;
|
|
103
|
-
parent: z.ZodOptional<z.ZodNumber>;
|
|
104
|
-
color: z.ZodOptional<z.ZodString>;
|
|
105
|
-
readonly: z.ZodOptional<z.ZodBoolean>;
|
|
106
|
-
data: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
107
|
-
}, z.core.$strip>], "kind">>;
|
|
108
|
-
links: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
109
|
-
id: z.ZodNumber;
|
|
110
|
-
source: z.ZodNumber;
|
|
111
|
-
target: z.ZodNumber;
|
|
112
|
-
type: z.ZodDefault<z.ZodEnum<{
|
|
113
|
-
FS: "FS";
|
|
114
|
-
SS: "SS";
|
|
115
|
-
FF: "FF";
|
|
116
|
-
SF: "SF";
|
|
117
|
-
}>>;
|
|
118
|
-
readonly: z.ZodOptional<z.ZodBoolean>;
|
|
119
|
-
data: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
120
|
-
}, z.core.$strip>>>;
|
|
121
|
-
}, z.core.$strip>;
|
|
75
|
+
/** @internal */
|
|
122
76
|
/** @internal */
|
|
123
77
|
type ZodTaskInferred = z.infer<typeof TaskSchema>;
|
|
124
78
|
/** @internal */
|
|
@@ -182,7 +136,7 @@ type GanttInput<TTaskData = never, TLinkData = never> = {
|
|
|
182
136
|
links: Link<TLinkData>[];
|
|
183
137
|
};
|
|
184
138
|
/**
|
|
185
|
-
* The raw
|
|
139
|
+
* The raw input shape that consumers pass to {@link GanttChart.update}.
|
|
186
140
|
*
|
|
187
141
|
* Fields with defaults in the schema (e.g. `percentComplete`, `type`) remain optional here.
|
|
188
142
|
*
|
|
@@ -190,8 +144,8 @@ type GanttInput<TTaskData = never, TLinkData = never> = {
|
|
|
190
144
|
* @param TLinkData - The type of the `data` property on links. Defaults to `never`.
|
|
191
145
|
*/
|
|
192
146
|
type GanttInputRaw<TTaskData = never, TLinkData = never> = {
|
|
193
|
-
tasks: TaskRaw<TTaskData>[];
|
|
194
|
-
links?: LinkRaw<TLinkData>[];
|
|
147
|
+
tasks: readonly TaskRaw<TTaskData>[];
|
|
148
|
+
links?: readonly LinkRaw<TLinkData>[];
|
|
195
149
|
};
|
|
196
150
|
/** Allowed dependency link type values: `'FS'`, `'SS'`, `'FF'`, or `'SF'`. */
|
|
197
151
|
type LinkType = z.infer<typeof LinkTypeSchema>;
|
|
@@ -199,18 +153,6 @@ type LinkType = z.infer<typeof LinkTypeSchema>;
|
|
|
199
153
|
type TaskKind = z.infer<typeof TaskKindSchema>;
|
|
200
154
|
type SpecialDayKind = z.infer<typeof SpecialDayKindSchema>;
|
|
201
155
|
type SpecialDay = z.infer<typeof SpecialDaySchema>;
|
|
202
|
-
/**
|
|
203
|
-
* Parses raw external data.
|
|
204
|
-
*
|
|
205
|
-
* Compile-time generics enforce the `data` shape on tasks and links.
|
|
206
|
-
* Runtime validation is done by the zod schemas; the `data` field is
|
|
207
|
-
* validated as a generic object at runtime regardless of the type parameter.
|
|
208
|
-
*
|
|
209
|
-
* @param raw - The unvalidated input from the consumer.
|
|
210
|
-
* @returns The parsed and validated input with `data` typed per the type parameters.
|
|
211
|
-
* @throws {import('zod').ZodError} On schema validation failure.
|
|
212
|
-
*/
|
|
213
|
-
declare function parseGanttInput<TTaskData = never, TLinkData = never>(raw: GanttInputRaw<TTaskData, TLinkData>): GanttInput<TTaskData, TLinkData>;
|
|
214
156
|
//#endregion
|
|
215
157
|
//#region src/lib/timeline/scale.d.ts
|
|
216
158
|
type TimeScale = 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';
|
|
@@ -623,7 +565,7 @@ type GanttOptions = {
|
|
|
623
565
|
showAddTaskButton?: boolean;
|
|
624
566
|
};
|
|
625
567
|
type GanttInstance<TTaskData = never, TLinkData = never> = {
|
|
626
|
-
update: (input:
|
|
568
|
+
update: (input: GanttInputRaw<TTaskData, TLinkData>) => void;
|
|
627
569
|
setOptions: (opts: Partial<GanttOptions>) => void;
|
|
628
570
|
setCallbacks: (cbs: GanttCallbacks<TTaskData, TLinkData>) => void;
|
|
629
571
|
select: (id: number | null, fireCallback?: boolean) => void;
|
|
@@ -672,7 +614,7 @@ declare class GanttChart<TTaskData = never, TLinkData = never> implements GanttI
|
|
|
672
614
|
* @param newInput - The new {@link GanttInput} to apply.
|
|
673
615
|
* @throws {GanttError} When the instance has been destroyed.
|
|
674
616
|
*/
|
|
675
|
-
update(newInput:
|
|
617
|
+
update(newInput: GanttInputRaw<TTaskData, TLinkData>): void;
|
|
676
618
|
/**
|
|
677
619
|
* Merges the supplied options into the current configuration and re-renders
|
|
678
620
|
* only the panes affected by the changed options.
|
|
@@ -724,5 +666,5 @@ declare class GanttError extends Error {
|
|
|
724
666
|
constructor(code: GanttErrorCode, message: string);
|
|
725
667
|
}
|
|
726
668
|
//#endregion
|
|
727
|
-
export { BAR_HEIGHT, BAR_Y_OFFSET, type BarLayout, CHART_LOCALE_DE_DE, CHART_LOCALE_EN_GB, CHART_LOCALE_EN_US, CHART_LOCALE_ES_ES, CHART_LOCALE_FR_FR, CHART_LOCALE_IT_IT, CHART_LOCALE_JA_JP, CHART_LOCALE_PT_PT, CHART_LOCALE_ZH_CN, type ChartLocale, DEFAULT_GRID_COLUMNS, DENSITY, EN_US_LABELS, GRID_COLUMN_FR_MIN_WIDTH, type GanttCallbacks, GanttChart, GanttError, type GanttErrorCode, type GanttInput, type GanttInputRaw,
|
|
669
|
+
export { BAR_HEIGHT, BAR_Y_OFFSET, type BarLayout, CHART_LOCALE_DE_DE, CHART_LOCALE_EN_GB, CHART_LOCALE_EN_US, CHART_LOCALE_ES_ES, CHART_LOCALE_FR_FR, CHART_LOCALE_IT_IT, CHART_LOCALE_JA_JP, CHART_LOCALE_PT_PT, CHART_LOCALE_ZH_CN, type ChartLocale, DEFAULT_GRID_COLUMNS, DENSITY, EN_US_LABELS, GRID_COLUMN_FR_MIN_WIDTH, type GanttCallbacks, GanttChart, GanttError, type GanttErrorCode, type GanttInput, type GanttInputRaw, type GanttInstance, type GanttOptions, type GridColumn, type Link, type LinkType, type LocaleLabelKey, MILESTONE_HALF, MILESTONE_SIZE, type OnLinkClick, type OnLinkCreate, type OnLinkDblClick, type OnProgressChange, type OnTaskAdd, type OnTaskClick, type OnTaskDoubleClick, type OnTaskMove, type OnTaskResize, type OnTooltipText, type PixelMapper, type Point, ROW_HEIGHT, type RoutedLink, SCALE_CONFIGS, type ScaleConfig, type SpecialDay, type SpecialDayKind, type Task, type TaskDataField, type TaskKind, type TaskNode, type ThemeMode, type TimeScale, addDays, addHours, buildTaskTree, computeLayout, createPixelMapper, deriveViewport, deriveWeekNumbering, deriveWeekStartsOn, deriveWeekendDays, detectCycles, diffDays, diffHours, flattenTree, formatLabel, formatWeekNumber, gridColumnDefaults, gridNaturalWidth, gridTemplateColumns, isParent, parseDate, resolveChartLocale, routeLinks, validateLinkRefs, visibleColumns };
|
|
728
670
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -1,174 +1,4 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
//#region src/lib/validation/schemas.ts
|
|
3
|
-
const LinkTypeSchema = z.enum([
|
|
4
|
-
"FS",
|
|
5
|
-
"SS",
|
|
6
|
-
"FF",
|
|
7
|
-
"SF"
|
|
8
|
-
]);
|
|
9
|
-
const TaskKindSchema = z.enum([
|
|
10
|
-
"task",
|
|
11
|
-
"project",
|
|
12
|
-
"milestone"
|
|
13
|
-
]);
|
|
14
|
-
const SpecialDayKindSchema = z.enum(["holiday", "custom"]);
|
|
15
|
-
const SpecialDaySchema = z.object({
|
|
16
|
-
/** ISO date: YYYY-MM-DD */
|
|
17
|
-
date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/u, "Expected YYYY-MM-DD"),
|
|
18
|
-
kind: SpecialDayKindSchema,
|
|
19
|
-
label: z.string().min(1).optional(),
|
|
20
|
-
className: z.string().min(1).optional()
|
|
21
|
-
});
|
|
22
|
-
const taskBase = {
|
|
23
|
-
/** Unique positive integer identifier for the task. */
|
|
24
|
-
id: z.number().int().positive(),
|
|
25
|
-
/** Display name / label of the task. */
|
|
26
|
-
text: z.string().min(1),
|
|
27
|
-
/** ISO date: YYYY-MM-DD */
|
|
28
|
-
startDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/u, "Expected YYYY-MM-DD"),
|
|
29
|
-
/** Optional id of the parent task. When set, this task is a child in the hierarchy. */
|
|
30
|
-
parent: z.number().int().positive().optional(),
|
|
31
|
-
/** Optional CSS color value for the task bar. Overrides the default color assignment. */
|
|
32
|
-
color: z.string().optional(),
|
|
33
|
-
/** When `true`, the task bar cannot be dragged or resized. */
|
|
34
|
-
readonly: z.boolean().optional(),
|
|
35
|
-
/** Optional arbitrary metadata for consumer use. Preserved in the parsed output. */
|
|
36
|
-
data: z.record(z.string(), z.unknown()).optional()
|
|
37
|
-
};
|
|
38
|
-
/** @internal */
|
|
39
|
-
const TaskLeafSchema = z.object({
|
|
40
|
-
...taskBase,
|
|
41
|
-
kind: z.literal("task"),
|
|
42
|
-
/** ISO date: YYYY-MM-DD */
|
|
43
|
-
endDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/u, "Expected YYYY-MM-DD"),
|
|
44
|
-
/** 0–100 completion percentage (integer). */
|
|
45
|
-
percentComplete: z.number().int().min(0).max(100).default(0)
|
|
46
|
-
}).refine((t) => t.endDate >= t.startDate, {
|
|
47
|
-
message: "endDate must be on or after startDate",
|
|
48
|
-
path: ["endDate"]
|
|
49
|
-
});
|
|
50
|
-
/** @internal */
|
|
51
|
-
const TaskProjectSchema = z.object({
|
|
52
|
-
...taskBase,
|
|
53
|
-
kind: z.literal("project"),
|
|
54
|
-
/** ISO date: YYYY-MM-DD */
|
|
55
|
-
endDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/u, "Expected YYYY-MM-DD"),
|
|
56
|
-
/** 0–100 completion percentage (integer). */
|
|
57
|
-
percentComplete: z.number().int().min(0).max(100).default(0),
|
|
58
|
-
/**
|
|
59
|
-
* Initial expanded state for tree hierarchy.
|
|
60
|
-
* When `false`, children of this task are hidden on initial render.
|
|
61
|
-
*
|
|
62
|
-
* @default true
|
|
63
|
-
*/
|
|
64
|
-
open: z.boolean().default(true)
|
|
65
|
-
}).refine((t) => t.endDate >= t.startDate, {
|
|
66
|
-
message: "endDate must be on or after startDate",
|
|
67
|
-
path: ["endDate"]
|
|
68
|
-
});
|
|
69
|
-
/** @internal */
|
|
70
|
-
const TaskMilestoneSchema = z.object({
|
|
71
|
-
...taskBase,
|
|
72
|
-
kind: z.literal("milestone")
|
|
73
|
-
});
|
|
74
|
-
const TaskSchema = z.discriminatedUnion("kind", [
|
|
75
|
-
TaskLeafSchema,
|
|
76
|
-
TaskProjectSchema,
|
|
77
|
-
TaskMilestoneSchema
|
|
78
|
-
]);
|
|
79
|
-
const LinkSchema = z.object({
|
|
80
|
-
/** Unique positive integer identifier for the dependency link. */
|
|
81
|
-
id: z.number().int().positive(),
|
|
82
|
-
/** The `id` of the predecessor task (the task that drives the dependency). */
|
|
83
|
-
source: z.number().int().positive(),
|
|
84
|
-
/** The `id` of the successor task (the task that depends on the predecessor). */
|
|
85
|
-
target: z.number().int().positive(),
|
|
86
|
-
/**
|
|
87
|
-
* Dependency type.
|
|
88
|
-
*
|
|
89
|
-
* - `'FS'` — Finish-to-start: successor starts after predecessor finishes.
|
|
90
|
-
* - `'SS'` — Start-to-start: successor starts at the same time as predecessor.
|
|
91
|
-
* - `'FF'` — Finish-to-finish: successor finishes at the same time as predecessor.
|
|
92
|
-
* - `'SF'` — Start-to-finish: successor finishes after predecessor starts.
|
|
93
|
-
*
|
|
94
|
-
* @default 'FS'
|
|
95
|
-
*/
|
|
96
|
-
type: LinkTypeSchema.default("FS"),
|
|
97
|
-
/** When `true`, the link cannot be modified or deleted through the UI. */
|
|
98
|
-
readonly: z.boolean().optional(),
|
|
99
|
-
/** Optional arbitrary metadata for consumer use. Preserved in the parsed output. */
|
|
100
|
-
data: z.record(z.string(), z.unknown()).optional()
|
|
101
|
-
}).refine((l) => l.source !== l.target, {
|
|
102
|
-
message: "A link cannot connect a task to itself",
|
|
103
|
-
path: ["target"]
|
|
104
|
-
});
|
|
105
|
-
const GanttInputSchema = z.object({
|
|
106
|
-
/** Array of task objects. At least one task is required. */
|
|
107
|
-
tasks: z.array(TaskSchema).min(1),
|
|
108
|
-
/** Optional array of dependency link objects. Defaults to empty array. */
|
|
109
|
-
links: z.array(LinkSchema).default([])
|
|
110
|
-
}).superRefine((data, ctx) => {
|
|
111
|
-
const taskIds = /* @__PURE__ */ new Set();
|
|
112
|
-
for (let i = 0; i < data.tasks.length; i++) {
|
|
113
|
-
const task = data.tasks[i];
|
|
114
|
-
if (task !== void 0) {
|
|
115
|
-
if (taskIds.has(task.id)) ctx.addIssue({
|
|
116
|
-
code: "custom",
|
|
117
|
-
message: `Duplicate task id: ${task.id}`,
|
|
118
|
-
path: [
|
|
119
|
-
"tasks",
|
|
120
|
-
i,
|
|
121
|
-
"id"
|
|
122
|
-
]
|
|
123
|
-
});
|
|
124
|
-
taskIds.add(task.id);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
const linkIds = /* @__PURE__ */ new Set();
|
|
128
|
-
for (let i = 0; i < data.links.length; i++) {
|
|
129
|
-
const link = data.links[i];
|
|
130
|
-
if (link !== void 0) {
|
|
131
|
-
if (linkIds.has(link.id)) ctx.addIssue({
|
|
132
|
-
code: "custom",
|
|
133
|
-
message: `Duplicate link id: ${link.id}`,
|
|
134
|
-
path: [
|
|
135
|
-
"links",
|
|
136
|
-
i,
|
|
137
|
-
"id"
|
|
138
|
-
]
|
|
139
|
-
});
|
|
140
|
-
linkIds.add(link.id);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
const pairKeys = /* @__PURE__ */ new Set();
|
|
144
|
-
for (let i = 0; i < data.links.length; i++) {
|
|
145
|
-
const link = data.links[i];
|
|
146
|
-
if (link !== void 0) {
|
|
147
|
-
const key = `${link.source}:${link.target}`;
|
|
148
|
-
if (pairKeys.has(key)) ctx.addIssue({
|
|
149
|
-
code: "custom",
|
|
150
|
-
message: `Duplicate link pair: source=${link.source} target=${link.target}`,
|
|
151
|
-
path: ["links", i]
|
|
152
|
-
});
|
|
153
|
-
pairKeys.add(key);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
/**
|
|
158
|
-
* Parses raw external data.
|
|
159
|
-
*
|
|
160
|
-
* Compile-time generics enforce the `data` shape on tasks and links.
|
|
161
|
-
* Runtime validation is done by the zod schemas; the `data` field is
|
|
162
|
-
* validated as a generic object at runtime regardless of the type parameter.
|
|
163
|
-
*
|
|
164
|
-
* @param raw - The unvalidated input from the consumer.
|
|
165
|
-
* @returns The parsed and validated input with `data` typed per the type parameters.
|
|
166
|
-
* @throws {import('zod').ZodError} On schema validation failure.
|
|
167
|
-
*/
|
|
168
|
-
function parseGanttInput(raw) {
|
|
169
|
-
return GanttInputSchema.parse(raw);
|
|
170
|
-
}
|
|
171
|
-
//#endregion
|
|
172
2
|
//#region src/lib/errors.ts
|
|
173
3
|
/**
|
|
174
4
|
* Domain-specific error with a machine-readable {@link GanttErrorCode}.
|
|
@@ -1447,6 +1277,164 @@ function routeLinks(links, layouts) {
|
|
|
1447
1277
|
}).filter((r) => r !== null);
|
|
1448
1278
|
}
|
|
1449
1279
|
//#endregion
|
|
1280
|
+
//#region src/lib/validation/schemas.ts
|
|
1281
|
+
const LinkTypeSchema = z.enum([
|
|
1282
|
+
"FS",
|
|
1283
|
+
"SS",
|
|
1284
|
+
"FF",
|
|
1285
|
+
"SF"
|
|
1286
|
+
]);
|
|
1287
|
+
z.enum([
|
|
1288
|
+
"task",
|
|
1289
|
+
"project",
|
|
1290
|
+
"milestone"
|
|
1291
|
+
]);
|
|
1292
|
+
const SpecialDayKindSchema = z.enum(["holiday", "custom"]);
|
|
1293
|
+
/** @internal */
|
|
1294
|
+
const SpecialDaySchema = z.object({
|
|
1295
|
+
/** ISO date: YYYY-MM-DD */
|
|
1296
|
+
date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/u, "Expected YYYY-MM-DD"),
|
|
1297
|
+
kind: SpecialDayKindSchema,
|
|
1298
|
+
label: z.string().min(1).optional(),
|
|
1299
|
+
className: z.string().min(1).optional()
|
|
1300
|
+
});
|
|
1301
|
+
const taskBase = {
|
|
1302
|
+
/** Unique positive integer identifier for the task. */
|
|
1303
|
+
id: z.number().int().positive(),
|
|
1304
|
+
/** Display name / label of the task. */
|
|
1305
|
+
text: z.string().min(1),
|
|
1306
|
+
/** ISO date: YYYY-MM-DD */
|
|
1307
|
+
startDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/u, "Expected YYYY-MM-DD"),
|
|
1308
|
+
/** Optional id of the parent task. When set, this task is a child in the hierarchy. */
|
|
1309
|
+
parent: z.number().int().positive().optional(),
|
|
1310
|
+
/** Optional CSS color value for the task bar. Overrides the default color assignment. */
|
|
1311
|
+
color: z.string().optional(),
|
|
1312
|
+
/** When `true`, the task bar cannot be dragged or resized. */
|
|
1313
|
+
readonly: z.boolean().optional(),
|
|
1314
|
+
/** Optional arbitrary metadata for consumer use. Preserved in the parsed output. */
|
|
1315
|
+
data: z.record(z.string(), z.unknown()).optional()
|
|
1316
|
+
};
|
|
1317
|
+
/** @internal */
|
|
1318
|
+
const TaskLeafSchema = z.object({
|
|
1319
|
+
...taskBase,
|
|
1320
|
+
kind: z.literal("task"),
|
|
1321
|
+
/** ISO date: YYYY-MM-DD */
|
|
1322
|
+
endDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/u, "Expected YYYY-MM-DD"),
|
|
1323
|
+
/** 0–100 completion percentage (integer). */
|
|
1324
|
+
percentComplete: z.number().int().min(0).max(100).default(0)
|
|
1325
|
+
}).refine((t) => t.endDate >= t.startDate, {
|
|
1326
|
+
message: "endDate must be on or after startDate",
|
|
1327
|
+
path: ["endDate"]
|
|
1328
|
+
});
|
|
1329
|
+
/** @internal */
|
|
1330
|
+
const TaskProjectSchema = z.object({
|
|
1331
|
+
...taskBase,
|
|
1332
|
+
kind: z.literal("project"),
|
|
1333
|
+
/** ISO date: YYYY-MM-DD */
|
|
1334
|
+
endDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/u, "Expected YYYY-MM-DD"),
|
|
1335
|
+
/** 0–100 completion percentage (integer). */
|
|
1336
|
+
percentComplete: z.number().int().min(0).max(100).default(0),
|
|
1337
|
+
/**
|
|
1338
|
+
* Initial expanded state for tree hierarchy.
|
|
1339
|
+
* When `false`, children of this task are hidden on initial render.
|
|
1340
|
+
*
|
|
1341
|
+
* @default true
|
|
1342
|
+
*/
|
|
1343
|
+
open: z.boolean().default(true)
|
|
1344
|
+
}).refine((t) => t.endDate >= t.startDate, {
|
|
1345
|
+
message: "endDate must be on or after startDate",
|
|
1346
|
+
path: ["endDate"]
|
|
1347
|
+
});
|
|
1348
|
+
/** @internal */
|
|
1349
|
+
const TaskMilestoneSchema = z.object({
|
|
1350
|
+
...taskBase,
|
|
1351
|
+
kind: z.literal("milestone")
|
|
1352
|
+
});
|
|
1353
|
+
const TaskSchema = z.discriminatedUnion("kind", [
|
|
1354
|
+
TaskLeafSchema,
|
|
1355
|
+
TaskProjectSchema,
|
|
1356
|
+
TaskMilestoneSchema
|
|
1357
|
+
]);
|
|
1358
|
+
const LinkSchema = z.object({
|
|
1359
|
+
/** Unique positive integer identifier for the dependency link. */
|
|
1360
|
+
id: z.number().int().positive(),
|
|
1361
|
+
/** The `id` of the predecessor task (the task that drives the dependency). */
|
|
1362
|
+
source: z.number().int().positive(),
|
|
1363
|
+
/** The `id` of the successor task (the task that depends on the predecessor). */
|
|
1364
|
+
target: z.number().int().positive(),
|
|
1365
|
+
/**
|
|
1366
|
+
* Dependency type.
|
|
1367
|
+
*
|
|
1368
|
+
* - `'FS'` — Finish-to-start: successor starts after predecessor finishes.
|
|
1369
|
+
* - `'SS'` — Start-to-start: successor starts at the same time as predecessor.
|
|
1370
|
+
* - `'FF'` — Finish-to-finish: successor finishes at the same time as predecessor.
|
|
1371
|
+
* - `'SF'` — Start-to-finish: successor finishes after predecessor starts.
|
|
1372
|
+
*
|
|
1373
|
+
* @default 'FS'
|
|
1374
|
+
*/
|
|
1375
|
+
type: LinkTypeSchema.default("FS"),
|
|
1376
|
+
/** When `true`, the link cannot be modified or deleted through the UI. */
|
|
1377
|
+
readonly: z.boolean().optional(),
|
|
1378
|
+
/** Optional arbitrary metadata for consumer use. Preserved in the parsed output. */
|
|
1379
|
+
data: z.record(z.string(), z.unknown()).optional()
|
|
1380
|
+
}).refine((l) => l.source !== l.target, {
|
|
1381
|
+
message: "A link cannot connect a task to itself",
|
|
1382
|
+
path: ["target"]
|
|
1383
|
+
});
|
|
1384
|
+
/** @internal */
|
|
1385
|
+
const GanttInputSchema = z.object({
|
|
1386
|
+
/** Array of task objects. At least one task is required. */
|
|
1387
|
+
tasks: z.array(TaskSchema).min(1),
|
|
1388
|
+
/** Optional array of dependency link objects. Defaults to empty array. */
|
|
1389
|
+
links: z.array(LinkSchema).default([])
|
|
1390
|
+
}).superRefine((data, ctx) => {
|
|
1391
|
+
const taskIds = /* @__PURE__ */ new Set();
|
|
1392
|
+
for (let i = 0; i < data.tasks.length; i++) {
|
|
1393
|
+
const task = data.tasks[i];
|
|
1394
|
+
if (task !== void 0) {
|
|
1395
|
+
if (taskIds.has(task.id)) ctx.addIssue({
|
|
1396
|
+
code: "custom",
|
|
1397
|
+
message: `Duplicate task id: ${task.id}`,
|
|
1398
|
+
path: [
|
|
1399
|
+
"tasks",
|
|
1400
|
+
i,
|
|
1401
|
+
"id"
|
|
1402
|
+
]
|
|
1403
|
+
});
|
|
1404
|
+
taskIds.add(task.id);
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
const linkIds = /* @__PURE__ */ new Set();
|
|
1408
|
+
for (let i = 0; i < data.links.length; i++) {
|
|
1409
|
+
const link = data.links[i];
|
|
1410
|
+
if (link !== void 0) {
|
|
1411
|
+
if (linkIds.has(link.id)) ctx.addIssue({
|
|
1412
|
+
code: "custom",
|
|
1413
|
+
message: `Duplicate link id: ${link.id}`,
|
|
1414
|
+
path: [
|
|
1415
|
+
"links",
|
|
1416
|
+
i,
|
|
1417
|
+
"id"
|
|
1418
|
+
]
|
|
1419
|
+
});
|
|
1420
|
+
linkIds.add(link.id);
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
const pairKeys = /* @__PURE__ */ new Set();
|
|
1424
|
+
for (let i = 0; i < data.links.length; i++) {
|
|
1425
|
+
const link = data.links[i];
|
|
1426
|
+
if (link !== void 0) {
|
|
1427
|
+
const key = `${link.source}:${link.target}`;
|
|
1428
|
+
if (pairKeys.has(key)) ctx.addIssue({
|
|
1429
|
+
code: "custom",
|
|
1430
|
+
message: `Duplicate link pair: source=${link.source} target=${link.target}`,
|
|
1431
|
+
path: ["links", i]
|
|
1432
|
+
});
|
|
1433
|
+
pairKeys.add(key);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
});
|
|
1437
|
+
//#endregion
|
|
1450
1438
|
//#region src/lib/vanilla/dom/helpers.ts
|
|
1451
1439
|
/**
|
|
1452
1440
|
* Batches style assignments; avoids repeated style recalculations.
|
|
@@ -2300,19 +2288,10 @@ function attachDrag(barEl, resizeHandleEl, task, getMapper, cbs) {
|
|
|
2300
2288
|
window.addEventListener("pointermove", onMove);
|
|
2301
2289
|
window.addEventListener("pointerup", onUp);
|
|
2302
2290
|
}
|
|
2303
|
-
function onBarClick(event) {
|
|
2304
|
-
if (event.detail !== 2) return;
|
|
2305
|
-
cbs.onTaskDoubleClick?.({
|
|
2306
|
-
id: task.id,
|
|
2307
|
-
task: toTask(task)
|
|
2308
|
-
});
|
|
2309
|
-
}
|
|
2310
2291
|
barEl.addEventListener("pointerdown", onBarDown);
|
|
2311
|
-
barEl.addEventListener("click", onBarClick);
|
|
2312
2292
|
resizeHandleEl.addEventListener("pointerdown", onResizeDown);
|
|
2313
2293
|
return () => {
|
|
2314
2294
|
barEl.removeEventListener("pointerdown", onBarDown);
|
|
2315
|
-
barEl.removeEventListener("click", onBarClick);
|
|
2316
2295
|
resizeHandleEl.removeEventListener("pointerdown", onResizeDown);
|
|
2317
2296
|
};
|
|
2318
2297
|
}
|
|
@@ -3122,8 +3101,9 @@ var GanttChart = class {
|
|
|
3122
3101
|
this.#taskIndex = /* @__PURE__ */ new Map();
|
|
3123
3102
|
this.#locale = resolveChartLocale(opts.locale);
|
|
3124
3103
|
this.#columns = opts.gridColumns ?? gridColumnDefaults(this.#locale);
|
|
3125
|
-
this.#leftPaneDefaultWidth = opts.leftPaneWidth ?? gridNaturalWidth(this.#columns);
|
|
3126
3104
|
this.#showAddTaskButton = opts.showAddTaskButton ?? true;
|
|
3105
|
+
this.#syncActionsColumnVisibility();
|
|
3106
|
+
this.#leftPaneDefaultWidth = opts.leftPaneWidth ?? gridNaturalWidth(this.#columns);
|
|
3127
3107
|
this.#height = opts.height ?? 500;
|
|
3128
3108
|
this.#timelineMinWidth = opts.timelineMinWidth ?? 220;
|
|
3129
3109
|
this.#weekendDays = normalizeWeekendDays(opts.weekendDays ?? this.#locale.weekendDays);
|
|
@@ -3328,7 +3308,7 @@ var GanttChart = class {
|
|
|
3328
3308
|
*/
|
|
3329
3309
|
update(newInput) {
|
|
3330
3310
|
this.#assertAlive();
|
|
3331
|
-
const input = newInput;
|
|
3311
|
+
const input = GanttInputSchema.parse(newInput);
|
|
3332
3312
|
validateLinkRefs(input.tasks, input.links);
|
|
3333
3313
|
detectCycles(input.tasks, input.links);
|
|
3334
3314
|
this.#input = structuredClone(input);
|
|
@@ -3382,7 +3362,16 @@ var GanttChart = class {
|
|
|
3382
3362
|
if (opts.weekendDays !== void 0) this.#weekendDays = normalizeWeekendDays(opts.weekendDays);
|
|
3383
3363
|
if (opts.specialDays !== void 0) this.#specialDaysByDate = buildSpecialDayIndex(opts.specialDays);
|
|
3384
3364
|
if (opts.theme !== void 0) this.#applyTheme();
|
|
3385
|
-
if (opts.showAddTaskButton !== void 0)
|
|
3365
|
+
if (opts.showAddTaskButton !== void 0) {
|
|
3366
|
+
this.#showAddTaskButton = opts.showAddTaskButton;
|
|
3367
|
+
this.#syncActionsColumnVisibility();
|
|
3368
|
+
const naturalWidth = gridNaturalWidth(this.#columns);
|
|
3369
|
+
if (naturalWidth !== this.#leftPaneDefaultWidth) {
|
|
3370
|
+
this.#leftPaneDefaultWidth = naturalWidth;
|
|
3371
|
+
columnsChanged = true;
|
|
3372
|
+
this.#rebuildLeftPaneHeader();
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3386
3375
|
const hasLayoutChange = opts.leftPaneWidth !== void 0 || opts.responsiveSplitPane !== void 0 || opts.mobileBreakpoint !== void 0 || opts.mobileLeftPaneMinWidth !== void 0 || opts.mobileLeftPaneMaxRatio !== void 0 || opts.timelineMinWidth !== void 0;
|
|
3387
3376
|
if (hasLayoutChange) this.#applyResponsivePaneStyles();
|
|
3388
3377
|
const hasLeftPaneChange = columnsChanged || opts.locale !== void 0 || opts.showAddTaskButton !== void 0;
|
|
@@ -3487,6 +3476,11 @@ var GanttChart = class {
|
|
|
3487
3476
|
#findTask(id) {
|
|
3488
3477
|
return this.#input?.tasks.find((t) => t.id === id);
|
|
3489
3478
|
}
|
|
3479
|
+
#syncActionsColumnVisibility() {
|
|
3480
|
+
const actionsCol = this.#columns.find((c) => c.id === "actions");
|
|
3481
|
+
if (actionsCol !== void 0) if (this.#showAddTaskButton) delete actionsCol.visible;
|
|
3482
|
+
else actionsCol.visible = false;
|
|
3483
|
+
}
|
|
3490
3484
|
#handleGridClick = (payload) => {
|
|
3491
3485
|
const now = Date.now();
|
|
3492
3486
|
const prev = this.#lastGridClick;
|
|
@@ -3736,6 +3730,6 @@ var GanttChart = class {
|
|
|
3736
3730
|
}
|
|
3737
3731
|
};
|
|
3738
3732
|
//#endregion
|
|
3739
|
-
export { BAR_HEIGHT, BAR_Y_OFFSET, CHART_LOCALE_DE_DE, CHART_LOCALE_EN_GB, CHART_LOCALE_EN_US, CHART_LOCALE_ES_ES, CHART_LOCALE_FR_FR, CHART_LOCALE_IT_IT, CHART_LOCALE_JA_JP, CHART_LOCALE_PT_PT, CHART_LOCALE_ZH_CN, DEFAULT_GRID_COLUMNS, DENSITY, EN_US_LABELS, GRID_COLUMN_FR_MIN_WIDTH, GanttChart, GanttError,
|
|
3733
|
+
export { BAR_HEIGHT, BAR_Y_OFFSET, CHART_LOCALE_DE_DE, CHART_LOCALE_EN_GB, CHART_LOCALE_EN_US, CHART_LOCALE_ES_ES, CHART_LOCALE_FR_FR, CHART_LOCALE_IT_IT, CHART_LOCALE_JA_JP, CHART_LOCALE_PT_PT, CHART_LOCALE_ZH_CN, DEFAULT_GRID_COLUMNS, DENSITY, EN_US_LABELS, GRID_COLUMN_FR_MIN_WIDTH, GanttChart, GanttError, MILESTONE_HALF, MILESTONE_SIZE, ROW_HEIGHT, SCALE_CONFIGS, addDays, addHours, buildTaskTree, computeLayout, createPixelMapper, deriveViewport, deriveWeekNumbering, deriveWeekStartsOn, deriveWeekendDays, detectCycles, diffDays, diffHours, flattenTree, formatLabel, formatWeekNumber, gridColumnDefaults, gridNaturalWidth, gridTemplateColumns, isParent, parseDate, resolveChartLocale, routeLinks, validateLinkRefs, visibleColumns };
|
|
3740
3734
|
|
|
3741
3735
|
//# sourceMappingURL=index.mjs.map
|