gantt-lib 0.135.8 → 0.136.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
@@ -3,7 +3,7 @@
3
3
  "use client";
4
4
 
5
5
  // src/components/GanttChart/GanttChart.tsx
6
- import { useMemo as useMemo13, useCallback as useCallback10, useRef as useRef12, useState as useState12, useEffect as useEffect10, useImperativeHandle, forwardRef } from "react";
6
+ import { useMemo as useMemo13, useCallback as useCallback10, useRef as useRef13, useState as useState13, useEffect as useEffect11, useImperativeHandle, forwardRef } from "react";
7
7
 
8
8
  // src/core/scheduling/dateMath.ts
9
9
  var DAY_MS = 24 * 60 * 60 * 1e3;
@@ -3155,7 +3155,8 @@ var TimeScaleHeader = ({
3155
3155
  var TimeScaleHeader_default = TimeScaleHeader;
3156
3156
 
3157
3157
  // src/components/TaskRow/TaskRow.tsx
3158
- import React2, { useCallback as useCallback2, useMemo as useMemo2, useSyncExternalStore } from "react";
3158
+ import React2, { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2, useSyncExternalStore } from "react";
3159
+ import { createPortal } from "react-dom";
3159
3160
 
3160
3161
  // src/utils/geometry.ts
3161
3162
  var getUTCDayDifference = (date1, date2) => {
@@ -3241,19 +3242,20 @@ var pixelsToDate = (pixels, monthStart, dayWidth) => {
3241
3242
  var calculateGridWidth = (daysInMonth, dayWidth) => {
3242
3243
  return Math.round(daysInMonth * dayWidth);
3243
3244
  };
3245
+ var MIN_EDGE_ZONE_PX = 8;
3246
+ var MIN_MOVE_ZONE_PX = 16;
3244
3247
  var detectEdgeZone = (clientX, taskBarElement, edgeZoneWidth = 12) => {
3245
3248
  const rect = taskBarElement.getBoundingClientRect();
3246
3249
  const relativeX = Math.round(clientX - rect.left);
3247
3250
  const width = Math.round(rect.width);
3248
- if (width <= 2 * edgeZoneWidth) {
3249
- const distanceToLeft = relativeX;
3250
- const distanceToRight = width - relativeX;
3251
- return distanceToLeft <= distanceToRight ? "left" : "right";
3252
- }
3253
- if (relativeX >= 0 && relativeX <= edgeZoneWidth) {
3251
+ const effectiveZone = Math.max(
3252
+ MIN_EDGE_ZONE_PX,
3253
+ Math.min(edgeZoneWidth, (width - MIN_MOVE_ZONE_PX) / 2)
3254
+ );
3255
+ if (relativeX >= 0 && relativeX <= effectiveZone) {
3254
3256
  return "left";
3255
3257
  }
3256
- if (relativeX >= width - edgeZoneWidth && relativeX <= width) {
3258
+ if (relativeX >= width - effectiveZone && relativeX <= width) {
3257
3259
  return "right";
3258
3260
  }
3259
3261
  return "move";
@@ -3873,8 +3875,7 @@ var useTaskDrag = (options) => {
3873
3875
  locked = false,
3874
3876
  disableTaskDrag = false,
3875
3877
  businessDays = true,
3876
- weekendPredicate,
3877
- viewMode = "day"
3878
+ weekendPredicate
3878
3879
  } = options;
3879
3880
  const rawHookTask = allTasks.find((t) => t.id === taskId);
3880
3881
  const hookTask = rawHookTask ? normalizeTaskDatesForType(rawHookTask) : void 0;
@@ -4146,23 +4147,18 @@ var useTaskDrag = (options) => {
4146
4147
  if (effectiveLocked) return;
4147
4148
  const target = e.currentTarget;
4148
4149
  const currentTask = allTasks.find((t) => t.id === taskId);
4149
- const isSingleDayTask = !!currentTask && !isMilestoneTask(currentTask) && !isTaskParent(taskId, allTasks) && currentWidth <= dayWidth && viewMode === "day";
4150
4150
  let mode = null;
4151
- if (isSingleDayTask) {
4152
- mode = "move";
4153
- } else {
4154
- const edgeZone = detectEdgeZone(e.clientX, target, edgeZoneWidth);
4155
- switch (edgeZone) {
4156
- case "left":
4157
- mode = "resize-left";
4158
- break;
4159
- case "right":
4160
- mode = "resize-right";
4161
- break;
4162
- case "move":
4163
- mode = "move";
4164
- break;
4165
- }
4151
+ const edgeZone = detectEdgeZone(e.clientX, target, edgeZoneWidth);
4152
+ switch (edgeZone) {
4153
+ case "left":
4154
+ mode = "resize-left";
4155
+ break;
4156
+ case "right":
4157
+ mode = "resize-right";
4158
+ break;
4159
+ case "move":
4160
+ mode = "move";
4161
+ break;
4166
4162
  }
4167
4163
  if (mode === "resize-left" || mode === "resize-right") {
4168
4164
  if (currentTask && isMilestoneTask(currentTask)) {
@@ -4212,7 +4208,7 @@ var useTaskDrag = (options) => {
4212
4208
  cascadeContext: shouldBuildCascadeContext ? createCascadeContext(allTasks) : void 0,
4213
4209
  scheduleIntentMode: Boolean(onScheduleIntent)
4214
4210
  };
4215
- }, [edgeZoneWidth, currentLeft, currentWidth, dayWidth, monthStart, taskId, onDragStateChange, handleProgress, handleComplete, handleCancel, allTasks, disableConstraints, deferCascadePreview, onCascadeProgress, onCascade, effectiveLocked, viewMode]);
4211
+ }, [edgeZoneWidth, currentLeft, currentWidth, dayWidth, monthStart, taskId, onDragStateChange, handleProgress, handleComplete, handleCancel, allTasks, disableConstraints, deferCascadePreview, onCascadeProgress, onCascade, effectiveLocked]);
4216
4212
  const handleMouseDown = useCallback((e) => {
4217
4213
  startDrag(e);
4218
4214
  }, [startDrag]);
@@ -4231,14 +4227,13 @@ var useTaskDrag = (options) => {
4231
4227
  }
4232
4228
  const target = e.currentTarget;
4233
4229
  const currentTask = allTasks.find((t) => t.id === taskId);
4234
- const isSingleDayTask = !!currentTask && !isMilestoneTask(currentTask) && !isTaskParent(taskId, allTasks) && currentWidth <= dayWidth && viewMode === "day";
4235
- if (isSingleDayTask || currentTask && isMilestoneTask(currentTask)) {
4230
+ if (currentTask && isMilestoneTask(currentTask)) {
4236
4231
  setHoverCursor("grab");
4237
4232
  return;
4238
4233
  }
4239
4234
  const edgeZone = detectEdgeZone(e.clientX, target, edgeZoneWidth);
4240
4235
  setHoverCursor(edgeZone === "move" ? "grab" : "ew-resize");
4241
- }, [disableTaskDrag, locked, isDragging, allTasks, taskId, currentWidth, dayWidth, viewMode, edgeZoneWidth]);
4236
+ }, [disableTaskDrag, locked, isDragging, allTasks, taskId, edgeZoneWidth]);
4242
4237
  const handleMouseLeave = useCallback(() => {
4243
4238
  setHoverCursor(disableTaskDrag ? "grab" : locked ? "not-allowed" : isDragging ? "grabbing" : "grab");
4244
4239
  }, [disableTaskDrag, locked, isDragging]);
@@ -4269,12 +4264,35 @@ var useTaskDrag = (options) => {
4269
4264
 
4270
4265
  // src/components/TaskRow/TaskRow.tsx
4271
4266
  import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
4267
+ var formatCompositePreviewDate = (date) => {
4268
+ const month = new Intl.DateTimeFormat("ru-RU", { month: "short", timeZone: "UTC" }).format(date).replace(/\.$/, "");
4269
+ const yearSuffix = date.getUTCFullYear() === (/* @__PURE__ */ new Date()).getFullYear() ? "" : ` ${String(date.getUTCFullYear()).slice(-2)}`;
4270
+ return `${date.getUTCDate()} ${month}${yearSuffix}`;
4271
+ };
4272
4272
  var arePropsEqual = (prevProps, nextProps) => {
4273
- return prevProps.task.id === nextProps.task.id && prevProps.task.name === nextProps.task.name && prevProps.task.startDate === nextProps.task.startDate && prevProps.task.endDate === nextProps.task.endDate && prevProps.task.baselineStartDate === nextProps.task.baselineStartDate && prevProps.task.baselineEndDate === nextProps.task.baselineEndDate && prevProps.task.type === nextProps.task.type && prevProps.task.color === nextProps.task.color && prevProps.task.progress === nextProps.task.progress && prevProps.task.accepted === nextProps.task.accepted && prevProps.monthStart.getTime() === nextProps.monthStart.getTime() && prevProps.dayWidth === nextProps.dayWidth && prevProps.rowHeight === nextProps.rowHeight && prevProps.overridePosition?.left === nextProps.overridePosition?.left && prevProps.overridePosition?.width === nextProps.overridePosition?.width && prevProps.previewPositionStore === nextProps.previewPositionStore && prevProps.allTasks === nextProps.allTasks && prevProps.disableConstraints === nextProps.disableConstraints && prevProps.deferCascadePreview === nextProps.deferCascadePreview && prevProps.task.locked === nextProps.task.locked && prevProps.task.synced === nextProps.task.synced && prevProps.task.divider === nextProps.task.divider && prevProps.highlightExpiredTasks === nextProps.highlightExpiredTasks && prevProps.isCritical === nextProps.isCritical && prevProps.showBaseline === nextProps.showBaseline && prevProps.isFilterMatch === nextProps.isFilterMatch && prevProps.businessDays === nextProps.businessDays && prevProps.customDays === nextProps.customDays && prevProps.isWeekend === nextProps.isWeekend && prevProps.disableTaskDrag === nextProps.disableTaskDrag && prevProps.disableDependencyEditing === nextProps.disableDependencyEditing && prevProps.isDependencyDragActive === nextProps.isDependencyDragActive && prevProps.viewMode === nextProps.viewMode && prevProps.showTaskDateLabels === nextProps.showTaskDateLabels && prevProps.showTaskNames === nextProps.showTaskNames;
4273
+ return prevProps.task.id === nextProps.task.id && prevProps.task.name === nextProps.task.name && prevProps.task.startDate === nextProps.task.startDate && prevProps.task.endDate === nextProps.task.endDate && prevProps.task.baselineStartDate === nextProps.task.baselineStartDate && prevProps.task.baselineEndDate === nextProps.task.baselineEndDate && prevProps.task.type === nextProps.task.type && prevProps.task.color === nextProps.task.color && prevProps.task.progress === nextProps.task.progress && prevProps.task.accepted === nextProps.task.accepted && prevProps.monthStart.getTime() === nextProps.monthStart.getTime() && prevProps.dayWidth === nextProps.dayWidth && prevProps.rowHeight === nextProps.rowHeight && prevProps.overridePosition?.left === nextProps.overridePosition?.left && prevProps.overridePosition?.width === nextProps.overridePosition?.width && prevProps.previewPositionStore === nextProps.previewPositionStore && prevProps.allTasks === nextProps.allTasks && prevProps.disableConstraints === nextProps.disableConstraints && prevProps.deferCascadePreview === nextProps.deferCascadePreview && prevProps.task.locked === nextProps.task.locked && prevProps.task.synced === nextProps.task.synced && prevProps.task.divider === nextProps.task.divider && prevProps.highlightExpiredTasks === nextProps.highlightExpiredTasks && prevProps.isCritical === nextProps.isCritical && prevProps.showBaseline === nextProps.showBaseline && prevProps.onCompositeToggle === nextProps.onCompositeToggle && prevProps.compactDetail === nextProps.compactDetail && prevProps.compositeParentColor === nextProps.compositeParentColor && prevProps.compositeExpanded === nextProps.compositeExpanded && prevProps.isFilterMatch === nextProps.isFilterMatch && prevProps.businessDays === nextProps.businessDays && prevProps.customDays === nextProps.customDays && prevProps.isWeekend === nextProps.isWeekend && prevProps.disableTaskDrag === nextProps.disableTaskDrag && prevProps.disableDependencyEditing === nextProps.disableDependencyEditing && prevProps.isDependencyDragActive === nextProps.isDependencyDragActive && prevProps.viewMode === nextProps.viewMode && prevProps.showTaskDateLabels === nextProps.showTaskDateLabels && prevProps.showTaskNames === nextProps.showTaskNames;
4274
4274
  };
4275
4275
  var TaskRow = React2.memo(
4276
- ({ task, monthStart, dayWidth, rowHeight, onTasksChange, onScheduleIntent, onDragStateChange, rowIndex, allTasks, enableAutoSchedule, disableConstraints, deferCascadePreview = false, overridePosition, previewPositionStore, onCascadeProgress, onCascade, divider, highlightExpiredTasks, isCritical = false, showBaseline = false, isFilterMatch = false, businessDays, customDays, isWeekend: isWeekend3, disableTaskDrag = false, disableDependencyEditing = false, onDependencyPortPointerDown, isDependencyDragActive = false, viewMode = "day", showTaskDateLabels = true, showTaskNames = true }) => {
4276
+ ({ task, monthStart, dayWidth, rowHeight, onTasksChange, onScheduleIntent, onDragStateChange, rowIndex, allTasks, enableAutoSchedule, disableConstraints, deferCascadePreview = false, overridePosition, previewPositionStore, onCascadeProgress, onCascade, divider, highlightExpiredTasks, isCritical = false, showBaseline = false, isFilterMatch = false, businessDays, customDays, isWeekend: isWeekend3, disableTaskDrag = false, disableDependencyEditing = false, onDependencyPortPointerDown, isDependencyDragActive = false, viewMode = "day", showTaskDateLabels = true, showTaskNames = true, onCompositeToggle, compactDetail = false, compositeParentColor, compositeExpanded = false }) => {
4277
4277
  const defaultParentBarColor = "#782FC4";
4278
+ const [showCompositePreview, setShowCompositePreview] = useState2(false);
4279
+ const [compositePreviewPosition, setCompositePreviewPosition] = useState2(null);
4280
+ const pointerStart = useRef2(null);
4281
+ const suppressCompositeClick = useRef2(false);
4282
+ const compositeChildren = useMemo2(
4283
+ () => task.composite ? (allTasks ?? []).filter((child) => child.parentId === task.id) : [],
4284
+ [allTasks, task.composite, task.id]
4285
+ );
4286
+ useEffect2(() => {
4287
+ if (!showCompositePreview) return;
4288
+ const hide = () => setShowCompositePreview(false);
4289
+ window.addEventListener("scroll", hide, true);
4290
+ window.addEventListener("resize", hide);
4291
+ return () => {
4292
+ window.removeEventListener("scroll", hide, true);
4293
+ window.removeEventListener("resize", hide);
4294
+ };
4295
+ }, [showCompositePreview]);
4278
4296
  const { divider: taskDivider } = task;
4279
4297
  const normalizedTask = useMemo2(() => normalizeTaskDatesForType(task), [task]);
4280
4298
  const milestone = useMemo2(() => isMilestoneTask(normalizedTask), [normalizedTask]);
@@ -4291,6 +4309,7 @@ var TaskRow = React2.memo(
4291
4309
  const isParent = useMemo2(() => {
4292
4310
  return allTasks ? isTaskParent(task.id, allTasks) : false;
4293
4311
  }, [allTasks, task.id]);
4312
+ const isVisualParent = isParent && !task.composite;
4294
4313
  const childCount = useMemo2(() => {
4295
4314
  return allTasks ? getChildren(task.id, allTasks).length : 0;
4296
4315
  }, [allTasks, task.id]);
@@ -4336,19 +4355,19 @@ var TaskRow = React2.memo(
4336
4355
  if (isExpired) {
4337
4356
  return "color-mix(in srgb, var(--gantt-expired-color) 40%, black)";
4338
4357
  }
4339
- const baseColor = isParent ? task.color || defaultParentBarColor : task.color || "var(--gantt-task-bar-default-color)";
4358
+ const baseColor = isVisualParent ? task.color || defaultParentBarColor : task.color || "var(--gantt-task-bar-default-color)";
4340
4359
  return `color-mix(in srgb, ${baseColor} 40%, black)`;
4341
- }, [defaultParentBarColor, isCritical, isExpired, isParent, task.color]);
4360
+ }, [defaultParentBarColor, isCritical, isExpired, isVisualParent, task.color]);
4342
4361
  const barStyle = useMemo2(() => {
4343
4362
  const parentBarColor = task.color || defaultParentBarColor;
4344
4363
  if (isCritical && !isExpired) {
4345
4364
  const c = criticalBarColor;
4346
- if (isParent) {
4365
+ if (isVisualParent) {
4347
4366
  return { backgroundColor: c, "--gantt-parent-bar-color": c };
4348
4367
  }
4349
4368
  return { backgroundColor: c };
4350
4369
  }
4351
- if (isParent) {
4370
+ if (isVisualParent) {
4352
4371
  if (progressWidth >= 100) {
4353
4372
  const c = isExpired ? "color-mix(in srgb, var(--gantt-expired-color) 40%, black)" : `color-mix(in srgb, ${parentBarColor} 40%, black)`;
4354
4373
  return { backgroundColor: c, "--gantt-parent-bar-color": c };
@@ -4359,7 +4378,7 @@ var TaskRow = React2.memo(
4359
4378
  return { backgroundColor: progressColor };
4360
4379
  }
4361
4380
  return { backgroundColor: barColor };
4362
- }, [isCritical, defaultParentBarColor, isExpired, isParent, progressWidth, barColor, progressColor, task.color]);
4381
+ }, [isCritical, defaultParentBarColor, isExpired, isVisualParent, progressWidth, barColor, progressColor, task.color]);
4363
4382
  const handleDragEnd = (result) => {
4364
4383
  const updatedTask = {
4365
4384
  ...normalizedTask,
@@ -4433,6 +4452,25 @@ var TaskRow = React2.memo(
4433
4452
  }, [displayLeft, dayWidth, milestoneGeometry.size]);
4434
4453
  const visualLeft = milestone ? displayMilestoneGeometry.left : displayLeft;
4435
4454
  const visualWidth = milestone ? displayMilestoneGeometry.size : displayWidth;
4455
+ const compositeSegments = useMemo2(() => {
4456
+ if (!task.composite || width <= 0) return [];
4457
+ const laneEnds = [];
4458
+ return compositeChildren.map((child) => {
4459
+ const segment = calculateTaskBar(parseUTCDate(child.startDate), parseUTCDate(child.endDate), monthStart, dayWidth);
4460
+ let lane = laneEnds.findIndex((end) => end <= segment.left);
4461
+ if (lane < 0) lane = laneEnds.length;
4462
+ laneEnds[lane] = segment.left + segment.width;
4463
+ return {
4464
+ id: child.id,
4465
+ name: child.name,
4466
+ left: (segment.left - left) / width * 100,
4467
+ width: segment.width / width * 100,
4468
+ lane
4469
+ };
4470
+ });
4471
+ }, [task.composite, compositeChildren, monthStart, dayWidth, left, width, displayLeft]);
4472
+ const compositeLaneCount = Math.max(1, ...compositeSegments.map((segment) => segment.lane + 1));
4473
+ const showCompositeSegments = task.composite && compositeSegments.length > 0 && visualWidth / compositeSegments.length >= 22;
4436
4474
  const shouldRenderBaseline = showBaseline && baselineGeometry !== null;
4437
4475
  const hasPreviewPosition = isDragging || effectiveOverridePosition !== void 0;
4438
4476
  const currentStartDate = hasPreviewPosition ? pixelsToDate(displayLeft, monthStart, dayWidth) : taskStartDate;
@@ -4450,17 +4488,20 @@ var TaskRow = React2.memo(
4450
4488
  if (lastDigit >= 2 && lastDigit <= 4) return `${count} \u0440\u0430\u0431\u043E\u0442\u044B`;
4451
4489
  return `${count} \u0440\u0430\u0431\u043E\u0442`;
4452
4490
  };
4453
- const estimatedTextWidth = isParent ? 120 : durationDays >= 10 ? 76 : 62;
4454
- const showProgressInside = !milestone && progressWidth > 0 && displayWidth > estimatedTextWidth;
4455
- const MIN_DURATION_WIDTH = isParent ? 80 : 50;
4456
- const showDurationInside = !milestone && durationDays >= 2 && displayWidth > MIN_DURATION_WIDTH;
4491
+ const estimatedTextWidth = isVisualParent ? 120 : durationDays >= 10 ? 76 : 62;
4492
+ const showProgressInside = !milestone && progressWidth > 0 && displayWidth > (compactDetail ? 28 : estimatedTextWidth);
4493
+ const MIN_DURATION_WIDTH = isVisualParent ? 80 : task.composite ? 92 : 50;
4494
+ const showDurationInside = !compactDetail && !milestone && durationDays >= 2 && displayWidth > MIN_DURATION_WIDTH;
4457
4495
  return /* @__PURE__ */ jsxs2(
4458
4496
  "div",
4459
4497
  {
4460
4498
  "data-filter-match": isFilterMatch ? "true" : "false",
4461
4499
  "data-gantt-task-row-id": task.id,
4462
- className: `gantt-tr-row ${isFilterMatch ? "gantt-tr-row-filter-match" : ""}`,
4463
- style: { height: `${rowHeight}px` },
4500
+ className: `gantt-tr-row ${compactDetail ? "gantt-tr-row-compositeDetail" : ""} ${isFilterMatch ? "gantt-tr-row-filter-match" : ""}`,
4501
+ style: {
4502
+ height: `${rowHeight}px`,
4503
+ "--gantt-tr-composite-detail-color": compositeParentColor ?? "var(--gantt-task-bar-default-color, #8b5cf6)"
4504
+ },
4464
4505
  children: [
4465
4506
  taskDivider === "top" && /* @__PURE__ */ jsx2("div", { className: "gantt-tr-divider gantt-tr-divider-top" }),
4466
4507
  /* @__PURE__ */ jsxs2("div", { className: "gantt-tr-taskContainer", children: [
@@ -4478,7 +4519,11 @@ var TaskRow = React2.memo(
4478
4519
  "div",
4479
4520
  {
4480
4521
  "data-taskbar": true,
4481
- className: `gantt-tr-taskBar ${isDragging ? "gantt-tr-dragging" : ""} ${task.locked ? "gantt-tr-locked" : ""} ${task.synced === false ? "gantt-tr-unsynced" : ""} ${isParent ? "gantt-tr-parentBar" : ""} ${milestone ? "gantt-tr-milestone" : ""} ${isCritical ? "gantt-tr-critical" : ""}`,
4522
+ role: task.composite ? "button" : void 0,
4523
+ tabIndex: task.composite ? 0 : void 0,
4524
+ "aria-label": task.composite ? `${task.name}: ${compositeExpanded ? "\u0441\u0432\u0435\u0440\u043D\u0443\u0442\u044C" : "\u0440\u0430\u0441\u043A\u0440\u044B\u0442\u044C"} \u0434\u0435\u0442\u0430\u043B\u0438` : void 0,
4525
+ "aria-expanded": task.composite ? compositeExpanded : void 0,
4526
+ className: `gantt-tr-taskBar ${isDragging ? "gantt-tr-dragging" : ""} ${task.locked ? "gantt-tr-locked" : ""} ${task.synced === false ? "gantt-tr-unsynced" : ""} ${isVisualParent ? "gantt-tr-parentBar" : ""} ${task.composite ? "gantt-tr-compositeBar" : ""} ${milestone ? "gantt-tr-milestone" : ""} ${isCritical ? "gantt-tr-critical" : ""}`,
4482
4527
  style: {
4483
4528
  left: `${visualLeft}px`,
4484
4529
  ...barStyle,
@@ -4488,14 +4533,57 @@ var TaskRow = React2.memo(
4488
4533
  padding: 0
4489
4534
  } : {
4490
4535
  width: `${visualWidth}px`,
4491
- height: isParent ? "var(--gantt-parent-bar-height, 14px)" : "var(--gantt-task-bar-height)"
4536
+ height: compactDetail ? "18px" : isVisualParent ? "var(--gantt-parent-bar-height, 14px)" : "var(--gantt-task-bar-height)"
4492
4537
  },
4493
4538
  cursor: dragHandleProps.style.cursor,
4494
4539
  userSelect: dragHandleProps.style.userSelect
4495
4540
  },
4496
4541
  onMouseDown: dragHandleProps.onMouseDown,
4497
4542
  onMouseMove: dragHandleProps.onMouseMove,
4498
- onMouseLeave: dragHandleProps.onMouseLeave,
4543
+ onMouseLeave: () => {
4544
+ dragHandleProps.onMouseLeave();
4545
+ setShowCompositePreview(false);
4546
+ },
4547
+ onMouseEnter: task.composite ? (event) => {
4548
+ const rect = event.currentTarget.getBoundingClientRect();
4549
+ const previewWidth = 220;
4550
+ const previewHeight = Math.min(340, 68 + Math.min(compositeChildren.length, 12) * 20);
4551
+ setCompositePreviewPosition({
4552
+ left: Math.max(8, Math.min(rect.left, window.innerWidth - previewWidth - 8)),
4553
+ top: rect.bottom + previewHeight + 8 < window.innerHeight ? rect.bottom + 6 : Math.max(8, rect.top - previewHeight - 6)
4554
+ });
4555
+ setShowCompositePreview(true);
4556
+ } : void 0,
4557
+ onMouseDownCapture: task.composite ? (event) => {
4558
+ suppressCompositeClick.current = Boolean(event.target.closest(".gantt-tr-resizeHandle"));
4559
+ pointerStart.current = { x: event.clientX, y: event.clientY };
4560
+ } : void 0,
4561
+ onMouseMoveCapture: task.composite ? (event) => {
4562
+ const start = pointerStart.current;
4563
+ if (start && Math.hypot(event.clientX - start.x, event.clientY - start.y) >= 4) {
4564
+ suppressCompositeClick.current = true;
4565
+ }
4566
+ } : void 0,
4567
+ onMouseUpCapture: task.composite ? (event) => {
4568
+ const start = pointerStart.current;
4569
+ if (start && Math.hypot(event.clientX - start.x, event.clientY - start.y) >= 4) {
4570
+ suppressCompositeClick.current = true;
4571
+ }
4572
+ pointerStart.current = null;
4573
+ } : void 0,
4574
+ onClick: task.composite ? () => {
4575
+ if (!isDragging && !suppressCompositeClick.current) {
4576
+ setShowCompositePreview(false);
4577
+ onCompositeToggle?.("click");
4578
+ }
4579
+ suppressCompositeClick.current = false;
4580
+ } : void 0,
4581
+ onKeyDown: task.composite ? (event) => {
4582
+ if (event.key === "Enter" || event.key === " ") {
4583
+ event.preventDefault();
4584
+ onCompositeToggle?.("keyboard");
4585
+ }
4586
+ } : void 0,
4499
4587
  children: [
4500
4588
  !milestone && progressWidth > 0 && progressWidth < 100 && /* @__PURE__ */ jsx2(
4501
4589
  "div",
@@ -4504,14 +4592,28 @@ var TaskRow = React2.memo(
4504
4592
  style: {
4505
4593
  width: `${progressWidth}%`,
4506
4594
  backgroundColor: progressColor,
4507
- ...isParent && {
4595
+ ...isVisualParent && {
4508
4596
  borderRadius: "var(--gantt-parent-bar-radius, 8px) 0 0 0"
4509
4597
  }
4510
4598
  }
4511
4599
  }
4512
4600
  ),
4513
4601
  !milestone && /* @__PURE__ */ jsx2("div", { className: "gantt-tr-resizeHandle gantt-tr-resizeHandleLeft" }),
4514
- showDurationInside && /* @__PURE__ */ jsx2("span", { className: "gantt-tr-taskDuration", children: isParent ? getChildCountLabel(childCount) : `${durationDays} \u0434` }),
4602
+ showCompositeSegments && /* @__PURE__ */ jsx2("span", { className: "gantt-tr-compositeSegments", "aria-hidden": "true", children: compositeSegments.map((segment) => /* @__PURE__ */ jsx2(
4603
+ "span",
4604
+ {
4605
+ className: "gantt-tr-compositeSegment",
4606
+ style: {
4607
+ left: `${segment.left}%`,
4608
+ width: `${segment.width}%`,
4609
+ top: `${segment.lane / compositeLaneCount * 100}%`,
4610
+ height: `${100 / compositeLaneCount}%`
4611
+ }
4612
+ },
4613
+ segment.id
4614
+ )) }),
4615
+ task.composite && /* @__PURE__ */ jsx2("span", { className: "gantt-tr-compositeHoverChevron", "aria-hidden": "true", children: /* @__PURE__ */ jsx2("svg", { className: `gantt-tr-compositeChevron${compositeExpanded ? " gantt-tr-compositeChevron-open" : ""}`, viewBox: "0 0 20 20", children: /* @__PURE__ */ jsx2("path", { d: "m7 4 6 6-6 6", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }),
4616
+ showDurationInside && /* @__PURE__ */ jsx2("span", { className: "gantt-tr-taskDuration", children: isVisualParent ? getChildCountLabel(childCount) : `${durationDays} \u0434` }),
4515
4617
  progressWidth > 0 && showProgressInside && /* @__PURE__ */ jsxs2("span", { className: "gantt-tr-progressText", children: [
4516
4618
  progressWidth,
4517
4619
  "%"
@@ -4520,6 +4622,41 @@ var TaskRow = React2.memo(
4520
4622
  ]
4521
4623
  }
4522
4624
  ),
4625
+ task.composite && showCompositePreview && compositePreviewPosition && compositeChildren.length > 0 && !isDragging && typeof document !== "undefined" && createPortal(
4626
+ /* @__PURE__ */ jsxs2(
4627
+ "div",
4628
+ {
4629
+ className: "gantt-tr-compositePreview",
4630
+ style: { left: `${compositePreviewPosition.left}px`, top: `${compositePreviewPosition.top}px` },
4631
+ role: "tooltip",
4632
+ children: [
4633
+ /* @__PURE__ */ jsxs2("div", { className: "gantt-tr-compositePreviewAxis", children: [
4634
+ /* @__PURE__ */ jsx2("span", { children: formatCompositePreviewDate(currentStartDate) }),
4635
+ /* @__PURE__ */ jsx2("span", { children: formatCompositePreviewDate(currentEndDate) })
4636
+ ] }),
4637
+ compositeChildren.slice(0, 12).map((child) => /* @__PURE__ */ jsxs2("div", { className: "gantt-tr-compositePreviewRow", children: [
4638
+ /* @__PURE__ */ jsx2("span", { children: child.name }),
4639
+ /* @__PURE__ */ jsx2("span", { className: "gantt-tr-compositePreviewTrack", children: /* @__PURE__ */ jsx2(
4640
+ "span",
4641
+ {
4642
+ className: "gantt-tr-compositePreviewBar",
4643
+ style: {
4644
+ left: `${Math.max(0, (parseUTCDate(child.startDate).getTime() - taskStartDate.getTime()) / (taskEndDate.getTime() - taskStartDate.getTime() + 864e5) * 100)}%`,
4645
+ width: `${Math.max(2, (parseUTCDate(child.endDate).getTime() - parseUTCDate(child.startDate).getTime() + 864e5) / (taskEndDate.getTime() - taskStartDate.getTime() + 864e5) * 100)}%`,
4646
+ backgroundColor: barColor
4647
+ }
4648
+ }
4649
+ ) })
4650
+ ] }, child.id)),
4651
+ compositeChildren.length > 12 && /* @__PURE__ */ jsxs2("div", { children: [
4652
+ "+",
4653
+ compositeChildren.length - 12
4654
+ ] })
4655
+ ]
4656
+ }
4657
+ ),
4658
+ document.body
4659
+ ),
4523
4660
  !disableDependencyEditing && onDependencyPortPointerDown && /* @__PURE__ */ jsxs2(Fragment, { children: [
4524
4661
  /* @__PURE__ */ jsx2(
4525
4662
  "button",
@@ -4528,8 +4665,8 @@ var TaskRow = React2.memo(
4528
4665
  "data-gantt-dependency-port": true,
4529
4666
  "data-task-id": task.id,
4530
4667
  "data-port-side": "left",
4531
- className: `gantt-tr-dependencyPort gantt-tr-dependencyPortLeft ${milestone ? "gantt-tr-dependencyPortMilestone" : ""} ${isParent ? "gantt-tr-dependencyPortParent" : ""} ${isDependencyDragActive ? "gantt-tr-dependencyPortDragActive" : ""}`,
4532
- style: { left: `${visualLeft - 21}px` },
4668
+ className: `gantt-tr-dependencyPort gantt-tr-dependencyPortLeft ${milestone ? "gantt-tr-dependencyPortMilestone" : ""} ${isVisualParent ? "gantt-tr-dependencyPortParent" : ""} ${isDependencyDragActive ? "gantt-tr-dependencyPortDragActive" : ""}`,
4669
+ style: { left: `${visualLeft - 24}px` },
4533
4670
  "aria-label": `\u041D\u0430\u0447\u0430\u0442\u044C \u0441\u0432\u044F\u0437\u044C \u043E\u0442 \u043B\u0435\u0432\u043E\u0433\u043E \u043A\u0440\u0430\u044F: ${task.name}`,
4534
4671
  title: "\u041F\u043E\u0442\u044F\u043D\u0438\u0442\u0435 \u043A \u043A\u0440\u0430\u044E \u0434\u0440\u0443\u0433\u043E\u0439 \u043F\u043E\u043B\u043E\u0441\u044B, \u0447\u0442\u043E\u0431\u044B \u0441\u043E\u0437\u0434\u0430\u0442\u044C \u0441\u0432\u044F\u0437\u044C",
4535
4672
  onPointerDown: (event) => onDependencyPortPointerDown(task.id, "left", event)
@@ -4542,7 +4679,7 @@ var TaskRow = React2.memo(
4542
4679
  "data-gantt-dependency-port": true,
4543
4680
  "data-task-id": task.id,
4544
4681
  "data-port-side": "right",
4545
- className: `gantt-tr-dependencyPort gantt-tr-dependencyPortRight ${milestone ? "gantt-tr-dependencyPortMilestone" : ""} ${isParent ? "gantt-tr-dependencyPortParent" : ""} ${isDependencyDragActive ? "gantt-tr-dependencyPortDragActive" : ""}`,
4682
+ className: `gantt-tr-dependencyPort gantt-tr-dependencyPortRight ${milestone ? "gantt-tr-dependencyPortMilestone" : ""} ${isVisualParent ? "gantt-tr-dependencyPortParent" : ""} ${isDependencyDragActive ? "gantt-tr-dependencyPortDragActive" : ""}`,
4546
4683
  style: { left: `${visualLeft + visualWidth}px` },
4547
4684
  "aria-label": `\u041D\u0430\u0447\u0430\u0442\u044C \u0441\u0432\u044F\u0437\u044C \u043E\u0442 \u043F\u0440\u0430\u0432\u043E\u0433\u043E \u043A\u0440\u0430\u044F: ${task.name}`,
4548
4685
  title: "\u041F\u043E\u0442\u044F\u043D\u0438\u0442\u0435 \u043A \u043A\u0440\u0430\u044E \u0434\u0440\u0443\u0433\u043E\u0439 \u043F\u043E\u043B\u043E\u0441\u044B, \u0447\u0442\u043E\u0431\u044B \u0441\u043E\u0437\u0434\u0430\u0442\u044C \u0441\u0432\u044F\u0437\u044C",
@@ -4581,6 +4718,14 @@ var TaskRow = React2.memo(
4581
4718
  children: /* @__PURE__ */ jsx2("path", { d: "M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zM12 17c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z" })
4582
4719
  }
4583
4720
  ),
4721
+ !showDurationInside && !milestone && /* @__PURE__ */ jsx2(
4722
+ "span",
4723
+ {
4724
+ className: `gantt-tr-externalDuration gantt-tr-durationBeforeBar${compactDetail ? " gantt-tr-compositeDuration" : ""}`,
4725
+ style: { left: `${visualLeft - 46}px`, color: isParent ? task.color || defaultParentBarColor : barColor },
4726
+ children: isVisualParent ? getChildCountLabel(childCount) : `${durationDays} \u0434`
4727
+ }
4728
+ ),
4584
4729
  /* @__PURE__ */ jsxs2(
4585
4730
  "div",
4586
4731
  {
@@ -4590,7 +4735,6 @@ var TaskRow = React2.memo(
4590
4735
  color: isParent ? task.color || defaultParentBarColor : barColor
4591
4736
  },
4592
4737
  children: [
4593
- !showDurationInside && !milestone && /* @__PURE__ */ jsx2("span", { className: "gantt-tr-externalDuration", children: isParent ? getChildCountLabel(childCount) : `${durationDays} \u0434` }),
4594
4738
  progressWidth > 0 && !showProgressInside && /* @__PURE__ */ jsxs2("span", { className: "gantt-tr-externalProgress", children: [
4595
4739
  progressWidth,
4596
4740
  "%"
@@ -4855,7 +4999,7 @@ var DragGuideLines = ({
4855
4999
  var DragGuideLines_default = DragGuideLines;
4856
5000
 
4857
5001
  // src/components/DependencyLines/DependencyLines.tsx
4858
- import React6, { useMemo as useMemo6, useState as useState2 } from "react";
5002
+ import React6, { useMemo as useMemo6, useState as useState3 } from "react";
4859
5003
  import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
4860
5004
  function isTaskHidden(taskId, collapsedParentIds, taskMap) {
4861
5005
  const task = taskMap.get(taskId);
@@ -4894,6 +5038,8 @@ var DependencyLines = React6.memo(({
4894
5038
  gridWidth,
4895
5039
  totalHeight,
4896
5040
  rowIndexByTaskId,
5041
+ rowTops,
5042
+ rowHeights,
4897
5043
  dragOverrides,
4898
5044
  selectedDep,
4899
5045
  criticalTaskIds,
@@ -4901,7 +5047,7 @@ var DependencyLines = React6.memo(({
4901
5047
  weekendPredicate,
4902
5048
  onDependencyClick
4903
5049
  }) => {
4904
- const [hoveredLineId, setHoveredLineId] = useState2(null);
5050
+ const [hoveredLineId, setHoveredLineId] = useState3(null);
4905
5051
  const tasksForPositions = allTasks ?? tasks;
4906
5052
  const { taskPositions, taskIndices, hiddenTaskIds } = useMemo6(() => {
4907
5053
  const positions = /* @__PURE__ */ new Map();
@@ -4917,7 +5063,8 @@ var DependencyLines = React6.memo(({
4917
5063
  left: computed.left,
4918
5064
  right: computed.right,
4919
5065
  centerX: computed.centerX,
4920
- rowTop: rowIndex * rowHeight,
5066
+ rowTop: rowTops?.[rowIndex] ?? rowIndex * rowHeight,
5067
+ rowHeight: rowHeights?.[rowIndex] ?? rowHeight,
4921
5068
  isVirtual: false
4922
5069
  });
4923
5070
  });
@@ -4937,6 +5084,7 @@ var DependencyLines = React6.memo(({
4937
5084
  right: computed.right,
4938
5085
  centerX: computed.centerX,
4939
5086
  rowTop: ancestorPosition.rowTop,
5087
+ rowHeight: ancestorPosition.rowHeight,
4940
5088
  isVirtual: true
4941
5089
  });
4942
5090
  }
@@ -4950,18 +5098,19 @@ var DependencyLines = React6.memo(({
4950
5098
  const override = dragOverrides?.get(task.id);
4951
5099
  const computed = resolveTaskHorizontalGeometry(task, monthStart, dayWidth, override);
4952
5100
  const rowIndex = rowIndexByTaskId?.get(task.id);
4953
- const rowTop = rowIndex !== void 0 ? rowIndex * rowHeight : 0;
5101
+ const rowTop = rowIndex !== void 0 ? rowTops?.[rowIndex] ?? rowIndex * rowHeight : 0;
4954
5102
  positions.set(task.id, {
4955
5103
  left: computed.left,
4956
5104
  right: computed.right,
4957
5105
  centerX: computed.centerX,
4958
5106
  rowTop,
5107
+ rowHeight: rowIndex !== void 0 ? rowHeights?.[rowIndex] ?? rowHeight : rowHeight,
4959
5108
  isVirtual: false
4960
5109
  });
4961
5110
  }
4962
5111
  }
4963
5112
  return { taskPositions: positions, taskIndices: indices, hiddenTaskIds: hidden };
4964
- }, [tasks, tasksForPositions, allTasks, collapsedParentIds, monthStart, dayWidth, rowHeight, dragOverrides, rowIndexByTaskId]);
5113
+ }, [tasks, tasksForPositions, allTasks, collapsedParentIds, monthStart, dayWidth, rowHeight, rowTops, rowHeights, dragOverrides, rowIndexByTaskId]);
4965
5114
  const tasksForCycleDetection = allTasks ?? tasks;
4966
5115
  const cycleInfo = useMemo6(() => {
4967
5116
  const result = detectCycles(tasksForCycleDetection);
@@ -5003,7 +5152,9 @@ var DependencyLines = React6.memo(({
5003
5152
  continue;
5004
5153
  }
5005
5154
  }
5006
- if (!renderedTaskIdSet.has(edge.predecessorId) && !renderedTaskIdSet.has(edge.successorId)) {
5155
+ const predecessorVisibleId = predecessor.isVirtual ? findVisibleAncestor(taskMap.get(edge.predecessorId), collapsedParentIds, taskMap)?.id : edge.predecessorId;
5156
+ const successorVisibleId = successor.isVirtual ? findVisibleAncestor(taskMap.get(edge.successorId), collapsedParentIds, taskMap)?.id : edge.successorId;
5157
+ if (!renderedTaskIdSet.has(predecessorVisibleId ?? "") && !renderedTaskIdSet.has(successorVisibleId ?? "")) {
5007
5158
  continue;
5008
5159
  }
5009
5160
  const isVirtual = predecessor.isVirtual || successor.isVirtual;
@@ -5017,9 +5168,9 @@ var DependencyLines = React6.memo(({
5017
5168
  let toY;
5018
5169
  if (reverseOrder) {
5019
5170
  fromY = predecessor.rowTop + 10;
5020
- toY = successor.rowTop + rowHeight - 6;
5171
+ toY = successor.rowTop + successor.rowHeight - 6;
5021
5172
  } else {
5022
- fromY = predecessor.rowTop + rowHeight - 10;
5173
+ fromY = predecessor.rowTop + predecessor.rowHeight - 10;
5023
5174
  toY = successor.rowTop + 6;
5024
5175
  }
5025
5176
  let fromX = predecessorTask && isMilestoneTask(predecessorTask) ? predecessor.centerX : edge.type === "SS" || edge.type === "SF" ? predecessor.left : predecessor.right;
@@ -5246,18 +5397,18 @@ var LINK_TYPE_LABELS_RU = {
5246
5397
  FF: "\u041E\u041E",
5247
5398
  SF: "\u041D\u041E"
5248
5399
  };
5400
+ var fmt = (n) => Math.round(n * 100) / 100;
5249
5401
  function getPath(drag) {
5250
5402
  const from = drag.source;
5251
5403
  const to = drag.current;
5252
5404
  const direction = to.y >= from.y ? 1 : -1;
5253
5405
  const bend = Math.max(18, Math.abs(to.y - from.y) * 0.45);
5254
- const middleX = Math.round((from.x + to.x) / 2);
5255
5406
  if (Math.abs(to.y - from.y) < 2) {
5256
- return `M ${Math.round(from.x)} ${Math.round(from.y)} H ${Math.round(to.x)}`;
5407
+ return `M ${fmt(from.x)} ${fmt(from.y)} H ${fmt(to.x)}`;
5257
5408
  }
5258
5409
  return [
5259
- `M ${Math.round(from.x)} ${Math.round(from.y)}`,
5260
- `C ${Math.round(from.x)} ${Math.round(from.y + bend * direction)}, ${Math.round(to.x)} ${Math.round(to.y - bend * direction)}, ${Math.round(to.x)} ${Math.round(to.y)}`
5410
+ `M ${fmt(from.x)} ${fmt(from.y)}`,
5411
+ `C ${fmt(from.x)} ${fmt(from.y + bend * direction)}, ${fmt(to.x)} ${fmt(to.y - bend * direction)}, ${fmt(to.x)} ${fmt(to.y)}`
5261
5412
  ].join(" ");
5262
5413
  }
5263
5414
  var DependencyCreationOverlay = ({ drag, width, height }) => {
@@ -5295,7 +5446,7 @@ var DependencyCreationOverlay = ({ drag, width, height }) => {
5295
5446
  };
5296
5447
 
5297
5448
  // src/components/TaskList/TaskList.tsx
5298
- import React13, { useMemo as useMemo9, useCallback as useCallback6, useState as useState7, useEffect as useEffect6, useRef as useRef7 } from "react";
5449
+ import React13, { useMemo as useMemo9, useCallback as useCallback6, useState as useState8, useEffect as useEffect7, useRef as useRef8 } from "react";
5299
5450
 
5300
5451
  // src/utils/taskListReorder.ts
5301
5452
  function getDescendantIds(taskId, tasks) {
@@ -5500,9 +5651,9 @@ var PopoverContent = ({
5500
5651
 
5501
5652
  // src/components/TaskList/TaskListRow.tsx
5502
5653
  import React10, {
5503
- useState as useState5,
5504
- useRef as useRef4,
5505
- useEffect as useEffect4,
5654
+ useState as useState6,
5655
+ useRef as useRef5,
5656
+ useEffect as useEffect5,
5506
5657
  useCallback as useCallback5,
5507
5658
  useMemo as useMemo8
5508
5659
  } from "react";
@@ -5525,14 +5676,14 @@ var Input = React7.forwardRef(
5525
5676
  Input.displayName = "Input";
5526
5677
 
5527
5678
  // src/components/ui/DatePicker.tsx
5528
- import { useState as useState4, useCallback as useCallback4, useEffect as useEffect3, useRef as useRef3 } from "react";
5679
+ import { useState as useState5, useCallback as useCallback4, useEffect as useEffect4, useRef as useRef4 } from "react";
5529
5680
  import { format as format3, isValid, parse, addDays, addMonths as addMonths2, addYears, subMonths as subMonths2, subYears, subDays } from "date-fns";
5530
5681
 
5531
5682
  // src/components/ui/Calendar.tsx
5532
5683
  import {
5533
- useState as useState3,
5534
- useRef as useRef2,
5535
- useEffect as useEffect2,
5684
+ useState as useState4,
5685
+ useRef as useRef3,
5686
+ useEffect as useEffect3,
5536
5687
  useCallback as useCallback3,
5537
5688
  useMemo as useMemo7
5538
5689
  } from "react";
@@ -5567,13 +5718,13 @@ var Calendar = ({
5567
5718
  disabled = false,
5568
5719
  isWeekend: isWeekendProp
5569
5720
  }) => {
5570
- const scrollRef = useRef2(null);
5721
+ const scrollRef = useRef3(null);
5571
5722
  const initialMonth = useMemo7(
5572
5723
  () => startOfMonth(initialDate ?? selected ?? /* @__PURE__ */ new Date()),
5573
5724
  // eslint-disable-next-line react-hooks/exhaustive-deps
5574
5725
  []
5575
5726
  );
5576
- const [months, setMonths] = useState3(() => [
5727
+ const [months, setMonths] = useState4(() => [
5577
5728
  subMonths(initialMonth, 1),
5578
5729
  initialMonth,
5579
5730
  addMonths(initialMonth, 1)
@@ -5587,7 +5738,7 @@ var Calendar = ({
5587
5738
  }
5588
5739
  });
5589
5740
  }, []);
5590
- useEffect2(() => {
5741
+ useEffect3(() => {
5591
5742
  const container = scrollRef.current;
5592
5743
  if (!container) return;
5593
5744
  const handleScroll = () => {
@@ -5605,7 +5756,7 @@ var Calendar = ({
5605
5756
  container.addEventListener("scroll", handleScroll);
5606
5757
  return () => container.removeEventListener("scroll", handleScroll);
5607
5758
  }, [loadMoreMonths]);
5608
- useEffect2(() => {
5759
+ useEffect3(() => {
5609
5760
  if (!scrollRef.current) return;
5610
5761
  const target = selected ?? initialDate ?? /* @__PURE__ */ new Date();
5611
5762
  const monthKey = format2(startOfMonth(target), "yyyy-MM");
@@ -5710,12 +5861,12 @@ var DatePicker = ({
5710
5861
  businessDays = true,
5711
5862
  footer
5712
5863
  }) => {
5713
- const [open, setOpen] = useState4(false);
5714
- const [inputValue, setInputValue] = useState4("");
5715
- const [draftDate, setDraftDate] = useState4(void 0);
5716
- const dateInputRef = useRef3(null);
5717
- const segIdxRef = useRef3(0);
5718
- const charPosRef = useRef3(0);
5864
+ const [open, setOpen] = useState5(false);
5865
+ const [inputValue, setInputValue] = useState5("");
5866
+ const [draftDate, setDraftDate] = useState5(void 0);
5867
+ const dateInputRef = useRef4(null);
5868
+ const segIdxRef = useRef4(0);
5869
+ const charPosRef = useRef4(0);
5719
5870
  const selectedDate = (() => {
5720
5871
  if (!value) return void 0;
5721
5872
  const d = /* @__PURE__ */ new Date(value + "T00:00:00Z");
@@ -5723,7 +5874,7 @@ var DatePicker = ({
5723
5874
  })();
5724
5875
  const activeDate = draftDate ?? selectedDate;
5725
5876
  const displayValue = selectedDate ? formatUTCDate(selectedDate, displayFormat) : placeholder;
5726
- useEffect3(() => {
5877
+ useEffect4(() => {
5727
5878
  if (value) {
5728
5879
  const d = /* @__PURE__ */ new Date(value + "T00:00:00Z");
5729
5880
  if (isValid(d)) {
@@ -5740,7 +5891,7 @@ var DatePicker = ({
5740
5891
  const seg = segments[idx] ?? segments[0];
5741
5892
  dateInputRef.current.setSelectionRange(seg.start, seg.end);
5742
5893
  }, []);
5743
- useEffect3(() => {
5894
+ useEffect4(() => {
5744
5895
  if (open) {
5745
5896
  setTimeout(() => {
5746
5897
  if (dateInputRef.current) {
@@ -6339,10 +6490,10 @@ var DepChip = ({
6339
6490
  businessDays = true,
6340
6491
  weekendPredicate
6341
6492
  }) => {
6342
- const [popoverOpen, setPopoverOpen] = useState5(false);
6493
+ const [popoverOpen, setPopoverOpen] = useState6(false);
6343
6494
  const lagAbs = Math.abs(lag ?? 0);
6344
- const [inputAbs, setInputAbs] = useState5(lagAbs === 0 ? "" : String(lagAbs));
6345
- useEffect4(() => {
6495
+ const [inputAbs, setInputAbs] = useState6(lagAbs === 0 ? "" : String(lagAbs));
6496
+ useEffect5(() => {
6346
6497
  const abs = Math.abs(lag ?? 0);
6347
6498
  setInputAbs(abs === 0 ? "" : String(abs));
6348
6499
  }, [lag]);
@@ -6721,13 +6872,14 @@ var TaskListRow = React10.memo(
6721
6872
  taskListMenuCommands = [],
6722
6873
  hideTaskListRowActions = false,
6723
6874
  rowClassName,
6875
+ compositeParentColor,
6724
6876
  getTaskListNamePrefixIcon,
6725
6877
  taskDateChangeMode = "preserve-duration",
6726
6878
  onTaskDateChangeModeChange,
6727
6879
  printDependencyLabels = false
6728
6880
  }) => {
6729
- const [editingColumnId, setEditingColumnId] = useState5(null);
6730
- const [editingColumnStartValue, setEditingColumnStartValue] = useState5(void 0);
6881
+ const [editingColumnId, setEditingColumnId] = useState6(null);
6882
+ const [editingColumnStartValue, setEditingColumnStartValue] = useState6(void 0);
6731
6883
  const editingName = editingColumnId === "name";
6732
6884
  const editingDuration = editingColumnId === "duration";
6733
6885
  const editingProgress = editingColumnId === "progress";
@@ -6758,27 +6910,27 @@ var TaskListRow = React10.memo(
6758
6910
  }, [columnWidthStyleMap]);
6759
6911
  const normalizedTask = useMemo8(() => normalizeTaskDatesForType(task), [task]);
6760
6912
  const isMilestone = useMemo8(() => isMilestoneTask(normalizedTask), [normalizedTask]);
6761
- const [nameValue, setNameValue] = useState5("");
6762
- const nameInputRef = useRef4(null);
6763
- const [durationValue, setDurationValue] = useState5(
6913
+ const [nameValue, setNameValue] = useState6("");
6914
+ const nameInputRef = useRef5(null);
6915
+ const [durationValue, setDurationValue] = useState6(
6764
6916
  () => getInclusiveDurationDays(normalizedTask.startDate, normalizedTask.endDate)
6765
6917
  );
6766
- const durationInputRef = useRef4(null);
6767
- const dependencySearchInputRef = useRef4(null);
6768
- const dependencySearchListRef = useRef4(null);
6769
- const [progressValue, setProgressValue] = useState5(0);
6770
- const progressInputRef = useRef4(null);
6771
- const [overflowOpen, setOverflowOpen] = useState5(false);
6772
- const [contextMenuOpen, setContextMenuOpen] = useState5(false);
6773
- const [colorMenuOpen, setColorMenuOpen] = useState5(false);
6774
- const nameConfirmedRef = useRef4(false);
6775
- const durationConfirmedRef = useRef4(false);
6776
- const progressConfirmedRef = useRef4(false);
6777
- const autoEditedForRef = useRef4(null);
6778
- const editTriggerRef = useRef4(
6918
+ const durationInputRef = useRef5(null);
6919
+ const dependencySearchInputRef = useRef5(null);
6920
+ const dependencySearchListRef = useRef5(null);
6921
+ const [progressValue, setProgressValue] = useState6(0);
6922
+ const progressInputRef = useRef5(null);
6923
+ const [overflowOpen, setOverflowOpen] = useState6(false);
6924
+ const [contextMenuOpen, setContextMenuOpen] = useState6(false);
6925
+ const [colorMenuOpen, setColorMenuOpen] = useState6(false);
6926
+ const nameConfirmedRef = useRef5(false);
6927
+ const durationConfirmedRef = useRef5(false);
6928
+ const progressConfirmedRef = useRef5(false);
6929
+ const autoEditedForRef = useRef5(null);
6930
+ const editTriggerRef = useRef5(
6779
6931
  "doubleclick"
6780
6932
  );
6781
- const rowRef = useRef4(null);
6933
+ const rowRef = useRef5(null);
6782
6934
  const isSelected = selectedTaskId === task.id;
6783
6935
  const isParent = useMemo8(
6784
6936
  () => isTaskParent(task.id, allTasks),
@@ -6805,13 +6957,42 @@ var TaskListRow = React10.memo(
6805
6957
  [businessDays, weekendPredicate]
6806
6958
  );
6807
6959
  const isCollapsed = collapsedParentIds.has(task.id);
6960
+ const [compositeIconMode, setCompositeIconMode] = useState6(
6961
+ () => isCollapsed ? "composite" : "down"
6962
+ );
6963
+ const compositeIconModeRef = useRef5(compositeIconMode);
6964
+ const [compositeIconAnimation, setCompositeIconAnimation] = useState6(null);
6965
+ const [compositeIconAnimationPhase, setCompositeIconAnimationPhase] = useState6(null);
6966
+ useEffect5(() => {
6967
+ const nextMode = isCollapsed ? "composite" : "down";
6968
+ if (nextMode === compositeIconModeRef.current) {
6969
+ setCompositeIconAnimation(null);
6970
+ setCompositeIconAnimationPhase(null);
6971
+ return;
6972
+ }
6973
+ setCompositeIconAnimation(isCollapsed ? "closing" : "opening");
6974
+ setCompositeIconAnimationPhase("outgoing");
6975
+ const swapTimeoutId = window.setTimeout(() => {
6976
+ compositeIconModeRef.current = nextMode;
6977
+ setCompositeIconMode(nextMode);
6978
+ setCompositeIconAnimationPhase("incoming");
6979
+ }, 120);
6980
+ const finishTimeoutId = window.setTimeout(() => {
6981
+ setCompositeIconAnimation(null);
6982
+ setCompositeIconAnimationPhase(null);
6983
+ }, 240);
6984
+ return () => {
6985
+ window.clearTimeout(swapTimeoutId);
6986
+ window.clearTimeout(finishTimeoutId);
6987
+ };
6988
+ }, [isCollapsed]);
6808
6989
  const getHierarchyLineColor = useCallback5((columnDepth) => {
6809
6990
  return columnDepth % 2 === 0 ? "#93c5fd" : "var(--gantt-hierarchy-line-color)";
6810
6991
  }, []);
6811
6992
  const isPicking = selectingPredecessorFor != null;
6812
6993
  const isSourceRow = isPicking && selectingPredecessorFor === task.id;
6813
- const [dependencySearchQuery, setDependencySearchQuery] = useState5("");
6814
- const [highlightedDependencyIndex, setHighlightedDependencyIndex] = useState5(0);
6994
+ const [dependencySearchQuery, setDependencySearchQuery] = useState6("");
6995
+ const [highlightedDependencyIndex, setHighlightedDependencyIndex] = useState6(0);
6815
6996
  const chips = useMemo8(() => {
6816
6997
  const taskById = new Map((allTasks ?? []).map((t) => [t.id, t]));
6817
6998
  return (task.dependencies ?? []).map((dep) => {
@@ -6848,7 +7029,7 @@ var TaskListRow = React10.memo(
6848
7029
  taskNumberMap,
6849
7030
  task.dependencies
6850
7031
  ]);
6851
- useEffect4(() => {
7032
+ useEffect5(() => {
6852
7033
  if (editingName && nameInputRef.current) {
6853
7034
  nameInputRef.current.focus();
6854
7035
  if (editTriggerRef.current === "keypress") {
@@ -6859,15 +7040,15 @@ var TaskListRow = React10.memo(
6859
7040
  }
6860
7041
  }
6861
7042
  }, [editingName]);
6862
- useEffect4(() => {
7043
+ useEffect5(() => {
6863
7044
  if (!isSourceRow && dependencySearchQuery !== "") {
6864
7045
  setDependencySearchQuery("");
6865
7046
  }
6866
7047
  }, [isSourceRow, dependencySearchQuery]);
6867
- useEffect4(() => {
7048
+ useEffect5(() => {
6868
7049
  setHighlightedDependencyIndex(0);
6869
7050
  }, [dependencySearchQuery, isSourceRow, dependencyPickMode]);
6870
- useEffect4(() => {
7051
+ useEffect5(() => {
6871
7052
  if (dependencySearchCandidates.length === 0) {
6872
7053
  setHighlightedDependencyIndex(0);
6873
7054
  return;
@@ -6876,13 +7057,13 @@ var TaskListRow = React10.memo(
6876
7057
  setHighlightedDependencyIndex(dependencySearchCandidates.length - 1);
6877
7058
  }
6878
7059
  }, [dependencySearchCandidates, highlightedDependencyIndex]);
6879
- useEffect4(() => {
7060
+ useEffect5(() => {
6880
7061
  if (isSourceRow && dependencySearchInputRef.current) {
6881
7062
  dependencySearchInputRef.current.focus();
6882
7063
  dependencySearchInputRef.current.select();
6883
7064
  }
6884
7065
  }, [isSourceRow, dependencyPickMode, activeLinkType]);
6885
- useEffect4(() => {
7066
+ useEffect5(() => {
6886
7067
  if (!isSourceRow || dependencySearchCandidates.length === 0) {
6887
7068
  return;
6888
7069
  }
@@ -6894,7 +7075,7 @@ var TaskListRow = React10.memo(
6894
7075
  block: "nearest"
6895
7076
  });
6896
7077
  }, [isSourceRow, highlightedDependencyIndex, dependencySearchCandidates]);
6897
- useEffect4(() => {
7078
+ useEffect5(() => {
6898
7079
  if (editingTaskId === task.id && !disableTaskNameEditing && autoEditedForRef.current !== editingTaskId) {
6899
7080
  autoEditedForRef.current = editingTaskId;
6900
7081
  nameConfirmedRef.current = false;
@@ -6913,6 +7094,7 @@ var TaskListRow = React10.memo(
6913
7094
  );
6914
7095
  const handleNameDoubleClick = useCallback5(
6915
7096
  (e) => {
7097
+ if (task.composite) return;
6916
7098
  if (disableTaskNameEditing) return;
6917
7099
  e.stopPropagation();
6918
7100
  nameConfirmedRef.current = false;
@@ -6920,7 +7102,7 @@ var TaskListRow = React10.memo(
6920
7102
  setNameValue(task.name);
6921
7103
  setEditingColumnId("name");
6922
7104
  },
6923
- [task.name, disableTaskNameEditing]
7105
+ [task.name, task.composite, disableTaskNameEditing]
6924
7106
  );
6925
7107
  const handleRowKeyDown = useCallback5(
6926
7108
  (e) => {
@@ -7075,16 +7257,16 @@ var TaskListRow = React10.memo(
7075
7257
  },
7076
7258
  [progressValue, task, onTasksChange, handleProgressCancel, allTasks]
7077
7259
  );
7078
- useEffect4(() => {
7260
+ useEffect5(() => {
7079
7261
  if (editingProgress && progressInputRef.current) {
7080
7262
  progressInputRef.current.focus();
7081
7263
  progressInputRef.current.select();
7082
7264
  }
7083
7265
  }, [editingProgress]);
7084
- useEffect4(() => {
7266
+ useEffect5(() => {
7085
7267
  setDurationValue(getDuration(normalizedTask.startDate, normalizedTask.endDate));
7086
7268
  }, [normalizedTask.startDate, normalizedTask.endDate, getDuration]);
7087
- useEffect4(() => {
7269
+ useEffect5(() => {
7088
7270
  if (editingDuration && durationInputRef.current) {
7089
7271
  durationInputRef.current.focus();
7090
7272
  durationInputRef.current.select();
@@ -7569,7 +7751,7 @@ var TaskListRow = React10.memo(
7569
7751
  ]
7570
7752
  }
7571
7753
  );
7572
- const nameTriggerPaddingLeft = isParent ? `${nestingDepth * 20 + 28}px` : nestingDepth > 0 ? `${nestingDepth * 20 + 8}px` : void 0;
7754
+ const nameTriggerPaddingLeft = isParent && !task.composite ? `${nestingDepth * 20 + 28}px` : nestingDepth > 0 ? `${nestingDepth * 20 + 8}px` : void 0;
7573
7755
  const nameInputPaddingLeft = nestingDepth > 0 ? `${nestingDepth * 20 + 8}px` : void 0;
7574
7756
  const nestedDropIndicatorLeft = (() => {
7575
7757
  const numberColumnWidth = resolvedColumns?.find((col) => col.id === "number")?.width ?? 40;
@@ -7667,36 +7849,35 @@ var TaskListRow = React10.memo(
7667
7849
  }
7668
7850
  )
7669
7851
  ] }),
7670
- isParent && !editingName && /* @__PURE__ */ jsxs10(Fragment3, { children: [
7671
- !isFilterHideMode && !isCollapsed && /* @__PURE__ */ jsx14(
7672
- "span",
7673
- {
7674
- style: {
7675
- position: "absolute",
7676
- left: `${nestingDepth * 20 + 9}px`,
7677
- top: `${rowHeight / 2 + 7}px`,
7678
- height: `${rowHeight / 2 - 7}px`,
7679
- width: "1.5px",
7680
- background: getHierarchyLineColor(nestingDepth),
7681
- borderRadius: "1px",
7682
- pointerEvents: "none"
7683
- }
7684
- }
7685
- ),
7686
- /* @__PURE__ */ jsx14(
7687
- "button",
7688
- {
7689
- type: "button",
7690
- className: `gantt-tl-collapse-btn ${isCollapsed ? "gantt-tl-collapse-btn-collapsed" : ""}`,
7691
- onClick: handleToggleCollapse,
7692
- style: { left: `${nestingDepth * 20 + 1}px` },
7693
- "aria-label": isCollapsed ? "Expand children" : "Collapse children",
7694
- children: /* @__PURE__ */ jsx14(ChevronRightIcon, {})
7852
+ task.composite && !isFilterHideMode && !isCollapsed && !editingName && /* @__PURE__ */ jsx14(
7853
+ "span",
7854
+ {
7855
+ "data-testid": "gantt-tl-parent-connector-tail",
7856
+ style: {
7857
+ position: "absolute",
7858
+ left: `${nestingDepth * 20 + 9}px`,
7859
+ top: `${rowHeight / 2 + 7}px`,
7860
+ height: `${rowHeight / 2 - 7}px`,
7861
+ width: "1.5px",
7862
+ background: getHierarchyLineColor(nestingDepth),
7863
+ borderRadius: "1px",
7864
+ pointerEvents: "none"
7695
7865
  }
7696
- )
7697
- ] })
7866
+ }
7867
+ ),
7868
+ isParent && !task.composite && !editingName && /* @__PURE__ */ jsx14(Fragment3, { children: /* @__PURE__ */ jsx14(
7869
+ "button",
7870
+ {
7871
+ type: "button",
7872
+ className: `gantt-tl-collapse-btn ${isCollapsed ? "gantt-tl-collapse-btn-collapsed" : ""}`,
7873
+ onClick: handleToggleCollapse,
7874
+ style: { left: `${nestingDepth * 20 - 2}px` },
7875
+ "aria-label": isCollapsed ? "Expand children" : "Collapse children",
7876
+ children: /* @__PURE__ */ jsx14(ChevronRightIcon, {})
7877
+ }
7878
+ ) })
7698
7879
  ] }),
7699
- !isChild && isParent && !editingName && /* @__PURE__ */ jsxs10(Fragment3, { children: [
7880
+ !isChild && isParent && !task.composite && !editingName && /* @__PURE__ */ jsxs10(Fragment3, { children: [
7700
7881
  !isFilterHideMode && !isCollapsed && /* @__PURE__ */ jsx14(
7701
7882
  "span",
7702
7883
  {
@@ -7719,7 +7900,7 @@ var TaskListRow = React10.memo(
7719
7900
  type: "button",
7720
7901
  className: `gantt-tl-collapse-btn ${isCollapsed ? "gantt-tl-collapse-btn-collapsed" : ""}`,
7721
7902
  onClick: handleToggleCollapse,
7722
- style: { left: `${nestingDepth * 20 + 1}px` },
7903
+ style: { left: `${nestingDepth * 20 - 2}px` },
7723
7904
  "aria-label": isCollapsed ? "Expand children" : "Collapse children",
7724
7905
  children: /* @__PURE__ */ jsx14(ChevronRightIcon, {})
7725
7906
  }
@@ -7738,27 +7919,57 @@ var TaskListRow = React10.memo(
7738
7919
  style: { paddingLeft: nameInputPaddingLeft },
7739
7920
  onClick: (e) => e.stopPropagation()
7740
7921
  }
7741
- ) : /* @__PURE__ */ jsxs10(
7742
- "button",
7743
- {
7744
- type: "button",
7745
- className: [
7746
- "gantt-tl-name-trigger",
7747
- disableTaskNameEditing ? "gantt-tl-name-locked" : ""
7748
- ].filter(Boolean).join(" "),
7749
- title: task.name,
7750
- onClick: handleNameClick,
7751
- onDoubleClick: handleNameDoubleClick,
7752
- style: {
7753
- paddingLeft: nameTriggerPaddingLeft,
7754
- paddingRight: task.color ? "20px" : void 0
7755
- },
7756
- children: [
7757
- taskListNamePrefixIcon ? /* @__PURE__ */ jsx14("span", { className: "gantt-tl-name-trigger-icon", "aria-hidden": "true", children: taskListNamePrefixIcon }) : null,
7758
- /* @__PURE__ */ jsx14("span", { className: "gantt-tl-name-trigger-text", children: task.name })
7759
- ]
7760
- }
7761
- ),
7922
+ ) : /* @__PURE__ */ jsxs10(Fragment3, { children: [
7923
+ task.composite && /* @__PURE__ */ jsx14(
7924
+ "button",
7925
+ {
7926
+ type: "button",
7927
+ className: "gantt-tl-compositeToggle",
7928
+ onClick: handleToggleCollapse,
7929
+ style: { left: `${nestingDepth * 20 - 3}px` },
7930
+ "aria-label": isCollapsed ? `\u0420\u0430\u0441\u043A\u0440\u044B\u0442\u044C ${task.name}` : `\u0421\u0432\u0435\u0440\u043D\u0443\u0442\u044C ${task.name}`,
7931
+ "aria-expanded": !isCollapsed,
7932
+ title: isCollapsed ? "\u0420\u0430\u0441\u043A\u0440\u044B\u0442\u044C \u044D\u0442\u0430\u0436\u0438" : "\u0421\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u044D\u0442\u0430\u0436\u0438",
7933
+ children: /* @__PURE__ */ jsx14(
7934
+ "svg",
7935
+ {
7936
+ className: compositeIconAnimation && compositeIconAnimationPhase ? `gantt-tl-compositeGlyph-${compositeIconMode}-${compositeIconAnimation}-${compositeIconAnimationPhase}` : void 0,
7937
+ viewBox: "0 0 24 24",
7938
+ fill: "none",
7939
+ stroke: "currentColor",
7940
+ strokeWidth: "2",
7941
+ strokeLinecap: "round",
7942
+ strokeLinejoin: "round",
7943
+ "aria-hidden": "true",
7944
+ children: compositeIconMode === "composite" ? /* @__PURE__ */ jsxs10(Fragment3, { children: [
7945
+ /* @__PURE__ */ jsx14("path", { d: "M21 5H11" }),
7946
+ /* @__PURE__ */ jsx14("path", { d: "M21 12H11" }),
7947
+ /* @__PURE__ */ jsx14("path", { d: "M21 19H11" }),
7948
+ /* @__PURE__ */ jsx14("path", { d: "m3 8 4 4-4 4" })
7949
+ ] }) : /* @__PURE__ */ jsx14("path", { d: "m6 9 6 6 6-6" })
7950
+ }
7951
+ )
7952
+ }
7953
+ ),
7954
+ /* @__PURE__ */ jsxs10(
7955
+ "button",
7956
+ {
7957
+ type: "button",
7958
+ className: `gantt-tl-name-trigger${disableTaskNameEditing ? " gantt-tl-name-locked" : ""}`,
7959
+ title: task.name,
7960
+ onClick: handleNameClick,
7961
+ onDoubleClick: handleNameDoubleClick,
7962
+ style: {
7963
+ paddingLeft: task.composite ? `${nestingDepth * 20 + 30}px` : nameTriggerPaddingLeft,
7964
+ paddingRight: task.color ? "20px" : void 0
7965
+ },
7966
+ children: [
7967
+ taskListNamePrefixIcon ? /* @__PURE__ */ jsx14("span", { className: "gantt-tl-name-trigger-icon", "aria-hidden": "true", children: taskListNamePrefixIcon }) : null,
7968
+ /* @__PURE__ */ jsx14("span", { className: "gantt-tl-name-trigger-text", children: task.name })
7969
+ ]
7970
+ }
7971
+ )
7972
+ ] }),
7762
7973
  !editingName && task.color && /* @__PURE__ */ jsx14(
7763
7974
  "span",
7764
7975
  {
@@ -8375,8 +8586,9 @@ var TaskListRow = React10.memo(
8375
8586
  isDragOver && isNestedDropTarget ? "gantt-tl-row-drag-over-nested" : "",
8376
8587
  isDragOver && isDirectChildDropTarget ? "gantt-tl-row-drag-over-direct-child" : "",
8377
8588
  isChild ? "gantt-tl-row-child" : "",
8378
- isParent ? "gantt-tl-row-parent" : "",
8379
- isParent && fillParentRow ? "gantt-tl-row-parent-filled" : "",
8589
+ isParent && !task.composite ? "gantt-tl-row-parent" : "",
8590
+ task.composite ? "gantt-tl-row-composite" : "",
8591
+ isParent && !task.composite && fillParentRow ? "gantt-tl-row-parent-filled" : "",
8380
8592
  `gantt-tl-row-level-${rowFillLevel}`,
8381
8593
  isTotalRow ? "gantt-tl-row-total" : "",
8382
8594
  rowClassName ?? ""
@@ -8384,6 +8596,7 @@ var TaskListRow = React10.memo(
8384
8596
  style: {
8385
8597
  height: `${rowHeight}px`,
8386
8598
  position: "relative",
8599
+ "--gantt-tl-composite-detail-color": compositeParentColor ?? "var(--gantt-task-bar-default-color, #8b5cf6)",
8387
8600
  "--gantt-tl-nested-drop-left": nestedDropIndicatorLeft,
8388
8601
  "--gantt-tl-nested-inside-drop-left": nestedInsideDropIndicatorLeft
8389
8602
  },
@@ -8525,7 +8738,7 @@ var TaskListRow = React10.memo(
8525
8738
  TaskListRow.displayName = "TaskListRow";
8526
8739
 
8527
8740
  // src/components/TaskList/NewTaskRow.tsx
8528
- import { useState as useState6, useRef as useRef5, useEffect as useEffect5 } from "react";
8741
+ import { useState as useState7, useRef as useRef6, useEffect as useEffect6 } from "react";
8529
8742
  import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
8530
8743
  var NewTaskRow = ({
8531
8744
  rowHeight,
@@ -8533,12 +8746,12 @@ var NewTaskRow = ({
8533
8746
  onCancel,
8534
8747
  nestingDepth = 0
8535
8748
  }) => {
8536
- const [nameValue, setNameValue] = useState6("");
8537
- const inputRef = useRef5(null);
8538
- const confirmedRef = useRef5(false);
8749
+ const [nameValue, setNameValue] = useState7("");
8750
+ const inputRef = useRef6(null);
8751
+ const confirmedRef = useRef6(false);
8539
8752
  const inputInnerPadding = 4;
8540
8753
  const levelOffset = nestingDepth > 0 ? nestingDepth * 20 + 8 : 0;
8541
- useEffect5(() => {
8754
+ useEffect6(() => {
8542
8755
  if (inputRef.current) {
8543
8756
  inputRef.current.focus();
8544
8757
  inputRef.current.select();
@@ -8591,7 +8804,7 @@ var NewTaskRow = ({
8591
8804
  };
8592
8805
 
8593
8806
  // src/components/GenerationSkeleton/GenerationSkeleton.tsx
8594
- import { useRef as useRef6 } from "react";
8807
+ import { useRef as useRef7 } from "react";
8595
8808
  import { Fragment as Fragment4, jsx as jsx16 } from "react/jsx-runtime";
8596
8809
  var MAX_SKELETON_ROWS = 200;
8597
8810
  var SHIMMER_DURATION_MS = 1450;
@@ -8609,14 +8822,15 @@ var GenerationSkeletonRows = ({
8609
8822
  rowHeight,
8610
8823
  variant,
8611
8824
  startIndex = 0,
8825
+ startTop,
8612
8826
  taskListLayout,
8613
8827
  chartDayWidth = 40,
8614
8828
  chartStartDayOffset = 0,
8615
8829
  chartStartJitterDays = 0
8616
8830
  }) => {
8617
8831
  const safeCount = clampCount(count);
8618
- const previousCountRef = useRef6(null);
8619
- const shimmerDelayRef = useRef6("0ms");
8832
+ const previousCountRef = useRef7(null);
8833
+ const shimmerDelayRef = useRef7("0ms");
8620
8834
  if (previousCountRef.current !== safeCount) {
8621
8835
  previousCountRef.current = safeCount;
8622
8836
  shimmerDelayRef.current = `-${Date.now() % SHIMMER_DURATION_MS}ms`;
@@ -8649,7 +8863,7 @@ var GenerationSkeletonRows = ({
8649
8863
  "div",
8650
8864
  {
8651
8865
  className: "gantt-generation-skeleton-row",
8652
- style: { top: `${rowIndex * rowHeight}px`, height: `${rowHeight}px` },
8866
+ style: { top: `${(startTop ?? startIndex * rowHeight) + index * rowHeight}px`, height: `${rowHeight}px` },
8653
8867
  children: variant === "task-list" ? /* @__PURE__ */ jsx16(Fragment4, { children: /* @__PURE__ */ jsx16("span", { className: `gantt-generation-skeleton-label ${shortShape ? "gantt-generation-skeleton-label-short" : ""} ${mediumShape ? "gantt-generation-skeleton-label-medium" : ""}` }) }) : /* @__PURE__ */ jsx16("span", { className: `gantt-generation-skeleton-bar ${parentShape ? "gantt-generation-skeleton-bar-parent" : ""} ${shortShape ? "gantt-generation-skeleton-bar-short" : ""}`, style: chartBarStyle })
8654
8868
  },
8655
8869
  rowIndex
@@ -8887,8 +9101,8 @@ var SelectAllCheckbox = ({
8887
9101
  indeterminate,
8888
9102
  onChange
8889
9103
  }) => {
8890
- const ref = useRef7(null);
8891
- useEffect6(() => {
9104
+ const ref = useRef8(null);
9105
+ useEffect7(() => {
8892
9106
  if (ref.current) {
8893
9107
  ref.current.indeterminate = indeterminate;
8894
9108
  }
@@ -8909,6 +9123,8 @@ var SelectAllCheckbox = ({
8909
9123
  var TaskList = ({
8910
9124
  tasks,
8911
9125
  rowHeight,
9126
+ rowTops,
9127
+ rowHeights,
8912
9128
  headerHeight,
8913
9129
  taskListWidth,
8914
9130
  onTasksChange,
@@ -8967,14 +9183,14 @@ var TaskList = ({
8967
9183
  skeletonRowCount = 0
8968
9184
  }) => {
8969
9185
  const reorderDisabled = disableTaskListReorder || disableTaskDrag;
8970
- const [internalSelectedTaskIds, setInternalSelectedTaskIds] = useState7(/* @__PURE__ */ new Set());
8971
- const [activeCustomCell, setActiveCustomCell] = useState7(null);
8972
- const [columnWidthOverrides, setColumnWidthOverrides] = useState7(() => normalizeColumnWidthMap(taskListColumnWidths));
8973
- const [isColumnResizeMode, setIsColumnResizeMode] = useState7(false);
8974
- const columnResizeHeaderRef = useRef7(null);
8975
- const resizeStateRef = useRef7(null);
9186
+ const [internalSelectedTaskIds, setInternalSelectedTaskIds] = useState8(/* @__PURE__ */ new Set());
9187
+ const [activeCustomCell, setActiveCustomCell] = useState8(null);
9188
+ const [columnWidthOverrides, setColumnWidthOverrides] = useState8(() => normalizeColumnWidthMap(taskListColumnWidths));
9189
+ const [isColumnResizeMode, setIsColumnResizeMode] = useState8(false);
9190
+ const columnResizeHeaderRef = useRef8(null);
9191
+ const resizeStateRef = useRef8(null);
8976
9192
  const effectiveSelectedTaskIds = selectedTaskIds ?? internalSelectedTaskIds;
8977
- useEffect6(() => {
9193
+ useEffect7(() => {
8978
9194
  if (!isColumnResizeMode) {
8979
9195
  return;
8980
9196
  }
@@ -8996,7 +9212,7 @@ var TaskList = ({
8996
9212
  }
8997
9213
  onSelectedTaskIdsChange?.(nextSelectedTaskIds);
8998
9214
  }, [onSelectedTaskIdsChange, selectedTaskIds]);
8999
- const [internalCollapsedParentIds, setInternalCollapsedParentIds] = useState7(/* @__PURE__ */ new Set());
9215
+ const [internalCollapsedParentIds, setInternalCollapsedParentIds] = useState8(/* @__PURE__ */ new Set());
9000
9216
  const collapsedParentIds = externalCollapsedParentIds ?? internalCollapsedParentIds;
9001
9217
  const handleToggleCollapse = externalOnToggleCollapse ?? useCallback6((parentId) => {
9002
9218
  setInternalCollapsedParentIds((prev) => {
@@ -9036,14 +9252,22 @@ var TaskList = ({
9036
9252
  return tasks2;
9037
9253
  }, [orderedTasks, collapsedParentIds, filterMode, filteredTaskIds, isFilterActive, criticalPathMode, criticalTaskIds]);
9038
9254
  const totalHeight = useMemo9(
9039
- () => (visibleTasks.length + Math.max(0, Math.floor(skeletonRowCount))) * rowHeight,
9040
- [rowHeight, skeletonRowCount, visibleTasks.length]
9255
+ () => (rowTops && rowHeights && visibleTasks.length > 0 ? rowTops[visibleTasks.length - 1] + rowHeights[visibleTasks.length - 1] : visibleTasks.length * rowHeight) + Math.max(0, Math.floor(skeletonRowCount)) * rowHeight,
9256
+ [rowHeight, rowTops, rowHeights, skeletonRowCount, visibleTasks.length]
9041
9257
  );
9042
9258
  const visibleTaskNumberMap = useMemo9(
9043
9259
  () => Object.fromEntries(buildTaskNumberMap(visibleTasks)),
9044
9260
  [visibleTasks]
9045
9261
  );
9046
9262
  const visibleTaskIds = useMemo9(() => visibleTasks.map((task) => task.id), [visibleTasks]);
9263
+ const compositeParentIds = useMemo9(
9264
+ () => new Set(tasks.filter((task) => task.composite).map((task) => task.id)),
9265
+ [tasks]
9266
+ );
9267
+ const compositeParentsById = useMemo9(
9268
+ () => new Map(tasks.filter((task) => task.composite).map((task) => [task.id, task])),
9269
+ [tasks]
9270
+ );
9047
9271
  const renderedVisibleRowIndices = useMemo9(
9048
9272
  () => visibleRowIndices ?? visibleTasks.map((_, index) => index),
9049
9273
  [visibleRowIndices, visibleTasks]
@@ -9154,14 +9378,14 @@ var TaskList = ({
9154
9378
  const handleRowClick = useCallback6((taskId) => {
9155
9379
  onTaskSelect?.(taskId);
9156
9380
  }, [onTaskSelect]);
9157
- const [activeLinkType, setActiveLinkType] = useState7("FS");
9158
- const [selectingPredecessorFor, setSelectingPredecessorFor] = useState7(null);
9159
- const [dependencyPickMode, setDependencyPickMode] = useState7("successor");
9160
- const [typeMenuOpen, setTypeMenuOpen] = useState7(false);
9161
- const [dependencyError, setDependencyError] = useState7(null);
9162
- const overlayRef = useRef7(null);
9163
- const [selectedChip, setSelectedChip] = useState7(controlledSelectedChip ? { ...controlledSelectedChip, linkType: controlledSelectedChip.linkType } : null);
9164
- useEffect6(() => {
9381
+ const [activeLinkType, setActiveLinkType] = useState8("FS");
9382
+ const [selectingPredecessorFor, setSelectingPredecessorFor] = useState8(null);
9383
+ const [dependencyPickMode, setDependencyPickMode] = useState8("successor");
9384
+ const [typeMenuOpen, setTypeMenuOpen] = useState8(false);
9385
+ const [dependencyError, setDependencyError] = useState8(null);
9386
+ const overlayRef = useRef8(null);
9387
+ const [selectedChip, setSelectedChip] = useState8(controlledSelectedChip ? { ...controlledSelectedChip, linkType: controlledSelectedChip.linkType } : null);
9388
+ useEffect7(() => {
9165
9389
  if (controlledSelectedChip !== void 0) {
9166
9390
  setSelectedChip(controlledSelectedChip ? { ...controlledSelectedChip, linkType: controlledSelectedChip.linkType } : null);
9167
9391
  }
@@ -9170,7 +9394,7 @@ var TaskList = ({
9170
9394
  setSelectedChip(chip);
9171
9395
  onSelectedChipChange?.(chip);
9172
9396
  }, [onSelectedChipChange]);
9173
- useEffect6(() => {
9397
+ useEffect7(() => {
9174
9398
  if (!selectingPredecessorFor && !selectedChip && !selectedTaskId && !activeCustomCell) return;
9175
9399
  const pendingCanvasMouseDownRef = {
9176
9400
  container: null,
@@ -9352,13 +9576,13 @@ var TaskList = ({
9352
9576
  );
9353
9577
  onTasksChange?.([{ ...task, dependencies: updatedDeps }]);
9354
9578
  }, [tasks, onTasksChange]);
9355
- const [isCreating, setIsCreating] = useState7(false);
9356
- const [pendingInsert, setPendingInsert] = useState7(null);
9357
- const [draggingIndex, setDraggingIndex] = useState7(null);
9358
- const [dragOverTarget, setDragOverTarget] = useState7(null);
9359
- const [isBeforeFirstDropZoneActive, setIsBeforeFirstDropZoneActive] = useState7(false);
9360
- const dragOriginIndexRef = useRef7(null);
9361
- const dragTaskIdRef = useRef7(null);
9579
+ const [isCreating, setIsCreating] = useState8(false);
9580
+ const [pendingInsert, setPendingInsert] = useState8(null);
9581
+ const [draggingIndex, setDraggingIndex] = useState8(null);
9582
+ const [dragOverTarget, setDragOverTarget] = useState8(null);
9583
+ const [isBeforeFirstDropZoneActive, setIsBeforeFirstDropZoneActive] = useState8(false);
9584
+ const dragOriginIndexRef = useRef8(null);
9585
+ const dragTaskIdRef = useRef8(null);
9362
9586
  const clearDragState = useCallback6(() => {
9363
9587
  setDraggingIndex(null);
9364
9588
  setDragOverTarget(null);
@@ -9694,7 +9918,7 @@ var TaskList = ({
9694
9918
  () => createBuiltInColumns({ businessDays, enableTaskMultiSelect }),
9695
9919
  [businessDays, enableTaskMultiSelect]
9696
9920
  );
9697
- useEffect6(() => {
9921
+ useEffect7(() => {
9698
9922
  setColumnWidthOverrides(normalizeColumnWidthMap(taskListColumnWidths));
9699
9923
  }, [taskListColumnWidths]);
9700
9924
  const resolvedColumns = useMemo9(
@@ -9746,7 +9970,7 @@ var TaskList = ({
9746
9970
  return next;
9747
9971
  });
9748
9972
  }, [onTaskListColumnWidthsChange]);
9749
- useEffect6(() => {
9973
+ useEffect7(() => {
9750
9974
  const updateResizedColumn = (clientX) => {
9751
9975
  const resizeState = resizeStateRef.current;
9752
9976
  if (!resizeState) {
@@ -10035,7 +10259,7 @@ var TaskList = ({
10035
10259
  renderedVisibleTasks.map(({ task, index }) => {
10036
10260
  const previousVisibleTask = index > 0 ? visibleTasks[index - 1] : void 0;
10037
10261
  const canDemoteTask = index === 0 || !task.parentId || previousVisibleTask?.id !== task.parentId;
10038
- const topOffset = index * rowHeight + (hasVisiblePendingInsert && index > pendingInsertRowIndex ? rowHeight : 0);
10262
+ const topOffset = (rowTops?.[index] ?? index * rowHeight) + (hasVisiblePendingInsert && index > pendingInsertRowIndex ? rowHeight : 0);
10039
10263
  return /* @__PURE__ */ jsx17(
10040
10264
  "div",
10041
10265
  {
@@ -10052,7 +10276,7 @@ var TaskList = ({
10052
10276
  rowIndex: index,
10053
10277
  taskNumber: originalTaskNumberMap[task.id] || "",
10054
10278
  taskNumberMap: originalTaskNumberMap,
10055
- rowHeight,
10279
+ rowHeight: rowHeights?.[index] ?? rowHeight,
10056
10280
  onTasksChange,
10057
10281
  onDurationChange,
10058
10282
  selectedTaskId,
@@ -10109,7 +10333,8 @@ var TaskList = ({
10109
10333
  onActiveCustomCellChange: setActiveCustomCell,
10110
10334
  taskListMenuCommands,
10111
10335
  hideTaskListRowActions,
10112
- rowClassName: getTaskListRowClassName?.(task),
10336
+ rowClassName: [task.parentId && compositeParentIds.has(task.parentId) ? "gantt-tl-row-composite-detail" : "", getTaskListRowClassName?.(task)].filter(Boolean).join(" "),
10337
+ compositeParentColor: task.parentId ? compositeParentsById.get(task.parentId)?.color : void 0,
10113
10338
  getTaskListNamePrefixIcon,
10114
10339
  taskDateChangeMode,
10115
10340
  onTaskDateChangeModeChange,
@@ -10125,7 +10350,7 @@ var TaskList = ({
10125
10350
  {
10126
10351
  style: {
10127
10352
  position: "absolute",
10128
- top: `${(pendingInsertRowIndex + 1) * rowHeight}px`,
10353
+ top: `${rowTops?.[pendingInsertRowIndex + 1] ?? (pendingInsertRowIndex + 1) * rowHeight}px`,
10129
10354
  left: 0,
10130
10355
  right: 0
10131
10356
  },
@@ -10147,6 +10372,7 @@ var TaskList = ({
10147
10372
  rowHeight,
10148
10373
  variant: "task-list",
10149
10374
  startIndex: visibleTasks.length,
10375
+ startTop: totalHeight - Math.max(0, Math.floor(skeletonRowCount)) * rowHeight,
10150
10376
  taskListLayout: generationSkeletonTaskListLayout
10151
10377
  }
10152
10378
  )
@@ -10200,7 +10426,7 @@ var TaskList = ({
10200
10426
  };
10201
10427
 
10202
10428
  // src/components/ResourceTimelineChart/ResourceTimelineChart.tsx
10203
- import React14, { useCallback as useCallback8, useEffect as useEffect8, useMemo as useMemo10, useRef as useRef9, useState as useState9 } from "react";
10429
+ import React14, { useCallback as useCallback8, useEffect as useEffect9, useMemo as useMemo10, useRef as useRef10, useState as useState10 } from "react";
10204
10430
 
10205
10431
  // src/utils/resourceTimelineLayout.ts
10206
10432
  var isInvalidDate = (date) => Number.isNaN(date.getTime());
@@ -10402,7 +10628,7 @@ var layoutResourceTimelineItems = (resources, options) => {
10402
10628
  };
10403
10629
 
10404
10630
  // src/hooks/useResourceItemDrag.ts
10405
- import { useCallback as useCallback7, useEffect as useEffect7, useRef as useRef8, useState as useState8 } from "react";
10631
+ import { useCallback as useCallback7, useEffect as useEffect8, useRef as useRef9, useState as useState9 } from "react";
10406
10632
  var RESOURCE_CURSOR_STYLE_ID = "gantt-resource-global-drag-cursor-style";
10407
10633
  function ensureResourceGlobalCursorStyle() {
10408
10634
  if (typeof document === "undefined") return;
@@ -10514,15 +10740,15 @@ var useResourceItemDrag = ({
10514
10740
  weekendPredicate,
10515
10741
  onResourceItemMove
10516
10742
  }) => {
10517
- const activeDragRef = useRef8(null);
10518
- const rowsRef = useRef8(rows);
10519
- const onResourceItemMoveRef = useRef8(onResourceItemMove);
10520
- const rafRef = useRef8(null);
10521
- const [preview, setPreview] = useState8(null);
10522
- useEffect7(() => {
10743
+ const activeDragRef = useRef9(null);
10744
+ const rowsRef = useRef9(rows);
10745
+ const onResourceItemMoveRef = useRef9(onResourceItemMove);
10746
+ const rafRef = useRef9(null);
10747
+ const [preview, setPreview] = useState9(null);
10748
+ useEffect8(() => {
10523
10749
  rowsRef.current = rows;
10524
10750
  }, [rows]);
10525
- useEffect7(() => {
10751
+ useEffect8(() => {
10526
10752
  onResourceItemMoveRef.current = onResourceItemMove;
10527
10753
  }, [onResourceItemMove]);
10528
10754
  const clearRaf = useCallback7(() => {
@@ -10537,7 +10763,7 @@ var useResourceItemDrag = ({
10537
10763
  activeDragRef.current = null;
10538
10764
  setPreview(null);
10539
10765
  }, [clearRaf]);
10540
- useEffect7(() => {
10766
+ useEffect8(() => {
10541
10767
  const handleMouseMove = (event) => {
10542
10768
  const activeDrag = activeDragRef.current;
10543
10769
  if (!activeDrag || rafRef.current !== null) {
@@ -10870,13 +11096,13 @@ var ResourceHeader = ({
10870
11096
  onConflictBadgeClick,
10871
11097
  columnTemplate
10872
11098
  }) => {
10873
- const [menuOpen, setMenuOpen] = useState9(false);
10874
- const [typeMenuOpen, setTypeMenuOpen] = useState9(false);
10875
- const [scopeMenuOpen, setScopeMenuOpen] = useState9(false);
10876
- const [editingName, setEditingName] = useState9(false);
10877
- const [nameValue, setNameValue] = useState9(resource.name);
10878
- const nameInputRef = useRef9(null);
10879
- const nameConfirmedRef = useRef9(false);
11099
+ const [menuOpen, setMenuOpen] = useState10(false);
11100
+ const [typeMenuOpen, setTypeMenuOpen] = useState10(false);
11101
+ const [scopeMenuOpen, setScopeMenuOpen] = useState10(false);
11102
+ const [editingName, setEditingName] = useState10(false);
11103
+ const [nameValue, setNameValue] = useState10(resource.name);
11104
+ const nameInputRef = useRef10(null);
11105
+ const nameConfirmedRef = useRef10(false);
10880
11106
  const visibleCommands = useMemo10(
10881
11107
  () => menuCommands.filter((command) => command.isVisible?.(resource) ?? true),
10882
11108
  [menuCommands, resource]
@@ -10889,12 +11115,12 @@ var ResourceHeader = ({
10889
11115
  const applyResourcePatch = useCallback8((patch) => {
10890
11116
  onResourceChange?.({ ...resource, ...patch });
10891
11117
  }, [onResourceChange, resource]);
10892
- useEffect8(() => {
11118
+ useEffect9(() => {
10893
11119
  if (!editingName) {
10894
11120
  setNameValue(resource.name);
10895
11121
  }
10896
11122
  }, [editingName, resource.name]);
10897
- useEffect8(() => {
11123
+ useEffect9(() => {
10898
11124
  if (editingName && nameInputRef.current) {
10899
11125
  nameInputRef.current.focus();
10900
11126
  nameInputRef.current.select();
@@ -11135,10 +11361,10 @@ var ResourceHeader = ({
11135
11361
  );
11136
11362
  };
11137
11363
  var NewResourceRow = ({ height, onConfirm, onCancel, columnTemplate }) => {
11138
- const [nameValue, setNameValue] = useState9("");
11139
- const inputRef = useRef9(null);
11140
- const confirmedRef = useRef9(false);
11141
- useEffect8(() => {
11364
+ const [nameValue, setNameValue] = useState10("");
11365
+ const inputRef = useRef10(null);
11366
+ const confirmedRef = useRef10(false);
11367
+ useEffect9(() => {
11142
11368
  inputRef.current?.focus();
11143
11369
  inputRef.current?.select();
11144
11370
  }, []);
@@ -11223,18 +11449,18 @@ function ResourceTimelineChart({
11223
11449
  resourceTableColumnWidths,
11224
11450
  onResourceTableColumnWidthsChange
11225
11451
  }) {
11226
- const scrollContainerRef = useRef9(null);
11227
- const gridRef = useRef9(null);
11228
- const panStateRef = useRef9(null);
11229
- const resourceColumnResizeRef = useRef9(null);
11230
- const conflictNavigationIndexRef = useRef9(/* @__PURE__ */ new Map());
11231
- const resourceNavigationIndexRef = useRef9(/* @__PURE__ */ new Map());
11232
- const conflictHighlightTimeoutRef = useRef9(null);
11233
- const [isCreatingResource, setIsCreatingResource] = useState9(false);
11234
- const [creatingResourceGroupType, setCreatingResourceGroupType] = useState9(null);
11235
- const [activeConflictItemId, setActiveConflictItemId] = useState9(null);
11236
- const [resourceColumnHasRightShadow, setResourceColumnHasRightShadow] = useState9(false);
11237
- const [resourceColumnWidthOverrides, setResourceColumnWidthOverrides] = useState9(() => normalizeResourceColumnWidthMap(resourceTableColumnWidths));
11452
+ const scrollContainerRef = useRef10(null);
11453
+ const gridRef = useRef10(null);
11454
+ const panStateRef = useRef10(null);
11455
+ const resourceColumnResizeRef = useRef10(null);
11456
+ const conflictNavigationIndexRef = useRef10(/* @__PURE__ */ new Map());
11457
+ const resourceNavigationIndexRef = useRef10(/* @__PURE__ */ new Map());
11458
+ const conflictHighlightTimeoutRef = useRef10(null);
11459
+ const [isCreatingResource, setIsCreatingResource] = useState10(false);
11460
+ const [creatingResourceGroupType, setCreatingResourceGroupType] = useState10(null);
11461
+ const [activeConflictItemId, setActiveConflictItemId] = useState10(null);
11462
+ const [resourceColumnHasRightShadow, setResourceColumnHasRightShadow] = useState10(false);
11463
+ const [resourceColumnWidthOverrides, setResourceColumnWidthOverrides] = useState10(() => normalizeResourceColumnWidthMap(resourceTableColumnWidths));
11238
11464
  const validItems = useMemo10(() => collectValidItems(resources), [resources]);
11239
11465
  const dateRange = useMemo10(() => getMultiMonthDays(validItems), [validItems]);
11240
11466
  const monthStart = useMemo10(() => {
@@ -11246,7 +11472,7 @@ function ResourceTimelineChart({
11246
11472
  [customDays, isWeekend3]
11247
11473
  );
11248
11474
  const gridWidth = useMemo10(() => Math.round(dateRange.length * dayWidth), [dateRange.length, dayWidth]);
11249
- useEffect8(() => {
11475
+ useEffect9(() => {
11250
11476
  setResourceColumnWidthOverrides(normalizeResourceColumnWidthMap(resourceTableColumnWidths));
11251
11477
  }, [resourceTableColumnWidths]);
11252
11478
  const resolvedResourceColumnWidths = useMemo10(
@@ -11302,7 +11528,7 @@ function ResourceTimelineChart({
11302
11528
  );
11303
11529
  const canAddResource = enableAddResource && Boolean(onAddResource);
11304
11530
  const resourceAddRowHeight = laneHeight + DEFAULT_RESOURCE_ROW_GAP;
11305
- const [collapsedResourceGroups, setCollapsedResourceGroups] = useState9(/* @__PURE__ */ new Set());
11531
+ const [collapsedResourceGroups, setCollapsedResourceGroups] = useState10(/* @__PURE__ */ new Set());
11306
11532
  const updateResourceColumnWidth = useCallback8((columnId, width) => {
11307
11533
  setResourceColumnWidthOverrides((prev) => {
11308
11534
  if (prev[columnId] === width) {
@@ -11313,7 +11539,7 @@ function ResourceTimelineChart({
11313
11539
  return next;
11314
11540
  });
11315
11541
  }, [onResourceTableColumnWidthsChange]);
11316
- useEffect8(() => {
11542
+ useEffect9(() => {
11317
11543
  const handleMouseMove = (event) => {
11318
11544
  const resizeState = resourceColumnResizeRef.current;
11319
11545
  if (!resizeState) {
@@ -11582,7 +11808,7 @@ function ResourceTimelineChart({
11582
11808
  weekendPredicate,
11583
11809
  onResourceItemMove
11584
11810
  });
11585
- useEffect8(() => {
11811
+ useEffect9(() => {
11586
11812
  const container = scrollContainerRef.current;
11587
11813
  if (!container) return;
11588
11814
  const updateShadow = () => {
@@ -11622,7 +11848,7 @@ function ResourceTimelineChart({
11622
11848
  container.style.cursor = "grabbing";
11623
11849
  event.preventDefault();
11624
11850
  }, []);
11625
- useEffect8(() => {
11851
+ useEffect9(() => {
11626
11852
  const handlePanMove = (event) => {
11627
11853
  const pan = panStateRef.current;
11628
11854
  const container = scrollContainerRef.current;
@@ -11651,7 +11877,7 @@ function ResourceTimelineChart({
11651
11877
  window.removeEventListener("mouseup", handlePanEnd);
11652
11878
  };
11653
11879
  }, [allowVerticalPan]);
11654
- useEffect8(() => {
11880
+ useEffect9(() => {
11655
11881
  const nextNavigation = /* @__PURE__ */ new Map();
11656
11882
  for (const [resourceId, resourceItems] of itemsByResourceId.entries()) {
11657
11883
  if (resourceItems.length === 0) {
@@ -11661,7 +11887,7 @@ function ResourceTimelineChart({
11661
11887
  }
11662
11888
  resourceNavigationIndexRef.current = nextNavigation;
11663
11889
  }, [itemsByResourceId]);
11664
- useEffect8(() => {
11890
+ useEffect9(() => {
11665
11891
  return () => {
11666
11892
  if (conflictHighlightTimeoutRef.current) {
11667
11893
  window.clearTimeout(conflictHighlightTimeoutRef.current);
@@ -12081,7 +12307,7 @@ function ResourceTimelineChart({
12081
12307
  }
12082
12308
 
12083
12309
  // src/components/TableMatrix/TableMatrix.tsx
12084
- import { useLayoutEffect, useMemo as useMemo11, useRef as useRef10, useState as useState10 } from "react";
12310
+ import { useLayoutEffect, useMemo as useMemo11, useRef as useRef11, useState as useState11 } from "react";
12085
12311
  import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
12086
12312
  var AUTO_COLUMN_MIN_WIDTH = 72;
12087
12313
  var AUTO_COLUMN_MAX_WIDTH = 180;
@@ -12148,8 +12374,8 @@ function TableMatrix({
12148
12374
  filterMode = "highlight",
12149
12375
  visibleRowIndices
12150
12376
  }) {
12151
- const measureRef = useRef10(null);
12152
- const [measuredAutoWidths, setMeasuredAutoWidths] = useState10([]);
12377
+ const measureRef = useRef11(null);
12378
+ const [measuredAutoWidths, setMeasuredAutoWidths] = useState11([]);
12153
12379
  const hasAutoWidthColumns = useMemo11(
12154
12380
  () => columns.some((column) => typeof column.width !== "number"),
12155
12381
  [columns]
@@ -12449,7 +12675,7 @@ function TableMatrix({
12449
12675
  }
12450
12676
 
12451
12677
  // src/components/PlanFactMatrix/PlanFactMatrix.tsx
12452
- import React16, { useCallback as useCallback9, useEffect as useEffect9, useMemo as useMemo12, useRef as useRef11, useState as useState11 } from "react";
12678
+ import React16, { useCallback as useCallback9, useEffect as useEffect10, useMemo as useMemo12, useRef as useRef12, useState as useState12 } from "react";
12453
12679
  import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
12454
12680
  var DAY_MS5 = 24 * 60 * 60 * 1e3;
12455
12681
  function joinClasses2(...values) {
@@ -12541,9 +12767,9 @@ function PlanFactCellEditor({
12541
12767
  onCommitRange,
12542
12768
  onCancel
12543
12769
  }) {
12544
- const [draft, setDraft] = useState11(startValue ?? (value === void 0 ? "" : String(value)));
12545
- const inputRef = useRef11(null);
12546
- useEffect9(() => {
12770
+ const [draft, setDraft] = useState12(startValue ?? (value === void 0 ? "" : String(value)));
12771
+ const inputRef = useRef12(null);
12772
+ useEffect10(() => {
12547
12773
  inputRef.current?.focus();
12548
12774
  if (startValue === void 0) {
12549
12775
  inputRef.current?.select();
@@ -12896,18 +13122,18 @@ function PlanFactMatrix({
12896
13122
  todayDateIndex,
12897
13123
  isCustomWeekend
12898
13124
  }) {
12899
- const [activeCell, setActiveCell] = useState11(null);
12900
- const [editingCell, setEditingCell] = useState11(null);
12901
- const [selectedRange, setSelectedRange] = useState11(null);
12902
- const [fillRange, setFillRange] = useState11(null);
12903
- const [overflowTooltip, setOverflowTooltip] = useState11(null);
12904
- const isSelectingRef = useRef11(false);
12905
- const isFillDraggingRef = useRef11(false);
12906
- const didDragSelectRef = useRef11(false);
12907
- const pendingHoverCellRef = useRef11(null);
12908
- const hoverFrameRef = useRef11(null);
12909
- const rootRef = useRef11(null);
12910
- const bodyRef = useRef11(null);
13125
+ const [activeCell, setActiveCell] = useState12(null);
13126
+ const [editingCell, setEditingCell] = useState12(null);
13127
+ const [selectedRange, setSelectedRange] = useState12(null);
13128
+ const [fillRange, setFillRange] = useState12(null);
13129
+ const [overflowTooltip, setOverflowTooltip] = useState12(null);
13130
+ const isSelectingRef = useRef12(false);
13131
+ const isFillDraggingRef = useRef12(false);
13132
+ const didDragSelectRef = useRef12(false);
13133
+ const pendingHoverCellRef = useRef12(null);
13134
+ const hoverFrameRef = useRef12(null);
13135
+ const rootRef = useRef12(null);
13136
+ const bodyRef = useRef12(null);
12911
13137
  const totalWidth = dateRange.length * dayWidth;
12912
13138
  const subrowHeight = rowHeight / 2;
12913
13139
  const renderedRowIndices = useMemo12(
@@ -13313,7 +13539,7 @@ function PlanFactMatrix({
13313
13539
  onTaskSelect?.(nextCell.taskId);
13314
13540
  focusCell(selectedRange?.anchor ?? cell);
13315
13541
  }, [dateRange.length, focusCell, onTaskSelect, parentTaskIds, selectedRange, tasks]);
13316
- useEffect9(() => {
13542
+ useEffect10(() => {
13317
13543
  const endSelection = () => {
13318
13544
  let pendingFillRange = null;
13319
13545
  if (hoverFrameRef.current !== null) {
@@ -13336,12 +13562,12 @@ function PlanFactMatrix({
13336
13562
  window.removeEventListener("mouseup", endSelection);
13337
13563
  };
13338
13564
  }, [applyFillRange, flushPendingHoverCell]);
13339
- useEffect9(() => () => {
13565
+ useEffect10(() => () => {
13340
13566
  if (hoverFrameRef.current !== null) {
13341
13567
  window.cancelAnimationFrame(hoverFrameRef.current);
13342
13568
  }
13343
13569
  }, []);
13344
- useEffect9(() => {
13570
+ useEffect10(() => {
13345
13571
  const handleKeyDown = (event) => {
13346
13572
  if (event.key !== "Escape") return;
13347
13573
  clearSelection();
@@ -14144,39 +14370,43 @@ function TaskGanttChartInner(props, ref) {
14144
14370
  const onMatrixCellClick = isTableMatrixMode ? props.onMatrixCellClick : void 0;
14145
14371
  const matrixDateOverlay = isTableMatrixMode ? props.matrixDateOverlay : void 0;
14146
14372
  const onPlanFactCellCommit = isPlanFactMode ? props.onPlanFactCellCommit : void 0;
14147
- const containerRef = useRef12(null);
14148
- const scrollContainerRef = useRef12(null);
14149
- const scrollContentRef = useRef12(null);
14150
- const clearSelectedTaskTimeoutRef = useRef12(null);
14151
- const hasAutoScrolledToTodayRef = useRef12(false);
14152
- const previewPositionStoreRef = useRef12(null);
14153
- const renderedTaskIdsRef = useRef12(/* @__PURE__ */ new Set());
14373
+ const containerRef = useRef13(null);
14374
+ const scrollContainerRef = useRef13(null);
14375
+ const scrollContentRef = useRef13(null);
14376
+ const clearSelectedTaskTimeoutRef = useRef13(null);
14377
+ const hasAutoScrolledToTodayRef = useRef13(false);
14378
+ const previewPositionStoreRef = useRef13(null);
14379
+ const renderedTaskIdsRef = useRef13(/* @__PURE__ */ new Set());
14154
14380
  if (previewPositionStoreRef.current === null) {
14155
14381
  previewPositionStoreRef.current = createTaskPreviewPositionStore();
14156
14382
  }
14157
14383
  const previewPositionStore = previewPositionStoreRef.current;
14158
- const [selectedTaskId, setSelectedTaskId] = useState12(null);
14159
- const [taskListHasRightShadow, setTaskListHasRightShadow] = useState12(false);
14160
- const [internalTaskDateChangeMode, setInternalTaskDateChangeMode] = useState12("preserve-duration");
14161
- const [scrollViewport, setScrollViewport] = useState12(() => ({
14384
+ const [selectedTaskId, setSelectedTaskId] = useState13(null);
14385
+ const [taskListHasRightShadow, setTaskListHasRightShadow] = useState13(false);
14386
+ const [internalTaskDateChangeMode, setInternalTaskDateChangeMode] = useState13("preserve-duration");
14387
+ const [scrollViewport, setScrollViewport] = useState13(() => ({
14162
14388
  scrollTop: 0,
14163
14389
  viewportHeight: getInitialScrollViewportHeight(containerHeight, headerHeight + 1)
14164
14390
  }));
14165
- const [forceFullRenderForPrint, setForceFullRenderForPrint] = useState12(false);
14166
- const [planFactDateWindow, setPlanFactDateWindow] = useState12({ start: 0, end: 0 });
14167
- const [selectedChip, setSelectedChip] = useState12(null);
14168
- const [dependencyCreationDrag, setDependencyCreationDrag] = useState12(null);
14169
- const dependencyCreationRef = useRef12(null);
14170
- const [dependencyCreationError, setDependencyCreationError] = useState12(null);
14171
- const [dependencyLineMenu, setDependencyLineMenu] = useState12(null);
14391
+ const [forceFullRenderForPrint, setForceFullRenderForPrint] = useState13(false);
14392
+ const [planFactDateWindow, setPlanFactDateWindow] = useState13({ start: 0, end: 0 });
14393
+ const [selectedChip, setSelectedChip] = useState13(null);
14394
+ const [dependencyCreationDrag, setDependencyCreationDrag] = useState13(null);
14395
+ const dependencyCreationRef = useRef13(null);
14396
+ const [dependencyCreationError, setDependencyCreationError] = useState13(null);
14397
+ const [dependencyLineMenu, setDependencyLineMenu] = useState13(null);
14172
14398
  const getDependencyPortPoint = useCallback10((element, chartRect) => {
14173
14399
  const rect = element.getBoundingClientRect();
14174
14400
  const side = element.getAttribute("data-port-side");
14175
14401
  return {
14176
- // The port hit-area is deliberately outside the bar. Anchor the preview
14177
- // to the actual bar boundary instead of the center of that outside zone.
14178
- x: Math.round((side === "left" ? rect.right - 6 : rect.left + 6) - chartRect.left),
14179
- y: Math.round(rect.top + rect.height / 2 - chartRect.top)
14402
+ // Anchor the preview at the port dot's center so the dragged polyline
14403
+ // ends exactly on the link handle. The dot core is 8px and sits 4px
14404
+ // inside the 24px zone from the bar side (see TaskRow.css), which puts
14405
+ // its center 8px off the button edge adjacent to the bar. No rounding:
14406
+ // the CSS ball centers at a fractional position, and a rounded SVG
14407
+ // coordinate landed ~1px off it.
14408
+ x: (side === "left" ? rect.right - 8 : rect.left + 8) - chartRect.left,
14409
+ y: rect.top + rect.height / 2 - chartRect.top
14180
14410
  };
14181
14411
  }, []);
14182
14412
  const getVisualDependencyType = useCallback10((sourceSide, targetSide) => {
@@ -14198,7 +14428,7 @@ function TaskGanttChartInner(props, ref) {
14198
14428
  dependencyCreationRef.current = nextDrag;
14199
14429
  setDependencyCreationDrag(nextDrag);
14200
14430
  }, [disableDependencyEditing, getDependencyPortPoint]);
14201
- useEffect10(() => {
14431
+ useEffect11(() => {
14202
14432
  if (!dependencyCreationDrag) return;
14203
14433
  const handlePointerMove = (event) => {
14204
14434
  const activeDrag = dependencyCreationRef.current;
@@ -14282,12 +14512,12 @@ function TaskGanttChartInner(props, ref) {
14282
14512
  document.removeEventListener("keydown", handleKeyDown);
14283
14513
  };
14284
14514
  }, [businessDays, customDays, dependencyCreationDrag !== null, getDependencyPortPoint, getVisualDependencyType, isWeekend3, onTasksChange, tasks]);
14285
- useEffect10(() => {
14515
+ useEffect11(() => {
14286
14516
  if (!dependencyCreationError) return;
14287
14517
  const timeout = window.setTimeout(() => setDependencyCreationError(null), 3e3);
14288
14518
  return () => window.clearTimeout(timeout);
14289
14519
  }, [dependencyCreationError]);
14290
- useEffect10(() => {
14520
+ useEffect11(() => {
14291
14521
  if (!dependencyLineMenu) return;
14292
14522
  const closeMenu = (event) => {
14293
14523
  const target = event.target;
@@ -14325,10 +14555,10 @@ function TaskGanttChartInner(props, ref) {
14325
14555
  setSelectedChip(null);
14326
14556
  setDependencyLineMenu(null);
14327
14557
  }, [dependencyLineMenu, onTasksChange, tasks]);
14328
- const [activeTimelineTooltip, setActiveTimelineTooltip] = useState12(null);
14329
- const [internalCollapsedParentIds, setInternalCollapsedParentIds] = useState12(/* @__PURE__ */ new Set());
14330
- const collapsedParentIds = externalCollapsedParentIds ?? internalCollapsedParentIds;
14331
- const [editingTaskId, setEditingTaskId] = useState12(null);
14558
+ const [activeTimelineTooltip, setActiveTimelineTooltip] = useState13(null);
14559
+ const [internalCollapsedParentIds, setInternalCollapsedParentIds] = useState13(/* @__PURE__ */ new Set());
14560
+ const [expandedCompositeIds, setExpandedCompositeIds] = useState13(/* @__PURE__ */ new Set());
14561
+ const [editingTaskId, setEditingTaskId] = useState13(null);
14332
14562
  const taskDateChangeMode = externalTaskDateChangeMode ?? internalTaskDateChangeMode;
14333
14563
  const handleTaskDateChangeMode = externalOnTaskDateChangeModeChange ?? setInternalTaskDateChangeMode;
14334
14564
  const resolvedRowContentLines = isPlanFactMode ? Math.max(2, Math.floor(rowContentLines)) : Math.max(1, Math.floor(rowContentLines));
@@ -14337,14 +14567,43 @@ function TaskGanttChartInner(props, ref) {
14337
14567
  () => Math.max(rowHeight, 10 + resolvedRowContentLines * 18),
14338
14568
  [resolvedRowContentLines, rowHeight]
14339
14569
  );
14340
- const normalizedTasks = useMemo13(() => normalizeHierarchyTasks(tasks), [tasks]);
14570
+ const normalizedTasks = useMemo13(() => {
14571
+ const ordered = normalizeHierarchyTasks(tasks);
14572
+ const ranges = /* @__PURE__ */ new Map();
14573
+ for (const task of ordered) {
14574
+ if (!task.parentId) continue;
14575
+ const start = parseUTCDate(task.startDate).getTime();
14576
+ const end = parseUTCDate(task.endDate).getTime();
14577
+ const range = ranges.get(task.parentId);
14578
+ ranges.set(task.parentId, {
14579
+ start: Math.min(range?.start ?? start, start),
14580
+ end: Math.max(range?.end ?? end, end)
14581
+ });
14582
+ }
14583
+ return ordered.map((task) => {
14584
+ const range = task.composite ? ranges.get(task.id) : void 0;
14585
+ return range ? {
14586
+ ...task,
14587
+ startDate: new Date(range.start).toISOString().slice(0, 10),
14588
+ endDate: new Date(range.end).toISOString().slice(0, 10)
14589
+ } : task;
14590
+ });
14591
+ }, [tasks]);
14592
+ const collapsedParentIds = useMemo13(() => {
14593
+ if (externalCollapsedParentIds) return externalCollapsedParentIds;
14594
+ const ids = new Set(internalCollapsedParentIds);
14595
+ for (const task of normalizedTasks) {
14596
+ if (task.composite && !expandedCompositeIds.has(task.id)) ids.add(task.id);
14597
+ }
14598
+ return ids;
14599
+ }, [externalCollapsedParentIds, internalCollapsedParentIds, normalizedTasks, expandedCompositeIds]);
14341
14600
  const controlledScheduleIdentity = useMemo13(
14342
14601
  () => buildControlledScheduleIdentity(normalizedTasks),
14343
14602
  [normalizedTasks]
14344
14603
  );
14345
- const previousControlledScheduleIdentityRef = useRef12(null);
14604
+ const previousControlledScheduleIdentityRef = useRef13(null);
14346
14605
  const controlledScheduleChanged = previousControlledScheduleIdentityRef.current !== null && previousControlledScheduleIdentityRef.current !== controlledScheduleIdentity;
14347
- const [previewTasksById, setPreviewTasksById] = useState12(/* @__PURE__ */ new Map());
14606
+ const [previewTasksById, setPreviewTasksById] = useState13(/* @__PURE__ */ new Map());
14348
14607
  const previewNormalizedTasks = useMemo13(() => {
14349
14608
  if (controlledScheduleChanged || previewTasksById.size === 0) return normalizedTasks;
14350
14609
  return normalizedTasks.map((task) => previewTasksById.get(task.id) ?? task);
@@ -14384,8 +14643,8 @@ function TaskGanttChartInner(props, ref) {
14384
14643
  },
14385
14644
  [dateRangeTasks, explicitDateRange, isPlanFactMode]
14386
14645
  );
14387
- const [validationResult, setValidationResult] = useState12(null);
14388
- const [cascadeOverrides, setCascadeOverrides] = useState12(/* @__PURE__ */ new Map());
14646
+ const [validationResult, setValidationResult] = useState13(null);
14647
+ const [cascadeOverrides, setCascadeOverrides] = useState13(/* @__PURE__ */ new Map());
14389
14648
  const gridWidth = useMemo13(
14390
14649
  () => Math.round(dateRange.length * dayWidth),
14391
14650
  [dateRange.length, dayWidth]
@@ -14417,6 +14676,23 @@ function TaskGanttChartInner(props, ref) {
14417
14676
  }
14418
14677
  return tasks2;
14419
14678
  }, [normalizedTasks, collapsedParentIds, filterMode, taskFilter, criticalPathMode, criticalTaskIds]);
14679
+ const compositeRowLayout = useMemo13(() => {
14680
+ const compositeIds = new Set(normalizedTasks.filter((task) => task.composite).map((task) => task.id));
14681
+ const tops = [];
14682
+ const heights = [];
14683
+ let top = 0;
14684
+ for (const task of visibleTasks) {
14685
+ tops.push(top);
14686
+ const height = task.parentId && compositeIds.has(task.parentId) ? Math.min(effectiveRowHeight, 22) : effectiveRowHeight;
14687
+ heights.push(height);
14688
+ top += height;
14689
+ }
14690
+ return { tops, heights, totalHeight: top };
14691
+ }, [normalizedTasks, visibleTasks, effectiveRowHeight]);
14692
+ const compositeParentsById = useMemo13(
14693
+ () => new Map(normalizedTasks.filter((task) => task.composite).map((task) => [task.id, task])),
14694
+ [normalizedTasks]
14695
+ );
14420
14696
  const matchedTaskIds = useMemo13(() => {
14421
14697
  if (!taskFilter) return /* @__PURE__ */ new Set();
14422
14698
  return new Set(visibleTasks.filter(taskFilter).map((task) => task.id));
@@ -14438,8 +14714,8 @@ function TaskGanttChartInner(props, ref) {
14438
14714
  return mergedHighlightedTaskIds;
14439
14715
  }, [filterMode, highlightedTaskIds, matchedTaskIds, criticalPathMode, criticalTaskIds]);
14440
14716
  const totalGridHeight = useMemo13(
14441
- () => (visibleTasks.length + Math.max(0, Math.floor(skeletonRowCount))) * effectiveRowHeight,
14442
- [effectiveRowHeight, skeletonRowCount, visibleTasks.length]
14717
+ () => compositeRowLayout.totalHeight + Math.max(0, Math.floor(skeletonRowCount)) * effectiveRowHeight,
14718
+ [compositeRowLayout.totalHeight, effectiveRowHeight, skeletonRowCount]
14443
14719
  );
14444
14720
  const timelineHeaderHeight = headerHeight + 1;
14445
14721
  const tableBodyMinHeight = useMemo13(() => {
@@ -14477,7 +14753,7 @@ function TaskGanttChartInner(props, ref) {
14477
14753
  return markerDate >= rangeStartMs && markerDate <= rangeEndMs;
14478
14754
  });
14479
14755
  }, [dateRange, isTableMatrixMode, timelineMarkers]);
14480
- useEffect10(() => {
14756
+ useEffect11(() => {
14481
14757
  if (isTableMatrixMode) return;
14482
14758
  if (hasAutoScrolledToTodayRef.current) return;
14483
14759
  const container = scrollContainerRef.current;
@@ -14491,7 +14767,7 @@ function TaskGanttChartInner(props, ref) {
14491
14767
  container.scrollLeft = Math.max(0, scrollLeft);
14492
14768
  hasAutoScrolledToTodayRef.current = true;
14493
14769
  }, [dateRange, dayWidth, isTableMatrixMode]);
14494
- useEffect10(() => {
14770
+ useEffect11(() => {
14495
14771
  const container = scrollContainerRef.current;
14496
14772
  if (!container) return;
14497
14773
  let frameId = null;
@@ -14566,7 +14842,7 @@ function TaskGanttChartInner(props, ref) {
14566
14842
  const rowIndex = visibleTasks.findIndex((visibleTask) => visibleTask.id === taskId);
14567
14843
  if (rowIndex === -1) return;
14568
14844
  const paddedRowIndex = Math.max(0, rowIndex - SCROLL_TO_ROW_CONTEXT_ROWS);
14569
- container2.scrollTo({ top: Math.max(0, effectiveRowHeight * paddedRowIndex), behavior: "smooth" });
14845
+ container2.scrollTo({ top: Math.max(0, compositeRowLayout.tops[paddedRowIndex] ?? 0), behavior: "smooth" });
14570
14846
  setSelectedTaskId(taskId);
14571
14847
  return;
14572
14848
  }
@@ -14585,7 +14861,7 @@ function TaskGanttChartInner(props, ref) {
14585
14861
  const taskOffset = taskIndex * dayWidth;
14586
14862
  const scrollLeft = Math.round(taskOffset - dayWidth * 2);
14587
14863
  container.scrollTo({ left: Math.max(0, scrollLeft), behavior: "smooth" });
14588
- }, [dateRange, dayWidth, effectiveRowHeight, isTableMatrixMode, tasks, visibleTasks]);
14864
+ }, [dateRange, dayWidth, effectiveRowHeight, compositeRowLayout, isTableMatrixMode, tasks, visibleTasks]);
14589
14865
  const scrollToRow = useCallback10((taskId, options = {}) => {
14590
14866
  const container = scrollContainerRef.current;
14591
14867
  if (!container) return;
@@ -14594,7 +14870,7 @@ function TaskGanttChartInner(props, ref) {
14594
14870
  const rowIndex = visibleTasks.findIndex((visibleTask) => visibleTask.id === task.id);
14595
14871
  if (rowIndex === -1) return;
14596
14872
  const paddedRowIndex = Math.max(0, rowIndex - SCROLL_TO_ROW_CONTEXT_ROWS);
14597
- const scrollTop = Math.max(0, effectiveRowHeight * paddedRowIndex);
14873
+ const scrollTop = Math.max(0, compositeRowLayout.tops[paddedRowIndex] ?? 0);
14598
14874
  const {
14599
14875
  select = true,
14600
14876
  behavior = "smooth",
@@ -14614,10 +14890,10 @@ function TaskGanttChartInner(props, ref) {
14614
14890
  }
14615
14891
  }
14616
14892
  container.scrollTo({ top: scrollTop, behavior });
14617
- }, [effectiveRowHeight, tasks, visibleTasks]);
14618
- const [dragGuideLines, setDragGuideLines] = useState12(null);
14619
- const [draggedTaskOverride, setDraggedTaskOverride] = useState12(null);
14620
- useEffect10(() => {
14893
+ }, [effectiveRowHeight, compositeRowLayout, tasks, visibleTasks]);
14894
+ const [dragGuideLines, setDragGuideLines] = useState13(null);
14895
+ const [draggedTaskOverride, setDraggedTaskOverride] = useState13(null);
14896
+ useEffect11(() => {
14621
14897
  const previousIdentity = previousControlledScheduleIdentityRef.current;
14622
14898
  previousControlledScheduleIdentityRef.current = controlledScheduleIdentity;
14623
14899
  if (previousIdentity === null || previousIdentity === controlledScheduleIdentity) {
@@ -14629,21 +14905,21 @@ function TaskGanttChartInner(props, ref) {
14629
14905
  setDraggedTaskOverride((current) => current === null ? current : null);
14630
14906
  setDragGuideLines((current) => current === null ? current : null);
14631
14907
  }, [controlledScheduleIdentity, previewPositionStore]);
14632
- useEffect10(() => {
14908
+ useEffect11(() => {
14633
14909
  const result = validateDependencies(tasks);
14634
14910
  setValidationResult(result);
14635
14911
  onValidateDependencies?.(result);
14636
14912
  }, [tasks, onValidateDependencies]);
14637
- useEffect10(() => () => {
14913
+ useEffect11(() => () => {
14638
14914
  if (clearSelectedTaskTimeoutRef.current !== null) {
14639
14915
  window.clearTimeout(clearSelectedTaskTimeoutRef.current);
14640
14916
  }
14641
14917
  previewPositionStore.clear();
14642
14918
  }, [previewPositionStore]);
14643
14919
  const handleTaskChange = useCallback10((updatedTasks, durationEdit) => {
14644
- const updatedTask = updatedTasks[0];
14920
+ let updatedTask = updatedTasks[0];
14645
14921
  if (!updatedTask) return;
14646
- const originalTask = tasks.find((t) => t.id === updatedTask.id);
14922
+ const originalTask = normalizedTasks.find((t) => t.id === updatedTask.id);
14647
14923
  if (!originalTask) {
14648
14924
  onTasksChange?.(updatedTasks);
14649
14925
  if (editingTaskId === updatedTask.id) {
@@ -14656,6 +14932,8 @@ function TaskGanttChartInner(props, ref) {
14656
14932
  const newStart = new Date(updatedTask.startDate);
14657
14933
  const newEnd = new Date(updatedTask.endDate);
14658
14934
  const datesChanged = origStart.getTime() !== newStart.getTime() || origEnd.getTime() !== newEnd.getTime();
14935
+ const isCompositeDetail = datesChanged && tasks.some((task) => task.id === updatedTask.parentId && task.composite);
14936
+ if (isCompositeDetail) updatedTask = { ...updatedTask, synced: false };
14659
14937
  if (!datesChanged) {
14660
14938
  if (updatedTasks.length > 1) {
14661
14939
  onTasksChange?.(updatedTasks);
@@ -14717,7 +14995,7 @@ function TaskGanttChartInner(props, ref) {
14717
14995
  }
14718
14996
  return;
14719
14997
  }
14720
- const movedParentCascade = disableConstraints ? [updatedTask] : universalCascade(updatedTask, newStart, newEnd, tasks, businessDays, isCustomWeekend);
14998
+ const movedParentCascade = disableConstraints && !originalTask.composite ? [updatedTask] : universalCascade(updatedTask, newStart, newEnd, normalizedTasks, businessDays, isCustomWeekend);
14721
14999
  if (onScheduleIntent) {
14722
15000
  onScheduleIntent({
14723
15001
  type: "move_task",
@@ -14741,7 +15019,8 @@ function TaskGanttChartInner(props, ref) {
14741
15019
  taskId: updatedTask.id,
14742
15020
  duration: durationEdit.duration,
14743
15021
  anchor: durationEdit.anchor,
14744
- taskType: updatedTask.type ?? "task"
15022
+ taskType: updatedTask.type ?? "task",
15023
+ ...isCompositeDetail ? { localOverride: true } : {}
14745
15024
  });
14746
15025
  } else {
14747
15026
  onTasksChange?.(cascadedTasks);
@@ -14753,7 +15032,8 @@ function TaskGanttChartInner(props, ref) {
14753
15032
  onScheduleIntent({
14754
15033
  type: "move_task",
14755
15034
  taskId: updatedTask.id,
14756
- startDate: newStart.toISOString().split("T")[0]
15035
+ startDate: newStart.toISOString().split("T")[0],
15036
+ ...isCompositeDetail ? { localOverride: true } : {}
14757
15037
  });
14758
15038
  } else {
14759
15039
  const anchor = startChanged ? "end" : "start";
@@ -14761,13 +15041,14 @@ function TaskGanttChartInner(props, ref) {
14761
15041
  type: "resize_task",
14762
15042
  taskId: updatedTask.id,
14763
15043
  anchor,
14764
- date: (startChanged ? newStart : newEnd).toISOString().split("T")[0]
15044
+ date: (startChanged ? newStart : newEnd).toISOString().split("T")[0],
15045
+ ...isCompositeDetail ? { localOverride: true } : {}
14765
15046
  });
14766
15047
  }
14767
15048
  } else {
14768
15049
  onTasksChange?.(cascadedTasks);
14769
15050
  }
14770
- }, [tasks, onTasksChange, onScheduleIntent, disableConstraints, editingTaskId, businessDays, isCustomWeekend, onSubtreeScaleResult]);
15051
+ }, [tasks, normalizedTasks, onTasksChange, onScheduleIntent, disableConstraints, editingTaskId, businessDays, isCustomWeekend, onSubtreeScaleResult]);
14771
15052
  const handleTaskDurationChange = useCallback10((task, duration) => {
14772
15053
  const updatedTask = {
14773
15054
  ...task,
@@ -14881,12 +15162,25 @@ function TaskGanttChartInner(props, ref) {
14881
15162
  if (scrollViewport.viewportHeight <= 0) {
14882
15163
  return Array.from({ length: totalTasks }, (_, index) => index);
14883
15164
  }
14884
- const viewportRows = Math.max(1, Math.ceil(scrollViewport.viewportHeight / effectiveRowHeight));
14885
- const rangeStart = Math.max(0, Math.floor(scrollViewport.scrollTop / effectiveRowHeight) - TASK_ROW_OVERSCAN);
14886
- const rangeEnd = Math.min(
14887
- totalTasks - 1,
14888
- rangeStart + viewportRows + TASK_ROW_OVERSCAN * 2 - 1
14889
- );
15165
+ let low = 0;
15166
+ let high = totalTasks;
15167
+ while (low < high) {
15168
+ const middle = low + high >>> 1;
15169
+ if (compositeRowLayout.tops[middle] + compositeRowLayout.heights[middle] < scrollViewport.scrollTop) low = middle + 1;
15170
+ else high = middle;
15171
+ }
15172
+ const first = Math.min(low, totalTasks - 1);
15173
+ const viewportBottom = scrollViewport.scrollTop + scrollViewport.viewportHeight;
15174
+ low = first;
15175
+ high = totalTasks;
15176
+ while (low < high) {
15177
+ const middle = low + high >>> 1;
15178
+ if (compositeRowLayout.tops[middle] < viewportBottom) low = middle + 1;
15179
+ else high = middle;
15180
+ }
15181
+ const last = Math.min(low, totalTasks - 1);
15182
+ const rangeStart = Math.max(0, first - TASK_ROW_OVERSCAN);
15183
+ const rangeEnd = Math.min(totalTasks - 1, last + TASK_ROW_OVERSCAN);
14890
15184
  const indices = /* @__PURE__ */ new Set();
14891
15185
  for (let index = rangeStart; index <= rangeEnd; index += 1) {
14892
15186
  indices.add(index);
@@ -14898,7 +15192,7 @@ function TaskGanttChartInner(props, ref) {
14898
15192
  }
14899
15193
  }
14900
15194
  return Array.from(indices).sort((left, right) => left - right);
14901
- }, [effectiveRowHeight, forceFullRenderForPrint, forcedRenderedTaskIds, scrollViewport, visibleTaskIndexMap, visibleTasks.length]);
15195
+ }, [compositeRowLayout, forceFullRenderForPrint, forcedRenderedTaskIds, scrollViewport, visibleTaskIndexMap, visibleTasks.length]);
14902
15196
  const visiblePlanFactDateIndices = useMemo13(() => {
14903
15197
  if (forceFullRenderForPrint) {
14904
15198
  return void 0;
@@ -14935,7 +15229,7 @@ function TaskGanttChartInner(props, ref) {
14935
15229
  const handleTaskSelect = useCallback10((taskId) => {
14936
15230
  setSelectedTaskId(taskId);
14937
15231
  }, []);
14938
- const hoveredRowElementsRef = useRef12([]);
15232
+ const hoveredRowElementsRef = useRef13([]);
14939
15233
  const clearHoveredRows = useCallback10(() => {
14940
15234
  for (const element of hoveredRowElementsRef.current) {
14941
15235
  element.classList.remove("gantt-tl-row-hovered", "gantt-tr-row-hovered", "gantt-mx-row-hovered", "gantt-pf-row-hovered");
@@ -14975,7 +15269,16 @@ function TaskGanttChartInner(props, ref) {
14975
15269
  }
14976
15270
  applyHoveredRows(taskId);
14977
15271
  }, [applyHoveredRows]);
14978
- const handleToggleCollapse = externalOnToggleCollapse ?? useCallback10((parentId) => {
15272
+ const handleToggleCollapse = externalOnToggleCollapse ?? ((parentId) => {
15273
+ if (normalizedTasks.some((task) => task.id === parentId && task.composite)) {
15274
+ setExpandedCompositeIds((prev) => {
15275
+ const next = new Set(prev);
15276
+ if (next.has(parentId)) next.delete(parentId);
15277
+ else next.add(parentId);
15278
+ return next;
15279
+ });
15280
+ return;
15281
+ }
14979
15282
  setInternalCollapsedParentIds((prev) => {
14980
15283
  const next = new Set(prev);
14981
15284
  if (next.has(parentId)) {
@@ -14985,7 +15288,14 @@ function TaskGanttChartInner(props, ref) {
14985
15288
  }
14986
15289
  return next;
14987
15290
  });
14988
- }, []);
15291
+ });
15292
+ const lastCompositeBarToggleRef = useRef13(null);
15293
+ const handleCompositeBarToggle = (taskId, source) => {
15294
+ const now = Date.now();
15295
+ if (source === "click" && lastCompositeBarToggleRef.current?.source === "release" && lastCompositeBarToggleRef.current.id === taskId && now - lastCompositeBarToggleRef.current.time < 150) return;
15296
+ lastCompositeBarToggleRef.current = { id: taskId, time: now, source };
15297
+ handleToggleCollapse(taskId);
15298
+ };
14989
15299
  const allParentIds = useMemo13(() => {
14990
15300
  const taskIds = new Set(normalizedTasks.map((task) => task.id));
14991
15301
  return new Set(normalizedTasks.map((task) => task.parentId).filter((parentId) => Boolean(parentId && taskIds.has(parentId))));
@@ -14993,11 +15303,13 @@ function TaskGanttChartInner(props, ref) {
14993
15303
  const handleCollapseAll = useCallback10(() => {
14994
15304
  if (externalCollapsedParentIds) return;
14995
15305
  setInternalCollapsedParentIds(allParentIds);
15306
+ setExpandedCompositeIds(/* @__PURE__ */ new Set());
14996
15307
  }, [allParentIds, externalCollapsedParentIds]);
14997
15308
  const handleExpandAll = useCallback10(() => {
14998
15309
  if (externalCollapsedParentIds) return;
14999
15310
  setInternalCollapsedParentIds(/* @__PURE__ */ new Set());
15000
- }, [externalCollapsedParentIds]);
15311
+ setExpandedCompositeIds(new Set(normalizedTasks.filter((task) => task.composite).map((task) => task.id)));
15312
+ }, [externalCollapsedParentIds, normalizedTasks]);
15001
15313
  const exportToPdf = useCallback10(async (options) => {
15002
15314
  const sourceContainer = containerRef.current;
15003
15315
  if (!sourceContainer || typeof window === "undefined" || typeof document === "undefined") {
@@ -15169,7 +15481,7 @@ function TaskGanttChartInner(props, ref) {
15169
15481
  onTasksChange?.(changedTasks);
15170
15482
  }
15171
15483
  }, [tasks, onTasksChange, onUngroupTask]);
15172
- const panStateRef = useRef12(null);
15484
+ const panStateRef = useRef13(null);
15173
15485
  const handlePanStart = useCallback10((e) => {
15174
15486
  if (e.button !== 0) return;
15175
15487
  const target = e.target;
@@ -15194,7 +15506,7 @@ function TaskGanttChartInner(props, ref) {
15194
15506
  container.style.cursor = "grabbing";
15195
15507
  e.preventDefault();
15196
15508
  }, []);
15197
- useEffect10(() => {
15509
+ useEffect11(() => {
15198
15510
  const flushPanMove = () => {
15199
15511
  const pan = panStateRef.current;
15200
15512
  if (!pan?.active) return;
@@ -15262,6 +15574,8 @@ function TaskGanttChartInner(props, ref) {
15262
15574
  {
15263
15575
  tasks: normalizedTasks,
15264
15576
  rowHeight: effectiveRowHeight,
15577
+ rowTops: compositeRowLayout.tops,
15578
+ rowHeights: compositeRowLayout.heights,
15265
15579
  headerHeight,
15266
15580
  taskListWidth,
15267
15581
  onTasksChange: handleTaskChange,
@@ -15456,6 +15770,8 @@ function TaskGanttChartInner(props, ref) {
15456
15770
  gridWidth: renderGridWidth,
15457
15771
  totalHeight: totalGridHeight,
15458
15772
  rowIndexByTaskId: visibleTaskIndexMap,
15773
+ rowTops: compositeRowLayout.tops,
15774
+ rowHeights: compositeRowLayout.heights,
15459
15775
  dragOverrides: dependencyOverrides,
15460
15776
  selectedDep: selectedChip,
15461
15777
  businessDays,
@@ -15542,10 +15858,10 @@ function TaskGanttChartInner(props, ref) {
15542
15858
  {
15543
15859
  style: {
15544
15860
  position: "absolute",
15545
- top: `${index * effectiveRowHeight}px`,
15861
+ top: `${compositeRowLayout.tops[index]}px`,
15546
15862
  left: 0,
15547
15863
  right: 0,
15548
- height: `${effectiveRowHeight}px`
15864
+ height: `${compositeRowLayout.heights[index]}px`
15549
15865
  },
15550
15866
  children: /* @__PURE__ */ jsx21(
15551
15867
  TaskRow_default,
@@ -15553,7 +15869,9 @@ function TaskGanttChartInner(props, ref) {
15553
15869
  task,
15554
15870
  monthStart,
15555
15871
  dayWidth,
15556
- rowHeight: effectiveRowHeight,
15872
+ rowHeight: compositeRowLayout.heights[index],
15873
+ compactDetail: compositeRowLayout.heights[index] < effectiveRowHeight,
15874
+ compositeParentColor: task.parentId ? compositeParentsById.get(task.parentId)?.color : void 0,
15557
15875
  onTasksChange: handleTaskChange,
15558
15876
  onDragStateChange: (state) => {
15559
15877
  if (state.isDragging) {
@@ -15590,7 +15908,9 @@ function TaskGanttChartInner(props, ref) {
15590
15908
  isDependencyDragActive: dependencyCreationDrag !== null,
15591
15909
  viewMode,
15592
15910
  showTaskDateLabels,
15593
- showTaskNames
15911
+ showTaskNames,
15912
+ onCompositeToggle: task.composite ? (source) => handleCompositeBarToggle(task.id, source) : void 0,
15913
+ compositeExpanded: task.composite ? !collapsedParentIds.has(task.id) : void 0
15594
15914
  }
15595
15915
  )
15596
15916
  },
@@ -15603,6 +15923,7 @@ function TaskGanttChartInner(props, ref) {
15603
15923
  rowHeight: effectiveRowHeight,
15604
15924
  variant: "chart",
15605
15925
  startIndex: visibleTasks.length,
15926
+ startTop: compositeRowLayout.totalHeight,
15606
15927
  chartDayWidth: dayWidth,
15607
15928
  chartStartDayOffset: todayInRange ? Math.max(0, todayIndex + Math.floor(Number.isFinite(skeletonStartOffsetDays) ? skeletonStartOffsetDays : 0)) : 0,
15608
15929
  chartStartJitterDays: skeletonStartJitterDays
@@ -15685,6 +16006,8 @@ export {
15685
16006
  GenerationSkeleton_default as GenerationSkeletonRows,
15686
16007
  GridBackground_default as GridBackground,
15687
16008
  Input,
16009
+ MIN_EDGE_ZONE_PX,
16010
+ MIN_MOVE_ZONE_PX,
15688
16011
  PlanFactMatrix,
15689
16012
  Popover,
15690
16013
  PopoverContent,