apexgantt 3.2.0 → 3.4.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/README.md CHANGED
@@ -357,14 +357,18 @@ ganttInstance.zoomOut();
357
357
 
358
358
  ## Events
359
359
 
360
- ApexGantt emits CustomEvents on the container element when tasks are updated through the dialog form.
361
-
362
- | Event | When | Detail |
363
- | --------------------- | ----------------------------- | --------------------------------------------- |
364
- | `taskUpdate` | Task is being updated | `{ taskId, updates, updatedTask, timestamp }` |
365
- | `taskUpdateSuccess` | Update completed successfully | `{ taskId, updatedTask, timestamp }` |
366
- | `taskValidationError` | Form validation failed | `{ taskId, errors, timestamp }` |
367
- | `taskUpdateError` | Update failed | `{ taskId, error, timestamp }` |
360
+ ApexGantt emits CustomEvents on the container element for various user interactions, allowing you to track and respond to changes in real-time.
361
+
362
+ ### Available Events
363
+
364
+ | Event | When | Detail |
365
+ | --- | --- | --- |
366
+ | `taskUpdate` | Task is being updated | `{ taskId, updates, updatedTask, timestamp }` |
367
+ | `taskUpdateSuccess` | Update completed successfully | `{ taskId, updatedTask, timestamp }` |
368
+ | `taskValidationError` | Form validation failed | `{ taskId, errors, timestamp }` |
369
+ | `taskUpdateError` | Update failed | `{ taskId, error, timestamp }` |
370
+ | `taskDragged` | Task bar is dragged | `{ taskId, oldStartTime, oldEndTime, newStartTime, newEndTime, daysMoved, affectedChildTasks, timestamp }` |
371
+ | `taskResized` | Task bar is resized | `{ taskId, resizeHandle, oldStartTime, oldEndTime, newStartTime, newEndTime, durationChange, timestamp }` |
368
372
 
369
373
  ### Events Usage
370
374
 
@@ -12929,6 +12929,32 @@ function getDefaultOptions(theme) {
12929
12929
  };
12930
12930
  }
12931
12931
  const DefaultOptions$1 = getDefaultOptions("light");
