@fatcore/gantt-lite 1.0.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.
Files changed (87) hide show
  1. package/.editorconfig +16 -0
  2. package/.vscode/extensions.json +4 -0
  3. package/.vscode/launch.json +20 -0
  4. package/.vscode/tasks.json +42 -0
  5. package/README.md +27 -0
  6. package/angular.json +165 -0
  7. package/dist17/gantt-lite/README.md +24 -0
  8. package/dist17/gantt-lite/esm2022/gantt-lite.mjs +5 -0
  9. package/dist17/gantt-lite/esm2022/gantt-lite.module.mjs +18 -0
  10. package/dist17/gantt-lite/esm2022/lib/gantt-lite-base.mjs +290 -0
  11. package/dist17/gantt-lite/esm2022/lib/gantt-lite.component.mjs +359 -0
  12. package/dist17/gantt-lite/esm2022/lib/gantt-lite.helper.mjs +107 -0
  13. package/dist17/gantt-lite/esm2022/lib/gantt-lite.model.mjs +2 -0
  14. package/dist17/gantt-lite/esm2022/public-api.mjs +3 -0
  15. package/dist17/gantt-lite/fesm2022/gantt-lite.mjs +774 -0
  16. package/dist17/gantt-lite/fesm2022/gantt-lite.mjs.map +1 -0
  17. package/dist17/gantt-lite/gantt-lite.module.d.ts +8 -0
  18. package/dist17/gantt-lite/index.d.ts +5 -0
  19. package/dist17/gantt-lite/lib/gantt-lite-base.d.ts +50 -0
  20. package/dist17/gantt-lite/lib/gantt-lite.component.d.ts +55 -0
  21. package/dist17/gantt-lite/lib/gantt-lite.helper.d.ts +20 -0
  22. package/dist17/gantt-lite/lib/gantt-lite.model.d.ts +47 -0
  23. package/dist17/gantt-lite/public-api.d.ts +2 -0
  24. package/dist18/gantt-lite/README.md +24 -0
  25. package/dist18/gantt-lite/esm2022/gantt-lite.mjs +5 -0
  26. package/dist18/gantt-lite/esm2022/gantt-lite.module.mjs +18 -0
  27. package/dist18/gantt-lite/esm2022/lib/gantt-lite-base.mjs +290 -0
  28. package/dist18/gantt-lite/esm2022/lib/gantt-lite.component.mjs +359 -0
  29. package/dist18/gantt-lite/esm2022/lib/gantt-lite.helper.mjs +107 -0
  30. package/dist18/gantt-lite/esm2022/lib/gantt-lite.model.mjs +2 -0
  31. package/dist18/gantt-lite/esm2022/public-api.mjs +3 -0
  32. package/dist18/gantt-lite/fesm2022/gantt-lite.mjs +774 -0
  33. package/dist18/gantt-lite/fesm2022/gantt-lite.mjs.map +1 -0
  34. package/dist18/gantt-lite/gantt-lite.module.d.ts +8 -0
  35. package/dist18/gantt-lite/index.d.ts +5 -0
  36. package/dist18/gantt-lite/lib/gantt-lite-base.d.ts +50 -0
  37. package/dist18/gantt-lite/lib/gantt-lite.component.d.ts +55 -0
  38. package/dist18/gantt-lite/lib/gantt-lite.helper.d.ts +20 -0
  39. package/dist18/gantt-lite/lib/gantt-lite.model.d.ts +47 -0
  40. package/dist18/gantt-lite/public-api.d.ts +2 -0
  41. package/dist19/gantt-lite/README.md +24 -0
  42. package/dist19/gantt-lite/fesm2022/gantt-lite.mjs +774 -0
  43. package/dist19/gantt-lite/fesm2022/gantt-lite.mjs.map +1 -0
  44. package/dist19/gantt-lite/gantt-lite.module.d.ts +8 -0
  45. package/dist19/gantt-lite/index.d.ts +5 -0
  46. package/dist19/gantt-lite/lib/gantt-lite-base.d.ts +50 -0
  47. package/dist19/gantt-lite/lib/gantt-lite.component.d.ts +55 -0
  48. package/dist19/gantt-lite/lib/gantt-lite.helper.d.ts +20 -0
  49. package/dist19/gantt-lite/lib/gantt-lite.model.d.ts +47 -0
  50. package/dist19/gantt-lite/public-api.d.ts +2 -0
  51. package/dist20/gantt-lite/README.md +24 -0
  52. package/dist20/gantt-lite/fesm2022/gantt-lite.mjs +774 -0
  53. package/dist20/gantt-lite/fesm2022/gantt-lite.mjs.map +1 -0
  54. package/dist20/gantt-lite/index.d.ts +159 -0
  55. package/my-workspace-0.0.0.tgz +0 -0
  56. package/package.json +45 -0
  57. package/projects/gantt-lite/README.md +24 -0
  58. package/projects/gantt-lite/ng-package.json +7 -0
  59. package/projects/gantt-lite/package.json +10 -0
  60. package/projects/gantt-lite/src/gantt-lite.module.ts +10 -0
  61. package/projects/gantt-lite/src/lib/gantt-lite-base.ts +300 -0
  62. package/projects/gantt-lite/src/lib/gantt-lite.component.html +128 -0
  63. package/projects/gantt-lite/src/lib/gantt-lite.component.scss +323 -0
  64. package/projects/gantt-lite/src/lib/gantt-lite.component.ts +391 -0
  65. package/projects/gantt-lite/src/lib/gantt-lite.helper.ts +124 -0
  66. package/projects/gantt-lite/src/lib/gantt-lite.model.ts +56 -0
  67. package/projects/gantt-lite/src/public-api.ts +5 -0
  68. package/projects/gantt-lite/tsconfig.lib.json +14 -0
  69. package/projects/gantt-lite/tsconfig.lib.prod.json +10 -0
  70. package/projects/gantt-lite/tsconfig.spec.json +14 -0
  71. package/projects/my-app/server.ts +56 -0
  72. package/projects/my-app/src/app/app.component.html +336 -0
  73. package/projects/my-app/src/app/app.component.scss +0 -0
  74. package/projects/my-app/src/app/app.component.spec.ts +29 -0
  75. package/projects/my-app/src/app/app.component.ts +13 -0
  76. package/projects/my-app/src/app/app.config.server.ts +11 -0
  77. package/projects/my-app/src/app/app.config.ts +9 -0
  78. package/projects/my-app/src/app/app.routes.ts +3 -0
  79. package/projects/my-app/src/assets/.gitkeep +0 -0
  80. package/projects/my-app/src/favicon.ico +0 -0
  81. package/projects/my-app/src/index.html +13 -0
  82. package/projects/my-app/src/main.server.ts +7 -0
  83. package/projects/my-app/src/main.ts +6 -0
  84. package/projects/my-app/src/styles.scss +1 -0
  85. package/projects/my-app/tsconfig.app.json +18 -0
  86. package/projects/my-app/tsconfig.spec.json +14 -0
  87. package/tsconfig.json +37 -0
