gantt-renderer 0.6.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,3 +1,33 @@
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
+
24
+ # [0.7.0](https://github.com/doberkofler/gantt-renderer/compare/v0.6.0...v0.7.0) (2026-05-11)
25
+
26
+
27
+ ### Features
28
+
29
+ * **types:** make GanttInputRaw and parseGanttInput generic ([e665221](https://github.com/doberkofler/gantt-renderer/commit/e665221cd13ed58138770365079884cfcc5067fe))
30
+
1
31
  # [0.6.0](https://github.com/doberkofler/gantt-renderer/compare/v0.5.0...v0.6.0) (2026-05-11)
2
32
 
3
33
 
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,58 +72,19 @@ 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 */
125
79
  type ZodLinkInferred = z.infer<typeof LinkSchema>;
80
+ /** @internal */
81
+ type ZodTaskInput = z.input<typeof TaskSchema>;
82
+ /** @internal */
83
+ type ZodLinkInput = z.input<typeof LinkSchema>;
84
+ /** Raw input version of a task — uses zod's input shape where fields with defaults are optional. */
85
+ type TaskRaw<TData = never> = WithoutData<ZodTaskInput> & WithData<TData>;
86
+ /** Raw input version of a link — uses zod's input shape where fields with defaults are optional. */
87
+ type LinkRaw<TData = never> = WithoutData<ZodLinkInput> & WithData<TData>;
126
88
  /**
127
89
  * Conditional data property helper.
128
90
  * When `T` is `never`, adds nothing. Otherwise adds `{data?: T | undefined}`.
@@ -173,24 +135,24 @@ type GanttInput<TTaskData = never, TLinkData = never> = {
173
135
  tasks: Task<TTaskData>[];
174
136
  links: Link<TLinkData>[];
175
137
  };
176
- /** @internal */
177
- type _GanttInputZod = z.infer<typeof GanttInputSchema>;
178
- /** The raw, unvalidated input shape that consumers pass to {@link parseGanttInput}. */
179
- type GanttInputRaw = z.input<typeof GanttInputSchema>;
138
+ /**
139
+ * The raw input shape that consumers pass to {@link GanttChart.update}.
140
+ *
141
+ * Fields with defaults in the schema (e.g. `percentComplete`, `type`) remain optional here.
142
+ *
143
+ * @param TTaskData - The type of the `data` property on tasks. Defaults to `never`.
144
+ * @param TLinkData - The type of the `data` property on links. Defaults to `never`.
145
+ */
146
+ type GanttInputRaw<TTaskData = never, TLinkData = never> = {
147
+ tasks: readonly TaskRaw<TTaskData>[];
148
+ links?: readonly LinkRaw<TLinkData>[];
149
+ };
180
150
  /** Allowed dependency link type values: `'FS'`, `'SS'`, `'FF'`, or `'SF'`. */
181
151
  type LinkType = z.infer<typeof LinkTypeSchema>;
182
152
  /** Allowed task kind values: `'task'`, `'project'`, or `'milestone'`. */
183
153
  type TaskKind = z.infer<typeof TaskKindSchema>;
184
154
  type SpecialDayKind = z.infer<typeof SpecialDayKindSchema>;
185
155
  type SpecialDay = z.infer<typeof SpecialDaySchema>;
186
- /**
187
- * Parses raw external data.
188
- *
189
- * @param raw - The unvalidated input from the consumer.
190
- * @returns The parsed and validated input with `data` typed as `Record<string, unknown>`.
191
- * @throws {import('zod').ZodError} On schema validation failure.
192
- */
193
- declare function parseGanttInput(raw: GanttInputRaw): GanttInput<Record<string, unknown>, Record<string, unknown>>;
194
156
  //#endregion
195
157
  //#region src/lib/timeline/scale.d.ts
196
158
  type TimeScale = 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';
@@ -511,8 +473,6 @@ declare const GRID_COLUMN_FR_MIN_WIDTH = 120;
511
473
  declare function gridNaturalWidth(columns: GridColumn[]): number;
512
474
  //#endregion
513
475
  //#region src/lib/vanilla/gantt-chart.d.ts
514
- /** Internal convenience aliases for the zod-inferred runtime types (include `data?: Record<string, unknown>`). */
515
- type GanttInput$1 = _GanttInputZod;
516
476
  type OnTaskClick<TTaskData = never, TLinkData = never> = (payload: {
517
477
  task: Task<TTaskData>;
518
478
  instance: GanttInstance<TTaskData, TLinkData>;
@@ -605,7 +565,7 @@ type GanttOptions = {
605
565
  showAddTaskButton?: boolean;
606
566
  };
607
567
  type GanttInstance<TTaskData = never, TLinkData = never> = {
608
- update: (input: GanttInput$1) => void;
568
+ update: (input: GanttInputRaw<TTaskData, TLinkData>) => void;
609
569
  setOptions: (opts: Partial<GanttOptions>) => void;
610
570
  setCallbacks: (cbs: GanttCallbacks<TTaskData, TLinkData>) => void;
611
571
  select: (id: number | null, fireCallback?: boolean) => void;
@@ -654,7 +614,7 @@ declare class GanttChart<TTaskData = never, TLinkData = never> implements GanttI
654
614
  * @param newInput - The new {@link GanttInput} to apply.
655
615
  * @throws {GanttError} When the instance has been destroyed.
656
616
  */
657
- update(newInput: GanttInput$1): void;
617
+ update(newInput: GanttInputRaw<TTaskData, TLinkData>): void;
658
618
  /**
659
619
  * Merges the supplied options into the current configuration and re-renders
660
620
  * only the panes affected by the changed options.
@@ -706,5 +666,5 @@ declare class GanttError extends Error {
706
666
  constructor(code: GanttErrorCode, message: string);
707
667
  }
708
668
  //#endregion
709
- 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 };
710
670
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -1,170 +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
- * @param raw - The unvalidated input from the consumer.
161
- * @returns The parsed and validated input with `data` typed as `Record<string, unknown>`.
162
- * @throws {import('zod').ZodError} On schema validation failure.
163
- */
164
- function parseGanttInput(raw) {
165
- return GanttInputSchema.parse(raw);
166
- }
167
- //#endregion
168
2
  //#region src/lib/errors.ts
169
3
  /**
170
4
  * Domain-specific error with a machine-readable {@link GanttErrorCode}.
@@ -1443,6 +1277,164 @@ function routeLinks(links, layouts) {
1443
1277
  }).filter((r) => r !== null);
1444
1278
  }
1445
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
1446
1438
  //#region src/lib/vanilla/dom/helpers.ts
1447
1439
  /**
1448
1440
  * Batches style assignments; avoids repeated style recalculations.
@@ -2296,19 +2288,10 @@ function attachDrag(barEl, resizeHandleEl, task, getMapper, cbs) {
2296
2288
  window.addEventListener("pointermove", onMove);
2297
2289
  window.addEventListener("pointerup", onUp);
2298
2290
  }
2299
- function onBarClick(event) {
2300
- if (event.detail !== 2) return;
2301
- cbs.onTaskDoubleClick?.({
2302
- id: task.id,
2303
- task: toTask(task)
2304
- });
2305
- }
2306
2291
  barEl.addEventListener("pointerdown", onBarDown);
2307
- barEl.addEventListener("click", onBarClick);
2308
2292
  resizeHandleEl.addEventListener("pointerdown", onResizeDown);
2309
2293
  return () => {
2310
2294
  barEl.removeEventListener("pointerdown", onBarDown);
2311
- barEl.removeEventListener("click", onBarClick);
2312
2295
  resizeHandleEl.removeEventListener("pointerdown", onResizeDown);
2313
2296
  };
2314
2297
  }
@@ -3118,8 +3101,9 @@ var GanttChart = class {
3118
3101
  this.#taskIndex = /* @__PURE__ */ new Map();
3119
3102
  this.#locale = resolveChartLocale(opts.locale);
3120
3103
  this.#columns = opts.gridColumns ?? gridColumnDefaults(this.#locale);
3121
- this.#leftPaneDefaultWidth = opts.leftPaneWidth ?? gridNaturalWidth(this.#columns);
3122
3104
  this.#showAddTaskButton = opts.showAddTaskButton ?? true;
3105
+ this.#syncActionsColumnVisibility();
3106
+ this.#leftPaneDefaultWidth = opts.leftPaneWidth ?? gridNaturalWidth(this.#columns);
3123
3107
  this.#height = opts.height ?? 500;
3124
3108
  this.#timelineMinWidth = opts.timelineMinWidth ?? 220;
3125
3109
  this.#weekendDays = normalizeWeekendDays(opts.weekendDays ?? this.#locale.weekendDays);
@@ -3324,9 +3308,10 @@ var GanttChart = class {
3324
3308
  */
3325
3309
  update(newInput) {
3326
3310
  this.#assertAlive();
3327
- validateLinkRefs(newInput.tasks, newInput.links);
3328
- detectCycles(newInput.tasks, newInput.links);
3329
- this.#input = structuredClone(newInput);
3311
+ const input = GanttInputSchema.parse(newInput);
3312
+ validateLinkRefs(input.tasks, input.links);
3313
+ detectCycles(input.tasks, input.links);
3314
+ this.#input = structuredClone(input);
3330
3315
  this.#taskIndex = buildTaskIndex(this.#input.tasks);
3331
3316
  this.#expandedIds = getInitialExpandedIds(this.#input.tasks);
3332
3317
  if (this.#rafPending && this.#rafId !== null) {
@@ -3377,7 +3362,16 @@ var GanttChart = class {
3377
3362
  if (opts.weekendDays !== void 0) this.#weekendDays = normalizeWeekendDays(opts.weekendDays);
3378
3363
  if (opts.specialDays !== void 0) this.#specialDaysByDate = buildSpecialDayIndex(opts.specialDays);
3379
3364
  if (opts.theme !== void 0) this.#applyTheme();
3380
- 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
+ }
3381
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;
3382
3376
  if (hasLayoutChange) this.#applyResponsivePaneStyles();
3383
3377
  const hasLeftPaneChange = columnsChanged || opts.locale !== void 0 || opts.showAddTaskButton !== void 0;
@@ -3482,6 +3476,11 @@ var GanttChart = class {
3482
3476
  #findTask(id) {
3483
3477
  return this.#input?.tasks.find((t) => t.id === id);
3484
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
+ }
3485
3484
  #handleGridClick = (payload) => {
3486
3485
  const now = Date.now();
3487
3486
  const prev = this.#lastGridClick;
@@ -3731,6 +3730,6 @@ var GanttChart = class {
3731
3730
  }
3732
3731
  };
3733
3732
  //#endregion
3734
- 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 };
3735
3734
 
3736
3735
  //# sourceMappingURL=index.mjs.map