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.mjs CHANGED
@@ -1862,10 +1862,10 @@ var calculateMilestoneGeometry = (taskDate, monthStart, dayWidth, size = 14) =>
1862
1862
  };
1863
1863
  var calculateMilestoneConnectionBounds = (dayLeft, dayWidth, size = 14) => {
1864
1864
  const halfDiagonal = Math.round(size / Math.SQRT2);
1865
- const visualNudge = 2;
1865
+ const centerX = Math.round(dayLeft + dayWidth / 2);
1866
1866
  return {
1867
- left: dayLeft + halfDiagonal + visualNudge,
1868
- right: dayLeft + dayWidth - halfDiagonal - visualNudge
1867
+ left: centerX - halfDiagonal,
1868
+ right: centerX + halfDiagonal
1869
1869
  };
1870
1870
  };
1871
1871
  var resolveTaskHorizontalGeometry = (task, monthStart, dayWidth, override) => {
@@ -1874,19 +1874,32 @@ var resolveTaskHorizontalGeometry = (task, monthStart, dayWidth, override) => {
1874
1874
  if (task.type === "milestone") {
1875
1875
  const size = 14;
1876
1876
  if (override) {
1877
- return calculateMilestoneConnectionBounds(override.left, dayWidth, size);
1877
+ const bounds2 = calculateMilestoneConnectionBounds(override.left, dayWidth, size);
1878
+ return {
1879
+ ...bounds2,
1880
+ centerX: Math.round(override.left + dayWidth / 2)
1881
+ };
1878
1882
  }
1879
1883
  const bar2 = calculateTaskBar(startDate, startDate, monthStart, dayWidth);
1880
- return calculateMilestoneConnectionBounds(bar2.left, dayWidth, size);
1884
+ const bounds = calculateMilestoneConnectionBounds(bar2.left, dayWidth, size);
1885
+ return {
1886
+ ...bounds,
1887
+ centerX: Math.round(bar2.left + dayWidth / 2)
1888
+ };
1881
1889
  }
1882
1890
  if (override) {
1883
1891
  return {
1884
1892
  left: override.left,
1885
- right: override.left + override.width
1893
+ right: override.left + override.width,
1894
+ centerX: Math.round(override.left + override.width / 2)
1886
1895
  };
1887
1896
  }
1888
1897
  const bar = calculateTaskBar(startDate, endDate, monthStart, dayWidth);
1889
- return { left: bar.left, right: bar.left + bar.width };
1898
+ return {
1899
+ left: bar.left,
1900
+ right: bar.left + bar.width,
1901
+ centerX: Math.round(bar.left + bar.width / 2)
1902
+ };
1890
1903
  };
1891
1904
  var pixelsToDate = (pixels, monthStart, dayWidth) => {
1892
1905
  const days = Math.round(pixels / dayWidth);
@@ -3221,6 +3234,7 @@ var DependencyLines = React5.memo(({
3221
3234
  positions.set(task.id, {
3222
3235
  left: computed.left,
3223
3236
  right: computed.right,
3237
+ centerX: computed.centerX,
3224
3238
  rowTop: index * rowHeight,
3225
3239
  isVirtual: false
3226
3240
  });
@@ -3239,6 +3253,7 @@ var DependencyLines = React5.memo(({
3239
3253
  positions.set(task.id, {
3240
3254
  left: computed.left,
3241
3255
  right: computed.right,
3256
+ centerX: computed.centerX,
3242
3257
  rowTop: ancestorPosition.rowTop,
3243
3258
  isVirtual: true
3244
3259
  });
@@ -3289,12 +3304,12 @@ var DependencyLines = React5.memo(({
3289
3304
  fromY = predecessor.rowTop + rowHeight - 10;
3290
3305
  toY = successor.rowTop + 6;
3291
3306
  }
3292
- let fromX = edge.type === "SS" || edge.type === "SF" ? predecessor.left : predecessor.right;
3293
- const toX = edge.type === "FF" || edge.type === "SF" ? successor.right : successor.left;
3307
+ let fromX = predecessorTask && isMilestoneTask(predecessorTask) ? predecessor.centerX : edge.type === "SS" || edge.type === "SF" ? predecessor.left : predecessor.right;
3308
+ const toX = successorTask && isMilestoneTask(successorTask) ? successor.centerX : edge.type === "FF" || edge.type === "SF" ? successor.right : successor.left;
3294
3309
  const stackedMilestonesSameDay = Boolean(
3295
3310
  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"
3296
3311
  );
3297
- const finalToX = stackedMilestonesSameDay ? Math.round(((predecessor.left + predecessor.right) / 2 + (successor.left + successor.right) / 2) / 2) : toX;
3312
+ const finalToX = stackedMilestonesSameDay ? Math.round((predecessor.centerX + successor.centerX) / 2) : toX;
3298
3313
  if (stackedMilestonesSameDay) {
3299
3314
  fromX = finalToX;
3300
3315
  }
@@ -7134,6 +7149,337 @@ var TaskList = ({
7134
7149
  );
7135
7150
  };
7136
7151
 
7152
+ // src/components/GanttChart/print.ts
7153
+ function getPrintDocumentTitle({ header, title, fileName }) {
7154
+ return header?.projectName || title || header?.serviceName || fileName || "Gantt chart";
7155
+ }
7156
+ function formatHeaderExportDate(exportDate) {
7157
+ if (!exportDate) return null;
7158
+ if (typeof exportDate === "string") return exportDate;
7159
+ return new Intl.DateTimeFormat(void 0, {
7160
+ year: "numeric",
7161
+ month: "2-digit",
7162
+ day: "2-digit"
7163
+ }).format(exportDate);
7164
+ }
7165
+ function createPrintHeader(targetDocument, title, header) {
7166
+ const headerElement = targetDocument.createElement("div");
7167
+ headerElement.className = "gantt-print-header";
7168
+ if (header?.logoUrl) {
7169
+ const logoParent = targetDocument.createElement("span");
7170
+ logoParent.className = "gantt-print-linkWrap";
7171
+ const logo = targetDocument.createElement("img");
7172
+ logo.className = "gantt-print-logo";
7173
+ logo.src = header.logoUrl;
7174
+ logo.alt = header.serviceName || title;
7175
+ logoParent.appendChild(logo);
7176
+ if (header.logoHref) {
7177
+ const logoLink = targetDocument.createElement("a");
7178
+ logoLink.href = header.logoHref;
7179
+ logoLink.target = "_blank";
7180
+ logoLink.rel = "noopener noreferrer";
7181
+ logoLink.className = "gantt-print-linkOverlay";
7182
+ logoLink.setAttribute("aria-label", header.serviceName || title);
7183
+ logoParent.appendChild(logoLink);
7184
+ }
7185
+ headerElement.appendChild(logoParent);
7186
+ }
7187
+ const serviceName = header?.serviceName;
7188
+ if (serviceName) {
7189
+ const service = targetDocument.createElement("span");
7190
+ service.className = header?.serviceHref ? "gantt-print-serviceName gantt-print-linkWrap" : "gantt-print-serviceName";
7191
+ if (header.serviceHref) {
7192
+ const serviceText = targetDocument.createElement("span");
7193
+ serviceText.textContent = serviceName;
7194
+ service.appendChild(serviceText);
7195
+ const serviceLink = targetDocument.createElement("a");
7196
+ serviceLink.href = header.serviceHref;
7197
+ serviceLink.target = "_blank";
7198
+ serviceLink.rel = "noopener noreferrer";
7199
+ serviceLink.className = "gantt-print-linkOverlay";
7200
+ serviceLink.setAttribute("aria-label", serviceName);
7201
+ service.appendChild(serviceLink);
7202
+ } else {
7203
+ service.textContent = serviceName;
7204
+ }
7205
+ headerElement.appendChild(service);
7206
+ const separator = targetDocument.createElement("span");
7207
+ separator.className = "gantt-print-separator";
7208
+ separator.textContent = "/";
7209
+ headerElement.appendChild(separator);
7210
+ }
7211
+ const project = targetDocument.createElement("span");
7212
+ project.className = "gantt-print-projectName";
7213
+ project.textContent = header?.projectName || title;
7214
+ headerElement.appendChild(project);
7215
+ const exportDate = formatHeaderExportDate(header?.exportDate);
7216
+ if (exportDate) {
7217
+ const date = targetDocument.createElement("span");
7218
+ date.className = "gantt-print-headerDate";
7219
+ date.textContent = exportDate;
7220
+ headerElement.appendChild(date);
7221
+ }
7222
+ return headerElement;
7223
+ }
7224
+ function copyGanttCssVariables(sourceElement, targetElement) {
7225
+ const computedStyle = window.getComputedStyle(sourceElement);
7226
+ for (const propertyName of Array.from(computedStyle)) {
7227
+ if (!propertyName.startsWith("--gantt-")) continue;
7228
+ const value = computedStyle.getPropertyValue(propertyName);
7229
+ if (value) {
7230
+ targetElement.style.setProperty(propertyName, value);
7231
+ }
7232
+ }
7233
+ }
7234
+ async function copyDocumentStyles(sourceDocument, targetDocument) {
7235
+ const headNodes = Array.from(
7236
+ sourceDocument.querySelectorAll('style, link[rel="stylesheet"]')
7237
+ );
7238
+ const pendingLoads = headNodes.map((node) => new Promise((resolve) => {
7239
+ if (node instanceof HTMLStyleElement) {
7240
+ const style = targetDocument.createElement("style");
7241
+ style.textContent = node.textContent;
7242
+ targetDocument.head.appendChild(style);
7243
+ resolve();
7244
+ return;
7245
+ }
7246
+ if (node instanceof HTMLLinkElement && node.href) {
7247
+ const link = targetDocument.createElement("link");
7248
+ link.rel = "stylesheet";
7249
+ link.href = node.href;
7250
+ if (node.media) {
7251
+ link.media = node.media;
7252
+ }
7253
+ link.onload = () => resolve();
7254
+ link.onerror = () => resolve();
7255
+ targetDocument.head.appendChild(link);
7256
+ return;
7257
+ }
7258
+ resolve();
7259
+ }));
7260
+ await Promise.all(pendingLoads);
7261
+ }
7262
+ function createPrintStyle(targetDocument, orientation) {
7263
+ const style = targetDocument.createElement("style");
7264
+ style.textContent = `
7265
+ @page {
7266
+ size: ${orientation};
7267
+ margin: 12mm;
7268
+ }
7269
+
7270
+ html, body {
7271
+ margin: 0;
7272
+ padding: 0;
7273
+ background: #ffffff;
7274
+ }
7275
+
7276
+ body {
7277
+ color: #111827;
7278
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
7279
+ }
7280
+
7281
+ .gantt-print-root,
7282
+ .gantt-print-root * {
7283
+ -webkit-print-color-adjust: exact;
7284
+ print-color-adjust: exact;
7285
+ box-sizing: border-box;
7286
+ }
7287
+
7288
+ .gantt-print-root {
7289
+ padding: 16px 20px 24px;
7290
+ width: max-content;
7291
+ min-width: 100%;
7292
+ }
7293
+
7294
+ .gantt-print-header {
7295
+ display: flex;
7296
+ align-items: center;
7297
+ gap: 18px;
7298
+ margin: 0 0 16px;
7299
+ padding-bottom: 12px;
7300
+ border-bottom: 1px solid #e5e7eb;
7301
+ white-space: nowrap;
7302
+ overflow: hidden;
7303
+ }
7304
+
7305
+ .gantt-print-logo {
7306
+ width: 32px;
7307
+ height: 32px;
7308
+ object-fit: contain;
7309
+ flex: 0 0 auto;
7310
+ }
7311
+
7312
+ .gantt-print-linkWrap {
7313
+ position: relative;
7314
+ display: inline-flex;
7315
+ align-items: center;
7316
+ flex: 0 0 auto;
7317
+ }
7318
+
7319
+ .gantt-print-linkOverlay,
7320
+ .gantt-print-linkOverlay:visited,
7321
+ .gantt-print-linkOverlay:hover,
7322
+ .gantt-print-linkOverlay:active {
7323
+ position: absolute;
7324
+ inset: 0;
7325
+ color: transparent !important;
7326
+ background: transparent !important;
7327
+ text-decoration: none !important;
7328
+ border: 0 !important;
7329
+ outline: none !important;
7330
+ font-size: 0 !important;
7331
+ line-height: 0 !important;
7332
+ }
7333
+
7334
+ .gantt-print-serviceName {
7335
+ font-size: 18px;
7336
+ font-weight: 600;
7337
+ line-height: 1.2;
7338
+ color: #111827;
7339
+ }
7340
+
7341
+ .gantt-print-separator {
7342
+ font-size: 18px;
7343
+ font-weight: 500;
7344
+ line-height: 1.2;
7345
+ color: #6b7280;
7346
+ }
7347
+
7348
+ .gantt-print-projectName {
7349
+ font-size: 18px;
7350
+ font-weight: 500;
7351
+ line-height: 1.2;
7352
+ color: #111827;
7353
+ min-width: 0;
7354
+ overflow: hidden;
7355
+ text-overflow: ellipsis;
7356
+ }
7357
+
7358
+ .gantt-print-headerDate {
7359
+ flex: 0 0 auto;
7360
+ font-size: 15px;
7361
+ font-weight: 500;
7362
+ white-space: nowrap;
7363
+ color: #6b7280;
7364
+ }
7365
+
7366
+ .gantt-print-root .gantt-container,
7367
+ .gantt-print-root .gantt-scrollContainer,
7368
+ .gantt-print-root .gantt-scrollContent,
7369
+ .gantt-print-root .gantt-chartSurface,
7370
+ .gantt-print-root .gantt-taskArea {
7371
+ overflow: visible !important;
7372
+ height: auto !important;
7373
+ max-height: none !important;
7374
+ }
7375
+
7376
+ .gantt-print-root .gantt-container {
7377
+ width: max-content;
7378
+ min-width: 100%;
7379
+ border-radius: 0;
7380
+ }
7381
+
7382
+ .gantt-print-root .gantt-scrollContainer {
7383
+ cursor: default !important;
7384
+ }
7385
+
7386
+ .gantt-print-root .gantt-stickyHeader,
7387
+ .gantt-print-root .gantt-tl-header {
7388
+ position: static !important;
7389
+ top: auto !important;
7390
+ }
7391
+
7392
+ .gantt-print-root .gantt-tl-overlay {
7393
+ left: auto !important;
7394
+ }
7395
+
7396
+ .gantt-print-root .gantt-tl-overlay-shadowed,
7397
+ .gantt-print-root .gantt-tl-row:hover,
7398
+ .gantt-print-root .gantt-tr-row:hover,
7399
+ .gantt-print-root .gantt-tr-taskBar:hover {
7400
+ box-shadow: none !important;
7401
+ }
7402
+
7403
+ .gantt-print-root .gantt-tl-drag-handle,
7404
+ .gantt-print-root .gantt-tl-add-btn,
7405
+ .gantt-print-root .gantt-tl-name-actions,
7406
+ .gantt-print-root .gantt-tl-context-menu,
7407
+ .gantt-print-root .gantt-tl-dep-add,
7408
+ .gantt-print-root .gantt-tl-dep-delete-label,
7409
+ .gantt-print-root .gantt-tl-dep-source-picker,
7410
+ .gantt-print-root .gantt-tl-dep-type-menu,
7411
+ .gantt-print-root .gantt-tl-dep-type-trigger,
7412
+ .gantt-print-root .gantt-tl-number-steppers,
7413
+ .gantt-print-root .gantt-tr-resizeHandle {
7414
+ display: none !important;
7415
+ }
7416
+ `;
7417
+ targetDocument.head.appendChild(style);
7418
+ }
7419
+ function waitForNextPaint(printWindow) {
7420
+ return new Promise((resolve) => {
7421
+ if (typeof printWindow.requestAnimationFrame === "function") {
7422
+ printWindow.requestAnimationFrame(() => {
7423
+ printWindow.requestAnimationFrame(() => resolve());
7424
+ });
7425
+ return;
7426
+ }
7427
+ printWindow.setTimeout(() => resolve(), 50);
7428
+ });
7429
+ }
7430
+ function createPrintFrame(sourceDocument) {
7431
+ const iframe = sourceDocument.createElement("iframe");
7432
+ iframe.setAttribute("aria-hidden", "true");
7433
+ iframe.style.position = "fixed";
7434
+ iframe.style.right = "0";
7435
+ iframe.style.bottom = "0";
7436
+ iframe.style.width = "0";
7437
+ iframe.style.height = "0";
7438
+ iframe.style.border = "0";
7439
+ iframe.style.opacity = "0";
7440
+ iframe.style.pointerEvents = "none";
7441
+ sourceDocument.body.appendChild(iframe);
7442
+ return iframe;
7443
+ }
7444
+ async function printGanttChart({
7445
+ sourceDocument,
7446
+ sourceContainer,
7447
+ printContent,
7448
+ header,
7449
+ title,
7450
+ fileName,
7451
+ orientation
7452
+ }) {
7453
+ const iframe = createPrintFrame(sourceDocument);
7454
+ try {
7455
+ const printWindow = iframe.contentWindow;
7456
+ const printDocument = iframe.contentDocument;
7457
+ if (!printWindow || !printDocument) {
7458
+ throw new Error("Unable to create print frame");
7459
+ }
7460
+ const printTitle = getPrintDocumentTitle({ header, title, fileName });
7461
+ printDocument.open();
7462
+ printDocument.write('<!doctype html><html><head><meta charset="utf-8" /></head><body></body></html>');
7463
+ printDocument.close();
7464
+ printDocument.title = printTitle;
7465
+ await copyDocumentStyles(sourceDocument, printDocument);
7466
+ createPrintStyle(printDocument, orientation);
7467
+ const root = printDocument.createElement("div");
7468
+ root.className = "gantt-print-root";
7469
+ copyGanttCssVariables(sourceContainer, root);
7470
+ root.appendChild(createPrintHeader(printDocument, printTitle, header));
7471
+ root.appendChild(printContent);
7472
+ printDocument.body.appendChild(root);
7473
+ await waitForNextPaint(printWindow);
7474
+ printWindow.focus();
7475
+ printWindow.print();
7476
+ } finally {
7477
+ window.setTimeout(() => {
7478
+ iframe.remove();
7479
+ }, 1e3);
7480
+ }
7481
+ }
7482
+
7137
7483
  // src/components/GanttChart/GanttChart.tsx
7138
7484
  import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
7139
7485
  var SCROLL_TO_ROW_CONTEXT_ROWS = 2;
@@ -7174,7 +7520,9 @@ function GanttChartInner(props, ref) {
7174
7520
  showChart = true,
7175
7521
  additionalColumns
7176
7522
  } = props;
7523
+ const containerRef = useRef7(null);
7177
7524
  const scrollContainerRef = useRef7(null);
7525
+ const scrollContentRef = useRef7(null);
7178
7526
  const [selectedTaskId, setSelectedTaskId] = useState7(null);
7179
7527
  const [taskListHasRightShadow, setTaskListHasRightShadow] = useState7(false);
7180
7528
  const [selectedChip, setSelectedChip] = useState7(null);
@@ -7456,6 +7804,40 @@ function GanttChartInner(props, ref) {
7456
7804
  if (externalCollapsedParentIds) return;
7457
7805
  setInternalCollapsedParentIds(/* @__PURE__ */ new Set());
7458
7806
  }, [externalCollapsedParentIds]);
7807
+ const exportToPdf = useCallback6(async (options) => {
7808
+ const sourceContainer = containerRef.current;
7809
+ const sourceContent = scrollContentRef.current;
7810
+ if (!sourceContainer || !sourceContent || typeof window === "undefined" || typeof document === "undefined") {
7811
+ return;
7812
+ }
7813
+ const includeTaskList = options?.includeTaskList ?? showTaskList;
7814
+ const includeChart = options?.includeChart ?? showChart;
7815
+ if (!includeTaskList && !includeChart) {
7816
+ return;
7817
+ }
7818
+ const printContent = sourceContent.cloneNode(true);
7819
+ const taskListClone = printContent.querySelector(".gantt-tl-overlay");
7820
+ const chartClone = printContent.querySelector(".gantt-chartSurface");
7821
+ if (includeTaskList) {
7822
+ taskListClone?.classList.remove("gantt-tl-hidden", "gantt-tl-overlay-shadowed");
7823
+ } else {
7824
+ taskListClone?.remove();
7825
+ }
7826
+ if (includeChart) {
7827
+ chartClone?.classList.remove("gantt-chart-hidden");
7828
+ } else {
7829
+ chartClone?.remove();
7830
+ }
7831
+ await printGanttChart({
7832
+ sourceDocument: document,
7833
+ sourceContainer,
7834
+ printContent,
7835
+ header: options?.header,
7836
+ title: options?.title,
7837
+ fileName: options?.fileName,
7838
+ orientation: options?.orientation ?? "landscape"
7839
+ });
7840
+ }, [showTaskList, showChart]);
7459
7841
  useImperativeHandle(
7460
7842
  ref,
7461
7843
  () => ({
@@ -7463,9 +7845,10 @@ function GanttChartInner(props, ref) {
7463
7845
  scrollToTask,
7464
7846
  scrollToRow,
7465
7847
  collapseAll: handleCollapseAll,
7466
- expandAll: handleExpandAll
7848
+ expandAll: handleExpandAll,
7849
+ exportToPdf
7467
7850
  }),
7468
- [scrollToToday, scrollToTask, scrollToRow, handleCollapseAll, handleExpandAll]
7851
+ [scrollToToday, scrollToTask, scrollToRow, handleCollapseAll, handleExpandAll, exportToPdf]
7469
7852
  );
7470
7853
  function getTaskDepth(taskId, tasks2) {
7471
7854
  let depth = 0;
@@ -7593,14 +7976,14 @@ function GanttChartInner(props, ref) {
7593
7976
  window.removeEventListener("mouseup", handlePanEnd);
7594
7977
  };
7595
7978
  }, []);
7596
- return /* @__PURE__ */ jsx15("div", { className: "gantt-container", children: /* @__PURE__ */ jsx15(
7979
+ return /* @__PURE__ */ jsx15("div", { ref: containerRef, className: "gantt-container", children: /* @__PURE__ */ jsx15(
7597
7980
  "div",
7598
7981
  {
7599
7982
  ref: scrollContainerRef,
7600
7983
  className: "gantt-scrollContainer",
7601
7984
  style: { height: containerHeight ?? "auto", cursor: "grab" },
7602
7985
  onMouseDown: handlePanStart,
7603
- children: /* @__PURE__ */ jsxs12("div", { className: "gantt-scrollContent", children: [
7986
+ children: /* @__PURE__ */ jsxs12("div", { ref: scrollContentRef, className: "gantt-scrollContent", children: [
7604
7987
  /* @__PURE__ */ jsx15(
7605
7988
  TaskList,
7606
7989
  {
@@ -7637,7 +8020,7 @@ function GanttChartInner(props, ref) {
7637
8020
  additionalColumns
7638
8021
  }
7639
8022
  ),
7640
- /* @__PURE__ */ jsxs12("div", { className: showChart ? "" : "gantt-chart-hidden", style: { minWidth: `${gridWidth}px`, flex: 1 }, children: [
8023
+ /* @__PURE__ */ jsxs12("div", { className: showChart ? "gantt-chartSurface" : "gantt-chartSurface gantt-chart-hidden", style: { minWidth: `${gridWidth}px`, flex: 1 }, children: [
7641
8024
  /* @__PURE__ */ jsx15("div", { className: "gantt-stickyHeader", style: { width: `${gridWidth}px` }, children: /* @__PURE__ */ jsx15(
7642
8025
  TimeScaleHeader_default,
7643
8026
  {