12932
+ const GanttEvents = {
12933
+ /**
12934
+ * emits when a task is being updated (before completion)
12935
+ */
12936
+ TASK_UPDATE: "taskUpdate",
12937
+ /**
12938
+ * emits when form validation fails
12939
+ */
12940
+ TASK_VALIDATION_ERROR: "taskValidationError",
12941
+ /**
12942
+ * emits after a task update completes successfully
12943
+ */
12944
+ TASK_UPDATE_SUCCESS: "taskUpdateSuccess",
12945
+ /**
12946
+ * emits when a task update fails
12947
+ */
12948
+ TASK_UPDATE_ERROR: "taskUpdateError",
12949
+ /**
12950
+ * emits when a task bar is dragged to a new position
12951
+ */
12952
+ TASK_DRAGGED: "taskDragged",
12953
+ /**
12954
+ * emits when a task bar is resized via handles
12955
+ */
12956
+ TASK_RESIZED: "taskResized"
12957
+ };
12932
12958
  class BarDragManager {
12933
12959
  constructor(taskId, options, viewMode, chartContext, dataManager) {
12934
12960
  this.taskId = taskId;
@@ -12940,6 +12966,8 @@ class BarDragManager {
12940
12966
  daysPerPixel: 0,
12941
12967
  initialLeft: 0,
12942
12968
  initialPosition: 0,
12969
+ initialStartTime: "",
12970
+ initialEndTime: "",
12943
12971
  isDragging: false,
12944
12972
  parentWidth: 0,
12945
12973
  parentX1: 0,
@@ -12970,6 +12998,8 @@ class BarDragManager {
12970
12998
  ...this.dragState,
12971
12999
  initialLeft: parseInt(barElement.style.left) || 0,
12972
13000
  initialPosition: e.clientX,
13001
+ initialStartTime: this.task.startTime,
13002
+ initialEndTime: this.task.endTime,
12973
13003
  isDragging: true,
12974
13004
  startX: e.clientX
12975
13005
  };
@@ -13016,10 +13046,47 @@ class BarDragManager {
13016
13046
  barElement.classList.remove("dragging");
13017
13047
  const currentWidth = parseInt(barElement.style.width) || 0;
13018
13048
  const { calculatedLeft, daysMoved } = this.calculateFinalPosition(e, currentWidth);
13049
+ if (daysMoved === 0) {
13050
+ this.dragState = {
13051
+ ...this.dragState,
13052
+ isDragging: false
13053
+ };
13054
+ return;
13055
+ }
13056
+ const newStart = dayjs(this.task.startTime).add(daysMoved, "day").format(this.options.inputDateFormat);
13057
+ const newEnd = dayjs(this.task.endTime).add(daysMoved, "day").format(this.options.inputDateFormat);
13058
+ const affectedChildTasks = this.dragState.childTasks.map((childTask) => ({
13059
+ taskId: childTask.id,
13060
+ newStartTime: dayjs(childTask.startTime).add(daysMoved, "day").format(this.options.inputDateFormat),
13061
+ newEndTime: childTask.endTime ? dayjs(childTask.endTime).add(daysMoved, "day").format(this.options.inputDateFormat) : null
13062
+ })).filter((child) => child.newEndTime !== null);
13063
+ this.emitTaskDraggedEvent(daysMoved, newStart, newEnd, affectedChildTasks);
13019
13064
  this.updateTaskPosition(barElement, calculatedLeft, daysMoved, onUpdate);
13020
13065
  this.updateChildrenPositions(calculatedLeft, daysMoved, onUpdate);
13021
13066
  };
13022
13067
  }
13068
+ emitTaskDraggedEvent(daysMoved, newStartTime, newEndTime, affectedChildTasks) {
13069
+ const eventDetail = {
13070
+ taskId: this.task.id,
13071
+ oldStartTime: this.dragState.initialStartTime,
13072
+ oldEndTime: this.dragState.initialEndTime,
13073
+ newStartTime,
13074
+ newEndTime,
13075
+ daysMoved,
13076
+ affectedChildTasks,
13077
+ timestamp: Date.now()
13078
+ };
13079
+ const event = new CustomEvent(GanttEvents.TASK_DRAGGED, {
13080
+ detail: eventDetail,
13081
+ bubbles: true,
13082
+ composed: true,
13083
+ cancelable: false
13084
+ });
13085
+ const chartContainer = this.chartContext.getChartContainer();
13086
+ if (chartContainer) {
13087
+ chartContainer.dispatchEvent(event);
13088
+ }
13089
+ }
13023
13090
  isOutOfBounds(currentLeft, currentWidth, dx2) {
13024
13091
  const { parentX1, parentX2 } = this.dragState;
13025
13092
  return dx2 < 0 && currentLeft <= parentX1 || dx2 > 0 && currentLeft + currentWidth >= parentX2;
@@ -13084,6 +13151,8 @@ class BarResizeManager {
13084
13151
  initialLeft: 0,
13085
13152
  initialPosition: 0,
13086
13153
  initialWidth: 0,
13154
+ initialStartTime: "",
13155
+ initialEndTime: "",
13087
13156
  isResizing: false,
13088
13157
  parentWidth: 0,
13089
13158
  parentX1: 0,
@@ -13130,6 +13199,11 @@ class BarResizeManager {
13130
13199
  barElement.classList.remove("resizing");
13131
13200
  const { daysPerPixel, initialLeft, initialPosition, initialWidth, parentX1, parentX2, resizeHandle } = this.interactionState;
13132
13201
  const dx2 = e.clientX - initialPosition;
13202
+ const oldStartTime = this.interactionState.initialStartTime;
13203
+ const oldEndTime = this.interactionState.initialEndTime;
13204
+ let newStartTime = oldStartTime;
13205
+ let newEndTime = oldEndTime;
13206
+ let durationChange = 0;
13133
13207
  if (resizeHandle === "left") {
13134
13208
  let daysChange = Math.round(dx2 / daysPerPixel);
13135
13209
  let calculatedLeft = initialLeft + daysChange * daysPerPixel;
@@ -13143,10 +13217,19 @@ class BarResizeManager {
13143
13217
  calculatedLeft = initialLeft + initialWidth - daysPerPixel;
13144
13218
  daysChange = Math.round((calculatedLeft - initialLeft) / daysPerPixel);
13145
13219
  }
13220
+ if (daysChange === 0) {
13221
+ this.interactionState = {
13222
+ ...this.interactionState,
13223
+ isResizing: false,
13224
+ resizeHandle: null
13225
+ };
13226
+ return;
13227
+ }
13146
13228
  barElement.style.left = `${calculatedLeft}px`;
13147
13229
  barElement.style.width = `${calculatedWidth}px`;
13148
- const newStart = dayjs(this.task.startTime).add(daysChange, "day").format(this.options.inputDateFormat);
13149
- onUpdate == null ? void 0 : onUpdate(this.task.id, { startTime: newStart });
13230
+ newStartTime = dayjs(this.task.startTime).add(daysChange, "day").format(this.options.inputDateFormat);
13231
+ durationChange = -daysChange;
13232
+ onUpdate == null ? void 0 : onUpdate(this.task.id, { startTime: newStartTime });
13150
13233
  } else {
13151
13234
  let daysWidth = Math.round(dx2 / daysPerPixel);
13152
13235
  let calculatedWidth = initialWidth + daysWidth * daysPerPixel;
@@ -13157,10 +13240,20 @@ class BarResizeManager {
13157
13240
  calculatedWidth = parentX2 - initialLeft;
13158
13241
  daysWidth = Math.round((calculatedWidth - initialWidth) / daysPerPixel);
13159
13242
  }
13243
+ if (daysWidth === 0) {
13244
+ this.interactionState = {
13245
+ ...this.interactionState,
13246
+ isResizing: false,
13247
+ resizeHandle: null
13248
+ };
13249
+ return;
13250
+ }
13160
13251
  barElement.style.width = `${calculatedWidth}px`;
13161
- const newEnd = dayjs(this.task.endTime).add(daysWidth, "day").format(this.options.inputDateFormat);
13162
- onUpdate == null ? void 0 : onUpdate(this.task.id, { endTime: newEnd });
13252
+ newEndTime = dayjs(this.task.endTime).add(daysWidth, "day").format(this.options.inputDateFormat);
13253
+ durationChange = daysWidth;
13254
+ onUpdate == null ? void 0 : onUpdate(this.task.id, { endTime: newEndTime });
13163
13255
  }
13256
+ this.emitTaskResizedEvent(resizeHandle, oldStartTime, oldEndTime, newStartTime, newEndTime, durationChange);
13164
13257
  this.dataManager.updateDependencyArrows(this.task.id, this.chartContext);
13165
13258
  this.interactionState = {
13166
13259
  ...this.interactionState,
@@ -13169,6 +13262,28 @@ class BarResizeManager {
13169
13262
  };
13170
13263
  };
13171
13264
  }
13265
+ emitTaskResizedEvent(resizeHandle, oldStartTime, oldEndTime, newStartTime, newEndTime, durationChange) {
13266
+ const eventDetail = {
13267
+ taskId: this.task.id,
13268
+ resizeHandle,
13269
+ oldStartTime,
13270
+ oldEndTime,
13271
+ newStartTime,
13272
+ newEndTime,
13273
+ durationChange,
13274
+ timestamp: Date.now()
13275
+ };
13276
+ const event = new CustomEvent(GanttEvents.TASK_RESIZED, {
13277
+ detail: eventDetail,
13278
+ bubbles: true,
13279
+ composed: true,
13280
+ cancelable: false
13281
+ });
13282
+ const chartContainer = this.chartContext.getChartContainer();
13283
+ if (chartContainer) {
13284
+ chartContainer.dispatchEvent(event);
13285
+ }
13286
+ }
13172
13287
  createResizeMouseDownHandler(barElement) {
13173
13288
  return (e, handle) => {
13174
13289
  var _a;
@@ -13179,6 +13294,8 @@ class BarResizeManager {
13179
13294
  initialLeft: parseInt(barElement.style.left) || 0,
13180
13295
  initialPosition: e.clientX,
13181
13296
  initialWidth: parseInt(barElement.style.width) || 0,
13297
+ initialStartTime: this.task.startTime,
13298
+ initialEndTime: this.task.endTime,
13182
13299
  isResizing: true,
13183
13300
  resizeHandle: handle,
13184
13301
  startX: e.clientX
@@ -13389,24 +13506,6 @@ class Dialog {
13389
13506
  firstFocusable == null ? void 0 : firstFocusable.focus();
13390
13507
  }
13391
13508
  }
13392
- const GanttEvents = {
13393
- /**
13394
- * emits when a task is being updated (before completion)
13395
- */
13396
- TASK_UPDATE: "taskUpdate",
13397
- /**
13398
- * emits when form validation fails
13399
- */
13400
- TASK_VALIDATION_ERROR: "taskValidationError",
13401
- /**
13402
- * emits after a task update completes successfully
13403
- */
13404
- TASK_UPDATE_SUCCESS: "taskUpdateSuccess",
13405
- /**
13406
- * emits when a task update fails
13407
- */
13408
- TASK_UPDATE_ERROR: "taskUpdateError"
13409
- };
13410
13509
  class TaskForm {
13411
13510
  constructor(chartContext, dataManager, task, containerElement, onSubmit, dateFormat = "MM-DD-YYYY") {
13412
13511
  this.chartContext = chartContext;
@@ -14330,6 +14429,32 @@ class DataManager {
14330
14429
  }
14331
14430
  getDateRange(add = 0, viewMode) {
14332
14431
  const tasks = this.getTasks();
14432
+ if (tasks.length === 0) {
14433
+ const today = dayjs();
14434
+ const startDate = today.startOf(viewMode);
14435
+ let defaultRange = 1;
14436
+ switch (viewMode) {
14437
+ case "day":
14438
+ defaultRange = 30;
14439
+ break;
14440
+ case "week":
14441
+ defaultRange = 12;
14442
+ break;
14443
+ case "month":
14444
+ defaultRange = 12;
14445
+ break;
14446
+ case "quarter":
14447
+ defaultRange = 4;
14448
+ break;
14449
+ case "year":
14450
+ defaultRange = 5;
14451
+ break;
14452
+ default:
14453
+ defaultRange = 12;
14454
+ }
14455
+ const endDate = startDate.add(defaultRange + add, viewMode);
14456
+ return [startDate, endDate];
14457
+ }
14333
14458
  const startDates = tasks.map((task) => dayjs(task.startTime));
14334
14459
  const endDates = tasks.filter((task) => !!task.endTime).map((task) => dayjs(task.endTime));
14335
14460
  return [dayjs.min(startDates), dayjs.max(endDates).add(add, viewMode)];
@@ -16534,12 +16659,11 @@ class GanttStateManager {
16534
16659
  */
16535
16660
  captureState(element, dataManager, viewMode) {
16536
16661
  const horizontalScroll = element.querySelector(".timeline-horizontal-scroll");
16537
- const tasksBodyWrapper = element.querySelector(".tasks-body-wrapper");
16538
- const timelineBodyWrapper = element.querySelector(".timeline-body-wrapper");
16662
+ const splitViewContainer = element.querySelector(".split-view-container");
16539
16663
  this.state.scrollPosition = {
16540
16664
  horizontal: (horizontalScroll == null ? void 0 : horizontalScroll.scrollLeft) || 0,
16541
- tasksVertical: (tasksBodyWrapper == null ? void 0 : tasksBodyWrapper.scrollTop) || 0,
16542
- timelineVertical: (timelineBodyWrapper == null ? void 0 : timelineBodyWrapper.scrollTop) || 0
16665
+ tasksVertical: (splitViewContainer == null ? void 0 : splitViewContainer.scrollTop) || 0,
16666
+ timelineVertical: (splitViewContainer == null ? void 0 : splitViewContainer.scrollTop) || 0
16543
16667
  };
16544
16668
  this.state.viewMode = viewMode;
16545
16669
  this.state.collapsedTasks = new Set(
@@ -16553,20 +16677,16 @@ class GanttStateManager {
16553
16677
  if (!skipScroll) {
16554
16678
  requestAnimationFrame(() => {
16555
16679
  const horizontalScroll = element.querySelector(".timeline-horizontal-scroll");
16556
- const tasksBodyWrapper = element.querySelector(".tasks-body-wrapper");
16557
- const timelineBodyWrapper = element.querySelector(".timeline-body-wrapper");
16680
+ const splitViewContainer = element.querySelector(".split-view-container");
16558
16681
  if (horizontalScroll) {
16559
16682
  horizontalScroll.scrollLeft = this.state.scrollPosition.horizontal;
16560
16683
  }
16561
- if (tasksBodyWrapper) {
16562
- tasksBodyWrapper.scrollTop = this.state.scrollPosition.tasksVertical;
16684
+ if (splitViewContainer) {
16685
+ splitViewContainer.scrollTop = this.state.scrollPosition.tasksVertical;
16563
16686
  }
16564
- if (timelineBodyWrapper) {
16565
- timelineBodyWrapper.scrollTop = this.state.scrollPosition.timelineVertical;
16566
- const timelineHeader = element.querySelector(".timeline-header");
16567
- if (timelineHeader) {
16568
- timelineHeader.scrollLeft = this.state.scrollPosition.horizontal;
16569
- }
16687
+ const timelineHeader = element.querySelector(".timeline-header");
16688
+ if (timelineHeader) {
16689
+ timelineHeader.scrollLeft = this.state.scrollPosition.horizontal;
16570
16690
  }
16571
16691
  });
16572
16692
  }
@@ -16754,6 +16874,7 @@ class ApexGantt extends BaseChart {
16754
16874
  this.splitBarResizeHandler = null;
16755
16875
  this.containerResizeObserver = null;
16756
16876
  this.lastKnownWidth = 0;
16877
+ this.lastKnownHeight = 0;
16757
16878
  this.resizeDebounceTimer = null;
16758
16879
  const themeDefaults = getDefaultOptions(options == null ? void 0 : options.theme);
16759
16880
  let processedSeries;
@@ -16989,16 +17110,11 @@ class ApexGantt extends BaseChart {
16989
17110
  this.setCSSVariables();
16990
17111
  this.initializeTooltip();
16991
17112
  this.element.innerHTML = "";
16992
- const existingDimensions = this.hasExplicitDimensions();
17113
+ this.hasExplicitDimensions();
16993
17114
  const normalizedHeight = this.normalizeDimension(height2);
16994
17115
  const normalizedWidth = this.normalizeDimension(width2);
16995
17116
  this.element.style.width = normalizedWidth;
16996
- if (normalizedHeight === "100%" && existingDimensions.height) {
16997
- const computedHeight = window.getComputedStyle(this.element).height;
16998
- this.element.style.height = computedHeight;
16999
- } else {
17000
- this.element.style.height = normalizedHeight;
17001
- }
17117
+ this.element.style.height = normalizedHeight;
17002
17118
  this.element.style.display = "flex";
17003
17119
  this.element.style.flexDirection = "column";
17004
17120
  this.element.style.boxSizing = "border-box";
@@ -17620,6 +17736,10 @@ class ApexGantt extends BaseChart {
17620
17736
  if (!ganttContainer || !timelineBody || !tasksBody) {
17621
17737
  return;
17622
17738
  }
17739
+ const oldEmptyTimelineRows = timelineBody.querySelectorAll(".timeline-empty-row");
17740
+ oldEmptyTimelineRows.forEach((row) => row.remove());
17741
+ const oldEmptyTaskRows = tasksBody.querySelectorAll(".tasks-empty-row");
17742
+ oldEmptyTaskRows.forEach((row) => row.remove());
17623
17743
  const containerHeight = ganttContainer.clientHeight;
17624
17744
  const existingRows = timelineBody.querySelectorAll(".timeline-data-row:not(.timeline-empty-row)");
17625
17745
  const existingRowCount = existingRows.length;
@@ -17628,8 +17748,17 @@ class ApexGantt extends BaseChart {
17628
17748
  const emptyRowsNeeded = Math.max(0, totalRowsNeeded - existingRowCount);
17629
17749
  if (emptyRowsNeeded === 0)
17630
17750
  return;
17751
+ let cellCount = 0;
17631
17752
  const firstRow = timelineBody.querySelector(".timeline-data-row");
17632
- const cellCount = firstRow ? firstRow.querySelectorAll(".timeline-data-cell").length : 0;
17753
+ if (firstRow) {
17754
+ cellCount = firstRow.querySelectorAll(".timeline-data-cell").length;
17755
+ } else {
17756
+ const timelineHeader = ganttContainer.querySelector(".timeline-header");
17757
+ if (timelineHeader) {
17758
+ const headerCells = timelineHeader.querySelectorAll(".timeline-header-cell");
17759
+ cellCount = headerCells.length;
17760
+ }
17761
+ }
17633
17762
  if (cellCount === 0)
17634
17763
  return;
17635
17764
  for (let i = 0; i < emptyRowsNeeded; i++) {
@@ -17785,7 +17914,7 @@ class ApexGantt extends BaseChart {
17785
17914
  return value;
17786
17915
  }
17787
17916
  /**
17788
- * resize observer for container to handle responsive width changes
17917
+ * resize observer for container to handle responsive width and height changes
17789
17918
  */
17790
17919
  setupContainerResizeObserver() {
17791
17920
  if (!this.element) {
@@ -17795,16 +17924,23 @@ class ApexGantt extends BaseChart {
17795
17924
  this.containerResizeObserver.disconnect();
17796
17925
  }
17797
17926
  const normalizedWidth = this.normalizeDimension(this.options.width);
17927
+ const normalizedHeight = this.normalizeDimension(this.options.height);
17798
17928
  const isPercentageWidth = typeof normalizedWidth === "string" && normalizedWidth.includes("%");
17799
- if (!isPercentageWidth) {
17929
+ const isPercentageHeight = typeof normalizedHeight === "string" && normalizedHeight.includes("%");
17930
+ if (!isPercentageWidth && !isPercentageHeight) {
17800
17931
  return;
17801
17932
  }
17802
17933
  this.lastKnownWidth = this.element.offsetWidth;
17934
+ this.lastKnownHeight = this.element.offsetHeight;
17803
17935
  this.containerResizeObserver = new ResizeObserver((entries) => {
17804
17936
  for (const entry of entries) {
17805
17937
  const newWidth = entry.contentRect.width;
17806
- if (Math.abs(newWidth - this.lastKnownWidth) > 1) {
17938
+ const newHeight = entry.contentRect.height;
17939
+ const widthChanged = Math.abs(newWidth - this.lastKnownWidth) > 1;
17940
+ const heightChanged = Math.abs(newHeight - this.lastKnownHeight) > 1;
17941
+ if (widthChanged || heightChanged) {
17807
17942
  this.lastKnownWidth = newWidth;
17943
+ this.lastKnownHeight = newHeight;
17808
17944
  this.handleContainerResize();
17809
17945
  }
17810
17946
  }
@@ -17825,7 +17961,10 @@ class ApexGantt extends BaseChart {
17825
17961
  return;
17826
17962
  }
17827
17963
  const normalizedWidth = this.normalizeDimension(this.options.width);
17828
- if (typeof normalizedWidth === "string" && normalizedWidth.includes("%")) {
17964
+ const normalizedHeight = this.normalizeDimension(this.options.height);
17965
+ const isPercentageWidth = typeof normalizedWidth === "string" && normalizedWidth.includes("%");
17966
+ const isPercentageHeight = typeof normalizedHeight === "string" && normalizedHeight.includes("%");
17967
+ if (isPercentageWidth) {
17829
17968
  const computedWidth = window.getComputedStyle(this.element.parentElement || this.element).width;
17830
17969
  const currentElementWidth = window.getComputedStyle(this.element).width;
17831
17970
  if (computedWidth !== currentElementWidth) {
@@ -17836,6 +17975,11 @@ class ApexGantt extends BaseChart {
17836
17975
  });
17837
17976
  }
17838
17977
  }
17978
+ if (isPercentageHeight) {
17979
+ requestAnimationFrame(() => {
17980
+ this.fillEmptyRowsAfterRender();
17981
+ });
17982
+ }
17839
17983
  }
17840
17984
  destroy() {
17841
17985
  try {