@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.js
CHANGED
|
@@ -25,7 +25,8 @@ var DEFAULT_THEME = {
|
|
|
25
25
|
backgroundColor: "#ffffff",
|
|
26
26
|
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
|
|
27
27
|
fontSize: 12,
|
|
28
|
-
borderRadius: 4
|
|
28
|
+
borderRadius: 4,
|
|
29
|
+
borderColor: "#e2e8f0"
|
|
29
30
|
};
|
|
30
31
|
var DARK_THEME = {
|
|
31
32
|
...DEFAULT_THEME,
|
|
@@ -42,7 +43,8 @@ var DARK_THEME = {
|
|
|
42
43
|
markerColor: "#a78bfa",
|
|
43
44
|
selectionColor: "#38bdf8",
|
|
44
45
|
textColor: "#e5e7eb",
|
|
45
|
-
backgroundColor: "#181c24"
|
|
46
|
+
backgroundColor: "#181c24",
|
|
47
|
+
borderColor: "#334155"
|
|
46
48
|
};
|
|
47
49
|
var DENSITY_PRESETS = {
|
|
48
50
|
compact: { rowHeight: 26, barHeight: 16, headerHeight: 38, fontSize: 11 },
|
|
@@ -81,7 +83,8 @@ var CSS_VAR_NAMES = {
|
|
|
81
83
|
backgroundColor: "--gantt-background-color",
|
|
82
84
|
fontFamily: "--gantt-font-family",
|
|
83
85
|
fontSize: "--gantt-font-size",
|
|
84
|
-
borderRadius: "--gantt-border-radius"
|
|
86
|
+
borderRadius: "--gantt-border-radius",
|
|
87
|
+
borderColor: "--gantt-border-color"
|
|
85
88
|
};
|
|
86
89
|
var PX_FIELDS = /* @__PURE__ */ new Set([
|
|
87
90
|
"rowHeight",
|
|
@@ -507,20 +510,38 @@ function labelFor(date, viewMode) {
|
|
|
507
510
|
const h = date.getHours();
|
|
508
511
|
const period = h >= 12 ? "PM" : "AM";
|
|
509
512
|
const h12 = h % 12 === 0 ? 12 : h % 12;
|
|
510
|
-
return
|
|
513
|
+
return {
|
|
514
|
+
label: `${h12} ${period}`,
|
|
515
|
+
parentLabel: `${WEEKDAY_LABELS[date.getDay()]} ${date.getDate()} ${MONTH_LABELS[date.getMonth()]} ${date.getFullYear()}`
|
|
516
|
+
};
|
|
511
517
|
}
|
|
512
518
|
case "day":
|
|
513
|
-
return
|
|
519
|
+
return {
|
|
520
|
+
label: `${WEEKDAY_LABELS[date.getDay()]} ${date.getDate()}`,
|
|
521
|
+
parentLabel: `${MONTH_LABELS[date.getMonth()]} ${date.getFullYear()}`
|
|
522
|
+
};
|
|
514
523
|
case "week":
|
|
515
|
-
return
|
|
524
|
+
return {
|
|
525
|
+
label: `${MONTH_LABELS[date.getMonth()]} ${date.getDate()}`,
|
|
526
|
+
parentLabel: `${MONTH_LABELS[date.getMonth()]} ${date.getFullYear()}`
|
|
527
|
+
};
|
|
516
528
|
case "month":
|
|
517
|
-
return
|
|
529
|
+
return {
|
|
530
|
+
label: MONTH_LABELS[date.getMonth()] ?? "",
|
|
531
|
+
parentLabel: `${date.getFullYear()}`
|
|
532
|
+
};
|
|
518
533
|
case "quarter": {
|
|
519
534
|
const q = Math.floor(date.getMonth() / 3) + 1;
|
|
520
|
-
return
|
|
535
|
+
return {
|
|
536
|
+
label: `Q${q}`,
|
|
537
|
+
parentLabel: `${date.getFullYear()}`
|
|
538
|
+
};
|
|
521
539
|
}
|
|
522
540
|
case "year":
|
|
523
|
-
return
|
|
541
|
+
return {
|
|
542
|
+
label: `${date.getFullYear()}`,
|
|
543
|
+
parentLabel: ""
|
|
544
|
+
};
|
|
524
545
|
}
|
|
525
546
|
}
|
|
526
547
|
function generateTicks(rangeStart, rangeEnd, viewMode, columnWidth, today = /* @__PURE__ */ new Date(), calendar) {
|
|
@@ -534,10 +555,12 @@ function generateTicks(rangeStart, rangeEnd, viewMode, columnWidth, today = /* @
|
|
|
534
555
|
while (cursor.getTime() < rangeEnd.getTime() && guard < MAX_TICKS) {
|
|
535
556
|
guard++;
|
|
536
557
|
const x = (cursor.getTime() - rangeStart.getTime()) * scale;
|
|
558
|
+
const labels = labelFor(cursor, viewMode);
|
|
537
559
|
ticks.push({
|
|
538
560
|
date: new Date(cursor.getTime()),
|
|
539
561
|
x,
|
|
540
|
-
label:
|
|
562
|
+
label: labels.label,
|
|
563
|
+
parentLabel: labels.parentLabel,
|
|
541
564
|
isWeekend: dayLevel ? isWeekend(cursor) : false,
|
|
542
565
|
isNonWorking: viewMode === "hour" && calendar?.workingHours ? !isWorkingTime(cursor, calendar) : dayLevel ? isNonWorkingDay(cursor, calendar) : false,
|
|
543
566
|
isToday: isSameDay(cursor, today) && dayLevel,
|
|
@@ -614,6 +637,29 @@ function computeProgressRollup(node) {
|
|
|
614
637
|
}
|
|
615
638
|
node.computedProgress = totalWeight > 0 ? weightedSum / totalWeight : 0;
|
|
616
639
|
}
|
|
640
|
+
function sortValue(node, col) {
|
|
641
|
+
if (!col) return null;
|
|
642
|
+
if (col.accessor) return col.accessor(node.task) ?? null;
|
|
643
|
+
return col.id === "name" ? node.task.name : null;
|
|
644
|
+
}
|
|
645
|
+
function compareValues(a, b) {
|
|
646
|
+
if (a === null && b === null) return 0;
|
|
647
|
+
if (a === null) return 1;
|
|
648
|
+
if (b === null) return -1;
|
|
649
|
+
if (typeof a === "number" && typeof b === "number") return a - b;
|
|
650
|
+
return String(a).localeCompare(String(b), void 0, { numeric: true, sensitivity: "base" });
|
|
651
|
+
}
|
|
652
|
+
function sortTree(roots, columns, sort) {
|
|
653
|
+
if (!sort) return;
|
|
654
|
+
const col = columns.find((c) => c.id === sort.columnId);
|
|
655
|
+
const dir = sort.direction === "desc" ? -1 : 1;
|
|
656
|
+
const compare = (a, b) => dir * compareValues(sortValue(a, col), sortValue(b, col));
|
|
657
|
+
const sortLevel = (nodes) => {
|
|
658
|
+
nodes.sort(compare);
|
|
659
|
+
for (const node of nodes) sortLevel(node.children);
|
|
660
|
+
};
|
|
661
|
+
sortLevel(roots);
|
|
662
|
+
}
|
|
617
663
|
function flatten(roots) {
|
|
618
664
|
const out = [];
|
|
619
665
|
const stack = [];
|
|
@@ -700,6 +746,14 @@ function linkPath(from, to) {
|
|
|
700
746
|
const c2y = to.y;
|
|
701
747
|
return `M ${from.x} ${from.y} C ${c1x} ${c1y}, ${c2x} ${c2y}, ${to.x} ${to.y}`;
|
|
702
748
|
}
|
|
749
|
+
var MIN_VISIBLE_UNITS = {
|
|
750
|
+
hour: 8,
|
|
751
|
+
day: 7,
|
|
752
|
+
week: 6,
|
|
753
|
+
month: 6,
|
|
754
|
+
quarter: 4,
|
|
755
|
+
year: 5
|
|
756
|
+
};
|
|
703
757
|
function computeLayout(input) {
|
|
704
758
|
const {
|
|
705
759
|
tasks,
|
|
@@ -715,7 +769,8 @@ function computeLayout(input) {
|
|
|
715
769
|
markers = [],
|
|
716
770
|
autoRollupProgress = false,
|
|
717
771
|
selectedTaskIds,
|
|
718
|
-
pagination
|
|
772
|
+
pagination,
|
|
773
|
+
sort = null
|
|
719
774
|
} = input;
|
|
720
775
|
validateInputs(tasks, dependencies);
|
|
721
776
|
const { roots, nodeById } = buildTree(tasks);
|
|
@@ -723,6 +778,7 @@ function computeLayout(input) {
|
|
|
723
778
|
if (autoRollupProgress) {
|
|
724
779
|
for (const root of roots) computeProgressRollup(root);
|
|
725
780
|
}
|
|
781
|
+
sortTree(roots, columns, sort);
|
|
726
782
|
let visibleRoots = roots;
|
|
727
783
|
if (pagination && pagination.pageSize > 0) {
|
|
728
784
|
const start = Math.max(0, (pagination.page - 1) * pagination.pageSize);
|
|
@@ -742,8 +798,18 @@ function computeLayout(input) {
|
|
|
742
798
|
const now = Date.now();
|
|
743
799
|
const rawRangeStart = rangeMin === null ? new Date(now) : new Date(rangeMin);
|
|
744
800
|
const rawRangeEnd = rangeMax === null ? new Date(now + 1) : new Date(rangeMax);
|
|
745
|
-
|
|
746
|
-
|
|
801
|
+
let paddedStart = addUnit(rawRangeStart, viewMode, -1);
|
|
802
|
+
let paddedEnd = addUnit(rawRangeEnd, viewMode, 1);
|
|
803
|
+
const minUnits = MIN_VISIBLE_UNITS[viewMode];
|
|
804
|
+
let targetMinUnits = minUnits;
|
|
805
|
+
if (input.timelineViewportWidth && input.timelineViewportWidth > 0) {
|
|
806
|
+
targetMinUnits = Math.max(targetMinUnits, Math.ceil(input.timelineViewportWidth / columnWidth));
|
|
807
|
+
}
|
|
808
|
+
const currentUnits = (paddedEnd.getTime() - paddedStart.getTime()) / approxUnitMs(viewMode);
|
|
809
|
+
if (currentUnits < targetMinUnits) {
|
|
810
|
+
const deficit = targetMinUnits - currentUnits;
|
|
811
|
+
paddedEnd = addUnit(paddedEnd, viewMode, Math.ceil(deficit));
|
|
812
|
+
}
|
|
747
813
|
const rows = [];
|
|
748
814
|
const bars = [];
|
|
749
815
|
const barByTaskId = /* @__PURE__ */ new Map();
|
|
@@ -860,6 +926,7 @@ function computeLayout(input) {
|
|
|
860
926
|
(t) => ({
|
|
861
927
|
x: t.x,
|
|
862
928
|
label: t.label,
|
|
929
|
+
parentLabel: t.parentLabel,
|
|
863
930
|
isWeekend: t.isWeekend,
|
|
864
931
|
isNonWorking: t.isNonWorking,
|
|
865
932
|
isToday: t.isToday,
|
|
@@ -894,7 +961,8 @@ function computeLayout(input) {
|
|
|
894
961
|
columns,
|
|
895
962
|
theme,
|
|
896
963
|
markers: renderMarkers,
|
|
897
|
-
rangeStart: paddedStart
|
|
964
|
+
rangeStart: paddedStart,
|
|
965
|
+
sort
|
|
898
966
|
};
|
|
899
967
|
}
|
|
900
968
|
|
|
@@ -1091,6 +1159,7 @@ var GanttRenderer = class {
|
|
|
1091
1159
|
this.lastModel = null;
|
|
1092
1160
|
this.lastTasks = [];
|
|
1093
1161
|
this.lastColumns = [];
|
|
1162
|
+
this.lastExplicitThemeKeys = /* @__PURE__ */ new Set();
|
|
1094
1163
|
this.onGridDoubleClick = (evt) => {
|
|
1095
1164
|
if (!this.options.onRenameCommit) return;
|
|
1096
1165
|
const target = evt.target;
|
|
@@ -1128,8 +1197,16 @@ var GanttRenderer = class {
|
|
|
1128
1197
|
this.root = el("div", "gantt-root");
|
|
1129
1198
|
this.gridPanel = el("div", "gantt-grid-panel");
|
|
1130
1199
|
this.timelineScroll = el("div", "gantt-timeline-scroll");
|
|
1200
|
+
this.headerContainer = el("div", "gantt-timeline-header");
|
|
1201
|
+
this.headerSvg = svgEl("svg");
|
|
1202
|
+
this.headerSvg.classList.add("gantt-header-svg");
|
|
1203
|
+
this.headerContainer.appendChild(this.headerSvg);
|
|
1131
1204
|
this.svg = svgEl("svg");
|
|
1132
1205
|
this.svg.classList.add("gantt-svg");
|
|
1206
|
+
this.timelineScroll.appendChild(this.headerContainer);
|
|
1207
|
+
if (this.options.readonly) {
|
|
1208
|
+
this.root.classList.add("gantt-readonly");
|
|
1209
|
+
}
|
|
1133
1210
|
this.timelineScroll.appendChild(this.svg);
|
|
1134
1211
|
this.root.appendChild(this.gridPanel);
|
|
1135
1212
|
this.root.appendChild(this.timelineScroll);
|
|
@@ -1145,9 +1222,14 @@ var GanttRenderer = class {
|
|
|
1145
1222
|
this.lastTasks = tasks;
|
|
1146
1223
|
this.lastColumns = columns;
|
|
1147
1224
|
const cssVars = explicitThemeToCssVars(explicitTheme);
|
|
1148
|
-
|
|
1225
|
+
const newKeys = new Set(Object.keys(cssVars));
|
|
1226
|
+
for (const key of this.lastExplicitThemeKeys) {
|
|
1227
|
+
if (!newKeys.has(key)) this.root.style.removeProperty(key);
|
|
1228
|
+
}
|
|
1229
|
+
for (const key of newKeys) {
|
|
1149
1230
|
this.root.style.setProperty(key, cssVars[key]);
|
|
1150
1231
|
}
|
|
1232
|
+
this.lastExplicitThemeKeys = newKeys;
|
|
1151
1233
|
this.renderGridPanel(model, tasks, columns);
|
|
1152
1234
|
this.renderTimeline(model, tasks);
|
|
1153
1235
|
}
|
|
@@ -1175,7 +1257,7 @@ var GanttRenderer = class {
|
|
|
1175
1257
|
const header = el("div", "gantt-grid-header-row");
|
|
1176
1258
|
header.style.height = `${model.headerHeight}px`;
|
|
1177
1259
|
for (const col of columns) {
|
|
1178
|
-
header.appendChild(this.renderHeaderCell(col, columns));
|
|
1260
|
+
header.appendChild(this.renderHeaderCell(col, columns, model.sort));
|
|
1179
1261
|
}
|
|
1180
1262
|
this.gridPanel.appendChild(header);
|
|
1181
1263
|
const body = el("div", "gantt-grid-body");
|
|
@@ -1188,11 +1270,26 @@ var GanttRenderer = class {
|
|
|
1188
1270
|
});
|
|
1189
1271
|
this.gridPanel.appendChild(body);
|
|
1190
1272
|
}
|
|
1191
|
-
renderHeaderCell(col, columns) {
|
|
1273
|
+
renderHeaderCell(col, columns, sort) {
|
|
1192
1274
|
const cell = el("div", "gantt-grid-header-cell");
|
|
1193
1275
|
cell.style.position = "relative";
|
|
1194
1276
|
if (col.width) cell.style.width = `${col.width}px`;
|
|
1195
|
-
|
|
1277
|
+
const titleSpan = document.createElement("span");
|
|
1278
|
+
titleSpan.className = "gantt-cell-text";
|
|
1279
|
+
titleSpan.textContent = col.title;
|
|
1280
|
+
cell.appendChild(titleSpan);
|
|
1281
|
+
if (col.sortable) {
|
|
1282
|
+
cell.classList.add("gantt-grid-header-cell-sortable");
|
|
1283
|
+
const active = sort?.columnId === col.id;
|
|
1284
|
+
const indicator = el("span", "gantt-sort-indicator");
|
|
1285
|
+
indicator.textContent = active ? sort.direction === "asc" ? " \u25B2" : " \u25BC" : "";
|
|
1286
|
+
cell.appendChild(indicator);
|
|
1287
|
+
cell.addEventListener("click", (evt) => {
|
|
1288
|
+
const target = evt.target;
|
|
1289
|
+
if (target.closest(".gantt-col-resize-handle")) return;
|
|
1290
|
+
this.options.onSortClick?.(col.id);
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1196
1293
|
if (this.options.onColumnReorder) {
|
|
1197
1294
|
cell.draggable = true;
|
|
1198
1295
|
cell.style.cursor = "grab";
|
|
@@ -1222,7 +1319,7 @@ var GanttRenderer = class {
|
|
|
1222
1319
|
evt.preventDefault();
|
|
1223
1320
|
evt.stopPropagation();
|
|
1224
1321
|
const startX = evt.clientX;
|
|
1225
|
-
const startWidth =
|
|
1322
|
+
const startWidth = cell.getBoundingClientRect().width;
|
|
1226
1323
|
const onMove = (moveEvt) => {
|
|
1227
1324
|
const newWidth = Math.max(30, startWidth + (moveEvt.clientX - startX));
|
|
1228
1325
|
cell.style.width = `${newWidth}px`;
|
|
@@ -1275,9 +1372,11 @@ var GanttRenderer = class {
|
|
|
1275
1372
|
const cell = el("div", "gantt-grid-cell");
|
|
1276
1373
|
if (col.width) cell.style.width = `${col.width}px`;
|
|
1277
1374
|
if (col.align) cell.style.textAlign = col.align;
|
|
1278
|
-
const
|
|
1375
|
+
const isTreeColumn = col.id === "name" || !columns.some((c) => c.id === "name") && colIndex === 0;
|
|
1376
|
+
console.log(`Column ${col.id}: isTreeColumn=${isTreeColumn}, colIndex=${colIndex}, hasNameCol=${columns.some((c) => c.id === "name")}`);
|
|
1377
|
+
const rawValue = col.accessor ? col.accessor(task) : isTreeColumn ? task.name : task[col.id] ?? "";
|
|
1279
1378
|
const textValue = rawValue === null || rawValue === void 0 ? "" : String(rawValue);
|
|
1280
|
-
if (
|
|
1379
|
+
if (isTreeColumn) {
|
|
1281
1380
|
cell.dataset.ganttNameCell = task.id;
|
|
1282
1381
|
cell.dataset.ganttNameValue = textValue;
|
|
1283
1382
|
const indent = el("span", "gantt-indent");
|
|
@@ -1286,17 +1385,27 @@ var GanttRenderer = class {
|
|
|
1286
1385
|
if (row.hasChildren) {
|
|
1287
1386
|
const toggle = el("button", "gantt-toggle");
|
|
1288
1387
|
toggle.type = "button";
|
|
1388
|
+
toggle.className = `gantt-toggle ${row.collapsed ? "collapsed" : "expanded"}`;
|
|
1289
1389
|
toggle.dataset.ganttToggle = task.id;
|
|
1290
1390
|
toggle.setAttribute("aria-label", row.collapsed ? "Expand" : "Collapse");
|
|
1291
1391
|
toggle.textContent = row.collapsed ? "\u25B8" : "\u25BE";
|
|
1292
1392
|
cell.appendChild(toggle);
|
|
1293
1393
|
}
|
|
1394
|
+
const addChildBtn = el("button", "gantt-add-child");
|
|
1395
|
+
addChildBtn.type = "button";
|
|
1396
|
+
addChildBtn.dataset.ganttAddChild = task.id;
|
|
1397
|
+
addChildBtn.setAttribute("aria-label", "Add child task");
|
|
1398
|
+
addChildBtn.textContent = "+";
|
|
1399
|
+
cell.appendChild(addChildBtn);
|
|
1294
1400
|
}
|
|
1295
1401
|
const rendered = col.render ? col.render(task) : void 0;
|
|
1296
1402
|
if (rendered instanceof HTMLElement) {
|
|
1297
1403
|
cell.appendChild(rendered);
|
|
1298
1404
|
} else if (typeof rendered === "string") {
|
|
1299
|
-
|
|
1405
|
+
const span = document.createElement("span");
|
|
1406
|
+
span.className = "gantt-cell-text";
|
|
1407
|
+
span.textContent = rendered;
|
|
1408
|
+
cell.appendChild(span);
|
|
1300
1409
|
} else {
|
|
1301
1410
|
const href = col.getHref ? col.getHref(task) : null;
|
|
1302
1411
|
if (href && isSafeHref(href)) {
|
|
@@ -1305,10 +1414,13 @@ var GanttRenderer = class {
|
|
|
1305
1414
|
anchor.target = col.linkTarget ?? "_blank";
|
|
1306
1415
|
anchor.rel = "noopener noreferrer";
|
|
1307
1416
|
anchor.textContent = textValue;
|
|
1417
|
+
anchor.className = "gantt-cell-text";
|
|
1308
1418
|
cell.appendChild(anchor);
|
|
1309
1419
|
} else {
|
|
1310
|
-
const
|
|
1311
|
-
cell
|
|
1420
|
+
const span = document.createElement("span");
|
|
1421
|
+
span.className = "gantt-cell-text";
|
|
1422
|
+
span.textContent = textValue;
|
|
1423
|
+
cell.appendChild(span);
|
|
1312
1424
|
}
|
|
1313
1425
|
}
|
|
1314
1426
|
rowEl.appendChild(cell);
|
|
@@ -1319,8 +1431,8 @@ var GanttRenderer = class {
|
|
|
1319
1431
|
renderTimeline(model, tasks) {
|
|
1320
1432
|
this.svg.replaceChildren();
|
|
1321
1433
|
this.svg.setAttribute("width", String(model.width));
|
|
1322
|
-
this.svg.setAttribute("height", String(model.
|
|
1323
|
-
this.svg.setAttribute("viewBox", `0 0 ${model.width} ${model.
|
|
1434
|
+
this.svg.setAttribute("height", String(model.height));
|
|
1435
|
+
this.svg.setAttribute("viewBox", `0 0 ${model.width} ${model.height}`);
|
|
1324
1436
|
const defs = svgEl("defs");
|
|
1325
1437
|
const marker = svgEl("marker");
|
|
1326
1438
|
marker.setAttribute("id", this.markerDefsId);
|
|
@@ -1337,11 +1449,10 @@ var GanttRenderer = class {
|
|
|
1337
1449
|
this.svg.appendChild(defs);
|
|
1338
1450
|
const { startY, endY } = this.getViewportRowRange(model);
|
|
1339
1451
|
const bodyGroup = svgEl("g");
|
|
1340
|
-
bodyGroup.setAttribute("transform", `translate(0, ${model.headerHeight})`);
|
|
1341
1452
|
this.renderGridLines(bodyGroup, model);
|
|
1342
1453
|
this.renderRowBackgrounds(bodyGroup, model, startY, endY);
|
|
1343
|
-
this.renderBars(bodyGroup, model, tasks, startY, endY);
|
|
1344
1454
|
this.renderLinks(bodyGroup, model, startY, endY);
|
|
1455
|
+
this.renderBars(bodyGroup, model, tasks, startY, endY);
|
|
1345
1456
|
this.renderMarkers(bodyGroup, model);
|
|
1346
1457
|
this.svg.appendChild(bodyGroup);
|
|
1347
1458
|
this.renderHeader(model);
|
|
@@ -1408,22 +1519,31 @@ var GanttRenderer = class {
|
|
|
1408
1519
|
clipRect.setAttribute("height", String(model.height));
|
|
1409
1520
|
clipPath.appendChild(clipRect);
|
|
1410
1521
|
defs.appendChild(clipPath);
|
|
1411
|
-
const
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1522
|
+
const fo = svgEl("foreignObject");
|
|
1523
|
+
fo.setAttribute("x", String(marker.x + 4));
|
|
1524
|
+
fo.setAttribute("y", "0");
|
|
1525
|
+
fo.setAttribute("width", String(cellWidth > 8 ? cellWidth - 8 : cellWidth));
|
|
1526
|
+
fo.setAttribute("height", String(model.height));
|
|
1527
|
+
const div = document.createElement("div");
|
|
1528
|
+
div.style.color = marker.color;
|
|
1529
|
+
div.style.fontSize = `${model.theme.fontSize}px`;
|
|
1530
|
+
div.style.fontFamily = model.theme.fontFamily;
|
|
1531
|
+
div.style.whiteSpace = "normal";
|
|
1532
|
+
div.style.overflowWrap = "break-word";
|
|
1533
|
+
div.style.paddingTop = "4px";
|
|
1534
|
+
div.textContent = marker.label;
|
|
1535
|
+
fo.appendChild(div);
|
|
1536
|
+
group.appendChild(fo);
|
|
1420
1537
|
}
|
|
1421
1538
|
});
|
|
1422
1539
|
}
|
|
1423
1540
|
renderHeader(model) {
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1541
|
+
this.headerSvg.replaceChildren();
|
|
1542
|
+
this.headerSvg.setAttribute("width", String(model.width));
|
|
1543
|
+
this.headerSvg.setAttribute("height", String(model.headerHeight));
|
|
1544
|
+
this.headerSvg.setAttribute("viewBox", `0 0 ${model.width} ${model.headerHeight}`);
|
|
1545
|
+
this.headerContainer.style.height = `${model.headerHeight}px`;
|
|
1546
|
+
const headerGroup = svgEl("g");
|
|
1427
1547
|
headerGroup.setAttribute("class", "gantt-header-group");
|
|
1428
1548
|
const bg = svgEl("rect");
|
|
1429
1549
|
bg.setAttribute("x", "0");
|
|
@@ -1434,6 +1554,60 @@ var GanttRenderer = class {
|
|
|
1434
1554
|
headerGroup.appendChild(bg);
|
|
1435
1555
|
const defs = svgEl("defs");
|
|
1436
1556
|
const ticks = model.ticks;
|
|
1557
|
+
const parentGroups = [];
|
|
1558
|
+
let currentGroup = null;
|
|
1559
|
+
for (const tick of ticks) {
|
|
1560
|
+
if (!currentGroup || currentGroup.label !== tick.parentLabel) {
|
|
1561
|
+
currentGroup = { x: tick.x, label: tick.parentLabel };
|
|
1562
|
+
parentGroups.push(currentGroup);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
const hasParentTier = parentGroups.some((g) => g.label !== "");
|
|
1566
|
+
const bottomTierY = hasParentTier ? model.headerHeight / 2 : 0;
|
|
1567
|
+
const tierHeight = hasParentTier ? model.headerHeight / 2 : model.headerHeight;
|
|
1568
|
+
if (hasParentTier) {
|
|
1569
|
+
for (let i = 0; i < parentGroups.length; i++) {
|
|
1570
|
+
const group = parentGroups[i];
|
|
1571
|
+
const nextX = parentGroups[i + 1]?.x ?? model.width;
|
|
1572
|
+
const cellWidth = nextX - group.x;
|
|
1573
|
+
const fo = svgEl("foreignObject");
|
|
1574
|
+
fo.setAttribute("x", String(group.x + 4));
|
|
1575
|
+
fo.setAttribute("y", "0");
|
|
1576
|
+
fo.setAttribute("width", String(Math.max(0, cellWidth - 4)));
|
|
1577
|
+
fo.setAttribute("height", String(tierHeight));
|
|
1578
|
+
const div = document.createElement("div");
|
|
1579
|
+
div.style.color = themedFill("textColor", model.theme.textColor);
|
|
1580
|
+
div.style.fontSize = `${model.theme.fontSize * 0.95}px`;
|
|
1581
|
+
div.style.fontFamily = model.theme.fontFamily;
|
|
1582
|
+
div.style.fontWeight = "600";
|
|
1583
|
+
div.style.whiteSpace = "nowrap";
|
|
1584
|
+
div.style.overflow = "hidden";
|
|
1585
|
+
div.style.textOverflow = "ellipsis";
|
|
1586
|
+
div.style.display = "flex";
|
|
1587
|
+
div.style.alignItems = "center";
|
|
1588
|
+
div.style.justifyContent = "center";
|
|
1589
|
+
div.style.height = "100%";
|
|
1590
|
+
div.textContent = group.label;
|
|
1591
|
+
fo.appendChild(div);
|
|
1592
|
+
headerGroup.appendChild(fo);
|
|
1593
|
+
const border = svgEl("line");
|
|
1594
|
+
border.setAttribute("x1", String(group.x));
|
|
1595
|
+
border.setAttribute("x2", String(nextX));
|
|
1596
|
+
border.setAttribute("y1", String(tierHeight));
|
|
1597
|
+
border.setAttribute("y2", String(tierHeight));
|
|
1598
|
+
border.style.stroke = themedFill("borderColor", model.theme.borderColor ?? "#e2e8f0");
|
|
1599
|
+
headerGroup.appendChild(border);
|
|
1600
|
+
if (i > 0) {
|
|
1601
|
+
const sep = svgEl("line");
|
|
1602
|
+
sep.setAttribute("x1", String(group.x));
|
|
1603
|
+
sep.setAttribute("x2", String(group.x));
|
|
1604
|
+
sep.setAttribute("y1", "0");
|
|
1605
|
+
sep.setAttribute("y2", String(tierHeight));
|
|
1606
|
+
sep.style.stroke = themedFill("borderColor", model.theme.borderColor ?? "#e2e8f0");
|
|
1607
|
+
headerGroup.appendChild(sep);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1437
1611
|
for (let i = 0; i < ticks.length; i++) {
|
|
1438
1612
|
const tick = ticks[i];
|
|
1439
1613
|
const cellWidth = (ticks[i + 1]?.x ?? model.width) - tick.x;
|
|
@@ -1442,23 +1616,33 @@ var GanttRenderer = class {
|
|
|
1442
1616
|
clipPath.setAttribute("id", clipId);
|
|
1443
1617
|
const clipRect = svgEl("rect");
|
|
1444
1618
|
clipRect.setAttribute("x", String(tick.x));
|
|
1445
|
-
clipRect.setAttribute("y",
|
|
1619
|
+
clipRect.setAttribute("y", String(bottomTierY));
|
|
1446
1620
|
clipRect.setAttribute("width", String(Math.max(0, cellWidth)));
|
|
1447
|
-
clipRect.setAttribute("height", String(
|
|
1621
|
+
clipRect.setAttribute("height", String(tierHeight));
|
|
1448
1622
|
clipPath.appendChild(clipRect);
|
|
1449
1623
|
defs.appendChild(clipPath);
|
|
1450
|
-
const
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1624
|
+
const fo = svgEl("foreignObject");
|
|
1625
|
+
fo.setAttribute("x", String(tick.x + 4));
|
|
1626
|
+
fo.setAttribute("y", String(bottomTierY));
|
|
1627
|
+
fo.setAttribute("width", String(cellWidth > 8 ? cellWidth - 8 : cellWidth));
|
|
1628
|
+
fo.setAttribute("height", String(tierHeight));
|
|
1629
|
+
const div = document.createElement("div");
|
|
1630
|
+
div.style.color = themedFill("textColor", model.theme.textColor);
|
|
1631
|
+
div.style.fontSize = `${model.theme.fontSize}px`;
|
|
1632
|
+
div.style.fontFamily = model.theme.fontFamily;
|
|
1633
|
+
div.style.whiteSpace = "normal";
|
|
1634
|
+
div.style.overflowWrap = "break-word";
|
|
1635
|
+
div.style.display = "flex";
|
|
1636
|
+
div.style.alignItems = hasParentTier ? "center" : "flex-end";
|
|
1637
|
+
div.style.justifyContent = "center";
|
|
1638
|
+
div.style.paddingBottom = hasParentTier ? "0" : "4px";
|
|
1639
|
+
div.style.height = "100%";
|
|
1640
|
+
div.textContent = tick.label;
|
|
1641
|
+
fo.appendChild(div);
|
|
1642
|
+
headerGroup.appendChild(fo);
|
|
1459
1643
|
}
|
|
1460
1644
|
headerGroup.insertBefore(defs, headerGroup.firstChild);
|
|
1461
|
-
this.
|
|
1645
|
+
this.headerSvg.appendChild(headerGroup);
|
|
1462
1646
|
}
|
|
1463
1647
|
/**
|
|
1464
1648
|
* Invisible full-width hit-test rects, one per row, behind the bars. Lets
|
|
@@ -1493,18 +1677,13 @@ var GanttRenderer = class {
|
|
|
1493
1677
|
g.setAttribute("class", "gantt-bar");
|
|
1494
1678
|
g.dataset.ganttBar = bar.taskId;
|
|
1495
1679
|
g.setAttribute("transform", `translate(${bar.x}, ${bar.y})`);
|
|
1496
|
-
if (task?.notes) {
|
|
1497
|
-
const title = svgEl("title");
|
|
1498
|
-
title.textContent = task.notes;
|
|
1499
|
-
g.appendChild(title);
|
|
1500
|
-
}
|
|
1501
1680
|
if (this.options.keyboardAccessible) {
|
|
1502
1681
|
g.setAttribute("tabindex", "0");
|
|
1503
1682
|
g.setAttribute("role", "button");
|
|
1504
1683
|
if (task) {
|
|
1505
1684
|
const progress = Math.round(task.progress ?? 0);
|
|
1506
|
-
const
|
|
1507
|
-
g.setAttribute("aria-label",
|
|
1685
|
+
const label = `${task.name}, ${task.start.toDateString()} to ${task.end.toDateString()}, ${progress}% complete`;
|
|
1686
|
+
g.setAttribute("aria-label", label);
|
|
1508
1687
|
}
|
|
1509
1688
|
}
|
|
1510
1689
|
if (bar.baseline) {
|
|
@@ -1552,17 +1731,15 @@ var GanttRenderer = class {
|
|
|
1552
1731
|
`rotate(45, 0, ${bar.height / 2})`
|
|
1553
1732
|
);
|
|
1554
1733
|
g.appendChild(diamond);
|
|
1555
|
-
const
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
g.appendChild(
|
|
1563
|
-
|
|
1564
|
-
}
|
|
1565
|
-
if (bar.segments && bar.segments.length > 0) {
|
|
1734
|
+
const label = svgEl("text");
|
|
1735
|
+
label.setAttribute("x", String(size / 2 + 8));
|
|
1736
|
+
label.setAttribute("y", String(bar.height / 2 + 4));
|
|
1737
|
+
label.style.fill = themedFill("textColor", model.theme.textColor);
|
|
1738
|
+
label.setAttribute("font-size", String(model.theme.fontSize));
|
|
1739
|
+
label.setAttribute("font-family", model.theme.fontFamily);
|
|
1740
|
+
label.textContent = bar.label;
|
|
1741
|
+
g.appendChild(label);
|
|
1742
|
+
} else if (bar.segments && bar.segments.length > 0) {
|
|
1566
1743
|
bar.segments.forEach((seg, i) => {
|
|
1567
1744
|
const segRect = svgEl("rect");
|
|
1568
1745
|
segRect.setAttribute("x", String(seg.x));
|
|
@@ -1635,29 +1812,31 @@ var GanttRenderer = class {
|
|
|
1635
1812
|
selectionOutline.setAttribute("stroke-width", "2");
|
|
1636
1813
|
g.appendChild(selectionOutline);
|
|
1637
1814
|
}
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1815
|
+
if (!bar.isMilestone) {
|
|
1816
|
+
const handleWidth = 6;
|
|
1817
|
+
const leftHandle = svgEl("rect");
|
|
1818
|
+
leftHandle.setAttribute("x", "0");
|
|
1819
|
+
leftHandle.setAttribute("y", "0");
|
|
1820
|
+
leftHandle.setAttribute("width", String(handleWidth));
|
|
1821
|
+
leftHandle.setAttribute("height", String(bar.height));
|
|
1822
|
+
leftHandle.setAttribute("fill", "transparent");
|
|
1823
|
+
leftHandle.dataset.ganttHandle = "left";
|
|
1824
|
+
leftHandle.dataset.ganttHandleFor = bar.taskId;
|
|
1825
|
+
leftHandle.style.cursor = "ew-resize";
|
|
1826
|
+
g.appendChild(leftHandle);
|
|
1827
|
+
const rightHandle = svgEl("rect");
|
|
1828
|
+
rightHandle.setAttribute("x", String(Math.max(0, bar.width - handleWidth)));
|
|
1829
|
+
rightHandle.setAttribute("y", "0");
|
|
1830
|
+
rightHandle.setAttribute("width", String(handleWidth));
|
|
1831
|
+
rightHandle.setAttribute("height", String(bar.height));
|
|
1832
|
+
rightHandle.setAttribute("fill", "transparent");
|
|
1833
|
+
rightHandle.dataset.ganttHandle = "right";
|
|
1834
|
+
rightHandle.dataset.ganttHandleFor = bar.taskId;
|
|
1835
|
+
rightHandle.style.cursor = "ew-resize";
|
|
1836
|
+
g.appendChild(rightHandle);
|
|
1837
|
+
}
|
|
1659
1838
|
const connector = svgEl("circle");
|
|
1660
|
-
connector.setAttribute("cx", String(bar.width));
|
|
1839
|
+
connector.setAttribute("cx", String(bar.width + (bar.isMilestone ? 10 : 0)));
|
|
1661
1840
|
connector.setAttribute("cy", String(bar.height / 2));
|
|
1662
1841
|
connector.setAttribute("r", "4");
|
|
1663
1842
|
connector.style.fill = themedFill("linkColor", model.theme.linkColor);
|
|
@@ -1665,14 +1844,16 @@ var GanttRenderer = class {
|
|
|
1665
1844
|
connector.dataset.ganttConnectorSide = "right";
|
|
1666
1845
|
connector.style.cursor = "crosshair";
|
|
1667
1846
|
g.appendChild(connector);
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1847
|
+
if (!bar.isMilestone) {
|
|
1848
|
+
const label = svgEl("text");
|
|
1849
|
+
label.setAttribute("x", String(bar.width + 6));
|
|
1850
|
+
label.setAttribute("y", String(bar.height / 2 + 4));
|
|
1851
|
+
label.style.fill = themedFill("textColor", model.theme.textColor);
|
|
1852
|
+
label.setAttribute("font-size", String(model.theme.fontSize));
|
|
1853
|
+
label.setAttribute("font-family", model.theme.fontFamily);
|
|
1854
|
+
label.textContent = bar.label;
|
|
1855
|
+
g.appendChild(label);
|
|
1856
|
+
}
|
|
1676
1857
|
if (bar.deadlineX !== void 0) {
|
|
1677
1858
|
const markerX = bar.deadlineX - bar.x;
|
|
1678
1859
|
const flag = svgEl("g");
|
|
@@ -1730,10 +1911,20 @@ var GanttRenderer = class {
|
|
|
1730
1911
|
for (const link of model.links) {
|
|
1731
1912
|
const fromBar = barByTaskId.get(link.fromId);
|
|
1732
1913
|
const toBar = barByTaskId.get(link.toId);
|
|
1733
|
-
|
|
1914
|
+
if (!fromBar || !toBar) continue;
|
|
1915
|
+
const relevantY = toBar.y ?? fromBar.y ?? 0;
|
|
1734
1916
|
if (relevantY < startY || relevantY > endY) continue;
|
|
1917
|
+
const type = link.type ?? "FS";
|
|
1918
|
+
const x1 = type.startsWith("F") ? fromBar.x + fromBar.width : fromBar.x;
|
|
1919
|
+
const y1 = fromBar.y + fromBar.height / 2;
|
|
1920
|
+
const x2 = type.endsWith("F") ? toBar.x + toBar.width : toBar.x;
|
|
1921
|
+
const y2 = toBar.y + toBar.height / 2;
|
|
1922
|
+
const offset = 10;
|
|
1923
|
+
const routingX1 = type.startsWith("F") ? x1 + offset : x1 - offset;
|
|
1924
|
+
const routingX2 = type.endsWith("F") ? x2 + offset : x2 - offset;
|
|
1925
|
+
const d = `M ${x1} ${y1} L ${routingX1} ${y1} C ${routingX1 + (routingX2 - routingX1) / 2} ${y1}, ${routingX1 + (routingX2 - routingX1) / 2} ${y2}, ${routingX2} ${y2} L ${x2} ${y2}`;
|
|
1735
1926
|
const path = svgEl("path");
|
|
1736
|
-
path.setAttribute("d",
|
|
1927
|
+
path.setAttribute("d", d);
|
|
1737
1928
|
path.setAttribute("fill", "none");
|
|
1738
1929
|
path.style.stroke = link.isCritical ? themedFill("criticalColor", model.theme.criticalColor) : themedFill("linkColor", model.theme.linkColor);
|
|
1739
1930
|
path.setAttribute("stroke-width", link.isCritical ? "2" : "1.5");
|
|
@@ -1742,8 +1933,30 @@ var GanttRenderer = class {
|
|
|
1742
1933
|
group.appendChild(path);
|
|
1743
1934
|
}
|
|
1744
1935
|
}
|
|
1936
|
+
/**
|
|
1937
|
+
* The header and body are separate DOM elements at runtime (so plain CSS `position: sticky`
|
|
1938
|
+
* can pin the header - see the constructor's doc comment), but exports need one self-contained
|
|
1939
|
+
* SVG with both, laid out exactly as the original single-SVG version was.
|
|
1940
|
+
*/
|
|
1745
1941
|
toSVGString() {
|
|
1746
|
-
|
|
1942
|
+
const headerHeight = this.lastModel?.headerHeight ?? 0;
|
|
1943
|
+
const width = this.lastModel?.width ?? 0;
|
|
1944
|
+
const bodyHeight = this.lastModel?.height ?? 0;
|
|
1945
|
+
const combined = svgEl("svg");
|
|
1946
|
+
combined.setAttribute("xmlns", SVG_NS);
|
|
1947
|
+
combined.setAttribute("width", String(width));
|
|
1948
|
+
combined.setAttribute("height", String(headerHeight + bodyHeight));
|
|
1949
|
+
combined.setAttribute("viewBox", `0 0 ${width} ${headerHeight + bodyHeight}`);
|
|
1950
|
+
for (const child of Array.from(this.headerSvg.children)) {
|
|
1951
|
+
combined.appendChild(child.cloneNode(true));
|
|
1952
|
+
}
|
|
1953
|
+
const bodyGroup = svgEl("g");
|
|
1954
|
+
bodyGroup.setAttribute("transform", `translate(0, ${headerHeight})`);
|
|
1955
|
+
for (const child of Array.from(this.svg.children)) {
|
|
1956
|
+
bodyGroup.appendChild(child.cloneNode(true));
|
|
1957
|
+
}
|
|
1958
|
+
combined.appendChild(bodyGroup);
|
|
1959
|
+
return new XMLSerializer().serializeToString(combined);
|
|
1747
1960
|
}
|
|
1748
1961
|
destroy() {
|
|
1749
1962
|
if (this.scrollListener) {
|
|
@@ -2109,6 +2322,12 @@ var InteractionController = class {
|
|
|
2109
2322
|
const toggleId = target.closest("[data-gantt-toggle]")?.dataset.ganttToggle;
|
|
2110
2323
|
if (toggleId) {
|
|
2111
2324
|
this.callbacks.onToggleCollapse(toggleId);
|
|
2325
|
+
return;
|
|
2326
|
+
}
|
|
2327
|
+
const addChildId = target.closest("[data-gantt-add-child]")?.dataset.ganttAddChild;
|
|
2328
|
+
if (addChildId && this.callbacks.onAddChild) {
|
|
2329
|
+
this.callbacks.onAddChild(addChildId);
|
|
2330
|
+
return;
|
|
2112
2331
|
}
|
|
2113
2332
|
};
|
|
2114
2333
|
this.onContextMenu = (evt) => {
|
|
@@ -2122,13 +2341,17 @@ var InteractionController = class {
|
|
|
2122
2341
|
}
|
|
2123
2342
|
};
|
|
2124
2343
|
this.onSvgDoubleClick = (evt) => {
|
|
2125
|
-
if (!this.callbacks.onLinkDblClick) return;
|
|
2126
2344
|
const target = evt.target;
|
|
2127
2345
|
const linkPath2 = target.closest("[data-gantt-link]");
|
|
2128
2346
|
const key = linkPath2?.dataset.ganttLink;
|
|
2129
|
-
if (
|
|
2130
|
-
|
|
2131
|
-
|
|
2347
|
+
if (key && this.callbacks.onLinkDblClick) {
|
|
2348
|
+
const [fromId, toId] = key.split("->");
|
|
2349
|
+
if (fromId && toId) this.callbacks.onLinkDblClick(fromId, toId);
|
|
2350
|
+
return;
|
|
2351
|
+
}
|
|
2352
|
+
const barGroup = target.closest("[data-gantt-bar]");
|
|
2353
|
+
const taskId = barGroup?.dataset.ganttBar;
|
|
2354
|
+
if (taskId && this.callbacks.onBarDblClick) this.callbacks.onBarDblClick(taskId);
|
|
2132
2355
|
};
|
|
2133
2356
|
this.onPointerDown = (evt) => {
|
|
2134
2357
|
const target = evt.target;
|
|
@@ -2391,6 +2614,119 @@ var InteractionController = class {
|
|
|
2391
2614
|
}
|
|
2392
2615
|
};
|
|
2393
2616
|
|
|
2617
|
+
// src/tooltip.ts
|
|
2618
|
+
function formatDate(d) {
|
|
2619
|
+
return d.toLocaleDateString(void 0, { month: "short", day: "numeric", year: "numeric" });
|
|
2620
|
+
}
|
|
2621
|
+
function defaultTooltipContent(task) {
|
|
2622
|
+
const el2 = document.createElement("div");
|
|
2623
|
+
const title = document.createElement("div");
|
|
2624
|
+
title.className = "gantt-tooltip-title";
|
|
2625
|
+
title.textContent = task.name;
|
|
2626
|
+
el2.appendChild(title);
|
|
2627
|
+
const dates = document.createElement("div");
|
|
2628
|
+
dates.className = "gantt-tooltip-row";
|
|
2629
|
+
dates.textContent = task.isMilestone ? formatDate(task.start) : `${formatDate(task.start)} \u2192 ${formatDate(task.end)}`;
|
|
2630
|
+
el2.appendChild(dates);
|
|
2631
|
+
if (task.progress !== void 0) {
|
|
2632
|
+
const progress = document.createElement("div");
|
|
2633
|
+
progress.className = "gantt-tooltip-row";
|
|
2634
|
+
progress.textContent = `${Math.round(task.progress)}% complete`;
|
|
2635
|
+
el2.appendChild(progress);
|
|
2636
|
+
}
|
|
2637
|
+
if (task.assignees && task.assignees.length > 0) {
|
|
2638
|
+
const assignees = document.createElement("div");
|
|
2639
|
+
assignees.className = "gantt-tooltip-row";
|
|
2640
|
+
assignees.textContent = task.assignees.map((a) => a.name).join(", ");
|
|
2641
|
+
el2.appendChild(assignees);
|
|
2642
|
+
}
|
|
2643
|
+
if (task.notes) {
|
|
2644
|
+
const notes = document.createElement("div");
|
|
2645
|
+
notes.className = "gantt-tooltip-notes";
|
|
2646
|
+
notes.textContent = task.notes;
|
|
2647
|
+
el2.appendChild(notes);
|
|
2648
|
+
}
|
|
2649
|
+
return el2;
|
|
2650
|
+
}
|
|
2651
|
+
var Tooltip = class {
|
|
2652
|
+
constructor(getOptions, getTask) {
|
|
2653
|
+
this.svg = null;
|
|
2654
|
+
this.hoveredTaskId = null;
|
|
2655
|
+
this.onPointerOver = (evt) => {
|
|
2656
|
+
if (!this.getOptions().enabled) return;
|
|
2657
|
+
const target = evt.target;
|
|
2658
|
+
const barGroup = target.closest("[data-gantt-bar]");
|
|
2659
|
+
const taskId = barGroup?.dataset.ganttBar;
|
|
2660
|
+
if (!taskId || taskId === this.hoveredTaskId) return;
|
|
2661
|
+
const task = this.getTask(taskId);
|
|
2662
|
+
if (!task) return;
|
|
2663
|
+
const { render } = this.getOptions();
|
|
2664
|
+
const content = render ? render(task) : defaultTooltipContent(task);
|
|
2665
|
+
if (content === null || content === void 0) {
|
|
2666
|
+
this.hide();
|
|
2667
|
+
return;
|
|
2668
|
+
}
|
|
2669
|
+
this.el.replaceChildren();
|
|
2670
|
+
if (content instanceof HTMLElement) {
|
|
2671
|
+
this.el.appendChild(content);
|
|
2672
|
+
} else {
|
|
2673
|
+
this.el.textContent = content;
|
|
2674
|
+
}
|
|
2675
|
+
this.hoveredTaskId = taskId;
|
|
2676
|
+
this.el.style.display = "block";
|
|
2677
|
+
this.position(evt.clientX, evt.clientY);
|
|
2678
|
+
};
|
|
2679
|
+
this.onPointerMove = (evt) => {
|
|
2680
|
+
if (this.hoveredTaskId === null) return;
|
|
2681
|
+
this.position(evt.clientX, evt.clientY);
|
|
2682
|
+
};
|
|
2683
|
+
this.onPointerOut = (evt) => {
|
|
2684
|
+
const related = evt.relatedTarget;
|
|
2685
|
+
const target = evt.target;
|
|
2686
|
+
const leftBarGroup = target.closest("[data-gantt-bar]");
|
|
2687
|
+
if (related && leftBarGroup?.contains(related)) return;
|
|
2688
|
+
this.hide();
|
|
2689
|
+
};
|
|
2690
|
+
this.getOptions = getOptions;
|
|
2691
|
+
this.getTask = getTask;
|
|
2692
|
+
this.el = document.createElement("div");
|
|
2693
|
+
this.el.className = "gantt-tooltip";
|
|
2694
|
+
this.el.setAttribute("role", "tooltip");
|
|
2695
|
+
this.el.style.display = "none";
|
|
2696
|
+
document.body.appendChild(this.el);
|
|
2697
|
+
}
|
|
2698
|
+
attach(svg) {
|
|
2699
|
+
this.svg = svg;
|
|
2700
|
+
svg.addEventListener("pointerover", this.onPointerOver);
|
|
2701
|
+
svg.addEventListener("pointermove", this.onPointerMove);
|
|
2702
|
+
svg.addEventListener("pointerout", this.onPointerOut);
|
|
2703
|
+
}
|
|
2704
|
+
position(clientX, clientY) {
|
|
2705
|
+
const OFFSET = 12;
|
|
2706
|
+
const rect = this.el.getBoundingClientRect();
|
|
2707
|
+
const viewportW = window.innerWidth;
|
|
2708
|
+
const viewportH = window.innerHeight;
|
|
2709
|
+
let left = clientX + OFFSET;
|
|
2710
|
+
let top = clientY + OFFSET;
|
|
2711
|
+
if (left + rect.width > viewportW) left = clientX - OFFSET - rect.width;
|
|
2712
|
+
if (top + rect.height > viewportH) top = clientY - OFFSET - rect.height;
|
|
2713
|
+
this.el.style.left = `${Math.max(0, left)}px`;
|
|
2714
|
+
this.el.style.top = `${Math.max(0, top)}px`;
|
|
2715
|
+
}
|
|
2716
|
+
hide() {
|
|
2717
|
+
this.hoveredTaskId = null;
|
|
2718
|
+
this.el.style.display = "none";
|
|
2719
|
+
}
|
|
2720
|
+
destroy() {
|
|
2721
|
+
if (this.svg) {
|
|
2722
|
+
this.svg.removeEventListener("pointerover", this.onPointerOver);
|
|
2723
|
+
this.svg.removeEventListener("pointermove", this.onPointerMove);
|
|
2724
|
+
this.svg.removeEventListener("pointerout", this.onPointerOut);
|
|
2725
|
+
}
|
|
2726
|
+
this.el.remove();
|
|
2727
|
+
}
|
|
2728
|
+
};
|
|
2729
|
+
|
|
2394
2730
|
// src/index.ts
|
|
2395
2731
|
var HAS_DOM = typeof document !== "undefined" && typeof window !== "undefined";
|
|
2396
2732
|
var DEFAULT_COLUMNS = [{ id: "name", title: "Name" }];
|
|
@@ -2410,6 +2746,9 @@ var _GanttChart = class _GanttChart {
|
|
|
2410
2746
|
this.history = null;
|
|
2411
2747
|
this.renderer = null;
|
|
2412
2748
|
this.interactions = null;
|
|
2749
|
+
this.tooltip = null;
|
|
2750
|
+
this.resizeObserver = null;
|
|
2751
|
+
this.sort = null;
|
|
2413
2752
|
this.renderModel = null;
|
|
2414
2753
|
this.rafHandle = null;
|
|
2415
2754
|
this.currentPage = 1;
|
|
@@ -2432,6 +2771,8 @@ var _GanttChart = class _GanttChart {
|
|
|
2432
2771
|
showCriticalPath: options.showCriticalPath ?? false,
|
|
2433
2772
|
showBaseline: options.showBaseline ?? false,
|
|
2434
2773
|
showDeadlines: options.showDeadlines ?? true,
|
|
2774
|
+
showTooltip: options.showTooltip ?? true,
|
|
2775
|
+
autoFitToViewport: options.autoFitToViewport ?? false,
|
|
2435
2776
|
showAssigneeAvatars: options.showAssigneeAvatars ?? false,
|
|
2436
2777
|
enableHistory: options.enableHistory ?? false,
|
|
2437
2778
|
keyboardAccessible: options.keyboardAccessible ?? false,
|
|
@@ -2446,19 +2787,23 @@ var _GanttChart = class _GanttChart {
|
|
|
2446
2787
|
markers: options.markers,
|
|
2447
2788
|
pagination: options.pagination,
|
|
2448
2789
|
snapToUnit: options.snapToUnit,
|
|
2790
|
+
headerPosition: options.headerPosition ?? "top",
|
|
2449
2791
|
onDateChange: options.onDateChange,
|
|
2450
2792
|
onProgressChange: options.onProgressChange,
|
|
2451
2793
|
onDependencyCreate: options.onDependencyCreate,
|
|
2452
2794
|
onDependencyRemove: options.onDependencyRemove,
|
|
2453
2795
|
onDependencyDblClick: options.onDependencyDblClick,
|
|
2454
2796
|
onTaskClick: options.onTaskClick,
|
|
2797
|
+
onTaskDblClick: options.onTaskDblClick,
|
|
2455
2798
|
onGroupToggle: options.onGroupToggle,
|
|
2456
2799
|
onContextMenu: options.onContextMenu,
|
|
2457
2800
|
onTaskCreate: options.onTaskCreate,
|
|
2458
2801
|
onColumnResize: options.onColumnResize,
|
|
2459
2802
|
onColumnReorder: options.onColumnReorder,
|
|
2460
2803
|
onTaskReorder: options.onTaskReorder,
|
|
2461
|
-
onSelectionChange: options.onSelectionChange
|
|
2804
|
+
onSelectionChange: options.onSelectionChange,
|
|
2805
|
+
onSortChange: options.onSortChange,
|
|
2806
|
+
renderTooltip: options.renderTooltip
|
|
2462
2807
|
};
|
|
2463
2808
|
if (this.options.enableHistory) {
|
|
2464
2809
|
this.history = new HistoryManager((state) => this.emitter.emit("history-change", state));
|
|
@@ -2475,14 +2820,23 @@ var _GanttChart = class _GanttChart {
|
|
|
2475
2820
|
this.darkMediaQuery.addEventListener?.("change", this.handleSchemeChange);
|
|
2476
2821
|
}
|
|
2477
2822
|
this.renderer = new GanttRenderer(this.container, {
|
|
2823
|
+
readonly: this.options.readonly,
|
|
2478
2824
|
keyboardAccessible: this.options.keyboardAccessible,
|
|
2479
2825
|
showAssigneeAvatars: this.options.showAssigneeAvatars,
|
|
2480
2826
|
virtualScroll: this.options.virtualScroll,
|
|
2481
2827
|
onRenameCommit: (taskId, name) => this.updateTask(taskId, { name }),
|
|
2482
2828
|
onColumnResize: this.options.onColumnResize ? (columnId, width) => this.handleColumnResize(columnId, width) : void 0,
|
|
2483
2829
|
onColumnReorder: this.options.onColumnReorder ? (order) => this.handleColumnReorder(order) : void 0,
|
|
2484
|
-
onRowReorder: this.options.onTaskReorder ? (draggedId, targetId, position) => this.handleRowReorder(draggedId, targetId, position) : void 0
|
|
2830
|
+
onRowReorder: this.options.onTaskReorder ? (draggedId, targetId, position) => this.handleRowReorder(draggedId, targetId, position) : void 0,
|
|
2831
|
+
onSortClick: (columnId) => this.handleSortClick(columnId)
|
|
2485
2832
|
});
|
|
2833
|
+
if (this.options.headerPosition && this.renderer?.root) {
|
|
2834
|
+
this.renderer.root.dataset.ganttHeaderPosition = this.options.headerPosition;
|
|
2835
|
+
}
|
|
2836
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
2837
|
+
this.resizeObserver = new ResizeObserver(() => this.scheduleRender());
|
|
2838
|
+
this.resizeObserver.observe(this.renderer.timelineScroll);
|
|
2839
|
+
}
|
|
2486
2840
|
this.interactions = new InteractionController(
|
|
2487
2841
|
this.renderer.svg,
|
|
2488
2842
|
this.renderer.gridPanel,
|
|
@@ -2502,7 +2856,17 @@ var _GanttChart = class _GanttChart {
|
|
|
2502
2856
|
onCreateTaskDrag: this.options.onTaskCreate ? (rowTaskId, startXPx, endXPx) => this.handleCreateTaskDrag(rowTaskId, startXPx, endXPx) : void 0,
|
|
2503
2857
|
onLinkDblClick: this.options.onDependencyDblClick ? (fromId, toId) => {
|
|
2504
2858
|
const dep = this.dependencies.find((d) => d.fromId === fromId && d.toId === toId);
|
|
2505
|
-
if (dep)
|
|
2859
|
+
if (dep) {
|
|
2860
|
+
this.emitter.emit("dependency-dblclick", dep);
|
|
2861
|
+
this.options.onDependencyDblClick?.(dep);
|
|
2862
|
+
}
|
|
2863
|
+
} : void 0,
|
|
2864
|
+
onBarDblClick: this.options.onTaskDblClick ? (taskId) => {
|
|
2865
|
+
const task = this.tasks.find((t) => t.id === taskId);
|
|
2866
|
+
if (task) {
|
|
2867
|
+
this.emitter.emit("task-dblclick", { task });
|
|
2868
|
+
this.options.onTaskDblClick?.(task);
|
|
2869
|
+
}
|
|
2506
2870
|
} : void 0,
|
|
2507
2871
|
onContextMenu: this.options.onContextMenu ? (taskId, evt) => {
|
|
2508
2872
|
const task = this.tasks.find((t) => t.id === taskId);
|
|
@@ -2511,7 +2875,8 @@ var _GanttChart = class _GanttChart {
|
|
|
2511
2875
|
} : void 0,
|
|
2512
2876
|
onSelectAll: this.options.selectable ? () => this.selectAllVisible() : void 0,
|
|
2513
2877
|
onJumpToStart: () => this.scrollToRangeStart(),
|
|
2514
|
-
onJumpToEnd: () => this.scrollToRangeEnd()
|
|
2878
|
+
onJumpToEnd: () => this.scrollToRangeEnd(),
|
|
2879
|
+
onAddChild: (parentId) => this.handleAddChild(parentId)
|
|
2515
2880
|
},
|
|
2516
2881
|
{
|
|
2517
2882
|
readonly: this.options.readonly,
|
|
@@ -2521,6 +2886,17 @@ var _GanttChart = class _GanttChart {
|
|
|
2521
2886
|
pxPerMs: () => pxPerMs(this.options.viewMode, this.columnWidth)
|
|
2522
2887
|
}
|
|
2523
2888
|
);
|
|
2889
|
+
this.tooltip = new Tooltip(
|
|
2890
|
+
() => ({ enabled: this.options.showTooltip, render: this.options.renderTooltip }),
|
|
2891
|
+
(taskId) => this.tasks.find((t) => t.id === taskId)
|
|
2892
|
+
);
|
|
2893
|
+
this.tooltip.attach(this.renderer.svg);
|
|
2894
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
2895
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
2896
|
+
if (this.options.autoFitToViewport) this.scheduleRender();
|
|
2897
|
+
});
|
|
2898
|
+
this.resizeObserver.observe(this.container);
|
|
2899
|
+
}
|
|
2524
2900
|
}
|
|
2525
2901
|
handleMove(taskId, dxMs) {
|
|
2526
2902
|
if (dxMs === 0) return;
|
|
@@ -2790,6 +3166,35 @@ var _GanttChart = class _GanttChart {
|
|
|
2790
3166
|
};
|
|
2791
3167
|
this.runCommand(cmd);
|
|
2792
3168
|
}
|
|
3169
|
+
handleAddChild(parentId) {
|
|
3170
|
+
const parent = this.tasks.find((t) => t.id === parentId);
|
|
3171
|
+
if (!parent) return;
|
|
3172
|
+
const newTask = {
|
|
3173
|
+
id: `task-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
|
3174
|
+
name: "New task",
|
|
3175
|
+
start: new Date(parent.start.getTime()),
|
|
3176
|
+
end: new Date(parent.start.getTime() + MS_PER_DAY),
|
|
3177
|
+
parentId: parent.id
|
|
3178
|
+
};
|
|
3179
|
+
const cmd = {
|
|
3180
|
+
label: "task-create",
|
|
3181
|
+
do: () => {
|
|
3182
|
+
const index = this.tasks.findIndex((t) => t.id === parentId);
|
|
3183
|
+
if (index >= 0) this.tasks.splice(index + 1, 0, newTask);
|
|
3184
|
+
else this.tasks.push(newTask);
|
|
3185
|
+
this.emitter.emit("task-create", { task: newTask });
|
|
3186
|
+
this.options.onTaskCreate?.(newTask);
|
|
3187
|
+
this.emitter.emit("tasks-change", { tasks: this.getTasks() });
|
|
3188
|
+
this.scheduleRender();
|
|
3189
|
+
},
|
|
3190
|
+
undo: () => {
|
|
3191
|
+
this.tasks = this.tasks.filter((t) => t.id !== newTask.id);
|
|
3192
|
+
this.emitter.emit("tasks-change", { tasks: this.getTasks() });
|
|
3193
|
+
this.scheduleRender();
|
|
3194
|
+
}
|
|
3195
|
+
};
|
|
3196
|
+
this.runCommand(cmd);
|
|
3197
|
+
}
|
|
2793
3198
|
handleBarClick(taskId, modifiers) {
|
|
2794
3199
|
const task = this.tasks.find((t) => t.id === taskId);
|
|
2795
3200
|
if (!task) return;
|
|
@@ -2925,6 +3330,24 @@ var _GanttChart = class _GanttChart {
|
|
|
2925
3330
|
this.options.onColumnReorder?.(order);
|
|
2926
3331
|
this.scheduleRender();
|
|
2927
3332
|
}
|
|
3333
|
+
/** Cycles a sortable column's header through asc -> desc -> none; clicking a different column starts it fresh at asc. */
|
|
3334
|
+
handleSortClick(columnId) {
|
|
3335
|
+
if (this.sort?.columnId === columnId) {
|
|
3336
|
+
this.setSort(columnId, this.sort.direction === "asc" ? "desc" : null);
|
|
3337
|
+
} else {
|
|
3338
|
+
this.setSort(columnId, "asc");
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
/** Sorts siblings at every tree level by a column's value (see GanttColumn.sortable); `direction: null`/omitted `columnId` clears it. */
|
|
3342
|
+
setSort(columnId, direction = "asc") {
|
|
3343
|
+
this.sort = columnId && direction ? { columnId, direction } : null;
|
|
3344
|
+
this.emitter.emit("sort-change", { sort: this.sort });
|
|
3345
|
+
this.options.onSortChange?.(this.sort);
|
|
3346
|
+
this.scheduleRender();
|
|
3347
|
+
}
|
|
3348
|
+
getSort() {
|
|
3349
|
+
return this.sort;
|
|
3350
|
+
}
|
|
2928
3351
|
handleRowReorder(draggedId, targetId, position) {
|
|
2929
3352
|
const draggedIndex = this.tasks.findIndex((t) => t.id === draggedId);
|
|
2930
3353
|
const targetIndex = this.tasks.findIndex((t) => t.id === targetId);
|
|
@@ -2991,13 +3414,24 @@ var _GanttChart = class _GanttChart {
|
|
|
2991
3414
|
this.scheduleRender();
|
|
2992
3415
|
}
|
|
2993
3416
|
setOptions(partial) {
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
if (
|
|
3417
|
+
const hasTheme = "theme" in partial;
|
|
3418
|
+
const hasColorScheme = "colorScheme" in partial;
|
|
3419
|
+
if (hasColorScheme) this.colorScheme = partial.colorScheme ?? "auto";
|
|
3420
|
+
if (hasTheme) this.explicitTheme = partial.theme;
|
|
3421
|
+
if (hasTheme || hasColorScheme) {
|
|
2997
3422
|
this.theme = mergeTheme(this.explicitTheme, this.colorScheme);
|
|
2998
3423
|
}
|
|
2999
|
-
if (partial
|
|
3424
|
+
if ("columns" in partial) this.columns = partial.columns ?? this.columns;
|
|
3000
3425
|
if (partial.columnWidth !== void 0) this.columnWidth = partial.columnWidth;
|
|
3426
|
+
if (partial.gridPanelWidth !== void 0) {
|
|
3427
|
+
this.container.style.setProperty("--gantt-grid-panel-width", `${partial.gridPanelWidth}px`);
|
|
3428
|
+
}
|
|
3429
|
+
if ("headerPosition" in partial) {
|
|
3430
|
+
this.options.headerPosition = partial.headerPosition ?? "top";
|
|
3431
|
+
if (this.renderer?.root) {
|
|
3432
|
+
this.renderer.root.dataset.ganttHeaderPosition = this.options.headerPosition;
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3001
3435
|
Object.assign(this.options, partial);
|
|
3002
3436
|
this.scheduleRender();
|
|
3003
3437
|
}
|
|
@@ -3085,9 +3519,11 @@ var _GanttChart = class _GanttChart {
|
|
|
3085
3519
|
}
|
|
3086
3520
|
computeModel() {
|
|
3087
3521
|
const pagination = this.options.pagination ? { pageSize: this.options.pagination.pageSize, page: this.currentPage } : void 0;
|
|
3522
|
+
const timelineViewportWidth = HAS_DOM && this.renderer ? this.renderer.timelineScroll.clientWidth : void 0;
|
|
3088
3523
|
return computeLayout({
|
|
3089
3524
|
tasks: this.tasks,
|
|
3090
3525
|
dependencies: this.dependencies,
|
|
3526
|
+
timelineViewportWidth,
|
|
3091
3527
|
viewMode: this.options.viewMode,
|
|
3092
3528
|
columnWidth: this.columnWidth,
|
|
3093
3529
|
theme: this.theme,
|
|
@@ -3100,7 +3536,8 @@ var _GanttChart = class _GanttChart {
|
|
|
3100
3536
|
markers: this.options.markers,
|
|
3101
3537
|
autoRollupProgress: this.options.autoRollupProgress,
|
|
3102
3538
|
selectedTaskIds: this.options.selectable ? this.selectedTaskIds : void 0,
|
|
3103
|
-
pagination
|
|
3539
|
+
pagination,
|
|
3540
|
+
sort: this.sort
|
|
3104
3541
|
});
|
|
3105
3542
|
}
|
|
3106
3543
|
scheduleRender(immediate = false) {
|
|
@@ -3115,6 +3552,7 @@ var _GanttChart = class _GanttChart {
|
|
|
3115
3552
|
});
|
|
3116
3553
|
}
|
|
3117
3554
|
doRender() {
|
|
3555
|
+
if (this.options.autoFitToViewport) this.applyAutoFit();
|
|
3118
3556
|
this.renderModel = this.computeModel();
|
|
3119
3557
|
this.renderer?.render(this.renderModel, this.tasks, this.columns, this.explicitTheme);
|
|
3120
3558
|
if (this.renderer && this.colorScheme !== "auto") {
|
|
@@ -3123,6 +3561,29 @@ var _GanttChart = class _GanttChart {
|
|
|
3123
3561
|
this.renderer?.root.removeAttribute("data-gantt-theme");
|
|
3124
3562
|
}
|
|
3125
3563
|
}
|
|
3564
|
+
/**
|
|
3565
|
+
* Stretch-only fit: bump columnWidth up so the timeline fills the container's available
|
|
3566
|
+
* width, but never shrink it below what the current zoom level already implies - a project
|
|
3567
|
+
* wider than the container should still scroll normally, not get crammed to fit.
|
|
3568
|
+
*/
|
|
3569
|
+
applyAutoFit() {
|
|
3570
|
+
const availableWidth = this.container.clientWidth - this.options.gridPanelWidth;
|
|
3571
|
+
if (!Number.isFinite(availableWidth) || availableWidth <= 0) return;
|
|
3572
|
+
let min = null;
|
|
3573
|
+
let max = null;
|
|
3574
|
+
for (const t of this.tasks) {
|
|
3575
|
+
if (!isValidDate(t.start) || !isValidDate(t.end)) continue;
|
|
3576
|
+
min = min === null ? t.start.getTime() : Math.min(min, t.start.getTime());
|
|
3577
|
+
max = max === null ? t.end.getTime() : Math.max(max, t.end.getTime());
|
|
3578
|
+
}
|
|
3579
|
+
if (min === null || max === null || max <= min) return;
|
|
3580
|
+
const rangeMs = max - min;
|
|
3581
|
+
const unitMs = approxUnitMs(this.options.viewMode);
|
|
3582
|
+
const neededColumnWidth = availableWidth * unitMs / rangeMs;
|
|
3583
|
+
if (neededColumnWidth > this.columnWidth) {
|
|
3584
|
+
this.columnWidth = Math.min(neededColumnWidth, _GanttChart.MAX_COLUMN_WIDTH);
|
|
3585
|
+
}
|
|
3586
|
+
}
|
|
3126
3587
|
undo() {
|
|
3127
3588
|
this.history?.undo();
|
|
3128
3589
|
}
|
|
@@ -3145,7 +3606,35 @@ var _GanttChart = class _GanttChart {
|
|
|
3145
3606
|
return this.renderer?.toSVGString() ?? "";
|
|
3146
3607
|
}
|
|
3147
3608
|
async rasterizeSVG(svgString, width, height) {
|
|
3148
|
-
|
|
3609
|
+
let cleanSvgString = svgString;
|
|
3610
|
+
if (typeof DOMParser !== "undefined") {
|
|
3611
|
+
const parser = new DOMParser();
|
|
3612
|
+
const doc = parser.parseFromString(svgString, "image/svg+xml");
|
|
3613
|
+
const foreignObjects = Array.from(doc.querySelectorAll("foreignObject"));
|
|
3614
|
+
for (const fo of foreignObjects) {
|
|
3615
|
+
const x = fo.getAttribute("x") || "0";
|
|
3616
|
+
const y = fo.getAttribute("y") || "0";
|
|
3617
|
+
const w = parseFloat(fo.getAttribute("width") || "0");
|
|
3618
|
+
const h = parseFloat(fo.getAttribute("height") || "0");
|
|
3619
|
+
const div = fo.querySelector("div");
|
|
3620
|
+
if (div) {
|
|
3621
|
+
const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
3622
|
+
text.setAttribute("x", String(parseFloat(x) + w / 2));
|
|
3623
|
+
text.setAttribute("y", String(parseFloat(y) + h / 2 + 4));
|
|
3624
|
+
text.setAttribute("text-anchor", "middle");
|
|
3625
|
+
text.setAttribute("fill", div.style.color || "#000");
|
|
3626
|
+
text.setAttribute("font-size", div.style.fontSize || "12px");
|
|
3627
|
+
text.setAttribute("font-family", div.style.fontFamily || "sans-serif");
|
|
3628
|
+
text.setAttribute("font-weight", div.style.fontWeight || "normal");
|
|
3629
|
+
text.textContent = div.textContent;
|
|
3630
|
+
fo.parentNode?.replaceChild(text, fo);
|
|
3631
|
+
} else {
|
|
3632
|
+
fo.parentNode?.removeChild(fo);
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
cleanSvgString = new XMLSerializer().serializeToString(doc);
|
|
3636
|
+
}
|
|
3637
|
+
const svgBlob = new Blob([cleanSvgString], { type: "image/svg+xml;charset=utf-8" });
|
|
3149
3638
|
const url = URL.createObjectURL(svgBlob);
|
|
3150
3639
|
try {
|
|
3151
3640
|
return await new Promise((resolve, reject) => {
|
|
@@ -3217,6 +3706,8 @@ var _GanttChart = class _GanttChart {
|
|
|
3217
3706
|
this.darkMediaQuery?.removeEventListener?.("change", this.handleSchemeChange);
|
|
3218
3707
|
this.emitter.removeAllListeners();
|
|
3219
3708
|
this.interactions?.destroy();
|
|
3709
|
+
this.tooltip?.destroy();
|
|
3710
|
+
this.resizeObserver?.disconnect();
|
|
3220
3711
|
this.renderer?.destroy();
|
|
3221
3712
|
}
|
|
3222
3713
|
};
|