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 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, parseGanttInput} from 'gantt-renderer';
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(input);
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. Validate the result with `parseGanttInput(yourData)`.
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
- declare const GanttInputSchema: z.ZodObject<{
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, unvalidated input shape that consumers pass to {@link parseGanttInput}.
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: GanttInput<TTaskData, TLinkData>) => void;
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: GanttInput<TTaskData, TLinkData>): void;
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, GanttInputSchema, type GanttInstance, type GanttOptions, type GridColumn, type Link, LinkSchema, type LinkType, LinkTypeSchema, 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, SpecialDayKindSchema, SpecialDaySchema, type Task, type TaskDataField, type TaskKind, TaskKindSchema, type TaskNode, TaskSchema, type ThemeMode, type TimeScale, addDays, addHours, buildTaskTree, computeLayout, createPixelMapper, deriveViewport, deriveWeekNumbering, deriveWeekStartsOn, deriveWeekendDays, detectCycles, diffDays, diffHours, flattenTree, formatLabel, formatWeekNumber, gridColumnDefaults, gridNaturalWidth, gridTemplateColumns, isParent, parseDate, parseGanttInput, resolveChartLocale, routeLinks, validateLinkRefs, visibleColumns };
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) this.#showAddTaskButton = opts.showAddTaskButton;
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, GanttInputSchema, LinkSchema, LinkTypeSchema, MILESTONE_HALF, MILESTONE_SIZE, ROW_HEIGHT, SCALE_CONFIGS, SpecialDayKindSchema, SpecialDaySchema, TaskKindSchema, TaskSchema, addDays, addHours, buildTaskTree, computeLayout, createPixelMapper, deriveViewport, deriveWeekNumbering, deriveWeekStartsOn, deriveWeekendDays, detectCycles, diffDays, diffHours, flattenTree, formatLabel, formatWeekNumber, gridColumnDefaults, gridNaturalWidth, gridTemplateColumns, isParent, parseDate, parseGanttInput, resolveChartLocale, routeLinks, validateLinkRefs, visibleColumns };
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