@ganttloom/gantt-core 0.2.1 → 0.4.3
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/README.md +96 -5
- package/dist/index.cjs +608 -117
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +77 -1
- package/dist/index.d.ts +77 -1
- package/dist/index.js +608 -117
- package/dist/index.js.map +1 -1
- package/dist/styles.css +163 -5
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -74,7 +74,8 @@ var DEFAULT_THEME = {
|
|
|
74
74
|
backgroundColor: "#ffffff",
|
|
75
75
|
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
|
|
76
76
|
fontSize: 12,
|
|
77
|
-
borderRadius: 4
|
|
77
|
+
borderRadius: 4,
|
|
78
|
+
borderColor: "#e2e8f0"
|
|
78
79
|
};
|
|
79
80
|
var DARK_THEME = {
|
|
80
81
|
...DEFAULT_THEME,
|
|
@@ -91,7 +92,8 @@ var DARK_THEME = {
|
|
|
91
92
|
markerColor: "#a78bfa",
|
|
92
93
|
selectionColor: "#38bdf8",
|
|
93
94
|
textColor: "#e5e7eb",
|
|
94
|
-
backgroundColor: "#181c24"
|
|
95
|
+
backgroundColor: "#181c24",
|
|
96
|
+
borderColor: "#334155"
|
|
95
97
|
};
|
|
96
98
|
var DENSITY_PRESETS = {
|
|
97
99
|
compact: { rowHeight: 26, barHeight: 16, headerHeight: 38, fontSize: 11 },
|
|
@@ -130,7 +132,8 @@ var CSS_VAR_NAMES = {
|
|
|
130
132
|
backgroundColor: "--gantt-background-color",
|
|
131
133
|
fontFamily: "--gantt-font-family",
|
|
132
134
|
fontSize: "--gantt-font-size",
|
|
133
|
-
borderRadius: "--gantt-border-radius"
|
|
135
|
+
borderRadius: "--gantt-border-radius",
|
|
136
|
+
borderColor: "--gantt-border-color"
|
|
134
137
|
};
|
|
135
138
|
var PX_FIELDS = /* @__PURE__ */ new Set([
|
|
136
139
|
"rowHeight",
|
|
@@ -556,20 +559,38 @@ function labelFor(date, viewMode) {
|
|
|
556
559
|
const h = date.getHours();
|
|
557
560
|
const period = h >= 12 ? "PM" : "AM";
|
|
558
561
|
const h12 = h % 12 === 0 ? 12 : h % 12;
|
|
559
|
-
return
|
|
562
|
+
return {
|
|
563
|
+
label: `${h12} ${period}`,
|
|
564
|
+
parentLabel: `${WEEKDAY_LABELS[date.getDay()]} ${date.getDate()} ${MONTH_LABELS[date.getMonth()]} ${date.getFullYear()}`
|
|
565
|
+
};
|
|
560
566
|
}
|
|
561
567
|
case "day":
|
|
562
|
-
return
|
|
568
|
+
return {
|
|
569
|
+
label: `${WEEKDAY_LABELS[date.getDay()]} ${date.getDate()}`,
|
|
570
|
+
parentLabel: `${MONTH_LABELS[date.getMonth()]} ${date.getFullYear()}`
|
|
571
|
+
};
|
|
563
572
|
case "week":
|
|
564
|
-
return
|
|
573
|
+
return {
|
|
574
|
+
label: `${MONTH_LABELS[date.getMonth()]} ${date.getDate()}`,
|
|
575
|
+
parentLabel: `${MONTH_LABELS[date.getMonth()]} ${date.getFullYear()}`
|
|
576
|
+
};
|
|
565
577
|
case "month":
|
|
566
|
-
return
|
|
578
|
+
return {
|
|
579
|
+
label: MONTH_LABELS[date.getMonth()] ?? "",
|
|
580
|
+
parentLabel: `${date.getFullYear()}`
|
|
581
|
+
};
|
|
567
582
|
case "quarter": {
|
|
568
583
|
const q = Math.floor(date.getMonth() / 3) + 1;
|
|
569
|
-
return
|
|
584
|
+
return {
|
|
585
|
+
label: `Q${q}`,
|
|
586
|
+
parentLabel: `${date.getFullYear()}`
|
|
587
|
+
};
|
|
570
588
|
}
|
|
571
589
|
case "year":
|
|
572
|
-
return
|
|
590
|
+
return {
|
|
591
|
+
label: `${date.getFullYear()}`,
|
|
592
|
+
parentLabel: ""
|
|
593
|
+
};
|
|
573
594
|
}
|
|
574
595
|
}
|
|
575
596
|
function generateTicks(rangeStart, rangeEnd, viewMode, columnWidth, today = /* @__PURE__ */ new Date(), calendar) {
|
|
@@ -583,10 +604,12 @@ function generateTicks(rangeStart, rangeEnd, viewMode, columnWidth, today = /* @
|
|
|
583
604
|
while (cursor.getTime() < rangeEnd.getTime() && guard < MAX_TICKS) {
|
|
584
605
|
guard++;
|
|
585
606
|
const x = (cursor.getTime() - rangeStart.getTime()) * scale;
|
|
607
|
+
const labels = labelFor(cursor, viewMode);
|
|
586
608
|
ticks.push({
|
|
587
609
|
date: new Date(cursor.getTime()),
|
|
588
610
|
x,
|
|
589
|
-
label:
|
|
611
|
+
label: labels.label,
|
|
612
|
+
parentLabel: labels.parentLabel,
|
|
590
613
|
isWeekend: dayLevel ? isWeekend(cursor) : false,
|
|
591
614
|
isNonWorking: viewMode === "hour" && calendar?.workingHours ? !isWorkingTime(cursor, calendar) : dayLevel ? isNonWorkingDay(cursor, calendar) : false,
|
|
592
615
|
isToday: isSameDay(cursor, today) && dayLevel,
|
|
@@ -663,6 +686,29 @@ function computeProgressRollup(node) {
|
|
|
663
686
|
}
|
|
664
687
|
node.computedProgress = totalWeight > 0 ? weightedSum / totalWeight : 0;
|
|
665
688
|
}
|
|
689
|
+
function sortValue(node, col) {
|
|
690
|
+
if (!col) return null;
|
|
691
|
+
if (col.accessor) return col.accessor(node.task) ?? null;
|
|
692
|
+
return col.id === "name" ? node.task.name : null;
|
|
693
|
+
}
|
|
694
|
+
function compareValues(a, b) {
|
|
695
|
+
if (a === null && b === null) return 0;
|
|
696
|
+
if (a === null) return 1;
|
|
697
|
+
if (b === null) return -1;
|
|
698
|
+
if (typeof a === "number" && typeof b === "number") return a - b;
|
|
699
|
+
return String(a).localeCompare(String(b), void 0, { numeric: true, sensitivity: "base" });
|
|
700
|
+
}
|
|
701
|
+
function sortTree(roots, columns, sort) {
|
|
702
|
+
if (!sort) return;
|
|
703
|
+
const col = columns.find((c) => c.id === sort.columnId);
|
|
704
|
+
const dir = sort.direction === "desc" ? -1 : 1;
|
|
705
|
+
const compare = (a, b) => dir * compareValues(sortValue(a, col), sortValue(b, col));
|
|
706
|
+
const sortLevel = (nodes) => {
|
|
707
|
+
nodes.sort(compare);
|
|
708
|
+
for (const node of nodes) sortLevel(node.children);
|
|
709
|
+
};
|
|
710
|
+
sortLevel(roots);
|
|
711
|
+
}
|
|
666
712
|
function flatten(roots) {
|
|
667
713
|
const out = [];
|
|
668
714
|
const stack = [];
|
|
@@ -749,6 +795,14 @@ function linkPath(from, to) {
|
|
|
749
795
|
const c2y = to.y;
|
|
750
796
|
return `M ${from.x} ${from.y} C ${c1x} ${c1y}, ${c2x} ${c2y}, ${to.x} ${to.y}`;
|
|
751
797
|
}
|
|
798
|
+
var MIN_VISIBLE_UNITS = {
|
|
799
|
+
hour: 8,
|
|
800
|
+
day: 7,
|
|
801
|
+
week: 6,
|
|
802
|
+
month: 6,
|
|
803
|
+
quarter: 4,
|
|
804
|
+
year: 5
|
|
805
|
+
};
|
|
752
806
|
function computeLayout(input) {
|
|
753
807
|
const {
|
|
754
808
|
tasks,
|
|
@@ -764,7 +818,8 @@ function computeLayout(input) {
|
|
|
764
818
|
markers = [],
|
|
765
819
|
autoRollupProgress = false,
|
|
766
820
|
selectedTaskIds,
|
|
767
|
-
pagination
|
|
821
|
+
pagination,
|
|
822
|
+
sort = null
|
|
768
823
|
} = input;
|
|
769
824
|
validateInputs(tasks, dependencies);
|
|
770
825
|
const { roots, nodeById } = buildTree(tasks);
|
|
@@ -772,6 +827,7 @@ function computeLayout(input) {
|
|
|
772
827
|
if (autoRollupProgress) {
|
|
773
828
|
for (const root of roots) computeProgressRollup(root);
|
|
774
829
|
}
|
|
830
|
+
sortTree(roots, columns, sort);
|
|
775
831
|
let visibleRoots = roots;
|
|
776
832
|
if (pagination && pagination.pageSize > 0) {
|
|
777
833
|
const start = Math.max(0, (pagination.page - 1) * pagination.pageSize);
|
|
@@ -791,8 +847,18 @@ function computeLayout(input) {
|
|
|
791
847
|
const now = Date.now();
|
|
792
848
|
const rawRangeStart = rangeMin === null ? new Date(now) : new Date(rangeMin);
|
|
793
849
|
const rawRangeEnd = rangeMax === null ? new Date(now + 1) : new Date(rangeMax);
|
|
794
|
-
|
|
795
|
-
|
|
850
|
+
let paddedStart = addUnit(rawRangeStart, viewMode, -1);
|
|
851
|
+
let paddedEnd = addUnit(rawRangeEnd, viewMode, 1);
|
|
852
|
+
const minUnits = MIN_VISIBLE_UNITS[viewMode];
|
|
853
|
+
let targetMinUnits = minUnits;
|
|
854
|
+
if (input.timelineViewportWidth && input.timelineViewportWidth > 0) {
|
|
855
|
+
targetMinUnits = Math.max(targetMinUnits, Math.ceil(input.timelineViewportWidth / columnWidth));
|
|
856
|
+
}
|
|
857
|
+
const currentUnits = (paddedEnd.getTime() - paddedStart.getTime()) / approxUnitMs(viewMode);
|
|
858
|
+
if (currentUnits < targetMinUnits) {
|
|
859
|
+
const deficit = targetMinUnits - currentUnits;
|
|
860
|
+
paddedEnd = addUnit(paddedEnd, viewMode, Math.ceil(deficit));
|
|
861
|
+
}
|
|
796
862
|
const rows = [];
|
|
797
863
|
const bars = [];
|
|
798
864
|
const barByTaskId = /* @__PURE__ */ new Map();
|
|
@@ -909,6 +975,7 @@ function computeLayout(input) {
|
|
|
909
975
|
(t) => ({
|
|
910
976
|
x: t.x,
|
|
911
977
|
label: t.label,
|
|
978
|
+
parentLabel: t.parentLabel,
|
|
912
979
|
isWeekend: t.isWeekend,
|
|
913
980
|
isNonWorking: t.isNonWorking,
|
|
914
981
|
isToday: t.isToday,
|
|
@@ -943,7 +1010,8 @@ function computeLayout(input) {
|
|
|
943
1010
|
columns,
|
|
944
1011
|
theme,
|
|
945
1012
|
markers: renderMarkers,
|
|
946
|
-
rangeStart: paddedStart
|
|
1013
|
+
rangeStart: paddedStart,
|
|
1014
|
+
sort
|
|
947
1015
|
};
|
|
948
1016
|
}
|
|
949
1017
|
|
|
@@ -1140,6 +1208,7 @@ var GanttRenderer = class {
|
|
|
1140
1208
|
this.lastModel = null;
|
|
1141
1209
|
this.lastTasks = [];
|
|
1142
1210
|
this.lastColumns = [];
|
|
1211
|
+
this.lastExplicitThemeKeys = /* @__PURE__ */ new Set();
|
|
1143
1212
|
this.onGridDoubleClick = (evt) => {
|
|
1144
1213
|
if (!this.options.onRenameCommit) return;
|
|
1145
1214
|
const target = evt.target;
|
|
@@ -1177,8 +1246,16 @@ var GanttRenderer = class {
|
|
|
1177
1246
|
this.root = el("div", "gantt-root");
|
|
1178
1247
|
this.gridPanel = el("div", "gantt-grid-panel");
|
|
1179
1248
|
this.timelineScroll = el("div", "gantt-timeline-scroll");
|
|
1249
|
+
this.headerContainer = el("div", "gantt-timeline-header");
|
|
1250
|
+
this.headerSvg = svgEl("svg");
|
|
1251
|
+
this.headerSvg.classList.add("gantt-header-svg");
|
|
1252
|
+
this.headerContainer.appendChild(this.headerSvg);
|
|
1180
1253
|
this.svg = svgEl("svg");
|
|
1181
1254
|
this.svg.classList.add("gantt-svg");
|
|
1255
|
+
this.timelineScroll.appendChild(this.headerContainer);
|
|
1256
|
+
if (this.options.readonly) {
|
|
1257
|
+
this.root.classList.add("gantt-readonly");
|
|
1258
|
+
}
|
|
1182
1259
|
this.timelineScroll.appendChild(this.svg);
|
|
1183
1260
|
this.root.appendChild(this.gridPanel);
|
|
1184
1261
|
this.root.appendChild(this.timelineScroll);
|
|
@@ -1194,9 +1271,14 @@ var GanttRenderer = class {
|
|
|
1194
1271
|
this.lastTasks = tasks;
|
|
1195
1272
|
this.lastColumns = columns;
|
|
1196
1273
|
const cssVars = explicitThemeToCssVars(explicitTheme);
|
|
1197
|
-
|
|
1274
|
+
const newKeys = new Set(Object.keys(cssVars));
|
|
1275
|
+
for (const key of this.lastExplicitThemeKeys) {
|
|
1276
|
+
if (!newKeys.has(key)) this.root.style.removeProperty(key);
|
|
1277
|
+
}
|
|
1278
|
+
for (const key of newKeys) {
|
|
1198
1279
|
this.root.style.setProperty(key, cssVars[key]);
|
|
1199
1280
|
}
|
|
1281
|
+
this.lastExplicitThemeKeys = newKeys;
|
|
1200
1282
|
this.renderGridPanel(model, tasks, columns);
|
|
1201
1283
|
this.renderTimeline(model, tasks);
|
|
1202
1284
|
}
|
|
@@ -1224,7 +1306,7 @@ var GanttRenderer = class {
|
|
|
1224
1306
|
const header = el("div", "gantt-grid-header-row");
|
|
1225
1307
|
header.style.height = `${model.headerHeight}px`;
|
|
1226
1308
|
for (const col of columns) {
|
|
1227
|
-
header.appendChild(this.renderHeaderCell(col, columns));
|
|
1309
|
+
header.appendChild(this.renderHeaderCell(col, columns, model.sort));
|
|
1228
1310
|
}
|
|
1229
1311
|
this.gridPanel.appendChild(header);
|
|
1230
1312
|
const body = el("div", "gantt-grid-body");
|
|
@@ -1237,11 +1319,26 @@ var GanttRenderer = class {
|
|
|
1237
1319
|
});
|
|
1238
1320
|
this.gridPanel.appendChild(body);
|
|
1239
1321
|
}
|
|
1240
|
-
renderHeaderCell(col, columns) {
|
|
1322
|
+
renderHeaderCell(col, columns, sort) {
|
|
1241
1323
|
const cell = el("div", "gantt-grid-header-cell");
|
|
1242
1324
|
cell.style.position = "relative";
|
|
1243
1325
|
if (col.width) cell.style.width = `${col.width}px`;
|
|
1244
|
-
|
|
1326
|
+
const titleSpan = document.createElement("span");
|
|
1327
|
+
titleSpan.className = "gantt-cell-text";
|
|
1328
|
+
titleSpan.textContent = col.title;
|
|
1329
|
+
cell.appendChild(titleSpan);
|
|
1330
|
+
if (col.sortable) {
|
|
1331
|
+
cell.classList.add("gantt-grid-header-cell-sortable");
|
|
1332
|
+
const active = sort?.columnId === col.id;
|
|
1333
|
+
const indicator = el("span", "gantt-sort-indicator");
|
|
1334
|
+
indicator.textContent = active ? sort.direction === "asc" ? " \u25B2" : " \u25BC" : "";
|
|
1335
|
+
cell.appendChild(indicator);
|
|
1336
|
+
cell.addEventListener("click", (evt) => {
|
|
1337
|
+
const target = evt.target;
|
|
1338
|
+
if (target.closest(".gantt-col-resize-handle")) return;
|
|
1339
|
+
this.options.onSortClick?.(col.id);
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1245
1342
|
if (this.options.onColumnReorder) {
|
|
1246
1343
|
cell.draggable = true;
|
|
1247
1344
|
cell.style.cursor = "grab";
|
|
@@ -1271,7 +1368,7 @@ var GanttRenderer = class {
|
|
|
1271
1368
|
evt.preventDefault();
|
|
1272
1369
|
evt.stopPropagation();
|
|
1273
1370
|
const startX = evt.clientX;
|
|
1274
|
-
const startWidth =
|
|
1371
|
+
const startWidth = cell.getBoundingClientRect().width;
|
|
1275
1372
|
const onMove = (moveEvt) => {
|
|
1276
1373
|
const newWidth = Math.max(30, startWidth + (moveEvt.clientX - startX));
|
|
1277
1374
|
cell.style.width = `${newWidth}px`;
|
|
@@ -1324,9 +1421,11 @@ var GanttRenderer = class {
|
|
|
1324
1421
|
const cell = el("div", "gantt-grid-cell");
|
|
1325
1422
|
if (col.width) cell.style.width = `${col.width}px`;
|
|
1326
1423
|
if (col.align) cell.style.textAlign = col.align;
|
|
1327
|
-
const
|
|
1424
|
+
const isTreeColumn = col.id === "name" || !columns.some((c) => c.id === "name") && colIndex === 0;
|
|
1425
|
+
console.log(`Column ${col.id}: isTreeColumn=${isTreeColumn}, colIndex=${colIndex}, hasNameCol=${columns.some((c) => c.id === "name")}`);
|
|
1426
|
+
const rawValue = col.accessor ? col.accessor(task) : isTreeColumn ? task.name : task[col.id] ?? "";
|
|
1328
1427
|
const textValue = rawValue === null || rawValue === void 0 ? "" : String(rawValue);
|
|
1329
|
-
if (
|
|
1428
|
+
if (isTreeColumn) {
|
|
1330
1429
|
cell.dataset.ganttNameCell = task.id;
|
|
1331
1430
|
cell.dataset.ganttNameValue = textValue;
|
|
1332
1431
|
const indent = el("span", "gantt-indent");
|
|
@@ -1335,17 +1434,27 @@ var GanttRenderer = class {
|
|
|
1335
1434
|
if (row.hasChildren) {
|
|
1336
1435
|
const toggle = el("button", "gantt-toggle");
|
|
1337
1436
|
toggle.type = "button";
|
|
1437
|
+
toggle.className = `gantt-toggle ${row.collapsed ? "collapsed" : "expanded"}`;
|
|
1338
1438
|
toggle.dataset.ganttToggle = task.id;
|
|
1339
1439
|
toggle.setAttribute("aria-label", row.collapsed ? "Expand" : "Collapse");
|
|
1340
1440
|
toggle.textContent = row.collapsed ? "\u25B8" : "\u25BE";
|
|
1341
1441
|
cell.appendChild(toggle);
|
|
1342
1442
|
}
|
|
1443
|
+
const addChildBtn = el("button", "gantt-add-child");
|
|
1444
|
+
addChildBtn.type = "button";
|
|
1445
|
+
addChildBtn.dataset.ganttAddChild = task.id;
|
|
1446
|
+
addChildBtn.setAttribute("aria-label", "Add child task");
|
|
1447
|
+
addChildBtn.textContent = "+";
|
|
1448
|
+
cell.appendChild(addChildBtn);
|
|
1343
1449
|
}
|
|
1344
1450
|
const rendered = col.render ? col.render(task) : void 0;
|
|
1345
1451
|
if (rendered instanceof HTMLElement) {
|
|
1346
1452
|
cell.appendChild(rendered);
|
|
1347
1453
|
} else if (typeof rendered === "string") {
|
|
1348
|
-
|
|
1454
|
+
const span = document.createElement("span");
|
|
1455
|
+
span.className = "gantt-cell-text";
|
|
1456
|
+
span.textContent = rendered;
|
|
1457
|
+
cell.appendChild(span);
|
|
1349
1458
|
} else {
|
|
1350
1459
|
const href = col.getHref ? col.getHref(task) : null;
|
|
1351
1460
|
if (href && isSafeHref(href)) {
|
|
@@ -1354,10 +1463,13 @@ var GanttRenderer = class {
|
|
|
1354
1463
|
anchor.target = col.linkTarget ?? "_blank";
|
|
1355
1464
|
anchor.rel = "noopener noreferrer";
|
|
1356
1465
|
anchor.textContent = textValue;
|
|
1466
|
+
anchor.className = "gantt-cell-text";
|
|
1357
1467
|
cell.appendChild(anchor);
|
|
1358
1468
|
} else {
|
|
1359
|
-
const
|
|
1360
|
-
cell
|
|
1469
|
+
const span = document.createElement("span");
|
|
1470
|
+
span.className = "gantt-cell-text";
|
|
1471
|
+
span.textContent = textValue;
|
|
1472
|
+
cell.appendChild(span);
|
|
1361
1473
|
}
|
|
1362
1474
|
}
|
|
1363
1475
|
rowEl.appendChild(cell);
|
|
@@ -1368,8 +1480,8 @@ var GanttRenderer = class {
|
|
|
1368
1480
|
renderTimeline(model, tasks) {
|
|
1369
1481
|
this.svg.replaceChildren();
|
|
1370
1482
|
this.svg.setAttribute("width", String(model.width));
|
|
1371
|
-
this.svg.setAttribute("height", String(model.
|
|
1372
|
-
this.svg.setAttribute("viewBox", `0 0 ${model.width} ${model.
|
|
1483
|
+
this.svg.setAttribute("height", String(model.height));
|
|
1484
|
+
this.svg.setAttribute("viewBox", `0 0 ${model.width} ${model.height}`);
|
|
1373
1485
|
const defs = svgEl("defs");
|
|
1374
1486
|
const marker = svgEl("marker");
|
|
1375
1487
|
marker.setAttribute("id", this.markerDefsId);
|
|
@@ -1386,11 +1498,10 @@ var GanttRenderer = class {
|
|
|
1386
1498
|
this.svg.appendChild(defs);
|
|
1387
1499
|
const { startY, endY } = this.getViewportRowRange(model);
|
|
1388
1500
|
const bodyGroup = svgEl("g");
|
|
1389
|
-
bodyGroup.setAttribute("transform", `translate(0, ${model.headerHeight})`);
|
|
1390
1501
|
this.renderGridLines(bodyGroup, model);
|
|
1391
1502
|
this.renderRowBackgrounds(bodyGroup, model, startY, endY);
|
|
1392
|
-
this.renderBars(bodyGroup, model, tasks, startY, endY);
|
|
1393
1503
|
this.renderLinks(bodyGroup, model, startY, endY);
|
|
1504
|
+
this.renderBars(bodyGroup, model, tasks, startY, endY);
|
|
1394
1505
|
this.renderMarkers(bodyGroup, model);
|
|
1395
1506
|
this.svg.appendChild(bodyGroup);
|
|
1396
1507
|
this.renderHeader(model);
|
|
@@ -1457,22 +1568,31 @@ var GanttRenderer = class {
|
|
|
1457
1568
|
clipRect.setAttribute("height", String(model.height));
|
|
1458
1569
|
clipPath.appendChild(clipRect);
|
|
1459
1570
|
defs.appendChild(clipPath);
|
|
1460
|
-
const
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1571
|
+
const fo = svgEl("foreignObject");
|
|
1572
|
+
fo.setAttribute("x", String(marker.x + 4));
|
|
1573
|
+
fo.setAttribute("y", "0");
|
|
1574
|
+
fo.setAttribute("width", String(cellWidth > 8 ? cellWidth - 8 : cellWidth));
|
|
1575
|
+
fo.setAttribute("height", String(model.height));
|
|
1576
|
+
const div = document.createElement("div");
|
|
1577
|
+
div.style.color = marker.color;
|
|
1578
|
+
div.style.fontSize = `${model.theme.fontSize}px`;
|
|
1579
|
+
div.style.fontFamily = model.theme.fontFamily;
|
|
1580
|
+
div.style.whiteSpace = "normal";
|
|
1581
|
+
div.style.overflowWrap = "break-word";
|
|
1582
|
+
div.style.paddingTop = "4px";
|
|
1583
|
+
div.textContent = marker.label;
|
|
1584
|
+
fo.appendChild(div);
|
|
1585
|
+
group.appendChild(fo);
|
|
1469
1586
|
}
|
|
1470
1587
|
});
|
|
1471
1588
|
}
|
|
1472
1589
|
renderHeader(model) {
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1590
|
+
this.headerSvg.replaceChildren();
|
|
1591
|
+
this.headerSvg.setAttribute("width", String(model.width));
|
|
1592
|
+
this.headerSvg.setAttribute("height", String(model.headerHeight));
|
|
1593
|
+
this.headerSvg.setAttribute("viewBox", `0 0 ${model.width} ${model.headerHeight}`);
|
|
1594
|
+
this.headerContainer.style.height = `${model.headerHeight}px`;
|
|
1595
|
+
const headerGroup = svgEl("g");
|
|
1476
1596
|
headerGroup.setAttribute("class", "gantt-header-group");
|
|
1477
1597
|
const bg = svgEl("rect");
|
|
1478
1598
|
bg.setAttribute("x", "0");
|
|
@@ -1483,6 +1603,60 @@ var GanttRenderer = class {
|
|
|
1483
1603
|
headerGroup.appendChild(bg);
|
|
1484
1604
|
const defs = svgEl("defs");
|
|
1485
1605
|
const ticks = model.ticks;
|
|
1606
|
+
const parentGroups = [];
|
|
1607
|
+
let currentGroup = null;
|
|
1608
|
+
for (const tick of ticks) {
|
|
1609
|
+
if (!currentGroup || currentGroup.label !== tick.parentLabel) {
|
|
1610
|
+
currentGroup = { x: tick.x, label: tick.parentLabel };
|
|
1611
|
+
parentGroups.push(currentGroup);
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
const hasParentTier = parentGroups.some((g) => g.label !== "");
|
|
1615
|
+
const bottomTierY = hasParentTier ? model.headerHeight / 2 : 0;
|
|
1616
|
+
const tierHeight = hasParentTier ? model.headerHeight / 2 : model.headerHeight;
|
|
1617
|
+
if (hasParentTier) {
|
|
1618
|
+
for (let i = 0; i < parentGroups.length; i++) {
|
|
1619
|
+
const group = parentGroups[i];
|
|
1620
|
+
const nextX = parentGroups[i + 1]?.x ?? model.width;
|
|
1621
|
+
const cellWidth = nextX - group.x;
|
|
1622
|
+
const fo = svgEl("foreignObject");
|
|
1623
|
+
fo.setAttribute("x", String(group.x + 4));
|
|
1624
|
+
fo.setAttribute("y", "0");
|
|
1625
|
+
fo.setAttribute("width", String(Math.max(0, cellWidth - 4)));
|
|
1626
|
+
fo.setAttribute("height", String(tierHeight));
|
|
1627
|
+
const div = document.createElement("div");
|
|
1628
|
+
div.style.color = themedFill("textColor", model.theme.textColor);
|
|
1629
|
+
div.style.fontSize = `${model.theme.fontSize * 0.95}px`;
|
|
1630
|
+
div.style.fontFamily = model.theme.fontFamily;
|
|
1631
|
+
div.style.fontWeight = "600";
|
|
1632
|
+
div.style.whiteSpace = "nowrap";
|
|
1633
|
+
div.style.overflow = "hidden";
|
|
1634
|
+
div.style.textOverflow = "ellipsis";
|
|
1635
|
+
div.style.display = "flex";
|
|
1636
|
+
div.style.alignItems = "center";
|
|
1637
|
+
div.style.justifyContent = "center";
|
|
1638
|
+
div.style.height = "100%";
|
|
1639
|
+
div.textContent = group.label;
|
|
1640
|
+
fo.appendChild(div);
|
|
1641
|
+
headerGroup.appendChild(fo);
|
|
1642
|
+
const border = svgEl("line");
|
|
1643
|
+
border.setAttribute("x1", String(group.x));
|
|
1644
|
+
border.setAttribute("x2", String(nextX));
|
|
1645
|
+
border.setAttribute("y1", String(tierHeight));
|
|
1646
|
+
border.setAttribute("y2", String(tierHeight));
|
|
1647
|
+
border.style.stroke = themedFill("borderColor", model.theme.borderColor ?? "#e2e8f0");
|
|
1648
|
+
headerGroup.appendChild(border);
|
|
1649
|
+
if (i > 0) {
|
|
1650
|
+
const sep = svgEl("line");
|
|
1651
|
+
sep.setAttribute("x1", String(group.x));
|
|
1652
|
+
sep.setAttribute("x2", String(group.x));
|
|
1653
|
+
sep.setAttribute("y1", "0");
|
|
1654
|
+
sep.setAttribute("y2", String(tierHeight));
|
|
1655
|
+
sep.style.stroke = themedFill("borderColor", model.theme.borderColor ?? "#e2e8f0");
|
|
1656
|
+
headerGroup.appendChild(sep);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1486
1660
|
for (let i = 0; i < ticks.length; i++) {
|
|
1487
1661
|
const tick = ticks[i];
|
|
1488
1662
|
const cellWidth = (ticks[i + 1]?.x ?? model.width) - tick.x;
|
|
@@ -1491,23 +1665,33 @@ var GanttRenderer = class {
|
|
|
1491
1665
|
clipPath.setAttribute("id", clipId);
|
|
1492
1666
|
const clipRect = svgEl("rect");
|
|
1493
1667
|
clipRect.setAttribute("x", String(tick.x));
|
|
1494
|
-
clipRect.setAttribute("y",
|
|
1668
|
+
clipRect.setAttribute("y", String(bottomTierY));
|
|
1495
1669
|
clipRect.setAttribute("width", String(Math.max(0, cellWidth)));
|
|
1496
|
-
clipRect.setAttribute("height", String(
|
|
1670
|
+
clipRect.setAttribute("height", String(tierHeight));
|
|
1497
1671
|
clipPath.appendChild(clipRect);
|
|
1498
1672
|
defs.appendChild(clipPath);
|
|
1499
|
-
const
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1673
|
+
const fo = svgEl("foreignObject");
|
|
1674
|
+
fo.setAttribute("x", String(tick.x + 4));
|
|
1675
|
+
fo.setAttribute("y", String(bottomTierY));
|
|
1676
|
+
fo.setAttribute("width", String(cellWidth > 8 ? cellWidth - 8 : cellWidth));
|
|
1677
|
+
fo.setAttribute("height", String(tierHeight));
|
|
1678
|
+
const div = document.createElement("div");
|
|
1679
|
+
div.style.color = themedFill("textColor", model.theme.textColor);
|
|
1680
|
+
div.style.fontSize = `${model.theme.fontSize}px`;
|
|
1681
|
+
div.style.fontFamily = model.theme.fontFamily;
|
|
1682
|
+
div.style.whiteSpace = "normal";
|
|
1683
|
+
div.style.overflowWrap = "break-word";
|
|
1684
|
+
div.style.display = "flex";
|
|
1685
|
+
div.style.alignItems = hasParentTier ? "center" : "flex-end";
|
|
1686
|
+
div.style.justifyContent = "center";
|
|
1687
|
+
div.style.paddingBottom = hasParentTier ? "0" : "4px";
|
|
1688
|
+
div.style.height = "100%";
|
|
1689
|
+
div.textContent = tick.label;
|
|
1690
|
+
fo.appendChild(div);
|
|
1691
|
+
headerGroup.appendChild(fo);
|
|
1508
1692
|
}
|
|
1509
1693
|
headerGroup.insertBefore(defs, headerGroup.firstChild);
|
|
1510
|
-
this.
|
|
1694
|
+
this.headerSvg.appendChild(headerGroup);
|
|
1511
1695
|
}
|
|
1512
1696
|
/**
|
|
1513
1697
|
* Invisible full-width hit-test rects, one per row, behind the bars. Lets
|
|
@@ -1542,18 +1726,13 @@ var GanttRenderer = class {
|
|
|
1542
1726
|
g.setAttribute("class", "gantt-bar");
|
|
1543
1727
|
g.dataset.ganttBar = bar.taskId;
|
|
1544
1728
|
g.setAttribute("transform", `translate(${bar.x}, ${bar.y})`);
|
|
1545
|
-
if (task?.notes) {
|
|
1546
|
-
const title = svgEl("title");
|
|
1547
|
-
title.textContent = task.notes;
|
|
1548
|
-
g.appendChild(title);
|
|
1549
|
-
}
|
|
1550
1729
|
if (this.options.keyboardAccessible) {
|
|
1551
1730
|
g.setAttribute("tabindex", "0");
|
|
1552
1731
|
g.setAttribute("role", "button");
|
|
1553
1732
|
if (task) {
|
|
1554
1733
|
const progress = Math.round(task.progress ?? 0);
|
|
1555
|
-
const
|
|
1556
|
-
g.setAttribute("aria-label",
|
|
1734
|
+
const label = `${task.name}, ${task.start.toDateString()} to ${task.end.toDateString()}, ${progress}% complete`;
|
|
1735
|
+
g.setAttribute("aria-label", label);
|
|
1557
1736
|
}
|
|
1558
1737
|
}
|
|
1559
1738
|
if (bar.baseline) {
|
|
@@ -1601,17 +1780,15 @@ var GanttRenderer = class {
|
|
|
1601
1780
|
`rotate(45, 0, ${bar.height / 2})`
|
|
1602
1781
|
);
|
|
1603
1782
|
g.appendChild(diamond);
|
|
1604
|
-
const
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
g.appendChild(
|
|
1612
|
-
|
|
1613
|
-
}
|
|
1614
|
-
if (bar.segments && bar.segments.length > 0) {
|
|
1783
|
+
const label = svgEl("text");
|
|
1784
|
+
label.setAttribute("x", String(size / 2 + 8));
|
|
1785
|
+
label.setAttribute("y", String(bar.height / 2 + 4));
|
|
1786
|
+
label.style.fill = themedFill("textColor", model.theme.textColor);
|
|
1787
|
+
label.setAttribute("font-size", String(model.theme.fontSize));
|
|
1788
|
+
label.setAttribute("font-family", model.theme.fontFamily);
|
|
1789
|
+
label.textContent = bar.label;
|
|
1790
|
+
g.appendChild(label);
|
|
1791
|
+
} else if (bar.segments && bar.segments.length > 0) {
|
|
1615
1792
|
bar.segments.forEach((seg, i) => {
|
|
1616
1793
|
const segRect = svgEl("rect");
|
|
1617
1794
|
segRect.setAttribute("x", String(seg.x));
|
|
@@ -1684,29 +1861,31 @@ var GanttRenderer = class {
|
|
|
1684
1861
|
selectionOutline.setAttribute("stroke-width", "2");
|
|
1685
1862
|
g.appendChild(selectionOutline);
|
|
1686
1863
|
}
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1864
|
+
if (!bar.isMilestone) {
|
|
1865
|
+
const handleWidth = 6;
|
|
1866
|
+
const leftHandle = svgEl("rect");
|
|
1867
|
+
leftHandle.setAttribute("x", "0");
|
|
1868
|
+
leftHandle.setAttribute("y", "0");
|
|
1869
|
+
leftHandle.setAttribute("width", String(handleWidth));
|
|
1870
|
+
leftHandle.setAttribute("height", String(bar.height));
|
|
1871
|
+
leftHandle.setAttribute("fill", "transparent");
|
|
1872
|
+
leftHandle.dataset.ganttHandle = "left";
|
|
1873
|
+
leftHandle.dataset.ganttHandleFor = bar.taskId;
|
|
1874
|
+
leftHandle.style.cursor = "ew-resize";
|
|
1875
|
+
g.appendChild(leftHandle);
|
|
1876
|
+
const rightHandle = svgEl("rect");
|
|
1877
|
+
rightHandle.setAttribute("x", String(Math.max(0, bar.width - handleWidth)));
|
|
1878
|
+
rightHandle.setAttribute("y", "0");
|
|
1879
|
+
rightHandle.setAttribute("width", String(handleWidth));
|
|
1880
|
+
rightHandle.setAttribute("height", String(bar.height));
|
|
1881
|
+
rightHandle.setAttribute("fill", "transparent");
|
|
1882
|
+
rightHandle.dataset.ganttHandle = "right";
|
|
1883
|
+
rightHandle.dataset.ganttHandleFor = bar.taskId;
|
|
1884
|
+
rightHandle.style.cursor = "ew-resize";
|
|
1885
|
+
g.appendChild(rightHandle);
|
|
1886
|
+
}
|
|
1708
1887
|
const connector = svgEl("circle");
|
|
1709
|
-
connector.setAttribute("cx", String(bar.width));
|
|
1888
|
+
connector.setAttribute("cx", String(bar.width + (bar.isMilestone ? 10 : 0)));
|
|
1710
1889
|
connector.setAttribute("cy", String(bar.height / 2));
|
|
1711
1890
|
connector.setAttribute("r", "4");
|
|
1712
1891
|
connector.style.fill = themedFill("linkColor", model.theme.linkColor);
|
|
@@ -1714,14 +1893,16 @@ var GanttRenderer = class {
|
|
|
1714
1893
|
connector.dataset.ganttConnectorSide = "right";
|
|
1715
1894
|
connector.style.cursor = "crosshair";
|
|
1716
1895
|
g.appendChild(connector);
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1896
|
+
if (!bar.isMilestone) {
|
|
1897
|
+
const label = svgEl("text");
|
|
1898
|
+
label.setAttribute("x", String(bar.width + 6));
|
|
1899
|
+
label.setAttribute("y", String(bar.height / 2 + 4));
|
|
1900
|
+
label.style.fill = themedFill("textColor", model.theme.textColor);
|
|
1901
|
+
label.setAttribute("font-size", String(model.theme.fontSize));
|
|
1902
|
+
label.setAttribute("font-family", model.theme.fontFamily);
|
|
1903
|
+
label.textContent = bar.label;
|
|
1904
|
+
g.appendChild(label);
|
|
1905
|
+
}
|
|
1725
1906
|
if (bar.deadlineX !== void 0) {
|
|
1726
1907
|
const markerX = bar.deadlineX - bar.x;
|
|
1727
1908
|
const flag = svgEl("g");
|
|
@@ -1779,10 +1960,20 @@ var GanttRenderer = class {
|
|
|
1779
1960
|
for (const link of model.links) {
|
|
1780
1961
|
const fromBar = barByTaskId.get(link.fromId);
|
|
1781
1962
|
const toBar = barByTaskId.get(link.toId);
|
|
1782
|
-
|
|
1963
|
+
if (!fromBar || !toBar) continue;
|
|
1964
|
+
const relevantY = toBar.y ?? fromBar.y ?? 0;
|
|
1783
1965
|
if (relevantY < startY || relevantY > endY) continue;
|
|
1966
|
+
const type = link.type ?? "FS";
|
|
1967
|
+
const x1 = type.startsWith("F") ? fromBar.x + fromBar.width : fromBar.x;
|
|
1968
|
+
const y1 = fromBar.y + fromBar.height / 2;
|
|
1969
|
+
const x2 = type.endsWith("F") ? toBar.x + toBar.width : toBar.x;
|
|
1970
|
+
const y2 = toBar.y + toBar.height / 2;
|
|
1971
|
+
const offset = 10;
|
|
1972
|
+
const routingX1 = type.startsWith("F") ? x1 + offset : x1 - offset;
|
|
1973
|
+
const routingX2 = type.endsWith("F") ? x2 + offset : x2 - offset;
|
|
1974
|
+
const d = `M ${x1} ${y1} L ${routingX1} ${y1} C ${routingX1 + (routingX2 - routingX1) / 2} ${y1}, ${routingX1 + (routingX2 - routingX1) / 2} ${y2}, ${routingX2} ${y2} L ${x2} ${y2}`;
|
|
1784
1975
|
const path = svgEl("path");
|
|
1785
|
-
path.setAttribute("d",
|
|
1976
|
+
path.setAttribute("d", d);
|
|
1786
1977
|
path.setAttribute("fill", "none");
|
|
1787
1978
|
path.style.stroke = link.isCritical ? themedFill("criticalColor", model.theme.criticalColor) : themedFill("linkColor", model.theme.linkColor);
|
|
1788
1979
|
path.setAttribute("stroke-width", link.isCritical ? "2" : "1.5");
|
|
@@ -1791,8 +1982,30 @@ var GanttRenderer = class {
|
|
|
1791
1982
|
group.appendChild(path);
|
|
1792
1983
|
}
|
|
1793
1984
|
}
|
|
1985
|
+
/**
|
|
1986
|
+
* The header and body are separate DOM elements at runtime (so plain CSS `position: sticky`
|
|
1987
|
+
* can pin the header - see the constructor's doc comment), but exports need one self-contained
|
|
1988
|
+
* SVG with both, laid out exactly as the original single-SVG version was.
|
|
1989
|
+
*/
|
|
1794
1990
|
toSVGString() {
|
|
1795
|
-
|
|
1991
|
+
const headerHeight = this.lastModel?.headerHeight ?? 0;
|
|
1992
|
+
const width = this.lastModel?.width ?? 0;
|
|
1993
|
+
const bodyHeight = this.lastModel?.height ?? 0;
|
|
1994
|
+
const combined = svgEl("svg");
|
|
1995
|
+
combined.setAttribute("xmlns", SVG_NS);
|
|
1996
|
+
combined.setAttribute("width", String(width));
|
|
1997
|
+
combined.setAttribute("height", String(headerHeight + bodyHeight));
|
|
1998
|
+
combined.setAttribute("viewBox", `0 0 ${width} ${headerHeight + bodyHeight}`);
|
|
1999
|
+
for (const child of Array.from(this.headerSvg.children)) {
|
|
2000
|
+
combined.appendChild(child.cloneNode(true));
|
|
2001
|
+
}
|
|
2002
|
+
const bodyGroup = svgEl("g");
|
|
2003
|
+
bodyGroup.setAttribute("transform", `translate(0, ${headerHeight})`);
|
|
2004
|
+
for (const child of Array.from(this.svg.children)) {
|
|
2005
|
+
bodyGroup.appendChild(child.cloneNode(true));
|
|
2006
|
+
}
|
|
2007
|
+
combined.appendChild(bodyGroup);
|
|
2008
|
+
return new XMLSerializer().serializeToString(combined);
|
|
1796
2009
|
}
|
|
1797
2010
|
destroy() {
|
|
1798
2011
|
if (this.scrollListener) {
|
|
@@ -2158,6 +2371,12 @@ var InteractionController = class {
|
|
|
2158
2371
|
const toggleId = target.closest("[data-gantt-toggle]")?.dataset.ganttToggle;
|
|
2159
2372
|
if (toggleId) {
|
|
2160
2373
|
this.callbacks.onToggleCollapse(toggleId);
|
|
2374
|
+
return;
|
|
2375
|
+
}
|
|
2376
|
+
const addChildId = target.closest("[data-gantt-add-child]")?.dataset.ganttAddChild;
|
|
2377
|
+
if (addChildId && this.callbacks.onAddChild) {
|
|
2378
|
+
this.callbacks.onAddChild(addChildId);
|
|
2379
|
+
return;
|
|
2161
2380
|
}
|
|
2162
2381
|
};
|
|
2163
2382
|
this.onContextMenu = (evt) => {
|
|
@@ -2171,13 +2390,17 @@ var InteractionController = class {
|
|
|
2171
2390
|
}
|
|
2172
2391
|
};
|
|
2173
2392
|
this.onSvgDoubleClick = (evt) => {
|
|
2174
|
-
if (!this.callbacks.onLinkDblClick) return;
|
|
2175
2393
|
const target = evt.target;
|
|
2176
2394
|
const linkPath2 = target.closest("[data-gantt-link]");
|
|
2177
2395
|
const key = linkPath2?.dataset.ganttLink;
|
|
2178
|
-
if (
|
|
2179
|
-
|
|
2180
|
-
|
|
2396
|
+
if (key && this.callbacks.onLinkDblClick) {
|
|
2397
|
+
const [fromId, toId] = key.split("->");
|
|
2398
|
+
if (fromId && toId) this.callbacks.onLinkDblClick(fromId, toId);
|
|
2399
|
+
return;
|
|
2400
|
+
}
|
|
2401
|
+
const barGroup = target.closest("[data-gantt-bar]");
|
|
2402
|
+
const taskId = barGroup?.dataset.ganttBar;
|
|
2403
|
+
if (taskId && this.callbacks.onBarDblClick) this.callbacks.onBarDblClick(taskId);
|
|
2181
2404
|
};
|
|
2182
2405
|
this.onPointerDown = (evt) => {
|
|
2183
2406
|
const target = evt.target;
|
|
@@ -2440,6 +2663,119 @@ var InteractionController = class {
|
|
|
2440
2663
|
}
|
|
2441
2664
|
};
|
|
2442
2665
|
|
|
2666
|
+
// src/tooltip.ts
|
|
2667
|
+
function formatDate(d) {
|
|
2668
|
+
return d.toLocaleDateString(void 0, { month: "short", day: "numeric", year: "numeric" });
|
|
2669
|
+
}
|
|
2670
|
+
function defaultTooltipContent(task) {
|
|
2671
|
+
const el2 = document.createElement("div");
|
|
2672
|
+
const title = document.createElement("div");
|
|
2673
|
+
title.className = "gantt-tooltip-title";
|
|
2674
|
+
title.textContent = task.name;
|
|
2675
|
+
el2.appendChild(title);
|
|
2676
|
+
const dates = document.createElement("div");
|
|
2677
|
+
dates.className = "gantt-tooltip-row";
|
|
2678
|
+
dates.textContent = task.isMilestone ? formatDate(task.start) : `${formatDate(task.start)} \u2192 ${formatDate(task.end)}`;
|
|
2679
|
+
el2.appendChild(dates);
|
|
2680
|
+
if (task.progress !== void 0) {
|
|
2681
|
+
const progress = document.createElement("div");
|
|
2682
|
+
progress.className = "gantt-tooltip-row";
|
|
2683
|
+
progress.textContent = `${Math.round(task.progress)}% complete`;
|
|
2684
|
+
el2.appendChild(progress);
|
|
2685
|
+
}
|
|
2686
|
+
if (task.assignees && task.assignees.length > 0) {
|
|
2687
|
+
const assignees = document.createElement("div");
|
|
2688
|
+
assignees.className = "gantt-tooltip-row";
|
|
2689
|
+
assignees.textContent = task.assignees.map((a) => a.name).join(", ");
|
|
2690
|
+
el2.appendChild(assignees);
|
|
2691
|
+
}
|
|
2692
|
+
if (task.notes) {
|
|
2693
|
+
const notes = document.createElement("div");
|
|
2694
|
+
notes.className = "gantt-tooltip-notes";
|
|
2695
|
+
notes.textContent = task.notes;
|
|
2696
|
+
el2.appendChild(notes);
|
|
2697
|
+
}
|
|
2698
|
+
return el2;
|
|
2699
|
+
}
|
|
2700
|
+
var Tooltip = class {
|
|
2701
|
+
constructor(getOptions, getTask) {
|
|
2702
|
+
this.svg = null;
|
|
2703
|
+
this.hoveredTaskId = null;
|
|
2704
|
+
this.onPointerOver = (evt) => {
|
|
2705
|
+
if (!this.getOptions().enabled) return;
|
|
2706
|
+
const target = evt.target;
|
|
2707
|
+
const barGroup = target.closest("[data-gantt-bar]");
|
|
2708
|
+
const taskId = barGroup?.dataset.ganttBar;
|
|
2709
|
+
if (!taskId || taskId === this.hoveredTaskId) return;
|
|
2710
|
+
const task = this.getTask(taskId);
|
|
2711
|
+
if (!task) return;
|
|
2712
|
+
const { render } = this.getOptions();
|
|
2713
|
+
const content = render ? render(task) : defaultTooltipContent(task);
|
|
2714
|
+
if (content === null || content === void 0) {
|
|
2715
|
+
this.hide();
|
|
2716
|
+
return;
|
|
2717
|
+
}
|
|
2718
|
+
this.el.replaceChildren();
|
|
2719
|
+
if (content instanceof HTMLElement) {
|
|
2720
|
+
this.el.appendChild(content);
|
|
2721
|
+
} else {
|
|
2722
|
+
this.el.textContent = content;
|
|
2723
|
+
}
|
|
2724
|
+
this.hoveredTaskId = taskId;
|
|
2725
|
+
this.el.style.display = "block";
|
|
2726
|
+
this.position(evt.clientX, evt.clientY);
|
|
2727
|
+
};
|
|
2728
|
+
this.onPointerMove = (evt) => {
|
|
2729
|
+
if (this.hoveredTaskId === null) return;
|
|
2730
|
+
this.position(evt.clientX, evt.clientY);
|
|
2731
|
+
};
|
|
2732
|
+
this.onPointerOut = (evt) => {
|
|
2733
|
+
const related = evt.relatedTarget;
|
|
2734
|
+
const target = evt.target;
|
|
2735
|
+
const leftBarGroup = target.closest("[data-gantt-bar]");
|
|
2736
|
+
if (related && leftBarGroup?.contains(related)) return;
|
|
2737
|
+
this.hide();
|
|
2738
|
+
};
|
|
2739
|
+
this.getOptions = getOptions;
|
|
2740
|
+
this.getTask = getTask;
|
|
2741
|
+
this.el = document.createElement("div");
|
|
2742
|
+
this.el.className = "gantt-tooltip";
|
|
2743
|
+
this.el.setAttribute("role", "tooltip");
|
|
2744
|
+
this.el.style.display = "none";
|
|
2745
|
+
document.body.appendChild(this.el);
|
|
2746
|
+
}
|
|
2747
|
+
attach(svg) {
|
|
2748
|
+
this.svg = svg;
|
|
2749
|
+
svg.addEventListener("pointerover", this.onPointerOver);
|
|
2750
|
+
svg.addEventListener("pointermove", this.onPointerMove);
|
|
2751
|
+
svg.addEventListener("pointerout", this.onPointerOut);
|
|
2752
|
+
}
|
|
2753
|
+
position(clientX, clientY) {
|
|
2754
|
+
const OFFSET = 12;
|
|
2755
|
+
const rect = this.el.getBoundingClientRect();
|
|
2756
|
+
const viewportW = window.innerWidth;
|
|
2757
|
+
const viewportH = window.innerHeight;
|
|
2758
|
+
let left = clientX + OFFSET;
|
|
2759
|
+
let top = clientY + OFFSET;
|
|
2760
|
+
if (left + rect.width > viewportW) left = clientX - OFFSET - rect.width;
|
|
2761
|
+
if (top + rect.height > viewportH) top = clientY - OFFSET - rect.height;
|
|
2762
|
+
this.el.style.left = `${Math.max(0, left)}px`;
|
|
2763
|
+
this.el.style.top = `${Math.max(0, top)}px`;
|
|
2764
|
+
}
|
|
2765
|
+
hide() {
|
|
2766
|
+
this.hoveredTaskId = null;
|
|
2767
|
+
this.el.style.display = "none";
|
|
2768
|
+
}
|
|
2769
|
+
destroy() {
|
|
2770
|
+
if (this.svg) {
|
|
2771
|
+
this.svg.removeEventListener("pointerover", this.onPointerOver);
|
|
2772
|
+
this.svg.removeEventListener("pointermove", this.onPointerMove);
|
|
2773
|
+
this.svg.removeEventListener("pointerout", this.onPointerOut);
|
|
2774
|
+
}
|
|
2775
|
+
this.el.remove();
|
|
2776
|
+
}
|
|
2777
|
+
};
|
|
2778
|
+
|
|
2443
2779
|
// src/index.ts
|
|
2444
2780
|
var HAS_DOM = typeof document !== "undefined" && typeof window !== "undefined";
|
|
2445
2781
|
var DEFAULT_COLUMNS = [{ id: "name", title: "Name" }];
|
|
@@ -2459,6 +2795,9 @@ var _GanttChart = class _GanttChart {
|
|
|
2459
2795
|
this.history = null;
|
|
2460
2796
|
this.renderer = null;
|
|
2461
2797
|
this.interactions = null;
|
|
2798
|
+
this.tooltip = null;
|
|
2799
|
+
this.resizeObserver = null;
|
|
2800
|
+
this.sort = null;
|
|
2462
2801
|
this.renderModel = null;
|
|
2463
2802
|
this.rafHandle = null;
|
|
2464
2803
|
this.currentPage = 1;
|
|
@@ -2481,6 +2820,8 @@ var _GanttChart = class _GanttChart {
|
|
|
2481
2820
|
showCriticalPath: options.showCriticalPath ?? false,
|
|
2482
2821
|
showBaseline: options.showBaseline ?? false,
|
|
2483
2822
|
showDeadlines: options.showDeadlines ?? true,
|
|
2823
|
+
showTooltip: options.showTooltip ?? true,
|
|
2824
|
+
autoFitToViewport: options.autoFitToViewport ?? false,
|
|
2484
2825
|
showAssigneeAvatars: options.showAssigneeAvatars ?? false,
|
|
2485
2826
|
enableHistory: options.enableHistory ?? false,
|
|
2486
2827
|
keyboardAccessible: options.keyboardAccessible ?? false,
|
|
@@ -2495,19 +2836,23 @@ var _GanttChart = class _GanttChart {
|
|
|
2495
2836
|
markers: options.markers,
|
|
2496
2837
|
pagination: options.pagination,
|
|
2497
2838
|
snapToUnit: options.snapToUnit,
|
|
2839
|
+
headerPosition: options.headerPosition ?? "top",
|
|
2498
2840
|
onDateChange: options.onDateChange,
|
|
2499
2841
|
onProgressChange: options.onProgressChange,
|
|
2500
2842
|
onDependencyCreate: options.onDependencyCreate,
|
|
2501
2843
|
onDependencyRemove: options.onDependencyRemove,
|
|
2502
2844
|
onDependencyDblClick: options.onDependencyDblClick,
|
|
2503
2845
|
onTaskClick: options.onTaskClick,
|
|
2846
|
+
onTaskDblClick: options.onTaskDblClick,
|
|
2504
2847
|
onGroupToggle: options.onGroupToggle,
|
|
2505
2848
|
onContextMenu: options.onContextMenu,
|
|
2506
2849
|
onTaskCreate: options.onTaskCreate,
|
|
2507
2850
|
onColumnResize: options.onColumnResize,
|
|
2508
2851
|
onColumnReorder: options.onColumnReorder,
|
|
2509
2852
|
onTaskReorder: options.onTaskReorder,
|
|
2510
|
-
onSelectionChange: options.onSelectionChange
|
|
2853
|
+
onSelectionChange: options.onSelectionChange,
|
|
2854
|
+
onSortChange: options.onSortChange,
|
|
2855
|
+
renderTooltip: options.renderTooltip
|
|
2511
2856
|
};
|
|
2512
2857
|
if (this.options.enableHistory) {
|
|
2513
2858
|
this.history = new HistoryManager((state) => this.emitter.emit("history-change", state));
|
|
@@ -2524,14 +2869,23 @@ var _GanttChart = class _GanttChart {
|
|
|
2524
2869
|
this.darkMediaQuery.addEventListener?.("change", this.handleSchemeChange);
|
|
2525
2870
|
}
|
|
2526
2871
|
this.renderer = new GanttRenderer(this.container, {
|
|
2872
|
+
readonly: this.options.readonly,
|
|
2527
2873
|
keyboardAccessible: this.options.keyboardAccessible,
|
|
2528
2874
|
showAssigneeAvatars: this.options.showAssigneeAvatars,
|
|
2529
2875
|
virtualScroll: this.options.virtualScroll,
|
|
2530
2876
|
onRenameCommit: (taskId, name) => this.updateTask(taskId, { name }),
|
|
2531
2877
|
onColumnResize: this.options.onColumnResize ? (columnId, width) => this.handleColumnResize(columnId, width) : void 0,
|
|
2532
2878
|
onColumnReorder: this.options.onColumnReorder ? (order) => this.handleColumnReorder(order) : void 0,
|
|
2533
|
-
onRowReorder: this.options.onTaskReorder ? (draggedId, targetId, position) => this.handleRowReorder(draggedId, targetId, position) : void 0
|
|
2879
|
+
onRowReorder: this.options.onTaskReorder ? (draggedId, targetId, position) => this.handleRowReorder(draggedId, targetId, position) : void 0,
|
|
2880
|
+
onSortClick: (columnId) => this.handleSortClick(columnId)
|
|
2534
2881
|
});
|
|
2882
|
+
if (this.options.headerPosition && this.renderer?.root) {
|
|
2883
|
+
this.renderer.root.dataset.ganttHeaderPosition = this.options.headerPosition;
|
|
2884
|
+
}
|
|
2885
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
2886
|
+
this.resizeObserver = new ResizeObserver(() => this.scheduleRender());
|
|
2887
|
+
this.resizeObserver.observe(this.renderer.timelineScroll);
|
|
2888
|
+
}
|
|
2535
2889
|
this.interactions = new InteractionController(
|
|
2536
2890
|
this.renderer.svg,
|
|
2537
2891
|
this.renderer.gridPanel,
|
|
@@ -2551,7 +2905,17 @@ var _GanttChart = class _GanttChart {
|
|
|
2551
2905
|
onCreateTaskDrag: this.options.onTaskCreate ? (rowTaskId, startXPx, endXPx) => this.handleCreateTaskDrag(rowTaskId, startXPx, endXPx) : void 0,
|
|
2552
2906
|
onLinkDblClick: this.options.onDependencyDblClick ? (fromId, toId) => {
|
|
2553
2907
|
const dep = this.dependencies.find((d) => d.fromId === fromId && d.toId === toId);
|
|
2554
|
-
if (dep)
|
|
2908
|
+
if (dep) {
|
|
2909
|
+
this.emitter.emit("dependency-dblclick", dep);
|
|
2910
|
+
this.options.onDependencyDblClick?.(dep);
|
|
2911
|
+
}
|
|
2912
|
+
} : void 0,
|
|
2913
|
+
onBarDblClick: this.options.onTaskDblClick ? (taskId) => {
|
|
2914
|
+
const task = this.tasks.find((t) => t.id === taskId);
|
|
2915
|
+
if (task) {
|
|
2916
|
+
this.emitter.emit("task-dblclick", { task });
|
|
2917
|
+
this.options.onTaskDblClick?.(task);
|
|
2918
|
+
}
|
|
2555
2919
|
} : void 0,
|
|
2556
2920
|
onContextMenu: this.options.onContextMenu ? (taskId, evt) => {
|
|
2557
2921
|
const task = this.tasks.find((t) => t.id === taskId);
|
|
@@ -2560,7 +2924,8 @@ var _GanttChart = class _GanttChart {
|
|
|
2560
2924
|
} : void 0,
|
|
2561
2925
|
onSelectAll: this.options.selectable ? () => this.selectAllVisible() : void 0,
|
|
2562
2926
|
onJumpToStart: () => this.scrollToRangeStart(),
|
|
2563
|
-
onJumpToEnd: () => this.scrollToRangeEnd()
|
|
2927
|
+
onJumpToEnd: () => this.scrollToRangeEnd(),
|
|
2928
|
+
onAddChild: (parentId) => this.handleAddChild(parentId)
|
|
2564
2929
|
},
|
|
2565
2930
|
{
|
|
2566
2931
|
readonly: this.options.readonly,
|
|
@@ -2570,6 +2935,17 @@ var _GanttChart = class _GanttChart {
|
|
|
2570
2935
|
pxPerMs: () => pxPerMs(this.options.viewMode, this.columnWidth)
|
|
2571
2936
|
}
|
|
2572
2937
|
);
|
|
2938
|
+
this.tooltip = new Tooltip(
|
|
2939
|
+
() => ({ enabled: this.options.showTooltip, render: this.options.renderTooltip }),
|
|
2940
|
+
(taskId) => this.tasks.find((t) => t.id === taskId)
|
|
2941
|
+
);
|
|
2942
|
+
this.tooltip.attach(this.renderer.svg);
|
|
2943
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
2944
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
2945
|
+
if (this.options.autoFitToViewport) this.scheduleRender();
|
|
2946
|
+
});
|
|
2947
|
+
this.resizeObserver.observe(this.container);
|
|
2948
|
+
}
|
|
2573
2949
|
}
|
|
2574
2950
|
handleMove(taskId, dxMs) {
|
|
2575
2951
|
if (dxMs === 0) return;
|
|
@@ -2839,6 +3215,35 @@ var _GanttChart = class _GanttChart {
|
|
|
2839
3215
|
};
|
|
2840
3216
|
this.runCommand(cmd);
|
|
2841
3217
|
}
|
|
3218
|
+
handleAddChild(parentId) {
|
|
3219
|
+
const parent = this.tasks.find((t) => t.id === parentId);
|
|
3220
|
+
if (!parent) return;
|
|
3221
|
+
const newTask = {
|
|
3222
|
+
id: `task-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
|
3223
|
+
name: "New task",
|
|
3224
|
+
start: new Date(parent.start.getTime()),
|
|
3225
|
+
end: new Date(parent.start.getTime() + MS_PER_DAY),
|
|
3226
|
+
parentId: parent.id
|
|
3227
|
+
};
|
|
3228
|
+
const cmd = {
|
|
3229
|
+
label: "task-create",
|
|
3230
|
+
do: () => {
|
|
3231
|
+
const index = this.tasks.findIndex((t) => t.id === parentId);
|
|
3232
|
+
if (index >= 0) this.tasks.splice(index + 1, 0, newTask);
|
|
3233
|
+
else this.tasks.push(newTask);
|
|
3234
|
+
this.emitter.emit("task-create", { task: newTask });
|
|
3235
|
+
this.options.onTaskCreate?.(newTask);
|
|
3236
|
+
this.emitter.emit("tasks-change", { tasks: this.getTasks() });
|
|
3237
|
+
this.scheduleRender();
|
|
3238
|
+
},
|
|
3239
|
+
undo: () => {
|
|
3240
|
+
this.tasks = this.tasks.filter((t) => t.id !== newTask.id);
|
|
3241
|
+
this.emitter.emit("tasks-change", { tasks: this.getTasks() });
|
|
3242
|
+
this.scheduleRender();
|
|
3243
|
+
}
|
|
3244
|
+
};
|
|
3245
|
+
this.runCommand(cmd);
|
|
3246
|
+
}
|
|
2842
3247
|
handleBarClick(taskId, modifiers) {
|
|
2843
3248
|
const task = this.tasks.find((t) => t.id === taskId);
|
|
2844
3249
|
if (!task) return;
|
|
@@ -2974,6 +3379,24 @@ var _GanttChart = class _GanttChart {
|
|
|
2974
3379
|
this.options.onColumnReorder?.(order);
|
|
2975
3380
|
this.scheduleRender();
|
|
2976
3381
|
}
|
|
3382
|
+
/** Cycles a sortable column's header through asc -> desc -> none; clicking a different column starts it fresh at asc. */
|
|
3383
|
+
handleSortClick(columnId) {
|
|
3384
|
+
if (this.sort?.columnId === columnId) {
|
|
3385
|
+
this.setSort(columnId, this.sort.direction === "asc" ? "desc" : null);
|
|
3386
|
+
} else {
|
|
3387
|
+
this.setSort(columnId, "asc");
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
/** Sorts siblings at every tree level by a column's value (see GanttColumn.sortable); `direction: null`/omitted `columnId` clears it. */
|
|
3391
|
+
setSort(columnId, direction = "asc") {
|
|
3392
|
+
this.sort = columnId && direction ? { columnId, direction } : null;
|
|
3393
|
+
this.emitter.emit("sort-change", { sort: this.sort });
|
|
3394
|
+
this.options.onSortChange?.(this.sort);
|
|
3395
|
+
this.scheduleRender();
|
|
3396
|
+
}
|
|
3397
|
+
getSort() {
|
|
3398
|
+
return this.sort;
|
|
3399
|
+
}
|
|
2977
3400
|
handleRowReorder(draggedId, targetId, position) {
|
|
2978
3401
|
const draggedIndex = this.tasks.findIndex((t) => t.id === draggedId);
|
|
2979
3402
|
const targetIndex = this.tasks.findIndex((t) => t.id === targetId);
|
|
@@ -3040,13 +3463,24 @@ var _GanttChart = class _GanttChart {
|
|
|
3040
3463
|
this.scheduleRender();
|
|
3041
3464
|
}
|
|
3042
3465
|
setOptions(partial) {
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
if (
|
|
3466
|
+
const hasTheme = "theme" in partial;
|
|
3467
|
+
const hasColorScheme = "colorScheme" in partial;
|
|
3468
|
+
if (hasColorScheme) this.colorScheme = partial.colorScheme ?? "auto";
|
|
3469
|
+
if (hasTheme) this.explicitTheme = partial.theme;
|
|
3470
|
+
if (hasTheme || hasColorScheme) {
|
|
3046
3471
|
this.theme = mergeTheme(this.explicitTheme, this.colorScheme);
|
|
3047
3472
|
}
|
|
3048
|
-
if (partial
|
|
3473
|
+
if ("columns" in partial) this.columns = partial.columns ?? this.columns;
|
|
3049
3474
|
if (partial.columnWidth !== void 0) this.columnWidth = partial.columnWidth;
|
|
3475
|
+
if (partial.gridPanelWidth !== void 0) {
|
|
3476
|
+
this.container.style.setProperty("--gantt-grid-panel-width", `${partial.gridPanelWidth}px`);
|
|
3477
|
+
}
|
|
3478
|
+
if ("headerPosition" in partial) {
|
|
3479
|
+
this.options.headerPosition = partial.headerPosition ?? "top";
|
|
3480
|
+
if (this.renderer?.root) {
|
|
3481
|
+
this.renderer.root.dataset.ganttHeaderPosition = this.options.headerPosition;
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3050
3484
|
Object.assign(this.options, partial);
|
|
3051
3485
|
this.scheduleRender();
|
|
3052
3486
|
}
|
|
@@ -3134,9 +3568,11 @@ var _GanttChart = class _GanttChart {
|
|
|
3134
3568
|
}
|
|
3135
3569
|
computeModel() {
|
|
3136
3570
|
const pagination = this.options.pagination ? { pageSize: this.options.pagination.pageSize, page: this.currentPage } : void 0;
|
|
3571
|
+
const timelineViewportWidth = HAS_DOM && this.renderer ? this.renderer.timelineScroll.clientWidth : void 0;
|
|
3137
3572
|
return computeLayout({
|
|
3138
3573
|
tasks: this.tasks,
|
|
3139
3574
|
dependencies: this.dependencies,
|
|
3575
|
+
timelineViewportWidth,
|
|
3140
3576
|
viewMode: this.options.viewMode,
|
|
3141
3577
|
columnWidth: this.columnWidth,
|
|
3142
3578
|
theme: this.theme,
|
|
@@ -3149,7 +3585,8 @@ var _GanttChart = class _GanttChart {
|
|
|
3149
3585
|
markers: this.options.markers,
|
|
3150
3586
|
autoRollupProgress: this.options.autoRollupProgress,
|
|
3151
3587
|
selectedTaskIds: this.options.selectable ? this.selectedTaskIds : void 0,
|
|
3152
|
-
pagination
|
|
3588
|
+
pagination,
|
|
3589
|
+
sort: this.sort
|
|
3153
3590
|
});
|
|
3154
3591
|
}
|
|
3155
3592
|
scheduleRender(immediate = false) {
|
|
@@ -3164,6 +3601,7 @@ var _GanttChart = class _GanttChart {
|
|
|
3164
3601
|
});
|
|
3165
3602
|
}
|
|
3166
3603
|
doRender() {
|
|
3604
|
+
if (this.options.autoFitToViewport) this.applyAutoFit();
|
|
3167
3605
|
this.renderModel = this.computeModel();
|
|
3168
3606
|
this.renderer?.render(this.renderModel, this.tasks, this.columns, this.explicitTheme);
|
|
3169
3607
|
if (this.renderer && this.colorScheme !== "auto") {
|
|
@@ -3172,6 +3610,29 @@ var _GanttChart = class _GanttChart {
|
|
|
3172
3610
|
this.renderer?.root.removeAttribute("data-gantt-theme");
|
|
3173
3611
|
}
|
|
3174
3612
|
}
|
|
3613
|
+
/**
|
|
3614
|
+
* Stretch-only fit: bump columnWidth up so the timeline fills the container's available
|
|
3615
|
+
* width, but never shrink it below what the current zoom level already implies - a project
|
|
3616
|
+
* wider than the container should still scroll normally, not get crammed to fit.
|
|
3617
|
+
*/
|
|
3618
|
+
applyAutoFit() {
|
|
3619
|
+
const availableWidth = this.container.clientWidth - this.options.gridPanelWidth;
|
|
3620
|
+
if (!Number.isFinite(availableWidth) || availableWidth <= 0) return;
|
|
3621
|
+
let min = null;
|
|
3622
|
+
let max = null;
|
|
3623
|
+
for (const t of this.tasks) {
|
|
3624
|
+
if (!isValidDate(t.start) || !isValidDate(t.end)) continue;
|
|
3625
|
+
min = min === null ? t.start.getTime() : Math.min(min, t.start.getTime());
|
|
3626
|
+
max = max === null ? t.end.getTime() : Math.max(max, t.end.getTime());
|
|
3627
|
+
}
|
|
3628
|
+
if (min === null || max === null || max <= min) return;
|
|
3629
|
+
const rangeMs = max - min;
|
|
3630
|
+
const unitMs = approxUnitMs(this.options.viewMode);
|
|
3631
|
+
const neededColumnWidth = availableWidth * unitMs / rangeMs;
|
|
3632
|
+
if (neededColumnWidth > this.columnWidth) {
|
|
3633
|
+
this.columnWidth = Math.min(neededColumnWidth, _GanttChart.MAX_COLUMN_WIDTH);
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
3175
3636
|
undo() {
|
|
3176
3637
|
this.history?.undo();
|
|
3177
3638
|
}
|
|
@@ -3194,7 +3655,35 @@ var _GanttChart = class _GanttChart {
|
|
|
3194
3655
|
return this.renderer?.toSVGString() ?? "";
|
|
3195
3656
|
}
|
|
3196
3657
|
async rasterizeSVG(svgString, width, height) {
|
|
3197
|
-
|
|
3658
|
+
let cleanSvgString = svgString;
|
|
3659
|
+
if (typeof DOMParser !== "undefined") {
|
|
3660
|
+
const parser = new DOMParser();
|
|
3661
|
+
const doc = parser.parseFromString(svgString, "image/svg+xml");
|
|
3662
|
+
const foreignObjects = Array.from(doc.querySelectorAll("foreignObject"));
|
|
3663
|
+
for (const fo of foreignObjects) {
|
|
3664
|
+
const x = fo.getAttribute("x") || "0";
|
|
3665
|
+
const y = fo.getAttribute("y") || "0";
|
|
3666
|
+
const w = parseFloat(fo.getAttribute("width") || "0");
|
|
3667
|
+
const h = parseFloat(fo.getAttribute("height") || "0");
|
|
3668
|
+
const div = fo.querySelector("div");
|
|
3669
|
+
if (div) {
|
|
3670
|
+
const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
3671
|
+
text.setAttribute("x", String(parseFloat(x) + w / 2));
|
|
3672
|
+
text.setAttribute("y", String(parseFloat(y) + h / 2 + 4));
|
|
3673
|
+
text.setAttribute("text-anchor", "middle");
|
|
3674
|
+
text.setAttribute("fill", div.style.color || "#000");
|
|
3675
|
+
text.setAttribute("font-size", div.style.fontSize || "12px");
|
|
3676
|
+
text.setAttribute("font-family", div.style.fontFamily || "sans-serif");
|
|
3677
|
+
text.setAttribute("font-weight", div.style.fontWeight || "normal");
|
|
3678
|
+
text.textContent = div.textContent;
|
|
3679
|
+
fo.parentNode?.replaceChild(text, fo);
|
|
3680
|
+
} else {
|
|
3681
|
+
fo.parentNode?.removeChild(fo);
|
|
3682
|
+
}
|
|
3683
|
+
}
|
|
3684
|
+
cleanSvgString = new XMLSerializer().serializeToString(doc);
|
|
3685
|
+
}
|
|
3686
|
+
const svgBlob = new Blob([cleanSvgString], { type: "image/svg+xml;charset=utf-8" });
|
|
3198
3687
|
const url = URL.createObjectURL(svgBlob);
|
|
3199
3688
|
try {
|
|
3200
3689
|
return await new Promise((resolve, reject) => {
|
|
@@ -3266,6 +3755,8 @@ var _GanttChart = class _GanttChart {
|
|
|
3266
3755
|
this.darkMediaQuery?.removeEventListener?.("change", this.handleSchemeChange);
|
|
3267
3756
|
this.emitter.removeAllListeners();
|
|
3268
3757
|
this.interactions?.destroy();
|
|
3758
|
+
this.tooltip?.destroy();
|
|
3759
|
+
this.resizeObserver?.disconnect();
|
|
3269
3760
|
this.renderer?.destroy();
|
|
3270
3761
|
}
|
|
3271
3762
|
};
|