gantt-lib 0.71.1 → 0.72.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1981,10 +1981,10 @@ var calculateMilestoneGeometry = (taskDate, monthStart, dayWidth, size = 14) =>
1981
1981
  };
1982
1982
  var calculateMilestoneConnectionBounds = (dayLeft, dayWidth, size = 14) => {
1983
1983
  const halfDiagonal = Math.round(size / Math.SQRT2);
1984
- const visualNudge = 2;
1984
+ const centerX = Math.round(dayLeft + dayWidth / 2);
1985
1985
  return {
1986
- left: dayLeft + halfDiagonal + visualNudge,
1987
- right: dayLeft + dayWidth - halfDiagonal - visualNudge
1986
+ left: centerX - halfDiagonal,
1987
+ right: centerX + halfDiagonal
1988
1988
  };
1989
1989
  };
1990
1990
  var resolveTaskHorizontalGeometry = (task, monthStart, dayWidth, override) => {
@@ -1993,19 +1993,32 @@ var resolveTaskHorizontalGeometry = (task, monthStart, dayWidth, override) => {
1993
1993
  if (task.type === "milestone") {
1994
1994
  const size = 14;
1995
1995
  if (override) {
1996
- return calculateMilestoneConnectionBounds(override.left, dayWidth, size);
1996
+ const bounds2 = calculateMilestoneConnectionBounds(override.left, dayWidth, size);
1997
+ return {
1998
+ ...bounds2,
1999
+ centerX: Math.round(override.left + dayWidth / 2)
2000
+ };
1997
2001
  }
1998
2002
  const bar2 = calculateTaskBar(startDate, startDate, monthStart, dayWidth);
1999
- return calculateMilestoneConnectionBounds(bar2.left, dayWidth, size);
2003
+ const bounds = calculateMilestoneConnectionBounds(bar2.left, dayWidth, size);
2004
+ return {
2005
+ ...bounds,
2006
+ centerX: Math.round(bar2.left + dayWidth / 2)
2007
+ };
2000
2008
  }
2001
2009
  if (override) {
2002
2010
  return {
2003
2011
  left: override.left,
2004
- right: override.left + override.width
2012
+ right: override.left + override.width,
2013
+ centerX: Math.round(override.left + override.width / 2)
2005
2014
  };
2006
2015
  }
2007
2016
  const bar = calculateTaskBar(startDate, endDate, monthStart, dayWidth);
2008
- return { left: bar.left, right: bar.left + bar.width };
2017
+ return {
2018
+ left: bar.left,
2019
+ right: bar.left + bar.width,
2020
+ centerX: Math.round(bar.left + bar.width / 2)
2021
+ };
2009
2022
  };
2010
2023
  var pixelsToDate = (pixels, monthStart, dayWidth) => {
2011
2024
  const days = Math.round(pixels / dayWidth);
@@ -3340,6 +3353,7 @@ var DependencyLines = import_react6.default.memo(({
3340
3353
  positions.set(task.id, {
3341
3354
  left: computed.left,
3342
3355
  right: computed.right,
3356
+ centerX: computed.centerX,
3343
3357
  rowTop: index * rowHeight,
3344
3358
  isVirtual: false
3345
3359
  });
@@ -3358,6 +3372,7 @@ var DependencyLines = import_react6.default.memo(({
3358
3372
  positions.set(task.id, {
3359
3373
  left: computed.left,
3360
3374
  right: computed.right,
3375
+ centerX: computed.centerX,
3361
3376
  rowTop: ancestorPosition.rowTop,
3362
3377
  isVirtual: true
3363
3378
  });
@@ -3408,12 +3423,12 @@ var DependencyLines = import_react6.default.memo(({
3408
3423
  fromY = predecessor.rowTop + rowHeight - 10;
3409
3424
  toY = successor.rowTop + 6;
3410
3425
  }
3411
- let fromX = edge.type === "SS" || edge.type === "SF" ? predecessor.left : predecessor.right;
3412
- const toX = edge.type === "FF" || edge.type === "SF" ? successor.right : successor.left;
3426
+ let fromX = predecessorTask && isMilestoneTask(predecessorTask) ? predecessor.centerX : edge.type === "SS" || edge.type === "SF" ? predecessor.left : predecessor.right;
3427
+ const toX = successorTask && isMilestoneTask(successorTask) ? successor.centerX : edge.type === "FF" || edge.type === "SF" ? successor.right : successor.left;
3413
3428
  const stackedMilestonesSameDay = Boolean(
3414
3429
  predecessorTask && successorTask && isMilestoneTask(predecessorTask) && isMilestoneTask(successorTask) && edge.lag === 0 && new Date(predecessorTask.startDate).toISOString().split("T")[0] === new Date(successorTask.startDate).toISOString().split("T")[0] && predecessor.rowTop !== successor.rowTop && edge.type === "FS"
3415
3430
  );
3416
- const finalToX = stackedMilestonesSameDay ? Math.round(((predecessor.left + predecessor.right) / 2 + (successor.left + successor.right) / 2) / 2) : toX;
3431
+ const finalToX = stackedMilestonesSameDay ? Math.round((predecessor.centerX + successor.centerX) / 2) : toX;
3417
3432
  if (stackedMilestonesSameDay) {
3418
3433
  fromX = finalToX;
3419
3434
  }
@@ -7229,6 +7244,337 @@ var TaskList = ({
7229
7244
  );
7230
7245
  };
7231
7246
 
7247
+ // src/components/GanttChart/print.ts
7248
+ function getPrintDocumentTitle({ header, title, fileName }) {
7249
+ return header?.projectName || title || header?.serviceName || fileName || "Gantt chart";
7250
+ }
7251
+ function formatHeaderExportDate(exportDate) {
7252
+ if (!exportDate) return null;
7253
+ if (typeof exportDate === "string") return exportDate;
7254
+ return new Intl.DateTimeFormat(void 0, {
7255
+ year: "numeric",
7256
+ month: "2-digit",
7257
+ day: "2-digit"
7258
+ }).format(exportDate);
7259
+ }
7260
+ function createPrintHeader(targetDocument, title, header) {
7261
+ const headerElement = targetDocument.createElement("div");
7262
+ headerElement.className = "gantt-print-header";
7263
+ if (header?.logoUrl) {
7264
+ const logoParent = targetDocument.createElement("span");
7265
+ logoParent.className = "gantt-print-linkWrap";
7266
+ const logo = targetDocument.createElement("img");
7267
+ logo.className = "gantt-print-logo";
7268
+ logo.src = header.logoUrl;
7269
+ logo.alt = header.serviceName || title;
7270
+ logoParent.appendChild(logo);
7271
+ if (header.logoHref) {
7272
+ const logoLink = targetDocument.createElement("a");
7273
+ logoLink.href = header.logoHref;
7274
+ logoLink.target = "_blank";
7275
+ logoLink.rel = "noopener noreferrer";
7276
+ logoLink.className = "gantt-print-linkOverlay";
7277
+ logoLink.setAttribute("aria-label", header.serviceName || title);
7278
+ logoParent.appendChild(logoLink);
7279
+ }
7280
+ headerElement.appendChild(logoParent);
7281
+ }
7282
+ const serviceName = header?.serviceName;
7283
+ if (serviceName) {
7284
+ const service = targetDocument.createElement("span");
7285
+ service.className = header?.serviceHref ? "gantt-print-serviceName gantt-print-linkWrap" : "gantt-print-serviceName";
7286
+ if (header.serviceHref) {
7287
+ const serviceText = targetDocument.createElement("span");
7288
+ serviceText.textContent = serviceName;
7289
+ service.appendChild(serviceText);
7290
+ const serviceLink = targetDocument.createElement("a");
7291
+ serviceLink.href = header.serviceHref;
7292
+ serviceLink.target = "_blank";
7293
+ serviceLink.rel = "noopener noreferrer";
7294
+ serviceLink.className = "gantt-print-linkOverlay";
7295
+ serviceLink.setAttribute("aria-label", serviceName);
7296
+ service.appendChild(serviceLink);
7297
+ } else {
7298
+ service.textContent = serviceName;
7299
+ }
7300
+ headerElement.appendChild(service);
7301
+ const separator = targetDocument.createElement("span");
7302
+ separator.className = "gantt-print-separator";
7303
+ separator.textContent = "/";
7304
+ headerElement.appendChild(separator);
7305
+ }
7306
+ const project = targetDocument.createElement("span");
7307
+ project.className = "gantt-print-projectName";
7308
+ project.textContent = header?.projectName || title;
7309
+ headerElement.appendChild(project);
7310
+ const exportDate = formatHeaderExportDate(header?.exportDate);
7311
+ if (exportDate) {
7312
+ const date = targetDocument.createElement("span");
7313
+ date.className = "gantt-print-headerDate";
7314
+ date.textContent = exportDate;
7315
+ headerElement.appendChild(date);
7316
+ }
7317
+ return headerElement;
7318
+ }
7319
+ function copyGanttCssVariables(sourceElement, targetElement) {
7320
+ const computedStyle = window.getComputedStyle(sourceElement);
7321
+ for (const propertyName of Array.from(computedStyle)) {
7322
+ if (!propertyName.startsWith("--gantt-")) continue;
7323
+ const value = computedStyle.getPropertyValue(propertyName);
7324
+ if (value) {
7325
+ targetElement.style.setProperty(propertyName, value);
7326
+ }
7327
+ }
7328
+ }
7329
+ async function copyDocumentStyles(sourceDocument, targetDocument) {
7330
+ const headNodes = Array.from(
7331
+ sourceDocument.querySelectorAll('style, link[rel="stylesheet"]')
7332
+ );
7333
+ const pendingLoads = headNodes.map((node) => new Promise((resolve) => {
7334
+ if (node instanceof HTMLStyleElement) {
7335
+ const style = targetDocument.createElement("style");
7336
+ style.textContent = node.textContent;
7337
+ targetDocument.head.appendChild(style);
7338
+ resolve();
7339
+ return;
7340
+ }
7341
+ if (node instanceof HTMLLinkElement && node.href) {
7342
+ const link = targetDocument.createElement("link");
7343
+ link.rel = "stylesheet";
7344
+ link.href = node.href;
7345
+ if (node.media) {
7346
+ link.media = node.media;
7347
+ }
7348
+ link.onload = () => resolve();
7349
+ link.onerror = () => resolve();
7350
+ targetDocument.head.appendChild(link);
7351
+ return;
7352
+ }
7353
+ resolve();
7354
+ }));
7355
+ await Promise.all(pendingLoads);
7356
+ }
7357
+ function createPrintStyle(targetDocument, orientation) {
7358
+ const style = targetDocument.createElement("style");
7359
+ style.textContent = `
7360
+ @page {
7361
+ size: ${orientation};
7362
+ margin: 12mm;
7363
+ }
7364
+
7365
+ html, body {
7366
+ margin: 0;
7367
+ padding: 0;
7368
+ background: #ffffff;
7369
+ }
7370
+
7371
+ body {
7372
+ color: #111827;
7373
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
7374
+ }
7375
+
7376
+ .gantt-print-root,
7377
+ .gantt-print-root * {
7378
+ -webkit-print-color-adjust: exact;
7379
+ print-color-adjust: exact;
7380
+ box-sizing: border-box;
7381
+ }
7382
+
7383
+ .gantt-print-root {
7384
+ padding: 16px 20px 24px;
7385
+ width: max-content;
7386
+ min-width: 100%;
7387
+ }
7388
+
7389
+ .gantt-print-header {
7390
+ display: flex;
7391
+ align-items: center;
7392
+ gap: 18px;
7393
+ margin: 0 0 16px;
7394
+ padding-bottom: 12px;
7395
+ border-bottom: 1px solid #e5e7eb;
7396
+ white-space: nowrap;
7397
+ overflow: hidden;
7398
+ }
7399
+
7400
+ .gantt-print-logo {
7401
+ width: 32px;
7402
+ height: 32px;
7403
+ object-fit: contain;
7404
+ flex: 0 0 auto;
7405
+ }
7406
+
7407
+ .gantt-print-linkWrap {
7408
+ position: relative;
7409
+ display: inline-flex;
7410
+ align-items: center;
7411
+ flex: 0 0 auto;
7412
+ }
7413
+
7414
+ .gantt-print-linkOverlay,
7415
+ .gantt-print-linkOverlay:visited,
7416
+ .gantt-print-linkOverlay:hover,
7417
+ .gantt-print-linkOverlay:active {
7418
+ position: absolute;
7419
+ inset: 0;
7420
+ color: transparent !important;
7421
+ background: transparent !important;
7422
+ text-decoration: none !important;
7423
+ border: 0 !important;
7424
+ outline: none !important;
7425
+ font-size: 0 !important;
7426
+ line-height: 0 !important;
7427
+ }
7428
+
7429
+ .gantt-print-serviceName {
7430
+ font-size: 18px;
7431
+ font-weight: 600;
7432
+ line-height: 1.2;
7433
+ color: #111827;
7434
+ }
7435
+
7436
+ .gantt-print-separator {
7437
+ font-size: 18px;
7438
+ font-weight: 500;
7439
+ line-height: 1.2;
7440
+ color: #6b7280;
7441
+ }
7442
+
7443
+ .gantt-print-projectName {
7444
+ font-size: 18px;
7445
+ font-weight: 500;
7446
+ line-height: 1.2;
7447
+ color: #111827;
7448
+ min-width: 0;
7449
+ overflow: hidden;
7450
+ text-overflow: ellipsis;
7451
+ }
7452
+
7453
+ .gantt-print-headerDate {
7454
+ flex: 0 0 auto;
7455
+ font-size: 15px;
7456
+ font-weight: 500;
7457
+ white-space: nowrap;
7458
+ color: #6b7280;
7459
+ }
7460
+
7461
+ .gantt-print-root .gantt-container,
7462
+ .gantt-print-root .gantt-scrollContainer,
7463
+ .gantt-print-root .gantt-scrollContent,
7464
+ .gantt-print-root .gantt-chartSurface,
7465
+ .gantt-print-root .gantt-taskArea {
7466
+ overflow: visible !important;
7467
+ height: auto !important;
7468
+ max-height: none !important;
7469
+ }
7470
+
7471
+ .gantt-print-root .gantt-container {
7472
+ width: max-content;
7473
+ min-width: 100%;
7474
+ border-radius: 0;
7475
+ }
7476
+
7477
+ .gantt-print-root .gantt-scrollContainer {
7478
+ cursor: default !important;
7479
+ }
7480
+
7481
+ .gantt-print-root .gantt-stickyHeader,
7482
+ .gantt-print-root .gantt-tl-header {
7483
+ position: static !important;
7484
+ top: auto !important;
7485
+ }
7486
+
7487
+ .gantt-print-root .gantt-tl-overlay {
7488
+ left: auto !important;
7489
+ }
7490
+
7491
+ .gantt-print-root .gantt-tl-overlay-shadowed,
7492
+ .gantt-print-root .gantt-tl-row:hover,
7493
+ .gantt-print-root .gantt-tr-row:hover,
7494
+ .gantt-print-root .gantt-tr-taskBar:hover {
7495
+ box-shadow: none !important;
7496
+ }
7497
+
7498
+ .gantt-print-root .gantt-tl-drag-handle,
7499
+ .gantt-print-root .gantt-tl-add-btn,
7500
+ .gantt-print-root .gantt-tl-name-actions,
7501
+ .gantt-print-root .gantt-tl-context-menu,
7502
+ .gantt-print-root .gantt-tl-dep-add,
7503
+ .gantt-print-root .gantt-tl-dep-delete-label,
7504
+ .gantt-print-root .gantt-tl-dep-source-picker,
7505
+ .gantt-print-root .gantt-tl-dep-type-menu,
7506
+ .gantt-print-root .gantt-tl-dep-type-trigger,
7507
+ .gantt-print-root .gantt-tl-number-steppers,
7508
+ .gantt-print-root .gantt-tr-resizeHandle {
7509
+ display: none !important;
7510
+ }
7511
+ `;
7512
+ targetDocument.head.appendChild(style);
7513
+ }
7514
+ function waitForNextPaint(printWindow) {
7515
+ return new Promise((resolve) => {
7516
+ if (typeof printWindow.requestAnimationFrame === "function") {
7517
+ printWindow.requestAnimationFrame(() => {
7518
+ printWindow.requestAnimationFrame(() => resolve());
7519
+ });
7520
+ return;
7521
+ }
7522
+ printWindow.setTimeout(() => resolve(), 50);
7523
+ });
7524
+ }
7525
+ function createPrintFrame(sourceDocument) {
7526
+ const iframe = sourceDocument.createElement("iframe");
7527
+ iframe.setAttribute("aria-hidden", "true");
7528
+ iframe.style.position = "fixed";
7529
+ iframe.style.right = "0";
7530
+ iframe.style.bottom = "0";
7531
+ iframe.style.width = "0";
7532
+ iframe.style.height = "0";
7533
+ iframe.style.border = "0";
7534
+ iframe.style.opacity = "0";
7535
+ iframe.style.pointerEvents = "none";
7536
+ sourceDocument.body.appendChild(iframe);
7537
+ return iframe;
7538
+ }
7539
+ async function printGanttChart({
7540
+ sourceDocument,
7541
+ sourceContainer,
7542
+ printContent,
7543
+ header,
7544
+ title,
7545
+ fileName,
7546
+ orientation
7547
+ }) {
7548
+ const iframe = createPrintFrame(sourceDocument);
7549
+ try {
7550
+ const printWindow = iframe.contentWindow;
7551
+ const printDocument = iframe.contentDocument;
7552
+ if (!printWindow || !printDocument) {
7553
+ throw new Error("Unable to create print frame");
7554
+ }
7555
+ const printTitle = getPrintDocumentTitle({ header, title, fileName });
7556
+ printDocument.open();
7557
+ printDocument.write('<!doctype html><html><head><meta charset="utf-8" /></head><body></body></html>');
7558
+ printDocument.close();
7559
+ printDocument.title = printTitle;
7560
+ await copyDocumentStyles(sourceDocument, printDocument);
7561
+ createPrintStyle(printDocument, orientation);
7562
+ const root = printDocument.createElement("div");
7563
+ root.className = "gantt-print-root";
7564
+ copyGanttCssVariables(sourceContainer, root);
7565
+ root.appendChild(createPrintHeader(printDocument, printTitle, header));
7566
+ root.appendChild(printContent);
7567
+ printDocument.body.appendChild(root);
7568
+ await waitForNextPaint(printWindow);
7569
+ printWindow.focus();
7570
+ printWindow.print();
7571
+ } finally {
7572
+ window.setTimeout(() => {
7573
+ iframe.remove();
7574
+ }, 1e3);
7575
+ }
7576
+ }
7577
+
7232
7578
  // src/components/GanttChart/GanttChart.tsx
7233
7579
  var import_jsx_runtime15 = require("react/jsx-runtime");
7234
7580
  var SCROLL_TO_ROW_CONTEXT_ROWS = 2;
@@ -7269,7 +7615,9 @@ function GanttChartInner(props, ref) {
7269
7615
  showChart = true,
7270
7616
  additionalColumns
7271
7617
  } = props;
7618
+ const containerRef = (0, import_react13.useRef)(null);
7272
7619
  const scrollContainerRef = (0, import_react13.useRef)(null);
7620
+ const scrollContentRef = (0, import_react13.useRef)(null);
7273
7621
  const [selectedTaskId, setSelectedTaskId] = (0, import_react13.useState)(null);
7274
7622
  const [taskListHasRightShadow, setTaskListHasRightShadow] = (0, import_react13.useState)(false);
7275
7623
  const [selectedChip, setSelectedChip] = (0, import_react13.useState)(null);
@@ -7551,6 +7899,40 @@ function GanttChartInner(props, ref) {
7551
7899
  if (externalCollapsedParentIds) return;
7552
7900
  setInternalCollapsedParentIds(/* @__PURE__ */ new Set());
7553
7901
  }, [externalCollapsedParentIds]);
7902
+ const exportToPdf = (0, import_react13.useCallback)(async (options) => {
7903
+ const sourceContainer = containerRef.current;
7904
+ const sourceContent = scrollContentRef.current;
7905
+ if (!sourceContainer || !sourceContent || typeof window === "undefined" || typeof document === "undefined") {
7906
+ return;
7907
+ }
7908
+ const includeTaskList = options?.includeTaskList ?? showTaskList;
7909
+ const includeChart = options?.includeChart ?? showChart;
7910
+ if (!includeTaskList && !includeChart) {
7911
+ return;
7912
+ }
7913
+ const printContent = sourceContent.cloneNode(true);
7914
+ const taskListClone = printContent.querySelector(".gantt-tl-overlay");
7915
+ const chartClone = printContent.querySelector(".gantt-chartSurface");
7916
+ if (includeTaskList) {
7917
+ taskListClone?.classList.remove("gantt-tl-hidden", "gantt-tl-overlay-shadowed");
7918
+ } else {
7919
+ taskListClone?.remove();
7920
+ }
7921
+ if (includeChart) {
7922
+ chartClone?.classList.remove("gantt-chart-hidden");
7923
+ } else {
7924
+ chartClone?.remove();
7925
+ }
7926
+ await printGanttChart({
7927
+ sourceDocument: document,
7928
+ sourceContainer,
7929
+ printContent,
7930
+ header: options?.header,
7931
+ title: options?.title,
7932
+ fileName: options?.fileName,
7933
+ orientation: options?.orientation ?? "landscape"
7934
+ });
7935
+ }, [showTaskList, showChart]);
7554
7936
  (0, import_react13.useImperativeHandle)(
7555
7937
  ref,
7556
7938
  () => ({
@@ -7558,9 +7940,10 @@ function GanttChartInner(props, ref) {
7558
7940
  scrollToTask,
7559
7941
  scrollToRow,
7560
7942
  collapseAll: handleCollapseAll,
7561
- expandAll: handleExpandAll
7943
+ expandAll: handleExpandAll,
7944
+ exportToPdf
7562
7945
  }),
7563
- [scrollToToday, scrollToTask, scrollToRow, handleCollapseAll, handleExpandAll]
7946
+ [scrollToToday, scrollToTask, scrollToRow, handleCollapseAll, handleExpandAll, exportToPdf]
7564
7947
  );
7565
7948
  function getTaskDepth(taskId, tasks2) {
7566
7949
  let depth = 0;
@@ -7688,14 +8071,14 @@ function GanttChartInner(props, ref) {
7688
8071
  window.removeEventListener("mouseup", handlePanEnd);
7689
8072
  };
7690
8073
  }, []);
7691
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "gantt-container", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8074
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { ref: containerRef, className: "gantt-container", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
7692
8075
  "div",
7693
8076
  {
7694
8077
  ref: scrollContainerRef,
7695
8078
  className: "gantt-scrollContainer",
7696
8079
  style: { height: containerHeight ?? "auto", cursor: "grab" },
7697
8080
  onMouseDown: handlePanStart,
7698
- children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "gantt-scrollContent", children: [
8081
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { ref: scrollContentRef, className: "gantt-scrollContent", children: [
7699
8082
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
7700
8083
  TaskList,
7701
8084
  {
@@ -7732,7 +8115,7 @@ function GanttChartInner(props, ref) {
7732
8115
  additionalColumns
7733
8116
  }
7734
8117
  ),
7735
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: showChart ? "" : "gantt-chart-hidden", style: { minWidth: `${gridWidth}px`, flex: 1 }, children: [
8118
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: showChart ? "gantt-chartSurface" : "gantt-chartSurface gantt-chart-hidden", style: { minWidth: `${gridWidth}px`, flex: 1 }, children: [
7736
8119
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "gantt-stickyHeader", style: { width: `${gridWidth}px` }, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
7737
8120
  TimeScaleHeader_default,
7738
8121
  {