@@ -0,0 +1,774 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Directive, Input, Component, ChangeDetectionStrategy, ViewChild, NgModule } from '@angular/core';
3
+ import * as i1 from '@angular/common';
4
+ import { CommonModule } from '@angular/common';
5
+
6
+ class SlotIndexToDateLabelConverter {
7
+ constructor(dateMode, originDate, slotSeconds, hasNegativeDependencySpace) {
8
+ this.dateMode = dateMode;
9
+ this.originDate = originDate;
10
+ this.slotSeconds = slotSeconds;
11
+ this.hasNegativeDependencySpace = hasNegativeDependencySpace;
12
+ this.initFormatters();
13
+ }
14
+ initFormatters() {
15
+ this.timeFormatter = new Intl.DateTimeFormat([], {
16
+ timeZone: this.dateMode === 'utc' ? 'UTC' : undefined,
17
+ day: '2-digit',
18
+ month: '2-digit',
19
+ year: 'numeric',
20
+ hour: '2-digit',
21
+ minute: '2-digit',
22
+ hour12: false
23
+ });
24
+ this.dayFormatter = new Intl.DateTimeFormat([], {
25
+ timeZone: this.dateMode === 'utc' ? 'UTC' : undefined,
26
+ day: '2-digit',
27
+ month: '2-digit',
28
+ year: 'numeric'
29
+ });
30
+ }
31
+ get slotMs() {
32
+ return this.slotSeconds * 1000;
33
+ }
34
+ indexToDate(index) {
35
+ const realIndex = this.hasNegativeDependencySpace && index > 0 ? index - 1 : index;
36
+ return new Date(this.originDate + realIndex * this.slotMs);
37
+ }
38
+ getLabelCalendar(index, scale) {
39
+ if (this.hasNegativeDependencySpace && index === 0) {
40
+ return 'N/A';
41
+ }
42
+ const date = this.indexToDate(index);
43
+ switch (scale) {
44
+ case '1m':
45
+ case '10m':
46
+ case '30m':
47
+ case 'hour':
48
+ case '6 hours':
49
+ case '12 hours':
50
+ return this.formatTime(date);
51
+ case 'day':
52
+ return this.formatDay(date);
53
+ case 'week':
54
+ return this.formatRange(date, 7);
55
+ case 'month':
56
+ return this.formatMonthRange(date);
57
+ case 'year':
58
+ return this.formatYearRange(date);
59
+ default:
60
+ return '';
61
+ }
62
+ }
63
+ getLabelDuration(index, scale) {
64
+ if (index === -1) {
65
+ return 'N/A';
66
+ }
67
+ switch (scale) {
68
+ case '1m':
69
+ return `${index * 1}m`;
70
+ case '10m':
71
+ return `${index * 10}m`;
72
+ case '30m':
73
+ return `${index * 30}m`;
74
+ case 'hour':
75
+ return `${index}h`;
76
+ case '6 hours':
77
+ return `${index * 6}h`;
78
+ case '12 hours':
79
+ return `${index * 12}h`;
80
+ case 'day':
81
+ return `${index}D`;
82
+ case 'week':
83
+ return `${index * 7}D - ${index * 7 + 7}D`;
84
+ case 'month':
85
+ return `${index * 30}D - ${index * 30 + 30}D`;
86
+ case 'year':
87
+ return `${index * 365}D - ${index * 365 + 365}D`;
88
+ default:
89
+ return '';
90
+ }
91
+ }
92
+ formatTime(date) {
93
+ return this.timeFormatter.format(date);
94
+ }
95
+ formatDay(date) {
96
+ return this.dayFormatter.format(date);
97
+ }
98
+ formatRange(start, days) {
99
+ const end = new Date(start);
100
+ end.setDate(end.getDate() + days);
101
+ return `${start.toLocaleDateString()} - ${end.toLocaleDateString()}`;
102
+ }
103
+ formatMonthRange(start) {
104
+ const date = new Date(start);
105
+ date.setDate(1);
106
+ return date.toLocaleDateString();
107
+ }
108
+ formatYearRange(start) {
109
+ return `${start.toLocaleDateString()}`;
110
+ }
111
+ }
112
+
113
+ class GanttLiteBase {
114
+ constructor() {
115
+ this.rowHeight = 40;
116
+ this.defaultScale = 'day';
117
+ this.dataType = 'duration';
118
+ this.dateMode = 'local';
119
+ this.extraColumns = [];
120
+ this.slots = [];
121
+ this.tasks = [];
122
+ this.dependencyPaths = [];
123
+ this.scaleSelected = this.getScaleConfig(this.defaultScale);
124
+ this.headerHeight = 32;
125
+ this.ganttHeight = 0;
126
+ this.ganttWidth = 0;
127
+ this.leftPanelWidth = 320;
128
+ this.visibleGridColumns = [];
129
+ this.visibleTooltipColumns = [];
130
+ this.layout = { rootMaxHeight: 0, bodyHeight: 0, scrollHeight: 0, resizerHeight: 0 };
131
+ this.scales = ['1m', '10m', '30m', 'hour', '6 hours', '12 hours', 'day', 'week', 'month', 'year'];
132
+ this.scalesDate = ['1m', '10m', '30m', 'hour', 'day', 'week', 'month', 'year'];
133
+ this.negativeSlotWidth = 50;
134
+ this.hasNegativeDependencySpace = false;
135
+ // minimum columns to show gantt
136
+ this.coreColumns = [
137
+ {
138
+ id: 'label',
139
+ title: 'Name',
140
+ valueGetter: t => t.label,
141
+ showInGrid: true,
142
+ showInTooltip: true
143
+ },
144
+ {
145
+ id: 'start',
146
+ title: 'Start',
147
+ valueGetter: t => this.getDateString(t.start),
148
+ showInGrid: true,
149
+ showInTooltip: true,
150
+ hidden: () => this.dataType !== 'calendar'
151
+ },
152
+ {
153
+ id: 'end',
154
+ title: 'End',
155
+ valueGetter: t => this.getDateString(t.end),
156
+ showInGrid: true,
157
+ showInTooltip: true,
158
+ hidden: () => this.dataType !== 'calendar'
159
+ },
160
+ {
161
+ id: 'duration',
162
+ title: 'Duration',
163
+ valueGetter: t => this.getDuration(t),
164
+ showInGrid: true,
165
+ showInTooltip: true,
166
+ hidden: () => this.dataType !== 'duration'
167
+ }
168
+ ];
169
+ }
170
+ // recompute all sizes
171
+ recomputeLayout() {
172
+ this.layout.rootMaxHeight = this.ganttHeight - this.headerHeight + this.rowHeight - 3;
173
+ this.layout.bodyHeight = this.ganttHeight - this.rowHeight / 3;
174
+ this.layout.scrollHeight = this.ganttHeight - this.rowHeight;
175
+ this.layout.resizerHeight = this.ganttHeight + this.headerHeight - this.rowHeight;
176
+ }
177
+ get columns() {
178
+ return [
179
+ ...this.coreColumns,
180
+ ...this.extraColumns
181
+ ];
182
+ }
183
+ ganttWidthCalcul() {
184
+ this.ganttWidth = this.slots.length * this.scaleSelected.px;
185
+ }
186
+ ganttHeightCalcul() {
187
+ const max = Math.max(...this.tasks.map((t) => t.row));
188
+ this.ganttHeight = (max + 2) * this.rowHeight;
189
+ }
190
+ refreshVisibleColumns() {
191
+ this.visibleGridColumns = this.columns.filter(c => c.showInGrid &&
192
+ (!c.hidden || !c.hidden()));
193
+ this.visibleTooltipColumns = this.columns.filter(c => c.showInTooltip &&
194
+ (!c.hidden || !c.hidden()));
195
+ }
196
+ getRawTaskX(task) {
197
+ return (task.startSlot ?? 0) * this.scaleSelected.px;
198
+ }
199
+ getDateString(date) {
200
+ if (!date)
201
+ return '';
202
+ if (this.dateMode === 'utc') {
203
+ return date
204
+ .toLocaleString([], {
205
+ timeZone: 'UTC',
206
+ day: '2-digit',
207
+ month: '2-digit',
208
+ year: 'numeric',
209
+ hour: '2-digit',
210
+ minute: '2-digit',
211
+ hour12: false,
212
+ })
213
+ .replace(',', '');
214
+ }
215
+ return date
216
+ .toLocaleString([], {
217
+ day: '2-digit',
218
+ month: '2-digit',
219
+ year: 'numeric',
220
+ hour: '2-digit',
221
+ minute: '2-digit',
222
+ hour12: false,
223
+ })
224
+ .replace(',', '');
225
+ }
226
+ getDuration(task) {
227
+ if (!task)
228
+ return '';
229
+ if (typeof task.start !== 'number' || typeof task.end !== 'number') {
230
+ throw new Error(`Task ${task.id} is not in duration mode`);
231
+ }
232
+ let total = task.end - task.start;
233
+ const days = Math.floor(total / 86400);
234
+ total %= 86400;
235
+ const hours = Math.floor(total / 3600);
236
+ total %= 3600;
237
+ const minutes = Math.floor(total / 60);
238
+ const seconds = total % 60;
239
+ let result = '';
240
+ if (days > 0)
241
+ result += `${days}d `;
242
+ if (hours > 0 || days > 0)
243
+ result += `${hours}h `;
244
+ if (minutes > 0 || hours > 0 || days > 0)
245
+ result += `${minutes}m `;
246
+ result += `${seconds}s`;
247
+ return result.trim();
248
+ }
249
+ getTaskWidth(task) {
250
+ return ((task.endSlot ?? 0) - (task.startSlot ?? 0)) * this.scaleSelected.px;
251
+ }
252
+ getGridTop(i) {
253
+ return i * this.rowHeight;
254
+ }
255
+ getTaskX(task) {
256
+ const base = (task.startSlot ?? 0) * this.scaleSelected.px;
257
+ return this.hasNegativeDependencySpace ? this.negativeSlotWidth + base : base;
258
+ }
259
+ getTaskY(task) {
260
+ return task.row * this.rowHeight + this.rowHeight / 10;
261
+ }
262
+ trackByIndex(index) {
263
+ return index;
264
+ }
265
+ // get per arrow the path
266
+ getDependencyPath(fromId, toId) {
267
+ const from = this.tasks.find((t) => t.id === fromId);
268
+ const to = this.tasks.find((t) => t.id === toId);
269
+ if (!from || !to)
270
+ return '';
271
+ const fromRight = this.getTaskX(from) + this.getTaskWidth(from);
272
+ const toLeft = this.getTaskX(to);
273
+ const fromY = this.getTaskY(from) + this.rowHeight / 2 - this.rowHeight / 12;
274
+ const toY = this.getTaskY(to) + this.rowHeight / 2 - this.rowHeight / 12;
275
+ if (from.row === to.row) {
276
+ return `
277
+ M ${fromRight} ${fromY}
278
+ H ${fromRight + 20}
279
+ V ${toY}
280
+ H ${toLeft}`;
281
+ }
282
+ // consistent U-turn style (even if not strictly needed)
283
+ const escapeY = toY - this.rowHeight * 0.54;
284
+ const midX1 = fromRight + 25;
285
+ const midX2 = toLeft - 25;
286
+ return `
287
+ M ${fromRight} ${fromY}
288
+ H ${midX1}
289
+ V ${escapeY}
290
+ H ${midX2}
291
+ V ${toY}
292
+ H ${toLeft}`;
293
+ }
294
+ // size of columns per minutes
295
+ getScaleConfig(scale) {
296
+ switch (scale) {
297
+ case '1m':
298
+ return { secondes: 60, px: 120 };
299
+ case '10m':
300
+ return { secondes: 600, px: 120 };
301
+ case '30m':
302
+ return { secondes: 1800, px: 120 };
303
+ case 'hour':
304
+ return { secondes: 3600, px: 120 };
305
+ case '6 hours':
306
+ return { secondes: 21600, px: 120 };
307
+ case '12 hours':
308
+ return { secondes: 43200, px: 120 };
309
+ case 'day':
310
+ return { secondes: 86400, px: 150 };
311
+ case 'week':
312
+ return { secondes: 604800, px: 200 };
313
+ case 'month':
314
+ return { secondes: 2592000, px: 350 };
315
+ case 'year':
316
+ return { secondes: 31536000, px: 500 };
317
+ default:
318
+ return { secondes: 1800, px: 120 };
319
+ }
320
+ }
321
+ alignOriginByScale(date, defaultScale) {
322
+ const d = new Date(date);
323
+ switch (defaultScale) {
324
+ case 'week': {
325
+ // align monday
326
+ const d = new Date(date);
327
+ const day = d.getDay(); // 0 = Sunday, 1 = Monday, ...
328
+ const diff = day === 0 ? -6 : 1 - day; // move back to Monday
329
+ d.setDate(d.getDate() + diff);
330
+ d.setHours(0, 0, 0, 0);
331
+ return d;
332
+ }
333
+ case 'month': {
334
+ d.setDate(1);
335
+ d.setHours(0, 0, 0, 0);
336
+ return d;
337
+ }
338
+ case 'year': {
339
+ d.setMonth(0, 1); // Jan 1
340
+ d.setHours(0, 0, 0, 0);
341
+ return d;
342
+ }
343
+ case 'day': {
344
+ // shift back by 1 day (buffer slot)
345
+ d.setDate(d.getDate() - 1);
346
+ d.setHours(0, 0, 0, 0);
347
+ return d;
348
+ }
349
+ case 'hour': {
350
+ // shift back by 1 hour
351
+ d.setHours(d.getHours() - 1);
352
+ d.setMinutes(0, 0, 0);
353
+ return d;
354
+ }
355
+ case '6 hours': {
356
+ d.setHours(d.getHours() - 6);
357
+ d.setMinutes(0, 0, 0);
358
+ return d;
359
+ }
360
+ case '12 hours': {
361
+ d.setHours(d.getHours() - 12);
362
+ d.setMinutes(0, 0, 0);
363
+ return d;
364
+ }
365
+ case '30m': {
366
+ d.setMinutes(d.getMinutes() - 30);
367
+ d.setSeconds(0, 0);
368
+ return d;
369
+ }
370
+ case '10m': {
371
+ d.setMinutes(d.getMinutes() - 10);
372
+ d.setSeconds(0, 0);
373
+ return d;
374
+ }
375
+ case '1m': {
376
+ d.setMinutes(d.getMinutes() - 1);
377
+ d.setSeconds(0, 0);
378
+ return d;
379
+ }
380
+ default:
381
+ return d;
382
+ }
383
+ }
384
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: GanttLiteBase, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
385
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: GanttLiteBase, inputs: { rowHeight: "rowHeight", defaultScale: "defaultScale", dataType: "dataType", dateMode: "dateMode", extraColumns: "extraColumns" }, ngImport: i0 }); }
386
+ }
387
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: GanttLiteBase, decorators: [{
388
+ type: Directive
389
+ }], ctorParameters: () => [], propDecorators: { rowHeight: [{
390
+ type: Input
391
+ }], defaultScale: [{
392
+ type: Input
393
+ }], dataType: [{
394
+ type: Input
395
+ }], dateMode: [{
396
+ type: Input
397
+ }], extraColumns: [{
398
+ type: Input
399
+ }] } });
400
+
401
+ class GanttLiteComponent extends GanttLiteBase {
402
+ set rawTasks(value) {
403
+ this._rawTasks = value ?? [];
404
+ this.recalculteAll();
405
+ }
406
+ get rawTasks() {
407
+ return this._rawTasks;
408
+ }
409
+ constructor(cdr) {
410
+ super();
411
+ this.cdr = cdr;
412
+ this.toolbarHeight = 27;
413
+ this.hoveredDependencyIds = new Set();
414
+ this.selectedTaskId = null;
415
+ this.tooltipTask = null;
416
+ this.tooltipX = 0;
417
+ this.tooltipY = 0;
418
+ this.maxSlotsAlert = false;
419
+ this.loading = false;
420
+ this.showLeftBtn = false;
421
+ this.showRightBtn = true;
422
+ this.buttonsHeight = 20;
423
+ this.viewTasks = [];
424
+ this._rawTasks = [];
425
+ this.originDate = 0;
426
+ this.resizing = false;
427
+ this.labelConverter = null;
428
+ }
429
+ // recalculate all Gantt
430
+ recalculteAll() {
431
+ this.loading = true;
432
+ this.cdr.markForCheck();
433
+ setTimeout(() => {
434
+ const start = performance.now();
435
+ this.refreshVisibleColumns();
436
+ this.scaleSelected = this.getScaleConfig(this.defaultScale);
437
+ if (this.dataType !== 'duration') {
438
+ const base = new Date(Math.min(...this.rawTasks.map((t) => t.start.getTime())));
439
+ this.originDate = this.alignOriginByScale(base, this.defaultScale).getTime();
440
+ this.labelConverter = new SlotIndexToDateLabelConverter(this.dateMode, this.originDate, this.scaleSelected.secondes, this.hasNegativeDependencySpace);
441
+ this.buildTaskSlotsFromDates();
442
+ }
443
+ else {
444
+ this.labelConverter = new SlotIndexToDateLabelConverter(this.dateMode, this.originDate, this.scaleSelected.secondes, this.hasNegativeDependencySpace);
445
+ this.buildTaskSlotsFromDuration();
446
+ }
447
+ this.detectNegativeDependencySpace();
448
+ this.calculateTimelineSlots();
449
+ this.getAllDependencyPaths();
450
+ this.ganttHeightCalcul();
451
+ this.ganttWidthCalcul();
452
+ this.recomputeLayout();
453
+ this.ganttScroll.nativeElement.style.height = this.layout.scrollHeight + 'px';
454
+ this.ganttBodyScroll.nativeElement.style.height = this.layout.bodyHeight + 'px';
455
+ this.timelineHeader.nativeElement.style.height = this.headerHeight + 'px';
456
+ this.ganttContent.nativeElement.style.height = this.ganttHeight - this.rowHeight + 'px';
457
+ this.ganttContent.nativeElement.style.width = this.ganttWidth + 'px';
458
+ this.leftPanel.nativeElement.style.width = this.leftPanelWidth + 'px';
459
+ this.buildViewTasks();
460
+ setTimeout(() => {
461
+ this.recenterSelectedTask();
462
+ }, 0);
463
+ this.loading = false;
464
+ this.cdr.markForCheck();
465
+ requestAnimationFrame(() => {
466
+ const end = performance.now();
467
+ console.log('frame time including render:', end - start, 'ms');
468
+ });
469
+ }, 0);
470
+ }
471
+ buildViewTasks() {
472
+ const h = this.rowHeight / 1.25;
473
+ this.viewTasks = this.tasks.map((t) => {
474
+ return {
475
+ ...t,
476
+ x: this.getTaskX(t),
477
+ y: this.getTaskY(t),
478
+ width: this.getTaskWidth(t),
479
+ height: h,
480
+ };
481
+ });
482
+ }
483
+ // click on button 10m, hour, months
484
+ setScale(scale) {
485
+ this.defaultScale = scale;
486
+ this.recalculteAll();
487
+ }
488
+ // if task was selected scroll back to it
489
+ recenterSelectedTask() {
490
+ if (!this.selectedTaskId) {
491
+ if (!this.ganttScroll)
492
+ return;
493
+ this.ganttScroll.nativeElement.scrollLeft = 0;
494
+ // force header sync
495
+ if (this.timelineHeader?.nativeElement) {
496
+ this.timelineHeader.nativeElement.scrollLeft = 0;
497
+ }
498
+ this.onScrolled();
499
+ }
500
+ const task = this.tasks.find((t) => t.id === this.selectedTaskId);
501
+ if (!task || !this.ganttScroll)
502
+ return;
503
+ // scroll X
504
+ const viewportWidth = this.ganttScroll.nativeElement.clientWidth;
505
+ const targetScrollLeft = this.getTaskX(task) - viewportWidth / 2;
506
+ this.ganttScroll.nativeElement.scrollTo({ left: Math.max(targetScrollLeft, 0), behavior: 'auto' });
507
+ // scroll Y
508
+ const viewportHeight = this.ganttBodyScroll.nativeElement.clientHeight;
509
+ const targetTop = task.row * this.rowHeight;
510
+ const scrollTop = targetTop - viewportHeight / 2 + this.rowHeight / 2;
511
+ this.ganttBodyScroll.nativeElement.scrollTo({ top: Math.max(scrollTop, 0), behavior: 'auto' });
512
+ this.onScrolled();
513
+ }
514
+ // detect if there is a need to add the N/A slot in order to be able to show dependency arrow before 0.
515
+ detectNegativeDependencySpace() {
516
+ const originTasks = this.tasks.filter((t) => (t.startSlot ?? 0) === 0);
517
+ this.hasNegativeDependencySpace = originTasks.some((task) => {
518
+ const fromRight = this.getRawTaskX(task) + this.getTaskWidth(task);
519
+ return (task.dependencies ?? []).some((depId) => {
520
+ const target = this.tasks.find((t) => t.id === depId);
521
+ if (!target)
522
+ return false;
523
+ const toLeft = this.getRawTaskX(target);
524
+ // true if dependency goes "backwards"
525
+ return toLeft < fromRight;
526
+ });
527
+ });
528
+ }
529
+ // scroll buttons left/right => click small go, stay clicked scroll a lot
530
+ startScroll(direction) {
531
+ // instant first move
532
+ this.scrollInterval = setInterval(() => {
533
+ this.ganttScroll.nativeElement.scrollLeft += direction === 'left' ? -100 : 100;
534
+ this.onScrolled();
535
+ }, 50);
536
+ }
537
+ // update buttons left/right after a scroll.
538
+ onScrolled() {
539
+ const el = this.ganttScroll.nativeElement;
540
+ const maxScrollLeft = el.scrollWidth - el.clientWidth;
541
+ // left button
542
+ this.showLeftBtn = el.scrollLeft > 0;
543
+ if (this.leftBtn?.nativeElement) {
544
+ if (this.showLeftBtn) {
545
+ this.leftBtn.nativeElement.style.display = 'block';
546
+ this.leftBtn.nativeElement.style.left = this.leftPanel.nativeElement.style.width;
547
+ }
548
+ else {
549
+ this.leftBtn.nativeElement.style.display = 'none';
550
+ this.leftBtn.nativeElement.style.left = this.leftPanel.nativeElement.style.width;
551
+ }
552
+ }
553
+ // right button
554
+ this.showRightBtn = el.scrollLeft < maxScrollLeft - 1 || el.scrollLeft === 0; // small tolerance
555
+ if (this.rightBtn?.nativeElement) {
556
+ if (this.showRightBtn) {
557
+ this.rightBtn.nativeElement.style.display = 'block';
558
+ }
559
+ else {
560
+ this.rightBtn.nativeElement.style.display = 'none';
561
+ }
562
+ }
563
+ if (!this.showLeftBtn || !this.showRightBtn) {
564
+ this.stopScroll();
565
+ }
566
+ }
567
+ stopScroll() {
568
+ clearInterval(this.scrollInterval);
569
+ }
570
+ // to sync header and grid
571
+ onHorizontalScroll(event) {
572
+ this.timelineHeader.nativeElement.scrollLeft = event.target.scrollLeft;
573
+ }
574
+ // highlight dependencies (arrows)
575
+ updateHoveredDependencies(task) {
576
+ this.hoveredDependencyIds.clear();
577
+ if (!task) {
578
+ return;
579
+ }
580
+ // outgoing dependencies
581
+ for (const dep of task.dependencies ?? []) {
582
+ this.hoveredDependencyIds.add(`${dep}->${task.id}`);
583
+ }
584
+ // incoming dependencies
585
+ for (const t of this.tasks) {
586
+ if ((t.dependencies ?? []).includes(task.id)) {
587
+ this.hoveredDependencyIds.add(`${task.id}->${t.id}`);
588
+ }
589
+ }
590
+ }
591
+ // select task left and right panel
592
+ selectTask(task, scroll) {
593
+ if (this.selectedTaskId === task.id) {
594
+ this.selectedTaskId = null;
595
+ this.hoveredDependencyIds.clear();
596
+ this.hideTooltip();
597
+ this.onScrolled();
598
+ return;
599
+ }
600
+ this.selectedTaskId = task.id;
601
+ this.updateHoveredDependencies(task);
602
+ this.onScrolled();
603
+ if (!scroll || !this.ganttScroll)
604
+ return;
605
+ const container = this.ganttScroll.nativeElement;
606
+ const viewportWidth = container.clientWidth;
607
+ const targetScrollLeft = this.getTaskX(task) - viewportWidth / 2;
608
+ container.scrollTo({
609
+ left: Math.max(targetScrollLeft, 0),
610
+ behavior: 'smooth',
611
+ });
612
+ this.onScrolled();
613
+ }
614
+ // resize between panel left and right
615
+ startResize(event) {
616
+ event.preventDefault();
617
+ this.resizing = true;
618
+ const containerRect = this.ganttBodyScroll.nativeElement.getBoundingClientRect();
619
+ const left = containerRect.left;
620
+ const min = 50;
621
+ const max = containerRect.width - 75;
622
+ const moveHandler = (e) => {
623
+ if (!this.resizing)
624
+ return;
625
+ const x = e.clientX - left;
626
+ const width = x < min ? min : x > max ? max : x;
627
+ // direct DOM update (no Angular)
628
+ this.leftPanel.nativeElement.style.width = width + 'px';
629
+ if (this.leftBtn?.nativeElement) {
630
+ this.leftBtn.nativeElement.style.left = `${width}px`;
631
+ }
632
+ };
633
+ const upHandler = () => {
634
+ this.resizing = false;
635
+ window.removeEventListener('mousemove', moveHandler);
636
+ window.removeEventListener('mouseup', upHandler);
637
+ };
638
+ window.addEventListener('mousemove', moveHandler);
639
+ window.addEventListener('mouseup', upHandler);
640
+ }
641
+ showTooltip(event, task) {
642
+ this.tooltipTask = task;
643
+ const tooltip = document.querySelector('.global-tooltip');
644
+ if (!tooltip)
645
+ return;
646
+ tooltip.style.left = `${event.clientX + 10}px`;
647
+ tooltip.style.top = `${event.clientY - 50}px`;
648
+ tooltip.style.display = 'block';
649
+ }
650
+ hideTooltip() {
651
+ this.tooltipTask = null;
652
+ const tooltip = document.querySelector('.global-tooltip');
653
+ if (!tooltip)
654
+ return;
655
+ tooltip.style.display = 'none';
656
+ }
657
+ buildTaskSlotsFromDates() {
658
+ const slotMs = this.scaleSelected.secondes * 1000;
659
+ this.tasks = this.rawTasks.map((t) => {
660
+ if (!(t.start instanceof Date) || !(t.end instanceof Date)) {
661
+ throw new Error(`Task "${t.id}" must have Date start/end values.`);
662
+ }
663
+ const startSlot = (t.start.getTime() - this.originDate) / slotMs;
664
+ const endSlot = (t.end.getTime() - this.originDate) / slotMs;
665
+ return { ...t, startSlot, endSlot };
666
+ });
667
+ }
668
+ buildTaskSlotsFromDuration() {
669
+ this.tasks = this.rawTasks.map((t) => {
670
+ if (typeof t.start !== 'number' || typeof t.end !== 'number') {
671
+ throw new Error(`Task "${t.id}" must have numeric start/end values expressed in minutes.`);
672
+ }
673
+ const startSlot = t.start / this.scaleSelected.secondes;
674
+ const endSlot = t.end / this.scaleSelected.secondes;
675
+ return { ...t, startSlot, endSlot };
676
+ });
677
+ }
678
+ // get all slots (timeline)
679
+ calculateTimelineSlots() {
680
+ const maxSlot = Math.max(...this.tasks.map(t => t.endSlot ?? 0));
681
+ if (maxSlot > 53000) {
682
+ this.maxSlotsAlert = true;
683
+ this.slots = [];
684
+ return;
685
+ }
686
+ this.maxSlotsAlert = false;
687
+ const buffer = [];
688
+ let offsetX = 0;
689
+ if (this.hasNegativeDependencySpace) {
690
+ buffer.push({
691
+ index: -1,
692
+ x: 0,
693
+ width: this.negativeSlotWidth,
694
+ label: 'N/A'
695
+ });
696
+ offsetX = this.negativeSlotWidth;
697
+ }
698
+ for (let i = 0; i <= maxSlot + 2; i++) {
699
+ buffer.push({
700
+ index: i,
701
+ x: offsetX + i * this.scaleSelected.px,
702
+ width: this.scaleSelected.px,
703
+ label: this.getSlotLabel(i) ?? 'Error',
704
+ });
705
+ }
706
+ this.slots = buffer;
707
+ }
708
+ // get all paths of arrows
709
+ getAllDependencyPaths() {
710
+ this.dependencyPaths = this.tasks.flatMap((task) => (task.dependencies ?? []).map((dep) => ({
711
+ from: dep,
712
+ to: task.id,
713
+ id: `${dep}->${task.id}`,
714
+ path: this.getDependencyPath(dep, task.id),
715
+ })));
716
+ }
717
+ // get label for date header
718
+ getSlotLabel(index) {
719
+ if (this.dataType === 'calendar') {
720
+ return this.labelConverter?.getLabelCalendar(index, this.defaultScale) ?? 'Error';
721
+ }
722
+ return this.labelConverter?.getLabelDuration(index, this.defaultScale) ?? 'Error';
723
+ }
724
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: GanttLiteComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
725
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: GanttLiteComponent, selector: "gantt-lite", inputs: { rawTasks: "rawTasks" }, viewQueries: [{ propertyName: "ganttBodyScroll", first: true, predicate: ["ganttBodyScroll"], descendants: true }, { propertyName: "ganttContent", first: true, predicate: ["ganttContent"], descendants: true }, { propertyName: "ganttScroll", first: true, predicate: ["ganttScroll"], descendants: true }, { propertyName: "leftPanel", first: true, predicate: ["leftPanel"], descendants: true }, { propertyName: "timelineHeader", first: true, predicate: ["timelineHeader"], descendants: true }, { propertyName: "leftBtn", first: true, predicate: ["leftBtn"], descendants: true }, { propertyName: "rightBtn", first: true, predicate: ["rightBtn"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"gantt-root\" [style.max-height.px]=\"layout.rootMaxHeight\" [style.min-height.px]=\"100\">\n <!-- TOOLBAR -->\n <div class=\"toolbar\" [style.height.px]=\"buttonsHeight\">\n <div *ngIf=\"dateMode === 'utc' || dataType === 'duration'\">\n <button class=\"gantt-button\" *ngFor=\"let s of scales\" (click)=\"setScale(s)\" [class.active]=\"defaultScale === s\">\n {{ s }}\n </button>\n </div>\n <div *ngIf=\"dateMode !== 'utc' && dataType === 'calendar'\">\n <button class=\"gantt-button\" *ngFor=\"let s of scalesDate\" (click)=\"setScale(s)\"\n [class.active]=\"defaultScale === s\">\n {{ s }}\n </button>\n </div>\n <label *ngIf=\"maxSlotsAlert\" style=\"color: red;\">\n Data is too massive to show Gantt smoothly, try changing the scale\n </label>\n </div>\n <!-- TOOLTIP -->\n <div class=\"global-tooltip\" [style.display]=\"tooltipTask ? 'block' : 'none'\">\n <ng-container *ngIf=\"tooltipTask as task\">\n <div *ngFor=\"let column of visibleTooltipColumns\">\n <strong>{{ column?.title }}:</strong>\n {{ column?.valueGetter(task) }}\n </div>\n </ng-container>\n </div>\n <!-- BUTTONS FOR SCROLL GANTT -->\n <div *ngIf=\"!maxSlotsAlert && loading === false\" class=\"floating-nav\">\n <button class=\"nav-btn left\" (mousedown)=\"startScroll('left')\"\n (mouseup)=\"stopScroll()\" (mouseleave)=\"stopScroll()\" #leftBtn >\n \u2039\n </button>\n <button class=\"nav-btn right\" (mousedown)=\"startScroll('right')\"\n (mouseup)=\"stopScroll()\" (mouseleave)=\"stopScroll()\" #rightBtn>\n \u203A\n </button>\n </div>\n <!-- LOADING -->\n <div class=\"loading-overlay\" *ngIf=\"loading\" [style.top]=\"'50%'\">\n <div class=\"spinner\"></div>\n </div>\n <!-- SINGLE VERTICAL SCROLL -->\n <div class=\"gantt-body\" #ganttBodyScroll>\n <!-- LEFT PANEL -->\n <div class=\"left-panel\" #leftPanel>\n <div class=\"task-row header\" [style.height.px]=\"headerHeight\">\n <div class=\"cell\" *ngFor=\"let column of visibleGridColumns\" [style.width.px]=\"column.width || 120\">\n {{ column?.title }}\n </div>\n </div>\n <div class=\"task-row\" *ngFor=\"let task of tasks\" [style.height.px]=\"rowHeight\"\n (click)=\"selectTask(task, true); $event.stopPropagation()\"\n [class.highlighted-row]=\"selectedTaskId === task.id\">\n <div class=\"cell\" *ngFor=\"let column of visibleGridColumns\" [style.width.px]=\"column.width || 120\">\n {{ column?.valueGetter(task) }}\n </div>\n </div>\n </div>\n <!-- RESIZER -->\n <div class=\"resizer\" [style.height.px]=\"layout.resizerHeight\" (mousedown)=\"startResize($event)\">\n </div>\n <!-- RIGHT PANEL -->\n <div class=\"right-panel\" *ngIf=\"!maxSlotsAlert\">\n <!-- TIMELINE -->\n <div class=\"timeline-header\" #timelineHeader>\n <div class=\"timeline-slot\" *ngFor=\"let slot of slots\" [style.width.px]=\"slot.width\">\n {{ slot.label }}\n </div>\n </div>\n <!-- HORIZONTAL SCROLL ONLY -->\n <div class=\"horizontal-scroll\" (scroll)=\"onHorizontalScroll($event)\" #ganttScroll>\n <div class=\"gantt-content\" #ganttContent>\n <!-- GRID -->\n <div class=\"grid-layer\">\n <div *ngFor=\"let slot of slots; let i = index; trackBy: trackByIndex\" class=\"grid-line\"\n [style.left.px]=\"slot.x - 1\">\n </div>\n <!-- final vertical line -->\n <div class=\"grid-line\" [style.left.px]=\"ganttWidth - 1\">\n </div>\n <!-- horizontal lines -->\n <div class=\"horizontal-grid-layer\">\n <div *ngFor=\"let task of tasks; let i = index; trackBy: trackByIndex\"\n class=\"horizontal-line\" [style.top.px]=\"getGridTop(i)\">\n </div>\n <!-- final horizontal line -->\n <div class=\"horizontal-line\" [style.top.px]=\"ganttHeight\">\n </div>\n </div>\n </div>\n <!-- TASKS -->\n <div class=\"tasks-layer\">\n <div *ngFor=\"let task of viewTasks; let i = index; trackBy: trackByIndex\" class=\"task\"\n (mouseenter)=\"showTooltip($event, task)\" (mouseleave)=\"hideTooltip()\"\n (click)=\"selectTask(task, false); $event.stopPropagation()\"\n [class.highlighted-task]=\"selectedTaskId === task.id\" [style.left.px]=\"task.x\"\n [style.top.px]=\"task.y\" [style.width.px]=\"task.width\" [style.height.px]=\"task.height\">\n <div class=\"task-label\">\n {{ task.label }}\n </div>\n </div>\n </div>\n <!-- DEPENDENCIES -->\n <svg class=\"dependencies-svg\">\n <defs>\n <marker id=\"arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\"\n orient=\"auto\">\n <path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"#868d97\" />\n </marker>\n </defs>\n <g class=\"dependencies-normal\">\n <path *ngFor=\"let dep of dependencyPaths\" [attr.d]=\"dep.path\" class=\"dependency\"\n [class.hidden]=\"hoveredDependencyIds.has(dep.id)\" marker-end=\"url(#arrow)\">\n </path>\n </g>\n <g class=\"dependencies-highlighted\">\n <path *ngFor=\"let dep of dependencyPaths\" [attr.d]=\"dep.path\" class=\"highlighted-dependency\"\n [class.hidden]=\"!hoveredDependencyIds.has(dep.id)\" marker-end=\"url(#arrow)\">\n </path>\n </g>\n </svg>\n </div>\n </div>\n </div>\n <div class=\"right-panel-dead\" *ngIf=\"maxSlotsAlert\"></div>\n </div>\n</div>", styles: [":host{display:block;width:100%;height:100%}.gantt-root{height:100%;width:100%;display:flex;flex-direction:column;overflow:hidden}.gantt-body{flex:1;display:flex;overflow-y:auto;overflow-x:hidden;border-width:1px;border-style:solid;border-color:#aaac}.toolbar{flex-shrink:0;display:flex;gap:8px;background:#fff;position:sticky;top:0;z-index:50}.toolbar button.active{font-weight:700}.loading-overlay{position:sticky;inset:0;display:flex;align-items:center;justify-content:center;z-index:1000}.global-tooltip{position:fixed;background:#585858e6;color:#fff;padding:8px;border-radius:6px;font-size:12px;z-index:1000;pointer-events:none}.left-panel{flex-shrink:0;background:#fff;border-right:1px solid #dcdcdc}.task-row{display:grid;grid-auto-flow:column;grid-auto-columns:120px;gap:0 10px;font-size:small;align-items:center;border-bottom:1px solid #eee;white-space:nowrap;overflow:hidden}.cell{padding:0 8px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.task-row.header{font-size:small;font-weight:600;position:sticky;top:0;background:#fff!important;z-index:1}.task-row:hover{background:#ff00001a}.highlighted-row{background:#ff00004d!important}.resizer{min-width:7px;cursor:col-resize;background:#b4b4b4}.resizer:hover{background:#696969}.right-panel{flex:1;min-width:100px!important;position:relative}.right-panel-dead{flex:1;height:10000px;min-width:100px!important;position:relative;background-color:#696969}.horizontal-scroll{overflow-x:hidden;overflow-y:hidden;position:relative}.gantt-content{position:relative}.timeline-header{display:flex;position:sticky;top:0;overflow:hidden;white-space:nowrap;z-index:20;background:#fff;border-bottom:1px solid #ccc}.timeline-slot{flex:0 0 auto;border-right:1px solid #e0e0e0;padding:8px;box-sizing:border-box;text-align:center;font-size:12px}.highlighted-task{background:#f006!important;border-color:#b71c1c!important;z-index:10}.grid-layer{position:absolute;inset:0;pointer-events:none}.grid-line{position:absolute;top:0;bottom:0;width:1px;background:#c8c8c859}.horizontal-grid-layer{position:absolute;inset:0;pointer-events:none}.horizontal-line{position:absolute;left:0;right:0;height:1px;background:#c8c8c840}.dependencies-svg{overflow:visible}.dependencies-normal,.dependencies-highlighted{pointer-events:none}.dependency{stroke:#9ba2ad;stroke-width:1.6;fill:none;opacity:.35}.highlighted-dependency{stroke:#52555a;stroke-width:2.7;fill:none;opacity:1}.dependencies-svg.hovering .dependency{opacity:.035}.tasks-layer{position:relative;z-index:2}.task{position:absolute;background:#9b94f7bf;opacity:.85;color:#505050;padding-top:4px;border-radius:4px;font-size:11px;white-space:nowrap;min-width:2px}.task:hover{background:#ff000026}.spinner{width:40px;height:40px;border:4px solid #ddd;border-top-color:#3f51b5;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}*{box-sizing:border-box}.floating-nav{position:sticky;top:50%;transform:translateY(-50%);z-index:100;pointer-events:none}.nav-btn{position:absolute;width:45px;height:45px;display:flex;align-items:center;justify-content:center;pointer-events:auto;background:#5c9af866;border:none;color:#535353;font-size:26px;cursor:pointer;backdrop-filter:blur(1px);border-radius:2px;line-height:1;text-align:center}.nav-btn:hover{background:#5c9af8b3}.nav-btn.left{margin-left:13px}.nav-btn.right{right:23px}.task-label{padding-left:6px}.hidden{display:none}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
726
+ }
727
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: GanttLiteComponent, decorators: [{
728
+ type: Component,
729
+ args: [{ selector: 'gantt-lite', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"gantt-root\" [style.max-height.px]=\"layout.rootMaxHeight\" [style.min-height.px]=\"100\">\n <!-- TOOLBAR -->\n <div class=\"toolbar\" [style.height.px]=\"buttonsHeight\">\n <div *ngIf=\"dateMode === 'utc' || dataType === 'duration'\">\n <button class=\"gantt-button\" *ngFor=\"let s of scales\" (click)=\"setScale(s)\" [class.active]=\"defaultScale === s\">\n {{ s }}\n </button>\n </div>\n <div *ngIf=\"dateMode !== 'utc' && dataType === 'calendar'\">\n <button class=\"gantt-button\" *ngFor=\"let s of scalesDate\" (click)=\"setScale(s)\"\n [class.active]=\"defaultScale === s\">\n {{ s }}\n </button>\n </div>\n <label *ngIf=\"maxSlotsAlert\" style=\"color: red;\">\n Data is too massive to show Gantt smoothly, try changing the scale\n </label>\n </div>\n <!-- TOOLTIP -->\n <div class=\"global-tooltip\" [style.display]=\"tooltipTask ? 'block' : 'none'\">\n <ng-container *ngIf=\"tooltipTask as task\">\n <div *ngFor=\"let column of visibleTooltipColumns\">\n <strong>{{ column?.title }}:</strong>\n {{ column?.valueGetter(task) }}\n </div>\n </ng-container>\n </div>\n <!-- BUTTONS FOR SCROLL GANTT -->\n <div *ngIf=\"!maxSlotsAlert && loading === false\" class=\"floating-nav\">\n <button class=\"nav-btn left\" (mousedown)=\"startScroll('left')\"\n (mouseup)=\"stopScroll()\" (mouseleave)=\"stopScroll()\" #leftBtn >\n \u2039\n </button>\n <button class=\"nav-btn right\" (mousedown)=\"startScroll('right')\"\n (mouseup)=\"stopScroll()\" (mouseleave)=\"stopScroll()\" #rightBtn>\n \u203A\n </button>\n </div>\n <!-- LOADING -->\n <div class=\"loading-overlay\" *ngIf=\"loading\" [style.top]=\"'50%'\">\n <div class=\"spinner\"></div>\n </div>\n <!-- SINGLE VERTICAL SCROLL -->\n <div class=\"gantt-body\" #ganttBodyScroll>\n <!-- LEFT PANEL -->\n <div class=\"left-panel\" #leftPanel>\n <div class=\"task-row header\" [style.height.px]=\"headerHeight\">\n <div class=\"cell\" *ngFor=\"let column of visibleGridColumns\" [style.width.px]=\"column.width || 120\">\n {{ column?.title }}\n </div>\n </div>\n <div class=\"task-row\" *ngFor=\"let task of tasks\" [style.height.px]=\"rowHeight\"\n (click)=\"selectTask(task, true); $event.stopPropagation()\"\n [class.highlighted-row]=\"selectedTaskId === task.id\">\n <div class=\"cell\" *ngFor=\"let column of visibleGridColumns\" [style.width.px]=\"column.width || 120\">\n {{ column?.valueGetter(task) }}\n </div>\n </div>\n </div>\n <!-- RESIZER -->\n <div class=\"resizer\" [style.height.px]=\"layout.resizerHeight\" (mousedown)=\"startResize($event)\">\n </div>\n <!-- RIGHT PANEL -->\n <div class=\"right-panel\" *ngIf=\"!maxSlotsAlert\">\n <!-- TIMELINE -->\n <div class=\"timeline-header\" #timelineHeader>\n <div class=\"timeline-slot\" *ngFor=\"let slot of slots\" [style.width.px]=\"slot.width\">\n {{ slot.label }}\n </div>\n </div>\n <!-- HORIZONTAL SCROLL ONLY -->\n <div class=\"horizontal-scroll\" (scroll)=\"onHorizontalScroll($event)\" #ganttScroll>\n <div class=\"gantt-content\" #ganttContent>\n <!-- GRID -->\n <div class=\"grid-layer\">\n <div *ngFor=\"let slot of slots; let i = index; trackBy: trackByIndex\" class=\"grid-line\"\n [style.left.px]=\"slot.x - 1\">\n </div>\n <!-- final vertical line -->\n <div class=\"grid-line\" [style.left.px]=\"ganttWidth - 1\">\n </div>\n <!-- horizontal lines -->\n <div class=\"horizontal-grid-layer\">\n <div *ngFor=\"let task of tasks; let i = index; trackBy: trackByIndex\"\n class=\"horizontal-line\" [style.top.px]=\"getGridTop(i)\">\n </div>\n <!-- final horizontal line -->\n <div class=\"horizontal-line\" [style.top.px]=\"ganttHeight\">\n </div>\n </div>\n </div>\n <!-- TASKS -->\n <div class=\"tasks-layer\">\n <div *ngFor=\"let task of viewTasks; let i = index; trackBy: trackByIndex\" class=\"task\"\n (mouseenter)=\"showTooltip($event, task)\" (mouseleave)=\"hideTooltip()\"\n (click)=\"selectTask(task, false); $event.stopPropagation()\"\n [class.highlighted-task]=\"selectedTaskId === task.id\" [style.left.px]=\"task.x\"\n [style.top.px]=\"task.y\" [style.width.px]=\"task.width\" [style.height.px]=\"task.height\">\n <div class=\"task-label\">\n {{ task.label }}\n </div>\n </div>\n </div>\n <!-- DEPENDENCIES -->\n <svg class=\"dependencies-svg\">\n <defs>\n <marker id=\"arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\"\n orient=\"auto\">\n <path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"#868d97\" />\n </marker>\n </defs>\n <g class=\"dependencies-normal\">\n <path *ngFor=\"let dep of dependencyPaths\" [attr.d]=\"dep.path\" class=\"dependency\"\n [class.hidden]=\"hoveredDependencyIds.has(dep.id)\" marker-end=\"url(#arrow)\">\n </path>\n </g>\n <g class=\"dependencies-highlighted\">\n <path *ngFor=\"let dep of dependencyPaths\" [attr.d]=\"dep.path\" class=\"highlighted-dependency\"\n [class.hidden]=\"!hoveredDependencyIds.has(dep.id)\" marker-end=\"url(#arrow)\">\n </path>\n </g>\n </svg>\n </div>\n </div>\n </div>\n <div class=\"right-panel-dead\" *ngIf=\"maxSlotsAlert\"></div>\n </div>\n</div>", styles: [":host{display:block;width:100%;height:100%}.gantt-root{height:100%;width:100%;display:flex;flex-direction:column;overflow:hidden}.gantt-body{flex:1;display:flex;overflow-y:auto;overflow-x:hidden;border-width:1px;border-style:solid;border-color:#aaac}.toolbar{flex-shrink:0;display:flex;gap:8px;background:#fff;position:sticky;top:0;z-index:50}.toolbar button.active{font-weight:700}.loading-overlay{position:sticky;inset:0;display:flex;align-items:center;justify-content:center;z-index:1000}.global-tooltip{position:fixed;background:#585858e6;color:#fff;padding:8px;border-radius:6px;font-size:12px;z-index:1000;pointer-events:none}.left-panel{flex-shrink:0;background:#fff;border-right:1px solid #dcdcdc}.task-row{display:grid;grid-auto-flow:column;grid-auto-columns:120px;gap:0 10px;font-size:small;align-items:center;border-bottom:1px solid #eee;white-space:nowrap;overflow:hidden}.cell{padding:0 8px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.task-row.header{font-size:small;font-weight:600;position:sticky;top:0;background:#fff!important;z-index:1}.task-row:hover{background:#ff00001a}.highlighted-row{background:#ff00004d!important}.resizer{min-width:7px;cursor:col-resize;background:#b4b4b4}.resizer:hover{background:#696969}.right-panel{flex:1;min-width:100px!important;position:relative}.right-panel-dead{flex:1;height:10000px;min-width:100px!important;position:relative;background-color:#696969}.horizontal-scroll{overflow-x:hidden;overflow-y:hidden;position:relative}.gantt-content{position:relative}.timeline-header{display:flex;position:sticky;top:0;overflow:hidden;white-space:nowrap;z-index:20;background:#fff;border-bottom:1px solid #ccc}.timeline-slot{flex:0 0 auto;border-right:1px solid #e0e0e0;padding:8px;box-sizing:border-box;text-align:center;font-size:12px}.highlighted-task{background:#f006!important;border-color:#b71c1c!important;z-index:10}.grid-layer{position:absolute;inset:0;pointer-events:none}.grid-line{position:absolute;top:0;bottom:0;width:1px;background:#c8c8c859}.horizontal-grid-layer{position:absolute;inset:0;pointer-events:none}.horizontal-line{position:absolute;left:0;right:0;height:1px;background:#c8c8c840}.dependencies-svg{overflow:visible}.dependencies-normal,.dependencies-highlighted{pointer-events:none}.dependency{stroke:#9ba2ad;stroke-width:1.6;fill:none;opacity:.35}.highlighted-dependency{stroke:#52555a;stroke-width:2.7;fill:none;opacity:1}.dependencies-svg.hovering .dependency{opacity:.035}.tasks-layer{position:relative;z-index:2}.task{position:absolute;background:#9b94f7bf;opacity:.85;color:#505050;padding-top:4px;border-radius:4px;font-size:11px;white-space:nowrap;min-width:2px}.task:hover{background:#ff000026}.spinner{width:40px;height:40px;border:4px solid #ddd;border-top-color:#3f51b5;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}*{box-sizing:border-box}.floating-nav{position:sticky;top:50%;transform:translateY(-50%);z-index:100;pointer-events:none}.nav-btn{position:absolute;width:45px;height:45px;display:flex;align-items:center;justify-content:center;pointer-events:auto;background:#5c9af866;border:none;color:#535353;font-size:26px;cursor:pointer;backdrop-filter:blur(1px);border-radius:2px;line-height:1;text-align:center}.nav-btn:hover{background:#5c9af8b3}.nav-btn.left{margin-left:13px}.nav-btn.right{right:23px}.task-label{padding-left:6px}.hidden{display:none}\n"] }]
730
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { rawTasks: [{
731
+ type: Input
732
+ }], ganttBodyScroll: [{
733
+ type: ViewChild,
734
+ args: ['ganttBodyScroll']
735
+ }], ganttContent: [{
736
+ type: ViewChild,
737
+ args: ['ganttContent']
738
+ }], ganttScroll: [{
739
+ type: ViewChild,
740
+ args: ['ganttScroll']
741
+ }], leftPanel: [{
742
+ type: ViewChild,
743
+ args: ['leftPanel']
744
+ }], timelineHeader: [{
745
+ type: ViewChild,
746
+ args: ['timelineHeader']
747
+ }], leftBtn: [{
748
+ type: ViewChild,
749
+ args: ['leftBtn']
750
+ }], rightBtn: [{
751
+ type: ViewChild,
752
+ args: ['rightBtn']
753
+ }] } });
754
+
755
+ class GanttLiteModule {
756
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: GanttLiteModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
757
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: GanttLiteModule, declarations: [GanttLiteComponent], imports: [CommonModule], exports: [GanttLiteComponent] }); }
758
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: GanttLiteModule, imports: [CommonModule] }); }
759
+ }
760
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: GanttLiteModule, decorators: [{
761
+ type: NgModule,
762
+ args: [{
763
+ declarations: [GanttLiteComponent],
764
+ imports: [CommonModule],
765
+ exports: [GanttLiteComponent]
766
+ }]
767
+ }] });
768
+
769
+ /**
770
+ * Generated bundle index. Do not edit.
771
+ */
772
+
773
+ export { GanttLiteComponent, GanttLiteModule };
774
+ //# sourceMappingURL=gantt-lite.mjs.map