gantt-task-react-v 1.3.6 → 1.4.1

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.
@@ -4688,9 +4688,9 @@ const getStartAndEnd = (containerEl, property, cellSize) => {
4688
4688
  const scrollValue = property === "scrollLeft" ? el.scrollLeft : el.scrollTop;
4689
4689
  const maxScrollValue = property === "scrollLeft" ? el.scrollWidth : el.scrollHeight;
4690
4690
  const fullValue = property === "scrollLeft" ? el.clientWidth : el.clientHeight;
4691
- const firstIndex = Math.max(0, Math.floor(scrollValue / cellSize));
4692
4691
  const visibleCount = Math.max(1, Math.ceil(fullValue / cellSize));
4693
4692
  const overscan = Math.min(100, Math.max(10, Math.ceil(visibleCount * 0.5)));
4693
+ const firstIndex = Math.max(0, Math.floor(scrollValue / cellSize) - overscan);
4694
4694
  const lastIndex = Math.floor((scrollValue + fullValue) / cellSize) + overscan;
4695
4695
  const isStartOfScroll = scrollValue < DELTA;
4696
4696
  const isEndOfScroll = scrollValue + fullValue > maxScrollValue - DELTA;
@@ -5536,6 +5536,37 @@ const TaskListTableHeadersDefaultInner = ({
5536
5536
  canMoveTasks,
5537
5537
  onColumnResizeStart
5538
5538
  }) => {
5539
+ const pinnedStyles = useMemo(() => {
5540
+ const result = {};
5541
+ let leftOffset = 0;
5542
+ if (canMoveTasks) {
5543
+ leftOffset = 24;
5544
+ }
5545
+ for (let i = 0; i < columns.length; i++) {
5546
+ if (columns[i].pinned === "left") {
5547
+ result[i] = {
5548
+ position: "sticky",
5549
+ left: leftOffset,
5550
+ zIndex: 2,
5551
+ backgroundColor: "var(--gantt-table-header-background-color, #fff)"
5552
+ };
5553
+ leftOffset += columns[i].width;
5554
+ }
5555
+ }
5556
+ let rightOffset = 0;
5557
+ for (let i = columns.length - 1; i >= 0; i--) {
5558
+ if (columns[i].pinned === "right") {
5559
+ result[i] = {
5560
+ position: "sticky",
5561
+ right: rightOffset,
5562
+ zIndex: 2,
5563
+ backgroundColor: "var(--gantt-table-header-background-color, #fff)"
5564
+ };
5565
+ rightOffset += columns[i].width;
5566
+ }
5567
+ }
5568
+ return result;
5569
+ }, [columns, canMoveTasks]);
5539
5570
  return /* @__PURE__ */ jsx(
5540
5571
  "div",
5541
5572
  {
@@ -5572,7 +5603,8 @@ const TaskListTableHeadersDefaultInner = ({
5572
5603
  className: styles$h.ganttTable_HeaderItem,
5573
5604
  style: {
5574
5605
  minWidth: width,
5575
- maxWidth: width
5606
+ maxWidth: width,
5607
+ ...pinnedStyles[index2]
5576
5608
  },
5577
5609
  children: [
5578
5610
  title,
@@ -10135,6 +10167,37 @@ const TaskListTableRowInner = forwardRef(
10135
10167
  }
10136
10168
  return classNames.join(" ");
10137
10169
  }, [isCut2, moveOverPosition, isOverlay2, isDragging]);
10170
+ const pinnedStyles = useMemo(() => {
10171
+ const result = {};
10172
+ let leftOffset = 0;
10173
+ if (moveHandleProps || !isOverlay2 && task.type !== "project" && task.id !== "no-project-asigned") {
10174
+ leftOffset = 24;
10175
+ }
10176
+ for (let i = 0; i < columns.length; i++) {
10177
+ if (columns[i].pinned === "left") {
10178
+ result[i] = {
10179
+ position: "sticky",
10180
+ left: leftOffset,
10181
+ zIndex: 1,
10182
+ backgroundColor: "inherit"
10183
+ };
10184
+ leftOffset += columns[i].width;
10185
+ }
10186
+ }
10187
+ let rightOffset = 0;
10188
+ for (let i = columns.length - 1; i >= 0; i--) {
10189
+ if (columns[i].pinned === "right") {
10190
+ result[i] = {
10191
+ position: "sticky",
10192
+ right: rightOffset,
10193
+ zIndex: 1,
10194
+ backgroundColor: "inherit"
10195
+ };
10196
+ rightOffset += columns[i].width;
10197
+ }
10198
+ }
10199
+ return result;
10200
+ }, [columns, moveHandleProps, isOverlay2, task.type, task.id]);
10138
10201
  return /* @__PURE__ */ jsxs(
10139
10202
  "div",
10140
10203
  {
@@ -10156,7 +10219,8 @@ const TaskListTableRowInner = forwardRef(
10156
10219
  className: styles$f.taskListCell,
10157
10220
  style: {
10158
10221
  minWidth: width,
10159
- maxWidth: width
10222
+ maxWidth: width,
10223
+ ...pinnedStyles[index2]
10160
10224
  },
10161
10225
  children: /* @__PURE__ */ jsx(Component, { data: columnData })
10162
10226
  },
@@ -13062,6 +13126,12 @@ const TaskGanttContentInner = (props) => {
13062
13126
  const tasksRes = [];
13063
13127
  const arrowsRes = [];
13064
13128
  const selectedTasksRes = [];
13129
+ const taskById = /* @__PURE__ */ new Map();
13130
+ mapGlobalRowIndexToTask.forEach((task) => {
13131
+ if (task.type !== "empty") {
13132
+ taskById.set(task.id, task);
13133
+ }
13134
+ });
13065
13135
  const addedSelectedTasks = {};
13066
13136
  const addedDependencies = {};
13067
13137
  for (let index2 = start; index2 <= end; ++index2) {
@@ -13115,19 +13185,73 @@ const TaskGanttContentInner = (props) => {
13115
13185
  const safeProgressWidth = isNaN(progressWidth) || !isFinite(progressWidth) ? 0 : Math.max(progressWidth, 0);
13116
13186
  const safeInnerX1 = isNaN(innerX1) || !isFinite(innerX1) ? 0 : innerX1;
13117
13187
  const safeInnerX2 = isNaN(innerX2) || !isFinite(innerX2) ? safeInnerX1 + safeWidth : innerX2;
13118
- let comparisonBarElement = null;
13188
+ tasksRes.push(
13189
+ /* @__PURE__ */ jsx(
13190
+ "svg",
13191
+ {
13192
+ id: task.id,
13193
+ className: `${styles$4.TaskItemWrapper} TaskItemWrapper`,
13194
+ x: Math.max(safeContainerX + (additionalLeftSpace || 0), 0),
13195
+ y: safeLevelY,
13196
+ width: Math.max(safeContainerWidth, 0),
13197
+ height: fullRowHeight,
13198
+ children: /* @__PURE__ */ jsx(
13199
+ TaskItem,
13200
+ {
13201
+ movingAction: taskBarMovingAction(task),
13202
+ allowMoveTaskBar,
13203
+ hasChildren: checkHasChildren(task, childTasksMap),
13204
+ progressWidth: safeProgressWidth,
13205
+ progressX: rtl ? safeInnerX2 : safeInnerX1,
13206
+ onSelectTaskOnMouseDown: selectTaskOnMouseDown,
13207
+ task,
13208
+ taskYOffset,
13209
+ width: safeWidth,
13210
+ x1: safeInnerX1,
13211
+ x2: safeInnerX2,
13212
+ distances,
13213
+ taskHeight,
13214
+ taskHalfHeight,
13215
+ isProgressChangeable: (t) => isProgressChangeable(t) && !waitCommitTasks,
13216
+ isDateChangeable: (t) => isDateChangeable(t) && !waitCommitTasks,
13217
+ isRelationChangeable: (t) => isRelationChangeable(t) && !waitCommitTasks,
13218
+ authorizedRelations,
13219
+ ganttRelationEvent,
13220
+ canDelete: !task.isDisabled && !waitCommitTasks,
13221
+ onDoubleClick,
13222
+ onClick,
13223
+ onEventStart: onTaskBarDragStart,
13224
+ onTooltipTask,
13225
+ onRelationStart: onTaskBarRelationStart,
13226
+ isSelected: Boolean(selectedIdsMirror[taskId]),
13227
+ isCritical,
13228
+ rtl,
13229
+ onDeleteTask,
13230
+ renderCustomLabel,
13231
+ viewMode,
13232
+ showProgress,
13233
+ progressColor
13234
+ }
13235
+ )
13236
+ },
13237
+ key2
13238
+ )
13239
+ );
13119
13240
  if (task.comparisonDates && comparisonDates) {
13120
13241
  const safeComparisonX = isNaN(comparisonDates.x) || !isFinite(comparisonDates.x) ? 0 : comparisonDates.x;
13121
- const safeComparisonWidth = isNaN(comparisonDates.width) || !isFinite(comparisonDates.width) ? 10 : Math.max(comparisonDates.width, 3);
13122
- const safeComparisonHeight = isNaN(comparisonDates.height) || !isFinite(comparisonDates.height) ? 6 : Math.max(comparisonDates.height, 3);
13123
- const comparisonRight = safeComparisonX + safeComparisonWidth;
13124
- const isVisible = comparisonRight >= 0 && safeComparisonX <= 1e4;
13125
- if (isVisible) {
13126
- const relativeComparisonX = safeComparisonX - safeContainerX;
13127
- comparisonBarElement = /* @__PURE__ */ jsx(
13128
- "g",
13242
+ const safeComparisonY = isNaN(comparisonDates.y) || !isFinite(comparisonDates.y) ? safeLevelY : comparisonDates.y;
13243
+ const safeComparisonWidth = isNaN(comparisonDates.width) || !isFinite(comparisonDates.width) ? 0 : Math.max(comparisonDates.width, 0);
13244
+ const safeComparisonHeight = isNaN(comparisonDates.height) || !isFinite(comparisonDates.height) ? 0 : Math.max(comparisonDates.height, 0);
13245
+ tasksRes.push(
13246
+ /* @__PURE__ */ jsx(
13247
+ "svg",
13129
13248
  {
13130
- transform: `translate(${relativeComparisonX}, ${taskHeight + 2})`,
13249
+ id: task.id + "_comparison",
13250
+ className: "TaskItemWrapperComparison",
13251
+ x: Math.max(safeComparisonX + (additionalLeftSpace || 0), 0),
13252
+ y: safeComparisonY,
13253
+ width: safeComparisonWidth,
13254
+ height: safeComparisonHeight * 2,
13131
13255
  children: /* @__PURE__ */ jsx(
13132
13256
  BarComparison,
13133
13257
  {
@@ -13139,70 +13263,16 @@ const TaskGanttContentInner = (props) => {
13139
13263
  height: safeComparisonHeight,
13140
13264
  width: safeComparisonWidth,
13141
13265
  borderHeight: distances.barComparisonTaskBorderHeight,
13142
- yOffset: 0,
13266
+ yOffset: distances.barComparisonTaskYOffset,
13143
13267
  task,
13144
13268
  onTooltipTask
13145
13269
  }
13146
13270
  )
13147
- }
13148
- );
13149
- }
13271
+ },
13272
+ key2 + "_comparison"
13273
+ )
13274
+ );
13150
13275
  }
13151
- tasksRes.push(
13152
- /* @__PURE__ */ jsxs(
13153
- "svg",
13154
- {
13155
- id: task.id,
13156
- className: `${styles$4.TaskItemWrapper} TaskItemWrapper`,
13157
- x: Math.max(safeContainerX + (additionalLeftSpace || 0), 0),
13158
- y: safeLevelY,
13159
- width: Math.max(safeContainerWidth, 0),
13160
- height: fullRowHeight,
13161
- children: [
13162
- /* @__PURE__ */ jsx(
13163
- TaskItem,
13164
- {
13165
- movingAction: taskBarMovingAction(task),
13166
- allowMoveTaskBar,
13167
- hasChildren: checkHasChildren(task, childTasksMap),
13168
- progressWidth: safeProgressWidth,
13169
- progressX: rtl ? safeInnerX2 : safeInnerX1,
13170
- onSelectTaskOnMouseDown: selectTaskOnMouseDown,
13171
- task,
13172
- taskYOffset,
13173
- width: safeWidth,
13174
- x1: safeInnerX1,
13175
- x2: safeInnerX2,
13176
- distances,
13177
- taskHeight,
13178
- taskHalfHeight,
13179
- isProgressChangeable: (t) => isProgressChangeable(t) && !waitCommitTasks,
13180
- isDateChangeable: (t) => isDateChangeable(t) && !waitCommitTasks,
13181
- isRelationChangeable: (t) => isRelationChangeable(t) && !waitCommitTasks,
13182
- authorizedRelations,
13183
- ganttRelationEvent,
13184
- canDelete: !task.isDisabled && !waitCommitTasks,
13185
- onDoubleClick,
13186
- onClick,
13187
- onEventStart: onTaskBarDragStart,
13188
- onTooltipTask,
13189
- onRelationStart: onTaskBarRelationStart,
13190
- isSelected: Boolean(selectedIdsMirror[taskId]),
13191
- isCritical,
13192
- rtl,
13193
- onDeleteTask,
13194
- renderCustomLabel,
13195
- viewMode,
13196
- showProgress,
13197
- progressColor
13198
- }
13199
- ),
13200
- comparisonBarElement
13201
- ]
13202
- },
13203
- key2
13204
- )
13205
- );
13206
13276
  const addedDependenciesAtLevel = addedDependencies[comparisonLevel] || {};
13207
13277
  if (!addedDependencies[comparisonLevel]) {
13208
13278
  addedDependencies[comparisonLevel] = addedDependenciesAtLevel;
@@ -13356,6 +13426,93 @@ const TaskGanttContentInner = (props) => {
13356
13426
  );
13357
13427
  }
13358
13428
  }
13429
+ const renderedTop = start * fullRowHeight;
13430
+ const renderedBottom = (end + 1) * fullRowHeight;
13431
+ for (const [comparisonLevel, dependenciesByLevel] of dependencyMap) {
13432
+ let addedDependenciesAtLevel = addedDependencies[comparisonLevel];
13433
+ if (!addedDependenciesAtLevel) {
13434
+ addedDependenciesAtLevel = {};
13435
+ addedDependencies[comparisonLevel] = addedDependenciesAtLevel;
13436
+ }
13437
+ const criticalPathOnLevel = criticalPaths ? criticalPaths.get(comparisonLevel) : void 0;
13438
+ for (const [taskId, dependencies] of dependenciesByLevel) {
13439
+ let addedDependenciesAtTask = addedDependenciesAtLevel[taskId];
13440
+ if (!addedDependenciesAtTask) {
13441
+ addedDependenciesAtTask = {};
13442
+ addedDependenciesAtLevel[taskId] = addedDependenciesAtTask;
13443
+ }
13444
+ const targetTask = taskById.get(taskId);
13445
+ if (!targetTask)
13446
+ continue;
13447
+ const criticalPathForTask = criticalPathOnLevel ? criticalPathOnLevel.dependencies.get(taskId) : void 0;
13448
+ dependencies.filter(({ source }) => {
13449
+ if (addedDependenciesAtTask[source.id])
13450
+ return false;
13451
+ if (!visibleTasksMirror[source.id])
13452
+ return false;
13453
+ return true;
13454
+ }).forEach(
13455
+ ({
13456
+ containerHeight,
13457
+ containerY,
13458
+ innerFromY,
13459
+ innerToY,
13460
+ ownTarget,
13461
+ source,
13462
+ sourceTarget
13463
+ }) => {
13464
+ if (containerY + containerHeight < renderedTop || containerY > renderedBottom) {
13465
+ return;
13466
+ }
13467
+ addedDependenciesAtTask[source.id] = true;
13468
+ const isCritical = criticalPathForTask ? criticalPathForTask.has(source.id) : false;
13469
+ const { x1: fromX1, x2: fromX2 } = getTaskCoordinates2(source);
13470
+ const { x1: targetX1, x2: targetX2 } = getTaskCoordinates2(targetTask);
13471
+ const safeFromX1 = isNaN(fromX1) || !isFinite(fromX1) ? 0 : fromX1;
13472
+ const safeFromX2 = isNaN(fromX2) || !isFinite(fromX2) ? safeFromX1 + 10 : fromX2;
13473
+ const safeTargetX1 = isNaN(targetX1) || !isFinite(targetX1) ? 0 : targetX1;
13474
+ const safeTargetX2 = isNaN(targetX2) || !isFinite(targetX2) ? safeTargetX1 + 10 : targetX2;
13475
+ const cX = Math.min(safeFromX1, safeTargetX1) - DELTA_RELATION_WIDTH;
13476
+ const cW = Math.max(safeFromX2, safeTargetX2) - cX + DELTA_RELATION_WIDTH;
13477
+ const safeCX = isNaN(cX) || !isFinite(cX) ? 0 : cX;
13478
+ const safeCW = isNaN(cW) || !isFinite(cW) ? 100 : Math.max(cW, 0);
13479
+ arrowsRes.push(
13480
+ /* @__PURE__ */ jsx(
13481
+ "svg",
13482
+ {
13483
+ x: Math.max(safeCX + (additionalLeftSpace || 0), 0),
13484
+ y: containerY,
13485
+ width: safeCW,
13486
+ height: containerHeight,
13487
+ children: /* @__PURE__ */ jsx(
13488
+ Arrow,
13489
+ {
13490
+ distances,
13491
+ taskFrom: source,
13492
+ targetFrom: sourceTarget,
13493
+ fromX1: safeFromX1 - safeCX,
13494
+ fromX2: safeFromX2 - safeCX,
13495
+ fromY: innerFromY,
13496
+ taskTo: targetTask,
13497
+ targetTo: ownTarget,
13498
+ toX1: safeTargetX1 - safeCX,
13499
+ toX2: safeTargetX2 - safeCX,
13500
+ toY: innerToY,
13501
+ fullRowHeight,
13502
+ taskHeight,
13503
+ isCritical,
13504
+ rtl,
13505
+ onArrowDoubleClick
13506
+ }
13507
+ )
13508
+ },
13509
+ `Arrow from ${source.id} to ${taskId} on ${comparisonLevel}`
13510
+ )
13511
+ );
13512
+ }
13513
+ );
13514
+ }
13515
+ }
13359
13516
  return [tasksRes, arrowsRes, selectedTasksRes];
13360
13517
  }, [
13361
13518
  viewMode,
@@ -13860,12 +14017,10 @@ const countTaskCoordinates = (task, taskToRowIndexMap, startDate, viewMode, rtl,
13860
14017
  );
13861
14018
  cx1 = isNaN(cx1) || !isFinite(cx1) ? x1 : cx1;
13862
14019
  cx2 = isNaN(cx2) || !isFinite(cx2) ? x2 : cx2;
13863
- const comparisonWidth = Math.max(Math.abs(cx2 - cx1), 3);
13864
- const comparisonX = Math.min(cx1, cx2);
13865
14020
  comparisonDates = {
13866
- x: comparisonX,
14021
+ x: cx1,
13867
14022
  y: y + taskHeight,
13868
- width: comparisonWidth,
14023
+ width: Math.max(cx2 - cx1, 0),
13869
14024
  height: barComparisonTaskHeight
13870
14025
  };
13871
14026
  }
@@ -19059,7 +19214,8 @@ const Gantt = (props) => {
19059
19214
  todayLabel = "Today",
19060
19215
  dataDateLabel = "Data Date",
19061
19216
  showProgress = true,
19062
- progressColor
19217
+ progressColor,
19218
+ scrollToTaskId
19063
19219
  } = props;
19064
19220
  const ganttSVGRef = useRef(null);
19065
19221
  const wrapperRef = useRef(null);
@@ -19367,6 +19523,44 @@ const Gantt = (props) => {
19367
19523
  },
19368
19524
  [mapTaskToCoordinates, setScrollXProgrammatically]
19369
19525
  );
19526
+ const prevScrollToTaskIdRef = useRef(void 0);
19527
+ useEffect(() => {
19528
+ if (!scrollToTaskId || scrollToTaskId === prevScrollToTaskIdRef.current) {
19529
+ return;
19530
+ }
19531
+ prevScrollToTaskIdRef.current = scrollToTaskId;
19532
+ for (const [comparisonLevel, levelMap] of tasksMap) {
19533
+ const task = levelMap.get(scrollToTaskId);
19534
+ if (!task || task.type === "empty") {
19535
+ continue;
19536
+ }
19537
+ const { x1 } = getTaskCoordinates(task, mapTaskToCoordinates);
19538
+ setScrollXProgrammatically(Math.max(0, x1 - 100));
19539
+ const rowIndexMap = taskToRowIndexMap.get(comparisonLevel);
19540
+ if (rowIndexMap) {
19541
+ const rowIndex = rowIndexMap.get(scrollToTaskId);
19542
+ if (typeof rowIndex === "number") {
19543
+ const targetScrollY = rowIndex * fullRowHeight - ganttHeight / 2 + fullRowHeight / 2;
19544
+ setScrollYProgrammatically(
19545
+ Math.max(0, Math.min(targetScrollY, ganttFullHeight - ganttHeight))
19546
+ );
19547
+ }
19548
+ }
19549
+ selectTask(scrollToTaskId);
19550
+ break;
19551
+ }
19552
+ }, [
19553
+ scrollToTaskId,
19554
+ tasksMap,
19555
+ mapTaskToCoordinates,
19556
+ taskToRowIndexMap,
19557
+ fullRowHeight,
19558
+ ganttHeight,
19559
+ ganttFullHeight,
19560
+ setScrollXProgrammatically,
19561
+ setScrollYProgrammatically,
19562
+ selectTask
19563
+ ]);
19370
19564
  const { contextMenu, handleCloseContextMenu, handleOpenContextMenu } = useContextMenu(wrapperRef, scrollToTask);
19371
19565
  const [ganttContextMenu, setGanttContextMenu] = useState({
19372
19566
  task: null,
@@ -4705,9 +4705,9 @@
4705
4705
  const scrollValue = property === "scrollLeft" ? el.scrollLeft : el.scrollTop;
4706
4706
  const maxScrollValue = property === "scrollLeft" ? el.scrollWidth : el.scrollHeight;
4707
4707
  const fullValue = property === "scrollLeft" ? el.clientWidth : el.clientHeight;
4708
- const firstIndex = Math.max(0, Math.floor(scrollValue / cellSize));
4709
4708
  const visibleCount = Math.max(1, Math.ceil(fullValue / cellSize));
4710
4709
  const overscan = Math.min(100, Math.max(10, Math.ceil(visibleCount * 0.5)));
4710
+ const firstIndex = Math.max(0, Math.floor(scrollValue / cellSize) - overscan);
4711
4711
  const lastIndex = Math.floor((scrollValue + fullValue) / cellSize) + overscan;
4712
4712
  const isStartOfScroll = scrollValue < DELTA;
4713
4713
  const isEndOfScroll = scrollValue + fullValue > maxScrollValue - DELTA;
@@ -5553,6 +5553,37 @@
5553
5553
  canMoveTasks,
5554
5554
  onColumnResizeStart
5555
5555
  }) => {
5556
+ const pinnedStyles = React.useMemo(() => {
5557
+ const result = {};
5558
+ let leftOffset = 0;
5559
+ if (canMoveTasks) {
5560
+ leftOffset = 24;
5561
+ }
5562
+ for (let i = 0; i < columns.length; i++) {
5563
+ if (columns[i].pinned === "left") {
5564
+ result[i] = {
5565
+ position: "sticky",
5566
+ left: leftOffset,
5567
+ zIndex: 2,
5568
+ backgroundColor: "var(--gantt-table-header-background-color, #fff)"
5569
+ };
5570
+ leftOffset += columns[i].width;
5571
+ }
5572
+ }
5573
+ let rightOffset = 0;
5574
+ for (let i = columns.length - 1; i >= 0; i--) {
5575
+ if (columns[i].pinned === "right") {
5576
+ result[i] = {
5577
+ position: "sticky",
5578
+ right: rightOffset,
5579
+ zIndex: 2,
5580
+ backgroundColor: "var(--gantt-table-header-background-color, #fff)"
5581
+ };
5582
+ rightOffset += columns[i].width;
5583
+ }
5584
+ }
5585
+ return result;
5586
+ }, [columns, canMoveTasks]);
5556
5587
  return /* @__PURE__ */ jsxRuntime.jsx(
5557
5588
  "div",
5558
5589
  {
@@ -5589,7 +5620,8 @@
5589
5620
  className: styles$h.ganttTable_HeaderItem,
5590
5621
  style: {
5591
5622
  minWidth: width,
5592
- maxWidth: width
5623
+ maxWidth: width,
5624
+ ...pinnedStyles[index2]
5593
5625
  },
5594
5626
  children: [
5595
5627
  title,
@@ -10152,6 +10184,37 @@
10152
10184
  }
10153
10185
  return classNames.join(" ");
10154
10186
  }, [isCut2, moveOverPosition, isOverlay2, isDragging]);
10187
+ const pinnedStyles = React.useMemo(() => {
10188
+ const result = {};
10189
+ let leftOffset = 0;
10190
+ if (moveHandleProps || !isOverlay2 && task.type !== "project" && task.id !== "no-project-asigned") {
10191
+ leftOffset = 24;
10192
+ }
10193
+ for (let i = 0; i < columns.length; i++) {
10194
+ if (columns[i].pinned === "left") {
10195
+ result[i] = {
10196
+ position: "sticky",
10197
+ left: leftOffset,
10198
+ zIndex: 1,
10199
+ backgroundColor: "inherit"
10200
+ };
10201
+ leftOffset += columns[i].width;
10202
+ }
10203
+ }
10204
+ let rightOffset = 0;
10205
+ for (let i = columns.length - 1; i >= 0; i--) {
10206
+ if (columns[i].pinned === "right") {
10207
+ result[i] = {
10208
+ position: "sticky",
10209
+ right: rightOffset,
10210
+ zIndex: 1,
10211
+ backgroundColor: "inherit"
10212
+ };
10213
+ rightOffset += columns[i].width;
10214
+ }
10215
+ }
10216
+ return result;
10217
+ }, [columns, moveHandleProps, isOverlay2, task.type, task.id]);
10155
10218
  return /* @__PURE__ */ jsxRuntime.jsxs(
10156
10219
  "div",
10157
10220
  {
@@ -10173,7 +10236,8 @@
10173
10236
  className: styles$f.taskListCell,
10174
10237
  style: {
10175
10238
  minWidth: width,
10176
- maxWidth: width
10239
+ maxWidth: width,
10240
+ ...pinnedStyles[index2]
10177
10241
  },
10178
10242
  children: /* @__PURE__ */ jsxRuntime.jsx(Component, { data: columnData })
10179
10243
  },
@@ -13079,6 +13143,12 @@
13079
13143
  const tasksRes = [];
13080
13144
  const arrowsRes = [];
13081
13145
  const selectedTasksRes = [];
13146
+ const taskById = /* @__PURE__ */ new Map();
13147
+ mapGlobalRowIndexToTask.forEach((task) => {
13148
+ if (task.type !== "empty") {
13149
+ taskById.set(task.id, task);
13150
+ }
13151
+ });
13082
13152
  const addedSelectedTasks = {};
13083
13153
  const addedDependencies = {};
13084
13154
  for (let index2 = start; index2 <= end; ++index2) {
@@ -13132,19 +13202,73 @@
13132
13202
  const safeProgressWidth = isNaN(progressWidth) || !isFinite(progressWidth) ? 0 : Math.max(progressWidth, 0);
13133
13203
  const safeInnerX1 = isNaN(innerX1) || !isFinite(innerX1) ? 0 : innerX1;
13134
13204
  const safeInnerX2 = isNaN(innerX2) || !isFinite(innerX2) ? safeInnerX1 + safeWidth : innerX2;
13135
- let comparisonBarElement = null;
13205
+ tasksRes.push(
13206
+ /* @__PURE__ */ jsxRuntime.jsx(
13207
+ "svg",
13208
+ {
13209
+ id: task.id,
13210
+ className: `${styles$4.TaskItemWrapper} TaskItemWrapper`,
13211
+ x: Math.max(safeContainerX + (additionalLeftSpace || 0), 0),
13212
+ y: safeLevelY,
13213
+ width: Math.max(safeContainerWidth, 0),
13214
+ height: fullRowHeight,
13215
+ children: /* @__PURE__ */ jsxRuntime.jsx(
13216
+ TaskItem,
13217
+ {
13218
+ movingAction: taskBarMovingAction(task),
13219
+ allowMoveTaskBar,
13220
+ hasChildren: checkHasChildren(task, childTasksMap),
13221
+ progressWidth: safeProgressWidth,
13222
+ progressX: rtl ? safeInnerX2 : safeInnerX1,
13223
+ onSelectTaskOnMouseDown: selectTaskOnMouseDown,
13224
+ task,
13225
+ taskYOffset,
13226
+ width: safeWidth,
13227
+ x1: safeInnerX1,
13228
+ x2: safeInnerX2,
13229
+ distances,
13230
+ taskHeight,
13231
+ taskHalfHeight,
13232
+ isProgressChangeable: (t) => isProgressChangeable(t) && !waitCommitTasks,
13233
+ isDateChangeable: (t) => isDateChangeable(t) && !waitCommitTasks,
13234
+ isRelationChangeable: (t) => isRelationChangeable(t) && !waitCommitTasks,
13235
+ authorizedRelations,
13236
+ ganttRelationEvent,
13237
+ canDelete: !task.isDisabled && !waitCommitTasks,
13238
+ onDoubleClick,
13239
+ onClick,
13240
+ onEventStart: onTaskBarDragStart,
13241
+ onTooltipTask,
13242
+ onRelationStart: onTaskBarRelationStart,
13243
+ isSelected: Boolean(selectedIdsMirror[taskId]),
13244
+ isCritical,
13245
+ rtl,
13246
+ onDeleteTask,
13247
+ renderCustomLabel,
13248
+ viewMode,
13249
+ showProgress,
13250
+ progressColor
13251
+ }
13252
+ )
13253
+ },
13254
+ key2
13255
+ )
13256
+ );
13136
13257
  if (task.comparisonDates && comparisonDates) {
13137
13258
  const safeComparisonX = isNaN(comparisonDates.x) || !isFinite(comparisonDates.x) ? 0 : comparisonDates.x;
13138
- const safeComparisonWidth = isNaN(comparisonDates.width) || !isFinite(comparisonDates.width) ? 10 : Math.max(comparisonDates.width, 3);
13139
- const safeComparisonHeight = isNaN(comparisonDates.height) || !isFinite(comparisonDates.height) ? 6 : Math.max(comparisonDates.height, 3);
13140
- const comparisonRight = safeComparisonX + safeComparisonWidth;
13141
- const isVisible = comparisonRight >= 0 && safeComparisonX <= 1e4;
13142
- if (isVisible) {
13143
- const relativeComparisonX = safeComparisonX - safeContainerX;
13144
- comparisonBarElement = /* @__PURE__ */ jsxRuntime.jsx(
13145
- "g",
13259
+ const safeComparisonY = isNaN(comparisonDates.y) || !isFinite(comparisonDates.y) ? safeLevelY : comparisonDates.y;
13260
+ const safeComparisonWidth = isNaN(comparisonDates.width) || !isFinite(comparisonDates.width) ? 0 : Math.max(comparisonDates.width, 0);
13261
+ const safeComparisonHeight = isNaN(comparisonDates.height) || !isFinite(comparisonDates.height) ? 0 : Math.max(comparisonDates.height, 0);
13262
+ tasksRes.push(
13263
+ /* @__PURE__ */ jsxRuntime.jsx(
13264
+ "svg",
13146
13265
  {
13147
- transform: `translate(${relativeComparisonX}, ${taskHeight + 2})`,
13266
+ id: task.id + "_comparison",
13267
+ className: "TaskItemWrapperComparison",
13268
+ x: Math.max(safeComparisonX + (additionalLeftSpace || 0), 0),
13269
+ y: safeComparisonY,
13270
+ width: safeComparisonWidth,
13271
+ height: safeComparisonHeight * 2,
13148
13272
  children: /* @__PURE__ */ jsxRuntime.jsx(
13149
13273
  BarComparison,
13150
13274
  {
@@ -13156,70 +13280,16 @@
13156
13280
  height: safeComparisonHeight,
13157
13281
  width: safeComparisonWidth,
13158
13282
  borderHeight: distances.barComparisonTaskBorderHeight,
13159
- yOffset: 0,
13283
+ yOffset: distances.barComparisonTaskYOffset,
13160
13284
  task,
13161
13285
  onTooltipTask
13162
13286
  }
13163
13287
  )
13164
- }
13165
- );
13166
- }
13288
+ },
13289
+ key2 + "_comparison"
13290
+ )
13291
+ );
13167
13292
  }
13168
- tasksRes.push(
13169
- /* @__PURE__ */ jsxRuntime.jsxs(
13170
- "svg",
13171
- {
13172
- id: task.id,
13173
- className: `${styles$4.TaskItemWrapper} TaskItemWrapper`,
13174
- x: Math.max(safeContainerX + (additionalLeftSpace || 0), 0),
13175
- y: safeLevelY,
13176
- width: Math.max(safeContainerWidth, 0),
13177
- height: fullRowHeight,
13178
- children: [
13179
- /* @__PURE__ */ jsxRuntime.jsx(
13180
- TaskItem,
13181
- {
13182
- movingAction: taskBarMovingAction(task),
13183
- allowMoveTaskBar,
13184
- hasChildren: checkHasChildren(task, childTasksMap),
13185
- progressWidth: safeProgressWidth,
13186
- progressX: rtl ? safeInnerX2 : safeInnerX1,
13187
- onSelectTaskOnMouseDown: selectTaskOnMouseDown,
13188
- task,
13189
- taskYOffset,
13190
- width: safeWidth,
13191
- x1: safeInnerX1,
13192
- x2: safeInnerX2,
13193
- distances,
13194
- taskHeight,
13195
- taskHalfHeight,
13196
- isProgressChangeable: (t) => isProgressChangeable(t) && !waitCommitTasks,
13197
- isDateChangeable: (t) => isDateChangeable(t) && !waitCommitTasks,
13198
- isRelationChangeable: (t) => isRelationChangeable(t) && !waitCommitTasks,
13199
- authorizedRelations,
13200
- ganttRelationEvent,
13201
- canDelete: !task.isDisabled && !waitCommitTasks,
13202
- onDoubleClick,
13203
- onClick,
13204
- onEventStart: onTaskBarDragStart,
13205
- onTooltipTask,
13206
- onRelationStart: onTaskBarRelationStart,
13207
- isSelected: Boolean(selectedIdsMirror[taskId]),
13208
- isCritical,
13209
- rtl,
13210
- onDeleteTask,
13211
- renderCustomLabel,
13212
- viewMode,
13213
- showProgress,
13214
- progressColor
13215
- }
13216
- ),
13217
- comparisonBarElement
13218
- ]
13219
- },
13220
- key2
13221
- )
13222
- );
13223
13293
  const addedDependenciesAtLevel = addedDependencies[comparisonLevel] || {};
13224
13294
  if (!addedDependencies[comparisonLevel]) {
13225
13295
  addedDependencies[comparisonLevel] = addedDependenciesAtLevel;
@@ -13373,6 +13443,93 @@
13373
13443
  );
13374
13444
  }
13375
13445
  }
13446
+ const renderedTop = start * fullRowHeight;
13447
+ const renderedBottom = (end + 1) * fullRowHeight;
13448
+ for (const [comparisonLevel, dependenciesByLevel] of dependencyMap) {
13449
+ let addedDependenciesAtLevel = addedDependencies[comparisonLevel];
13450
+ if (!addedDependenciesAtLevel) {
13451
+ addedDependenciesAtLevel = {};
13452
+ addedDependencies[comparisonLevel] = addedDependenciesAtLevel;
13453
+ }
13454
+ const criticalPathOnLevel = criticalPaths ? criticalPaths.get(comparisonLevel) : void 0;
13455
+ for (const [taskId, dependencies] of dependenciesByLevel) {
13456
+ let addedDependenciesAtTask = addedDependenciesAtLevel[taskId];
13457
+ if (!addedDependenciesAtTask) {
13458
+ addedDependenciesAtTask = {};
13459
+ addedDependenciesAtLevel[taskId] = addedDependenciesAtTask;
13460
+ }
13461
+ const targetTask = taskById.get(taskId);
13462
+ if (!targetTask)
13463
+ continue;
13464
+ const criticalPathForTask = criticalPathOnLevel ? criticalPathOnLevel.dependencies.get(taskId) : void 0;
13465
+ dependencies.filter(({ source }) => {
13466
+ if (addedDependenciesAtTask[source.id])
13467
+ return false;
13468
+ if (!visibleTasksMirror[source.id])
13469
+ return false;
13470
+ return true;
13471
+ }).forEach(
13472
+ ({
13473
+ containerHeight,
13474
+ containerY,
13475
+ innerFromY,
13476
+ innerToY,
13477
+ ownTarget,
13478
+ source,
13479
+ sourceTarget
13480
+ }) => {
13481
+ if (containerY + containerHeight < renderedTop || containerY > renderedBottom) {
13482
+ return;
13483
+ }
13484
+ addedDependenciesAtTask[source.id] = true;
13485
+ const isCritical = criticalPathForTask ? criticalPathForTask.has(source.id) : false;
13486
+ const { x1: fromX1, x2: fromX2 } = getTaskCoordinates2(source);
13487
+ const { x1: targetX1, x2: targetX2 } = getTaskCoordinates2(targetTask);
13488
+ const safeFromX1 = isNaN(fromX1) || !isFinite(fromX1) ? 0 : fromX1;
13489
+ const safeFromX2 = isNaN(fromX2) || !isFinite(fromX2) ? safeFromX1 + 10 : fromX2;
13490
+ const safeTargetX1 = isNaN(targetX1) || !isFinite(targetX1) ? 0 : targetX1;
13491
+ const safeTargetX2 = isNaN(targetX2) || !isFinite(targetX2) ? safeTargetX1 + 10 : targetX2;
13492
+ const cX = Math.min(safeFromX1, safeTargetX1) - DELTA_RELATION_WIDTH;
13493
+ const cW = Math.max(safeFromX2, safeTargetX2) - cX + DELTA_RELATION_WIDTH;
13494
+ const safeCX = isNaN(cX) || !isFinite(cX) ? 0 : cX;
13495
+ const safeCW = isNaN(cW) || !isFinite(cW) ? 100 : Math.max(cW, 0);
13496
+ arrowsRes.push(
13497
+ /* @__PURE__ */ jsxRuntime.jsx(
13498
+ "svg",
13499
+ {
13500
+ x: Math.max(safeCX + (additionalLeftSpace || 0), 0),
13501
+ y: containerY,
13502
+ width: safeCW,
13503
+ height: containerHeight,
13504
+ children: /* @__PURE__ */ jsxRuntime.jsx(
13505
+ Arrow,
13506
+ {
13507
+ distances,
13508
+ taskFrom: source,
13509
+ targetFrom: sourceTarget,
13510
+ fromX1: safeFromX1 - safeCX,
13511
+ fromX2: safeFromX2 - safeCX,
13512
+ fromY: innerFromY,
13513
+ taskTo: targetTask,
13514
+ targetTo: ownTarget,
13515
+ toX1: safeTargetX1 - safeCX,
13516
+ toX2: safeTargetX2 - safeCX,
13517
+ toY: innerToY,
13518
+ fullRowHeight,
13519
+ taskHeight,
13520
+ isCritical,
13521
+ rtl,
13522
+ onArrowDoubleClick
13523
+ }
13524
+ )
13525
+ },
13526
+ `Arrow from ${source.id} to ${taskId} on ${comparisonLevel}`
13527
+ )
13528
+ );
13529
+ }
13530
+ );
13531
+ }
13532
+ }
13376
13533
  return [tasksRes, arrowsRes, selectedTasksRes];
13377
13534
  }, [
13378
13535
  viewMode,
@@ -13877,12 +14034,10 @@
13877
14034
  );
13878
14035
  cx1 = isNaN(cx1) || !isFinite(cx1) ? x1 : cx1;
13879
14036
  cx2 = isNaN(cx2) || !isFinite(cx2) ? x2 : cx2;
13880
- const comparisonWidth = Math.max(Math.abs(cx2 - cx1), 3);
13881
- const comparisonX = Math.min(cx1, cx2);
13882
14037
  comparisonDates = {
13883
- x: comparisonX,
14038
+ x: cx1,
13884
14039
  y: y + taskHeight,
13885
- width: comparisonWidth,
14040
+ width: Math.max(cx2 - cx1, 0),
13886
14041
  height: barComparisonTaskHeight
13887
14042
  };
13888
14043
  }
@@ -19076,7 +19231,8 @@
19076
19231
  todayLabel = "Today",
19077
19232
  dataDateLabel = "Data Date",
19078
19233
  showProgress = true,
19079
- progressColor
19234
+ progressColor,
19235
+ scrollToTaskId
19080
19236
  } = props;
19081
19237
  const ganttSVGRef = React.useRef(null);
19082
19238
  const wrapperRef = React.useRef(null);
@@ -19384,6 +19540,44 @@
19384
19540
  },
19385
19541
  [mapTaskToCoordinates, setScrollXProgrammatically]
19386
19542
  );
19543
+ const prevScrollToTaskIdRef = React.useRef(void 0);
19544
+ React.useEffect(() => {
19545
+ if (!scrollToTaskId || scrollToTaskId === prevScrollToTaskIdRef.current) {
19546
+ return;
19547
+ }
19548
+ prevScrollToTaskIdRef.current = scrollToTaskId;
19549
+ for (const [comparisonLevel, levelMap] of tasksMap) {
19550
+ const task = levelMap.get(scrollToTaskId);
19551
+ if (!task || task.type === "empty") {
19552
+ continue;
19553
+ }
19554
+ const { x1 } = getTaskCoordinates(task, mapTaskToCoordinates);
19555
+ setScrollXProgrammatically(Math.max(0, x1 - 100));
19556
+ const rowIndexMap = taskToRowIndexMap.get(comparisonLevel);
19557
+ if (rowIndexMap) {
19558
+ const rowIndex = rowIndexMap.get(scrollToTaskId);
19559
+ if (typeof rowIndex === "number") {
19560
+ const targetScrollY = rowIndex * fullRowHeight - ganttHeight / 2 + fullRowHeight / 2;
19561
+ setScrollYProgrammatically(
19562
+ Math.max(0, Math.min(targetScrollY, ganttFullHeight - ganttHeight))
19563
+ );
19564
+ }
19565
+ }
19566
+ selectTask(scrollToTaskId);
19567
+ break;
19568
+ }
19569
+ }, [
19570
+ scrollToTaskId,
19571
+ tasksMap,
19572
+ mapTaskToCoordinates,
19573
+ taskToRowIndexMap,
19574
+ fullRowHeight,
19575
+ ganttHeight,
19576
+ ganttFullHeight,
19577
+ setScrollXProgrammatically,
19578
+ setScrollYProgrammatically,
19579
+ selectTask
19580
+ ]);
19387
19581
  const { contextMenu, handleCloseContextMenu, handleOpenContextMenu } = useContextMenu(wrapperRef, scrollToTask);
19388
19582
  const [ganttContextMenu, setGanttContextMenu] = React.useState({
19389
19583
  task: null,
@@ -368,6 +368,11 @@ export interface GanttProps {
368
368
  * Custom color for progress bars. If not provided, theme progress colors are used.
369
369
  */
370
370
  progressColor?: string;
371
+ /**
372
+ * When set (or changed), the gantt will scroll to reveal and select this task.
373
+ * Set to a task id to scroll both horizontally and vertically to that task.
374
+ */
375
+ scrollToTaskId?: TaskId;
371
376
  }
372
377
  export interface GanttTaskBarActions {
373
378
  allowMoveTaskBar?: (action: TaskBarMoveAction, task: RenderTask) => boolean;
@@ -397,6 +402,11 @@ export type Column = {
397
402
  width: number;
398
403
  title?: ReactNode;
399
404
  canResize?: boolean;
405
+ /**
406
+ * Pin column to the left or right side of the table.
407
+ * Pinned columns stay visible while scrolling horizontally.
408
+ */
409
+ pinned?: "left" | "right";
400
410
  };
401
411
  export type OnResizeColumn = (nextColumns: readonly Column[], columnIndex: number, deltaWidth: number) => void;
402
412
  export type ChangeAction = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gantt-task-react-v",
3
- "version": "1.3.6",
3
+ "version": "1.4.1",
4
4
  "description": "Interactive Gantt Chart for React with TypeScript.",
5
5
  "author": "aguilanbon",
6
6
  "homepage": "https://github.com/aguilanbon/gantt-task-react-v",