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 +30 -0
- package/README.md +3 -4
- package/dist/index.d.mts +25 -65
- package/dist/index.mjs +180 -181
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
|
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,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
|
-
|
|
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
|
-
/**
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
type
|
|
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:
|
|
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:
|
|
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,
|
|
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
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
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)
|
|
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,
|
|
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
|