gantt-lib 0.135.9 → 0.136.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3306,6 +3306,7 @@ var TimeScaleHeader_default = TimeScaleHeader;
3306
3306
 
3307
3307
  // src/components/TaskRow/TaskRow.tsx
3308
3308
  var import_react3 = __toESM(require("react"));
3309
+ var import_react_dom = require("react-dom");
3309
3310
 
3310
3311
  // src/utils/geometry.ts
3311
3312
  var getUTCDayDifference = (date1, date2) => {
@@ -4413,12 +4414,36 @@ var useTaskDrag = (options) => {
4413
4414
 
4414
4415
  // src/components/TaskRow/TaskRow.tsx
4415
4416
  var import_jsx_runtime2 = require("react/jsx-runtime");
4417
+ var COMPOSITE_SEGMENT_CUT_MIN_PX = 10;
4418
+ var formatCompositePreviewDate = (date) => {
4419
+ const month = new Intl.DateTimeFormat("ru-RU", { month: "short", timeZone: "UTC" }).format(date).replace(/\.$/, "");
4420
+ const yearSuffix = date.getUTCFullYear() === (/* @__PURE__ */ new Date()).getFullYear() ? "" : ` ${String(date.getUTCFullYear()).slice(-2)}`;
4421
+ return `${date.getUTCDate()} ${month}${yearSuffix}`;
4422
+ };
4416
4423
  var arePropsEqual = (prevProps, nextProps) => {
4417
- 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;
4424
+ 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 && prevProps.showCompositeSegments === nextProps.showCompositeSegments;
4418
4425
  };
4419
4426
  var TaskRow = import_react3.default.memo(
4420
- ({ 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 }) => {
4427
+ ({ 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, showCompositeSegments: showCompositeSegmentsProp = true, onCompositeToggle, compactDetail = false, compositeParentColor, compositeExpanded = false }) => {
4421
4428
  const defaultParentBarColor = "#782FC4";
4429
+ const [showCompositePreview, setShowCompositePreview] = (0, import_react3.useState)(false);
4430
+ const [compositePreviewPosition, setCompositePreviewPosition] = (0, import_react3.useState)(null);
4431
+ const pointerStart = (0, import_react3.useRef)(null);
4432
+ const suppressCompositeClick = (0, import_react3.useRef)(false);
4433
+ const compositeChildren = (0, import_react3.useMemo)(
4434
+ () => task.composite ? (allTasks ?? []).filter((child) => child.parentId === task.id) : [],
4435
+ [allTasks, task.composite, task.id]
4436
+ );
4437
+ (0, import_react3.useEffect)(() => {
4438
+ if (!showCompositePreview) return;
4439
+ const hide = () => setShowCompositePreview(false);
4440
+ window.addEventListener("scroll", hide, true);
4441
+ window.addEventListener("resize", hide);
4442
+ return () => {
4443
+ window.removeEventListener("scroll", hide, true);
4444
+ window.removeEventListener("resize", hide);
4445
+ };
4446
+ }, [showCompositePreview]);
4422
4447
  const { divider: taskDivider } = task;
4423
4448
  const normalizedTask = (0, import_react3.useMemo)(() => normalizeTaskDatesForType(task), [task]);
4424
4449
  const milestone = (0, import_react3.useMemo)(() => isMilestoneTask(normalizedTask), [normalizedTask]);
@@ -4435,6 +4460,7 @@ var TaskRow = import_react3.default.memo(
4435
4460
  const isParent = (0, import_react3.useMemo)(() => {
4436
4461
  return allTasks ? isTaskParent(task.id, allTasks) : false;
4437
4462
  }, [allTasks, task.id]);
4463
+ const isVisualParent = isParent && !task.composite;
4438
4464
  const childCount = (0, import_react3.useMemo)(() => {
4439
4465
  return allTasks ? getChildren(task.id, allTasks).length : 0;
4440
4466
  }, [allTasks, task.id]);
@@ -4480,19 +4506,19 @@ var TaskRow = import_react3.default.memo(
4480
4506
  if (isExpired) {
4481
4507
  return "color-mix(in srgb, var(--gantt-expired-color) 40%, black)";
4482
4508
  }
4483
- const baseColor = isParent ? task.color || defaultParentBarColor : task.color || "var(--gantt-task-bar-default-color)";
4509
+ const baseColor = isVisualParent ? task.color || defaultParentBarColor : task.color || "var(--gantt-task-bar-default-color)";
4484
4510
  return `color-mix(in srgb, ${baseColor} 40%, black)`;
4485
- }, [defaultParentBarColor, isCritical, isExpired, isParent, task.color]);
4511
+ }, [defaultParentBarColor, isCritical, isExpired, isVisualParent, task.color]);
4486
4512
  const barStyle = (0, import_react3.useMemo)(() => {
4487
4513
  const parentBarColor = task.color || defaultParentBarColor;
4488
4514
  if (isCritical && !isExpired) {
4489
4515
  const c = criticalBarColor;
4490
- if (isParent) {
4516
+ if (isVisualParent) {
4491
4517
  return { backgroundColor: c, "--gantt-parent-bar-color": c };
4492
4518
  }
4493
4519
  return { backgroundColor: c };
4494
4520
  }
4495
- if (isParent) {
4521
+ if (isVisualParent) {
4496
4522
  if (progressWidth >= 100) {
4497
4523
  const c = isExpired ? "color-mix(in srgb, var(--gantt-expired-color) 40%, black)" : `color-mix(in srgb, ${parentBarColor} 40%, black)`;
4498
4524
  return { backgroundColor: c, "--gantt-parent-bar-color": c };
@@ -4503,7 +4529,7 @@ var TaskRow = import_react3.default.memo(
4503
4529
  return { backgroundColor: progressColor };
4504
4530
  }
4505
4531
  return { backgroundColor: barColor };
4506
- }, [isCritical, defaultParentBarColor, isExpired, isParent, progressWidth, barColor, progressColor, task.color]);
4532
+ }, [isCritical, defaultParentBarColor, isExpired, isVisualParent, progressWidth, barColor, progressColor, task.color]);
4507
4533
  const handleDragEnd = (result) => {
4508
4534
  const updatedTask = {
4509
4535
  ...normalizedTask,
@@ -4577,6 +4603,35 @@ var TaskRow = import_react3.default.memo(
4577
4603
  }, [displayLeft, dayWidth, milestoneGeometry.size]);
4578
4604
  const visualLeft = milestone ? displayMilestoneGeometry.left : displayLeft;
4579
4605
  const visualWidth = milestone ? displayMilestoneGeometry.size : displayWidth;
4606
+ const compositeSegments = (0, import_react3.useMemo)(() => {
4607
+ if (!task.composite || width <= 0) return [];
4608
+ const laneEnds = [];
4609
+ const segments2 = compositeChildren.map((child) => {
4610
+ const segment = calculateTaskBar(parseUTCDate(child.startDate), parseUTCDate(child.endDate), monthStart, dayWidth);
4611
+ let lane = Math.max(0, laneEnds.length - 1);
4612
+ if (laneEnds.length > 0 && segment.left < laneEnds[lane]) {
4613
+ lane = laneEnds.length;
4614
+ }
4615
+ laneEnds[lane] = segment.left + segment.width;
4616
+ return {
4617
+ id: child.id,
4618
+ name: child.name,
4619
+ left: (segment.left - left) / width * 100,
4620
+ width: segment.width / width * 100,
4621
+ lane,
4622
+ pxLeft: segment.left - left,
4623
+ cut: false
4624
+ };
4625
+ });
4626
+ const lastCutByLane = [];
4627
+ for (const segment of [...segments2].sort((a, b) => a.pxLeft - b.pxLeft)) {
4628
+ segment.cut = segment.pxLeft >= 1 && segment.pxLeft - (lastCutByLane[segment.lane] ?? 0) >= COMPOSITE_SEGMENT_CUT_MIN_PX;
4629
+ if (segment.cut) lastCutByLane[segment.lane] = segment.pxLeft;
4630
+ }
4631
+ return segments2;
4632
+ }, [task.composite, compositeChildren, monthStart, dayWidth, left, width, displayLeft]);
4633
+ const compositeLaneCount = Math.max(1, ...compositeSegments.map((segment) => segment.lane + 1));
4634
+ const shouldRenderCompositeSegments = showCompositeSegmentsProp && task.composite && compositeSegments.length > 0 && visualWidth / compositeSegments.length >= 22;
4580
4635
  const shouldRenderBaseline = showBaseline && baselineGeometry !== null;
4581
4636
  const hasPreviewPosition = isDragging || effectiveOverridePosition !== void 0;
4582
4637
  const currentStartDate = hasPreviewPosition ? pixelsToDate(displayLeft, monthStart, dayWidth) : taskStartDate;
@@ -4594,17 +4649,20 @@ var TaskRow = import_react3.default.memo(
4594
4649
  if (lastDigit >= 2 && lastDigit <= 4) return `${count} \u0440\u0430\u0431\u043E\u0442\u044B`;
4595
4650
  return `${count} \u0440\u0430\u0431\u043E\u0442`;
4596
4651
  };
4597
- const estimatedTextWidth = isParent ? 120 : durationDays >= 10 ? 76 : 62;
4598
- const showProgressInside = !milestone && progressWidth > 0 && displayWidth > estimatedTextWidth;
4599
- const MIN_DURATION_WIDTH = isParent ? 80 : 50;
4600
- const showDurationInside = !milestone && durationDays >= 2 && displayWidth > MIN_DURATION_WIDTH;
4652
+ const estimatedTextWidth = isVisualParent ? 120 : durationDays >= 10 ? 76 : 62;
4653
+ const showProgressInside = !milestone && progressWidth > 0 && displayWidth > (compactDetail ? 28 : estimatedTextWidth);
4654
+ const MIN_DURATION_WIDTH = isVisualParent ? 80 : task.composite ? 92 : 50;
4655
+ const showDurationInside = !compactDetail && !milestone && durationDays >= 2 && displayWidth > MIN_DURATION_WIDTH;
4601
4656
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
4602
4657
  "div",
4603
4658
  {
4604
4659
  "data-filter-match": isFilterMatch ? "true" : "false",
4605
4660
  "data-gantt-task-row-id": task.id,
4606
- className: `gantt-tr-row ${isFilterMatch ? "gantt-tr-row-filter-match" : ""}`,
4607
- style: { height: `${rowHeight}px` },
4661
+ className: `gantt-tr-row ${compactDetail ? "gantt-tr-row-compositeDetail" : ""} ${isFilterMatch ? "gantt-tr-row-filter-match" : ""}`,
4662
+ style: {
4663
+ height: `${rowHeight}px`,
4664
+ "--gantt-tr-composite-detail-color": compositeParentColor ?? "var(--gantt-task-bar-default-color, #8b5cf6)"
4665
+ },
4608
4666
  children: [
4609
4667
  taskDivider === "top" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "gantt-tr-divider gantt-tr-divider-top" }),
4610
4668
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gantt-tr-taskContainer", children: [
@@ -4622,7 +4680,11 @@ var TaskRow = import_react3.default.memo(
4622
4680
  "div",
4623
4681
  {
4624
4682
  "data-taskbar": true,
4625
- 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" : ""}`,
4683
+ role: task.composite ? "button" : void 0,
4684
+ tabIndex: task.composite ? 0 : void 0,
4685
+ "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,
4686
+ "aria-expanded": task.composite ? compositeExpanded : void 0,
4687
+ 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" : ""}`,
4626
4688
  style: {
4627
4689
  left: `${visualLeft}px`,
4628
4690
  ...barStyle,
@@ -4632,14 +4694,57 @@ var TaskRow = import_react3.default.memo(
4632
4694
  padding: 0
4633
4695
  } : {
4634
4696
  width: `${visualWidth}px`,
4635
- height: isParent ? "var(--gantt-parent-bar-height, 14px)" : "var(--gantt-task-bar-height)"
4697
+ height: compactDetail ? "18px" : isVisualParent ? "var(--gantt-parent-bar-height, 14px)" : "var(--gantt-task-bar-height)"
4636
4698
  },
4637
4699
  cursor: dragHandleProps.style.cursor,
4638
4700
  userSelect: dragHandleProps.style.userSelect
4639
4701
  },
4640
4702
  onMouseDown: dragHandleProps.onMouseDown,
4641
4703
  onMouseMove: dragHandleProps.onMouseMove,
4642
- onMouseLeave: dragHandleProps.onMouseLeave,
4704
+ onMouseLeave: () => {
4705
+ dragHandleProps.onMouseLeave();
4706
+ setShowCompositePreview(false);
4707
+ },
4708
+ onMouseEnter: task.composite ? (event) => {
4709
+ const rect = event.currentTarget.getBoundingClientRect();
4710
+ const previewWidth = 220;
4711
+ const previewHeight = Math.min(340, 68 + Math.min(compositeChildren.length, 12) * 20);
4712
+ setCompositePreviewPosition({
4713
+ left: Math.max(8, Math.min(rect.left, window.innerWidth - previewWidth - 8)),
4714
+ top: rect.bottom + previewHeight + 8 < window.innerHeight ? rect.bottom + 6 : Math.max(8, rect.top - previewHeight - 6)
4715
+ });
4716
+ setShowCompositePreview(true);
4717
+ } : void 0,
4718
+ onMouseDownCapture: task.composite ? (event) => {
4719
+ suppressCompositeClick.current = Boolean(event.target.closest(".gantt-tr-resizeHandle"));
4720
+ pointerStart.current = { x: event.clientX, y: event.clientY };
4721
+ } : void 0,
4722
+ onMouseMoveCapture: task.composite ? (event) => {
4723
+ const start = pointerStart.current;
4724
+ if (start && Math.hypot(event.clientX - start.x, event.clientY - start.y) >= 4) {
4725
+ suppressCompositeClick.current = true;
4726
+ }
4727
+ } : void 0,
4728
+ onMouseUpCapture: task.composite ? (event) => {
4729
+ const start = pointerStart.current;
4730
+ if (start && Math.hypot(event.clientX - start.x, event.clientY - start.y) >= 4) {
4731
+ suppressCompositeClick.current = true;
4732
+ }
4733
+ pointerStart.current = null;
4734
+ } : void 0,
4735
+ onClick: task.composite ? () => {
4736
+ if (!isDragging && !suppressCompositeClick.current) {
4737
+ setShowCompositePreview(false);
4738
+ onCompositeToggle?.("click");
4739
+ }
4740
+ suppressCompositeClick.current = false;
4741
+ } : void 0,
4742
+ onKeyDown: task.composite ? (event) => {
4743
+ if (event.key === "Enter" || event.key === " ") {
4744
+ event.preventDefault();
4745
+ onCompositeToggle?.("keyboard");
4746
+ }
4747
+ } : void 0,
4643
4748
  children: [
4644
4749
  !milestone && progressWidth > 0 && progressWidth < 100 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
4645
4750
  "div",
@@ -4648,14 +4753,42 @@ var TaskRow = import_react3.default.memo(
4648
4753
  style: {
4649
4754
  width: `${progressWidth}%`,
4650
4755
  backgroundColor: progressColor,
4651
- ...isParent && {
4756
+ ...isVisualParent && {
4652
4757
  borderRadius: "var(--gantt-parent-bar-radius, 8px) 0 0 0"
4653
4758
  }
4654
4759
  }
4655
4760
  }
4656
4761
  ),
4657
4762
  !milestone && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "gantt-tr-resizeHandle gantt-tr-resizeHandleLeft" }),
4658
- showDurationInside && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-taskDuration", children: isParent ? getChildCountLabel(childCount) : `${durationDays} \u0434` }),
4763
+ shouldRenderCompositeSegments && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "gantt-tr-compositeSegments", "aria-hidden": "true", children: [
4764
+ compositeSegments.map((segment) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
4765
+ "span",
4766
+ {
4767
+ className: "gantt-tr-compositeSegment",
4768
+ style: {
4769
+ left: `${segment.left}%`,
4770
+ width: `${segment.width}%`,
4771
+ top: `${segment.lane / compositeLaneCount * 100}%`,
4772
+ height: `${100 / compositeLaneCount}%`
4773
+ }
4774
+ },
4775
+ segment.id
4776
+ )),
4777
+ compositeSegments.map((segment) => segment.cut ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
4778
+ "span",
4779
+ {
4780
+ className: "gantt-tr-compositeSegmentCut",
4781
+ style: {
4782
+ left: `${segment.left}%`,
4783
+ top: `${segment.lane / compositeLaneCount * 100}%`,
4784
+ height: `${100 / compositeLaneCount}%`
4785
+ }
4786
+ },
4787
+ `${segment.id}-cut`
4788
+ ) : null)
4789
+ ] }),
4790
+ task.composite && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-compositeHoverChevron", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: `gantt-tr-compositeChevron${compositeExpanded ? " gantt-tr-compositeChevron-open" : ""}`, viewBox: "0 0 20 20", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m7 4 6 6-6 6", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }),
4791
+ showDurationInside && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-taskDuration", children: isVisualParent ? getChildCountLabel(childCount) : `${durationDays} \u0434` }),
4659
4792
  progressWidth > 0 && showProgressInside && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "gantt-tr-progressText", children: [
4660
4793
  progressWidth,
4661
4794
  "%"
@@ -4664,6 +4797,41 @@ var TaskRow = import_react3.default.memo(
4664
4797
  ]
4665
4798
  }
4666
4799
  ),
4800
+ task.composite && showCompositePreview && compositePreviewPosition && compositeChildren.length > 0 && !isDragging && typeof document !== "undefined" && (0, import_react_dom.createPortal)(
4801
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
4802
+ "div",
4803
+ {
4804
+ className: "gantt-tr-compositePreview",
4805
+ style: { left: `${compositePreviewPosition.left}px`, top: `${compositePreviewPosition.top}px` },
4806
+ role: "tooltip",
4807
+ children: [
4808
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gantt-tr-compositePreviewAxis", children: [
4809
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: formatCompositePreviewDate(currentStartDate) }),
4810
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: formatCompositePreviewDate(currentEndDate) })
4811
+ ] }),
4812
+ compositeChildren.slice(0, 12).map((child) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gantt-tr-compositePreviewRow", children: [
4813
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: child.name }),
4814
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-compositePreviewTrack", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
4815
+ "span",
4816
+ {
4817
+ className: "gantt-tr-compositePreviewBar",
4818
+ style: {
4819
+ left: `${Math.max(0, (parseUTCDate(child.startDate).getTime() - taskStartDate.getTime()) / (taskEndDate.getTime() - taskStartDate.getTime() + 864e5) * 100)}%`,
4820
+ width: `${Math.max(2, (parseUTCDate(child.endDate).getTime() - parseUTCDate(child.startDate).getTime() + 864e5) / (taskEndDate.getTime() - taskStartDate.getTime() + 864e5) * 100)}%`,
4821
+ backgroundColor: barColor
4822
+ }
4823
+ }
4824
+ ) })
4825
+ ] }, child.id)),
4826
+ compositeChildren.length > 12 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
4827
+ "+",
4828
+ compositeChildren.length - 12
4829
+ ] })
4830
+ ]
4831
+ }
4832
+ ),
4833
+ document.body
4834
+ ),
4667
4835
  !disableDependencyEditing && onDependencyPortPointerDown && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
4668
4836
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
4669
4837
  "button",
@@ -4672,7 +4840,7 @@ var TaskRow = import_react3.default.memo(
4672
4840
  "data-gantt-dependency-port": true,
4673
4841
  "data-task-id": task.id,
4674
4842
  "data-port-side": "left",
4675
- className: `gantt-tr-dependencyPort gantt-tr-dependencyPortLeft ${milestone ? "gantt-tr-dependencyPortMilestone" : ""} ${isParent ? "gantt-tr-dependencyPortParent" : ""} ${isDependencyDragActive ? "gantt-tr-dependencyPortDragActive" : ""}`,
4843
+ className: `gantt-tr-dependencyPort gantt-tr-dependencyPortLeft ${milestone ? "gantt-tr-dependencyPortMilestone" : ""} ${isVisualParent ? "gantt-tr-dependencyPortParent" : ""} ${isDependencyDragActive ? "gantt-tr-dependencyPortDragActive" : ""}`,
4676
4844
  style: { left: `${visualLeft - 24}px` },
4677
4845
  "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}`,
4678
4846
  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",
@@ -4686,7 +4854,7 @@ var TaskRow = import_react3.default.memo(
4686
4854
  "data-gantt-dependency-port": true,
4687
4855
  "data-task-id": task.id,
4688
4856
  "data-port-side": "right",
4689
- className: `gantt-tr-dependencyPort gantt-tr-dependencyPortRight ${milestone ? "gantt-tr-dependencyPortMilestone" : ""} ${isParent ? "gantt-tr-dependencyPortParent" : ""} ${isDependencyDragActive ? "gantt-tr-dependencyPortDragActive" : ""}`,
4857
+ className: `gantt-tr-dependencyPort gantt-tr-dependencyPortRight ${milestone ? "gantt-tr-dependencyPortMilestone" : ""} ${isVisualParent ? "gantt-tr-dependencyPortParent" : ""} ${isDependencyDragActive ? "gantt-tr-dependencyPortDragActive" : ""}`,
4690
4858
  style: { left: `${visualLeft + visualWidth}px` },
4691
4859
  "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}`,
4692
4860
  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",
@@ -4725,6 +4893,14 @@ var TaskRow = import_react3.default.memo(
4725
4893
  children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" })
4726
4894
  }
4727
4895
  ),
4896
+ !showDurationInside && !milestone && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
4897
+ "span",
4898
+ {
4899
+ className: `gantt-tr-externalDuration gantt-tr-durationBeforeBar${compactDetail ? " gantt-tr-compositeDuration" : ""}`,
4900
+ style: { left: `${visualLeft - 46}px`, color: isParent ? task.color || defaultParentBarColor : barColor },
4901
+ children: isVisualParent ? getChildCountLabel(childCount) : `${durationDays} \u0434`
4902
+ }
4903
+ ),
4728
4904
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
4729
4905
  "div",
4730
4906
  {
@@ -4734,7 +4910,6 @@ var TaskRow = import_react3.default.memo(
4734
4910
  color: isParent ? task.color || defaultParentBarColor : barColor
4735
4911
  },
4736
4912
  children: [
4737
- !showDurationInside && !milestone && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-externalDuration", children: isParent ? getChildCountLabel(childCount) : `${durationDays} \u0434` }),
4738
4913
  progressWidth > 0 && !showProgressInside && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "gantt-tr-externalProgress", children: [
4739
4914
  progressWidth,
4740
4915
  "%"
@@ -5038,6 +5213,8 @@ var DependencyLines = import_react7.default.memo(({
5038
5213
  gridWidth,
5039
5214
  totalHeight,
5040
5215
  rowIndexByTaskId,
5216
+ rowTops,
5217
+ rowHeights,
5041
5218
  dragOverrides,
5042
5219
  selectedDep,
5043
5220
  criticalTaskIds,
@@ -5061,7 +5238,8 @@ var DependencyLines = import_react7.default.memo(({
5061
5238
  left: computed.left,
5062
5239
  right: computed.right,
5063
5240
  centerX: computed.centerX,
5064
- rowTop: rowIndex * rowHeight,
5241
+ rowTop: rowTops?.[rowIndex] ?? rowIndex * rowHeight,
5242
+ rowHeight: rowHeights?.[rowIndex] ?? rowHeight,
5065
5243
  isVirtual: false
5066
5244
  });
5067
5245
  });
@@ -5081,6 +5259,7 @@ var DependencyLines = import_react7.default.memo(({
5081
5259
  right: computed.right,
5082
5260
  centerX: computed.centerX,
5083
5261
  rowTop: ancestorPosition.rowTop,
5262
+ rowHeight: ancestorPosition.rowHeight,
5084
5263
  isVirtual: true
5085
5264
  });
5086
5265
  }
@@ -5094,18 +5273,19 @@ var DependencyLines = import_react7.default.memo(({
5094
5273
  const override = dragOverrides?.get(task.id);
5095
5274
  const computed = resolveTaskHorizontalGeometry(task, monthStart, dayWidth, override);
5096
5275
  const rowIndex = rowIndexByTaskId?.get(task.id);
5097
- const rowTop = rowIndex !== void 0 ? rowIndex * rowHeight : 0;
5276
+ const rowTop = rowIndex !== void 0 ? rowTops?.[rowIndex] ?? rowIndex * rowHeight : 0;
5098
5277
  positions.set(task.id, {
5099
5278
  left: computed.left,
5100
5279
  right: computed.right,
5101
5280
  centerX: computed.centerX,
5102
5281
  rowTop,
5282
+ rowHeight: rowIndex !== void 0 ? rowHeights?.[rowIndex] ?? rowHeight : rowHeight,
5103
5283
  isVirtual: false
5104
5284
  });
5105
5285
  }
5106
5286
  }
5107
5287
  return { taskPositions: positions, taskIndices: indices, hiddenTaskIds: hidden };
5108
- }, [tasks, tasksForPositions, allTasks, collapsedParentIds, monthStart, dayWidth, rowHeight, dragOverrides, rowIndexByTaskId]);
5288
+ }, [tasks, tasksForPositions, allTasks, collapsedParentIds, monthStart, dayWidth, rowHeight, rowTops, rowHeights, dragOverrides, rowIndexByTaskId]);
5109
5289
  const tasksForCycleDetection = allTasks ?? tasks;
5110
5290
  const cycleInfo = (0, import_react7.useMemo)(() => {
5111
5291
  const result = detectCycles(tasksForCycleDetection);
@@ -5147,7 +5327,9 @@ var DependencyLines = import_react7.default.memo(({
5147
5327
  continue;
5148
5328
  }
5149
5329
  }
5150
- if (!renderedTaskIdSet.has(edge.predecessorId) && !renderedTaskIdSet.has(edge.successorId)) {
5330
+ const predecessorVisibleId = predecessor.isVirtual ? findVisibleAncestor(taskMap.get(edge.predecessorId), collapsedParentIds, taskMap)?.id : edge.predecessorId;
5331
+ const successorVisibleId = successor.isVirtual ? findVisibleAncestor(taskMap.get(edge.successorId), collapsedParentIds, taskMap)?.id : edge.successorId;
5332
+ if (!renderedTaskIdSet.has(predecessorVisibleId ?? "") && !renderedTaskIdSet.has(successorVisibleId ?? "")) {
5151
5333
  continue;
5152
5334
  }
5153
5335
  const isVirtual = predecessor.isVirtual || successor.isVirtual;
@@ -5161,9 +5343,9 @@ var DependencyLines = import_react7.default.memo(({
5161
5343
  let toY;
5162
5344
  if (reverseOrder) {
5163
5345
  fromY = predecessor.rowTop + 10;
5164
- toY = successor.rowTop + rowHeight - 6;
5346
+ toY = successor.rowTop + successor.rowHeight - 6;
5165
5347
  } else {
5166
- fromY = predecessor.rowTop + rowHeight - 10;
5348
+ fromY = predecessor.rowTop + predecessor.rowHeight - 10;
5167
5349
  toY = successor.rowTop + 6;
5168
5350
  }
5169
5351
  let fromX = predecessorTask && isMilestoneTask(predecessorTask) ? predecessor.centerX : edge.type === "SS" || edge.type === "SF" ? predecessor.left : predecessor.right;
@@ -6841,6 +7023,7 @@ var TaskListRow = import_react11.default.memo(
6841
7023
  taskListMenuCommands = [],
6842
7024
  hideTaskListRowActions = false,
6843
7025
  rowClassName,
7026
+ compositeParentColor,
6844
7027
  getTaskListNamePrefixIcon,
6845
7028
  taskDateChangeMode = "preserve-duration",
6846
7029
  onTaskDateChangeModeChange,
@@ -6925,6 +7108,35 @@ var TaskListRow = import_react11.default.memo(
6925
7108
  [businessDays, weekendPredicate]
6926
7109
  );
6927
7110
  const isCollapsed = collapsedParentIds.has(task.id);
7111
+ const [compositeIconMode, setCompositeIconMode] = (0, import_react11.useState)(
7112
+ () => isCollapsed ? "composite" : "down"
7113
+ );
7114
+ const compositeIconModeRef = (0, import_react11.useRef)(compositeIconMode);
7115
+ const [compositeIconAnimation, setCompositeIconAnimation] = (0, import_react11.useState)(null);
7116
+ const [compositeIconAnimationPhase, setCompositeIconAnimationPhase] = (0, import_react11.useState)(null);
7117
+ (0, import_react11.useEffect)(() => {
7118
+ const nextMode = isCollapsed ? "composite" : "down";
7119
+ if (nextMode === compositeIconModeRef.current) {
7120
+ setCompositeIconAnimation(null);
7121
+ setCompositeIconAnimationPhase(null);
7122
+ return;
7123
+ }
7124
+ setCompositeIconAnimation(isCollapsed ? "closing" : "opening");
7125
+ setCompositeIconAnimationPhase("outgoing");
7126
+ const swapTimeoutId = window.setTimeout(() => {
7127
+ compositeIconModeRef.current = nextMode;
7128
+ setCompositeIconMode(nextMode);
7129
+ setCompositeIconAnimationPhase("incoming");
7130
+ }, 120);
7131
+ const finishTimeoutId = window.setTimeout(() => {
7132
+ setCompositeIconAnimation(null);
7133
+ setCompositeIconAnimationPhase(null);
7134
+ }, 240);
7135
+ return () => {
7136
+ window.clearTimeout(swapTimeoutId);
7137
+ window.clearTimeout(finishTimeoutId);
7138
+ };
7139
+ }, [isCollapsed]);
6928
7140
  const getHierarchyLineColor = (0, import_react11.useCallback)((columnDepth) => {
6929
7141
  return columnDepth % 2 === 0 ? "#93c5fd" : "var(--gantt-hierarchy-line-color)";
6930
7142
  }, []);
@@ -7033,6 +7245,7 @@ var TaskListRow = import_react11.default.memo(
7033
7245
  );
7034
7246
  const handleNameDoubleClick = (0, import_react11.useCallback)(
7035
7247
  (e) => {
7248
+ if (task.composite) return;
7036
7249
  if (disableTaskNameEditing) return;
7037
7250
  e.stopPropagation();
7038
7251
  nameConfirmedRef.current = false;
@@ -7040,7 +7253,7 @@ var TaskListRow = import_react11.default.memo(
7040
7253
  setNameValue(task.name);
7041
7254
  setEditingColumnId("name");
7042
7255
  },
7043
- [task.name, disableTaskNameEditing]
7256
+ [task.name, task.composite, disableTaskNameEditing]
7044
7257
  );
7045
7258
  const handleRowKeyDown = (0, import_react11.useCallback)(
7046
7259
  (e) => {
@@ -7689,7 +7902,7 @@ var TaskListRow = import_react11.default.memo(
7689
7902
  ]
7690
7903
  }
7691
7904
  );
7692
- const nameTriggerPaddingLeft = isParent ? `${nestingDepth * 20 + 28}px` : nestingDepth > 0 ? `${nestingDepth * 20 + 8}px` : void 0;
7905
+ const nameTriggerPaddingLeft = isParent && !task.composite ? `${nestingDepth * 20 + 28}px` : nestingDepth > 0 ? `${nestingDepth * 20 + 8}px` : void 0;
7693
7906
  const nameInputPaddingLeft = nestingDepth > 0 ? `${nestingDepth * 20 + 8}px` : void 0;
7694
7907
  const nestedDropIndicatorLeft = (() => {
7695
7908
  const numberColumnWidth = resolvedColumns?.find((col) => col.id === "number")?.width ?? 40;
@@ -7787,36 +8000,35 @@ var TaskListRow = import_react11.default.memo(
7787
8000
  }
7788
8001
  )
7789
8002
  ] }),
7790
- isParent && !editingName && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
7791
- !isFilterHideMode && !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
7792
- "span",
7793
- {
7794
- style: {
7795
- position: "absolute",
7796
- left: `${nestingDepth * 20 + 9}px`,
7797
- top: `${rowHeight / 2 + 7}px`,
7798
- height: `${rowHeight / 2 - 7}px`,
7799
- width: "1.5px",
7800
- background: getHierarchyLineColor(nestingDepth),
7801
- borderRadius: "1px",
7802
- pointerEvents: "none"
7803
- }
7804
- }
7805
- ),
7806
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
7807
- "button",
7808
- {
7809
- type: "button",
7810
- className: `gantt-tl-collapse-btn ${isCollapsed ? "gantt-tl-collapse-btn-collapsed" : ""}`,
7811
- onClick: handleToggleCollapse,
7812
- style: { left: `${nestingDepth * 20 + 1}px` },
7813
- "aria-label": isCollapsed ? "Expand children" : "Collapse children",
7814
- children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ChevronRightIcon, {})
8003
+ task.composite && !isFilterHideMode && !isCollapsed && !editingName && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
8004
+ "span",
8005
+ {
8006
+ "data-testid": "gantt-tl-parent-connector-tail",
8007
+ style: {
8008
+ position: "absolute",
8009
+ left: `${nestingDepth * 20 + 9}px`,
8010
+ top: `${rowHeight / 2 + 7}px`,
8011
+ height: `${rowHeight / 2 - 7}px`,
8012
+ width: "1.5px",
8013
+ background: getHierarchyLineColor(nestingDepth),
8014
+ borderRadius: "1px",
8015
+ pointerEvents: "none"
7815
8016
  }
7816
- )
7817
- ] })
8017
+ }
8018
+ ),
8019
+ isParent && !task.composite && !editingName && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_jsx_runtime14.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
8020
+ "button",
8021
+ {
8022
+ type: "button",
8023
+ className: `gantt-tl-collapse-btn ${isCollapsed ? "gantt-tl-collapse-btn-collapsed" : ""}`,
8024
+ onClick: handleToggleCollapse,
8025
+ style: { left: `${nestingDepth * 20 - 2}px` },
8026
+ "aria-label": isCollapsed ? "Expand children" : "Collapse children",
8027
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ChevronRightIcon, {})
8028
+ }
8029
+ ) })
7818
8030
  ] }),
7819
- !isChild && isParent && !editingName && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
8031
+ !isChild && isParent && !task.composite && !editingName && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
7820
8032
  !isFilterHideMode && !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
7821
8033
  "span",
7822
8034
  {
@@ -7839,7 +8051,7 @@ var TaskListRow = import_react11.default.memo(
7839
8051
  type: "button",
7840
8052
  className: `gantt-tl-collapse-btn ${isCollapsed ? "gantt-tl-collapse-btn-collapsed" : ""}`,
7841
8053
  onClick: handleToggleCollapse,
7842
- style: { left: `${nestingDepth * 20 + 1}px` },
8054
+ style: { left: `${nestingDepth * 20 - 2}px` },
7843
8055
  "aria-label": isCollapsed ? "Expand children" : "Collapse children",
7844
8056
  children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ChevronRightIcon, {})
7845
8057
  }
@@ -7858,27 +8070,57 @@ var TaskListRow = import_react11.default.memo(
7858
8070
  style: { paddingLeft: nameInputPaddingLeft },
7859
8071
  onClick: (e) => e.stopPropagation()
7860
8072
  }
7861
- ) : /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
7862
- "button",
7863
- {
7864
- type: "button",
7865
- className: [
7866
- "gantt-tl-name-trigger",
7867
- disableTaskNameEditing ? "gantt-tl-name-locked" : ""
7868
- ].filter(Boolean).join(" "),
7869
- title: task.name,
7870
- onClick: handleNameClick,
7871
- onDoubleClick: handleNameDoubleClick,
7872
- style: {
7873
- paddingLeft: nameTriggerPaddingLeft,
7874
- paddingRight: task.color ? "20px" : void 0
7875
- },
7876
- children: [
7877
- taskListNamePrefixIcon ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "gantt-tl-name-trigger-icon", "aria-hidden": "true", children: taskListNamePrefixIcon }) : null,
7878
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "gantt-tl-name-trigger-text", children: task.name })
7879
- ]
7880
- }
7881
- ),
8073
+ ) : /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
8074
+ task.composite && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
8075
+ "button",
8076
+ {
8077
+ type: "button",
8078
+ className: "gantt-tl-compositeToggle",
8079
+ onClick: handleToggleCollapse,
8080
+ style: { left: `${nestingDepth * 20 - 3}px` },
8081
+ "aria-label": isCollapsed ? `\u0420\u0430\u0441\u043A\u0440\u044B\u0442\u044C ${task.name}` : `\u0421\u0432\u0435\u0440\u043D\u0443\u0442\u044C ${task.name}`,
8082
+ "aria-expanded": !isCollapsed,
8083
+ 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",
8084
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
8085
+ "svg",
8086
+ {
8087
+ className: compositeIconAnimation && compositeIconAnimationPhase ? `gantt-tl-compositeGlyph-${compositeIconMode}-${compositeIconAnimation}-${compositeIconAnimationPhase}` : void 0,
8088
+ viewBox: "0 0 24 24",
8089
+ fill: "none",
8090
+ stroke: "currentColor",
8091
+ strokeWidth: "2",
8092
+ strokeLinecap: "round",
8093
+ strokeLinejoin: "round",
8094
+ "aria-hidden": "true",
8095
+ children: compositeIconMode === "composite" ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
8096
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("path", { d: "M21 5H11" }),
8097
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("path", { d: "M21 12H11" }),
8098
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("path", { d: "M21 19H11" }),
8099
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("path", { d: "m3 8 4 4-4 4" })
8100
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("path", { d: "m6 9 6 6 6-6" })
8101
+ }
8102
+ )
8103
+ }
8104
+ ),
8105
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
8106
+ "button",
8107
+ {
8108
+ type: "button",
8109
+ className: `gantt-tl-name-trigger${disableTaskNameEditing ? " gantt-tl-name-locked" : ""}`,
8110
+ title: task.name,
8111
+ onClick: handleNameClick,
8112
+ onDoubleClick: handleNameDoubleClick,
8113
+ style: {
8114
+ paddingLeft: task.composite ? `${nestingDepth * 20 + 30}px` : nameTriggerPaddingLeft,
8115
+ paddingRight: task.color ? "20px" : void 0
8116
+ },
8117
+ children: [
8118
+ taskListNamePrefixIcon ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "gantt-tl-name-trigger-icon", "aria-hidden": "true", children: taskListNamePrefixIcon }) : null,
8119
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "gantt-tl-name-trigger-text", children: task.name })
8120
+ ]
8121
+ }
8122
+ )
8123
+ ] }),
7882
8124
  !editingName && task.color && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
7883
8125
  "span",
7884
8126
  {
@@ -8495,8 +8737,9 @@ var TaskListRow = import_react11.default.memo(
8495
8737
  isDragOver && isNestedDropTarget ? "gantt-tl-row-drag-over-nested" : "",
8496
8738
  isDragOver && isDirectChildDropTarget ? "gantt-tl-row-drag-over-direct-child" : "",
8497
8739
  isChild ? "gantt-tl-row-child" : "",
8498
- isParent ? "gantt-tl-row-parent" : "",
8499
- isParent && fillParentRow ? "gantt-tl-row-parent-filled" : "",
8740
+ isParent && !task.composite ? "gantt-tl-row-parent" : "",
8741
+ task.composite ? "gantt-tl-row-composite" : "",
8742
+ isParent && !task.composite && fillParentRow ? "gantt-tl-row-parent-filled" : "",
8500
8743
  `gantt-tl-row-level-${rowFillLevel}`,
8501
8744
  isTotalRow ? "gantt-tl-row-total" : "",
8502
8745
  rowClassName ?? ""
@@ -8504,6 +8747,7 @@ var TaskListRow = import_react11.default.memo(
8504
8747
  style: {
8505
8748
  height: `${rowHeight}px`,
8506
8749
  position: "relative",
8750
+ "--gantt-tl-composite-detail-color": compositeParentColor ?? "var(--gantt-task-bar-default-color, #8b5cf6)",
8507
8751
  "--gantt-tl-nested-drop-left": nestedDropIndicatorLeft,
8508
8752
  "--gantt-tl-nested-inside-drop-left": nestedInsideDropIndicatorLeft
8509
8753
  },
@@ -8729,6 +8973,7 @@ var GenerationSkeletonRows = ({
8729
8973
  rowHeight,
8730
8974
  variant,
8731
8975
  startIndex = 0,
8976
+ startTop,
8732
8977
  taskListLayout,
8733
8978
  chartDayWidth = 40,
8734
8979
  chartStartDayOffset = 0,
@@ -8769,7 +9014,7 @@ var GenerationSkeletonRows = ({
8769
9014
  "div",
8770
9015
  {
8771
9016
  className: "gantt-generation-skeleton-row",
8772
- style: { top: `${rowIndex * rowHeight}px`, height: `${rowHeight}px` },
9017
+ style: { top: `${(startTop ?? startIndex * rowHeight) + index * rowHeight}px`, height: `${rowHeight}px` },
8773
9018
  children: variant === "task-list" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_jsx_runtime16.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: `gantt-generation-skeleton-label ${shortShape ? "gantt-generation-skeleton-label-short" : ""} ${mediumShape ? "gantt-generation-skeleton-label-medium" : ""}` }) }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: `gantt-generation-skeleton-bar ${parentShape ? "gantt-generation-skeleton-bar-parent" : ""} ${shortShape ? "gantt-generation-skeleton-bar-short" : ""}`, style: chartBarStyle })
8774
9019
  },
8775
9020
  rowIndex
@@ -9029,6 +9274,8 @@ var SelectAllCheckbox = ({
9029
9274
  var TaskList = ({
9030
9275
  tasks,
9031
9276
  rowHeight,
9277
+ rowTops,
9278
+ rowHeights,
9032
9279
  headerHeight,
9033
9280
  taskListWidth,
9034
9281
  onTasksChange,
@@ -9156,14 +9403,22 @@ var TaskList = ({
9156
9403
  return tasks2;
9157
9404
  }, [orderedTasks, collapsedParentIds, filterMode, filteredTaskIds, isFilterActive, criticalPathMode, criticalTaskIds]);
9158
9405
  const totalHeight = (0, import_react14.useMemo)(
9159
- () => (visibleTasks.length + Math.max(0, Math.floor(skeletonRowCount))) * rowHeight,
9160
- [rowHeight, skeletonRowCount, visibleTasks.length]
9406
+ () => (rowTops && rowHeights && visibleTasks.length > 0 ? rowTops[visibleTasks.length - 1] + rowHeights[visibleTasks.length - 1] : visibleTasks.length * rowHeight) + Math.max(0, Math.floor(skeletonRowCount)) * rowHeight,
9407
+ [rowHeight, rowTops, rowHeights, skeletonRowCount, visibleTasks.length]
9161
9408
  );
9162
9409
  const visibleTaskNumberMap = (0, import_react14.useMemo)(
9163
9410
  () => Object.fromEntries(buildTaskNumberMap(visibleTasks)),
9164
9411
  [visibleTasks]
9165
9412
  );
9166
9413
  const visibleTaskIds = (0, import_react14.useMemo)(() => visibleTasks.map((task) => task.id), [visibleTasks]);
9414
+ const compositeParentIds = (0, import_react14.useMemo)(
9415
+ () => new Set(tasks.filter((task) => task.composite).map((task) => task.id)),
9416
+ [tasks]
9417
+ );
9418
+ const compositeParentsById = (0, import_react14.useMemo)(
9419
+ () => new Map(tasks.filter((task) => task.composite).map((task) => [task.id, task])),
9420
+ [tasks]
9421
+ );
9167
9422
  const renderedVisibleRowIndices = (0, import_react14.useMemo)(
9168
9423
  () => visibleRowIndices ?? visibleTasks.map((_, index) => index),
9169
9424
  [visibleRowIndices, visibleTasks]
@@ -10155,7 +10410,7 @@ var TaskList = ({
10155
10410
  renderedVisibleTasks.map(({ task, index }) => {
10156
10411
  const previousVisibleTask = index > 0 ? visibleTasks[index - 1] : void 0;
10157
10412
  const canDemoteTask = index === 0 || !task.parentId || previousVisibleTask?.id !== task.parentId;
10158
- const topOffset = index * rowHeight + (hasVisiblePendingInsert && index > pendingInsertRowIndex ? rowHeight : 0);
10413
+ const topOffset = (rowTops?.[index] ?? index * rowHeight) + (hasVisiblePendingInsert && index > pendingInsertRowIndex ? rowHeight : 0);
10159
10414
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
10160
10415
  "div",
10161
10416
  {
@@ -10172,7 +10427,7 @@ var TaskList = ({
10172
10427
  rowIndex: index,
10173
10428
  taskNumber: originalTaskNumberMap[task.id] || "",
10174
10429
  taskNumberMap: originalTaskNumberMap,
10175
- rowHeight,
10430
+ rowHeight: rowHeights?.[index] ?? rowHeight,
10176
10431
  onTasksChange,
10177
10432
  onDurationChange,
10178
10433
  selectedTaskId,
@@ -10229,7 +10484,8 @@ var TaskList = ({
10229
10484
  onActiveCustomCellChange: setActiveCustomCell,
10230
10485
  taskListMenuCommands,
10231
10486
  hideTaskListRowActions,
10232
- rowClassName: getTaskListRowClassName?.(task),
10487
+ rowClassName: [task.parentId && compositeParentIds.has(task.parentId) ? "gantt-tl-row-composite-detail" : "", getTaskListRowClassName?.(task)].filter(Boolean).join(" "),
10488
+ compositeParentColor: task.parentId ? compositeParentsById.get(task.parentId)?.color : void 0,
10233
10489
  getTaskListNamePrefixIcon,
10234
10490
  taskDateChangeMode,
10235
10491
  onTaskDateChangeModeChange,
@@ -10245,7 +10501,7 @@ var TaskList = ({
10245
10501
  {
10246
10502
  style: {
10247
10503
  position: "absolute",
10248
- top: `${(pendingInsertRowIndex + 1) * rowHeight}px`,
10504
+ top: `${rowTops?.[pendingInsertRowIndex + 1] ?? (pendingInsertRowIndex + 1) * rowHeight}px`,
10249
10505
  left: 0,
10250
10506
  right: 0
10251
10507
  },
@@ -10267,6 +10523,7 @@ var TaskList = ({
10267
10523
  rowHeight,
10268
10524
  variant: "task-list",
10269
10525
  startIndex: visibleTasks.length,
10526
+ startTop: totalHeight - Math.max(0, Math.floor(skeletonRowCount)) * rowHeight,
10270
10527
  taskListLayout: generationSkeletonTaskListLayout
10271
10528
  }
10272
10529
  )
@@ -14237,6 +14494,7 @@ function TaskGanttChartInner(props, ref) {
14237
14494
  skeletonStartJitterDays = 0,
14238
14495
  showTaskDateLabels = true,
14239
14496
  showTaskNames = true,
14497
+ showCompositeSegments = true,
14240
14498
  timelineMarkers,
14241
14499
  additionalColumns,
14242
14500
  hiddenTaskListColumns,
@@ -14451,17 +14709,46 @@ function TaskGanttChartInner(props, ref) {
14451
14709
  }, [dependencyLineMenu, onTasksChange, tasks]);
14452
14710
  const [activeTimelineTooltip, setActiveTimelineTooltip] = (0, import_react19.useState)(null);
14453
14711
  const [internalCollapsedParentIds, setInternalCollapsedParentIds] = (0, import_react19.useState)(/* @__PURE__ */ new Set());
14454
- const collapsedParentIds = externalCollapsedParentIds ?? internalCollapsedParentIds;
14712
+ const [expandedCompositeIds, setExpandedCompositeIds] = (0, import_react19.useState)(/* @__PURE__ */ new Set());
14455
14713
  const [editingTaskId, setEditingTaskId] = (0, import_react19.useState)(null);
14456
14714
  const taskDateChangeMode = externalTaskDateChangeMode ?? internalTaskDateChangeMode;
14457
14715
  const handleTaskDateChangeMode = externalOnTaskDateChangeModeChange ?? setInternalTaskDateChangeMode;
14458
14716
  const resolvedRowContentLines = isPlanFactMode ? Math.max(2, Math.floor(rowContentLines)) : Math.max(1, Math.floor(rowContentLines));
14459
14717
  const shouldFillParentRowsInTaskList = fillParentRowsInTaskList ?? isPlanFactMode;
14460
14718
  const effectiveRowHeight = (0, import_react19.useMemo)(
14461
- () => Math.max(rowHeight, 10 + resolvedRowContentLines * 18),
14462
- [resolvedRowContentLines, rowHeight]
14719
+ () => rowContentLines === 0 && !isPlanFactMode ? rowHeight : Math.max(rowHeight, 10 + resolvedRowContentLines * 18),
14720
+ [isPlanFactMode, resolvedRowContentLines, rowContentLines, rowHeight]
14463
14721
  );
14464
- const normalizedTasks = (0, import_react19.useMemo)(() => normalizeHierarchyTasks(tasks), [tasks]);
14722
+ const normalizedTasks = (0, import_react19.useMemo)(() => {
14723
+ const ordered = normalizeHierarchyTasks(tasks);
14724
+ const ranges = /* @__PURE__ */ new Map();
14725
+ for (const task of ordered) {
14726
+ if (!task.parentId) continue;
14727
+ const start = parseUTCDate(task.startDate).getTime();
14728
+ const end = parseUTCDate(task.endDate).getTime();
14729
+ const range = ranges.get(task.parentId);
14730
+ ranges.set(task.parentId, {
14731
+ start: Math.min(range?.start ?? start, start),
14732
+ end: Math.max(range?.end ?? end, end)
14733
+ });
14734
+ }
14735
+ return ordered.map((task) => {
14736
+ const range = task.composite ? ranges.get(task.id) : void 0;
14737
+ return range ? {
14738
+ ...task,
14739
+ startDate: new Date(range.start).toISOString().slice(0, 10),
14740
+ endDate: new Date(range.end).toISOString().slice(0, 10)
14741
+ } : task;
14742
+ });
14743
+ }, [tasks]);
14744
+ const collapsedParentIds = (0, import_react19.useMemo)(() => {
14745
+ if (externalCollapsedParentIds) return externalCollapsedParentIds;
14746
+ const ids = new Set(internalCollapsedParentIds);
14747
+ for (const task of normalizedTasks) {
14748
+ if (task.composite && !expandedCompositeIds.has(task.id)) ids.add(task.id);
14749
+ }
14750
+ return ids;
14751
+ }, [externalCollapsedParentIds, internalCollapsedParentIds, normalizedTasks, expandedCompositeIds]);
14465
14752
  const controlledScheduleIdentity = (0, import_react19.useMemo)(
14466
14753
  () => buildControlledScheduleIdentity(normalizedTasks),
14467
14754
  [normalizedTasks]
@@ -14541,6 +14828,23 @@ function TaskGanttChartInner(props, ref) {
14541
14828
  }
14542
14829
  return tasks2;
14543
14830
  }, [normalizedTasks, collapsedParentIds, filterMode, taskFilter, criticalPathMode, criticalTaskIds]);
14831
+ const compositeRowLayout = (0, import_react19.useMemo)(() => {
14832
+ const compositeIds = new Set(normalizedTasks.filter((task) => task.composite).map((task) => task.id));
14833
+ const tops = [];
14834
+ const heights = [];
14835
+ let top = 0;
14836
+ for (const task of visibleTasks) {
14837
+ tops.push(top);
14838
+ const height = task.parentId && compositeIds.has(task.parentId) ? Math.min(effectiveRowHeight, 22) : effectiveRowHeight;
14839
+ heights.push(height);
14840
+ top += height;
14841
+ }
14842
+ return { tops, heights, totalHeight: top };
14843
+ }, [normalizedTasks, visibleTasks, effectiveRowHeight]);
14844
+ const compositeParentsById = (0, import_react19.useMemo)(
14845
+ () => new Map(normalizedTasks.filter((task) => task.composite).map((task) => [task.id, task])),
14846
+ [normalizedTasks]
14847
+ );
14544
14848
  const matchedTaskIds = (0, import_react19.useMemo)(() => {
14545
14849
  if (!taskFilter) return /* @__PURE__ */ new Set();
14546
14850
  return new Set(visibleTasks.filter(taskFilter).map((task) => task.id));
@@ -14562,8 +14866,8 @@ function TaskGanttChartInner(props, ref) {
14562
14866
  return mergedHighlightedTaskIds;
14563
14867
  }, [filterMode, highlightedTaskIds, matchedTaskIds, criticalPathMode, criticalTaskIds]);
14564
14868
  const totalGridHeight = (0, import_react19.useMemo)(
14565
- () => (visibleTasks.length + Math.max(0, Math.floor(skeletonRowCount))) * effectiveRowHeight,
14566
- [effectiveRowHeight, skeletonRowCount, visibleTasks.length]
14869
+ () => compositeRowLayout.totalHeight + Math.max(0, Math.floor(skeletonRowCount)) * effectiveRowHeight,
14870
+ [compositeRowLayout.totalHeight, effectiveRowHeight, skeletonRowCount]
14567
14871
  );
14568
14872
  const timelineHeaderHeight = headerHeight + 1;
14569
14873
  const tableBodyMinHeight = (0, import_react19.useMemo)(() => {
@@ -14690,7 +14994,7 @@ function TaskGanttChartInner(props, ref) {
14690
14994
  const rowIndex = visibleTasks.findIndex((visibleTask) => visibleTask.id === taskId);
14691
14995
  if (rowIndex === -1) return;
14692
14996
  const paddedRowIndex = Math.max(0, rowIndex - SCROLL_TO_ROW_CONTEXT_ROWS);
14693
- container2.scrollTo({ top: Math.max(0, effectiveRowHeight * paddedRowIndex), behavior: "smooth" });
14997
+ container2.scrollTo({ top: Math.max(0, compositeRowLayout.tops[paddedRowIndex] ?? 0), behavior: "smooth" });
14694
14998
  setSelectedTaskId(taskId);
14695
14999
  return;
14696
15000
  }
@@ -14709,7 +15013,7 @@ function TaskGanttChartInner(props, ref) {
14709
15013
  const taskOffset = taskIndex * dayWidth;
14710
15014
  const scrollLeft = Math.round(taskOffset - dayWidth * 2);
14711
15015
  container.scrollTo({ left: Math.max(0, scrollLeft), behavior: "smooth" });
14712
- }, [dateRange, dayWidth, effectiveRowHeight, isTableMatrixMode, tasks, visibleTasks]);
15016
+ }, [dateRange, dayWidth, effectiveRowHeight, compositeRowLayout, isTableMatrixMode, tasks, visibleTasks]);
14713
15017
  const scrollToRow = (0, import_react19.useCallback)((taskId, options = {}) => {
14714
15018
  const container = scrollContainerRef.current;
14715
15019
  if (!container) return;
@@ -14718,7 +15022,7 @@ function TaskGanttChartInner(props, ref) {
14718
15022
  const rowIndex = visibleTasks.findIndex((visibleTask) => visibleTask.id === task.id);
14719
15023
  if (rowIndex === -1) return;
14720
15024
  const paddedRowIndex = Math.max(0, rowIndex - SCROLL_TO_ROW_CONTEXT_ROWS);
14721
- const scrollTop = Math.max(0, effectiveRowHeight * paddedRowIndex);
15025
+ const scrollTop = Math.max(0, compositeRowLayout.tops[paddedRowIndex] ?? 0);
14722
15026
  const {
14723
15027
  select = true,
14724
15028
  behavior = "smooth",
@@ -14738,7 +15042,7 @@ function TaskGanttChartInner(props, ref) {
14738
15042
  }
14739
15043
  }
14740
15044
  container.scrollTo({ top: scrollTop, behavior });
14741
- }, [effectiveRowHeight, tasks, visibleTasks]);
15045
+ }, [effectiveRowHeight, compositeRowLayout, tasks, visibleTasks]);
14742
15046
  const [dragGuideLines, setDragGuideLines] = (0, import_react19.useState)(null);
14743
15047
  const [draggedTaskOverride, setDraggedTaskOverride] = (0, import_react19.useState)(null);
14744
15048
  (0, import_react19.useEffect)(() => {
@@ -14765,9 +15069,9 @@ function TaskGanttChartInner(props, ref) {
14765
15069
  previewPositionStore.clear();
14766
15070
  }, [previewPositionStore]);
14767
15071
  const handleTaskChange = (0, import_react19.useCallback)((updatedTasks, durationEdit) => {
14768
- const updatedTask = updatedTasks[0];
15072
+ let updatedTask = updatedTasks[0];
14769
15073
  if (!updatedTask) return;
14770
- const originalTask = tasks.find((t) => t.id === updatedTask.id);
15074
+ const originalTask = normalizedTasks.find((t) => t.id === updatedTask.id);
14771
15075
  if (!originalTask) {
14772
15076
  onTasksChange?.(updatedTasks);
14773
15077
  if (editingTaskId === updatedTask.id) {
@@ -14780,6 +15084,8 @@ function TaskGanttChartInner(props, ref) {
14780
15084
  const newStart = new Date(updatedTask.startDate);
14781
15085
  const newEnd = new Date(updatedTask.endDate);
14782
15086
  const datesChanged = origStart.getTime() !== newStart.getTime() || origEnd.getTime() !== newEnd.getTime();
15087
+ const isCompositeDetail = datesChanged && tasks.some((task) => task.id === updatedTask.parentId && task.composite);
15088
+ if (isCompositeDetail) updatedTask = { ...updatedTask, synced: false };
14783
15089
  if (!datesChanged) {
14784
15090
  if (updatedTasks.length > 1) {
14785
15091
  onTasksChange?.(updatedTasks);
@@ -14841,7 +15147,7 @@ function TaskGanttChartInner(props, ref) {
14841
15147
  }
14842
15148
  return;
14843
15149
  }
14844
- const movedParentCascade = disableConstraints ? [updatedTask] : universalCascade(updatedTask, newStart, newEnd, tasks, businessDays, isCustomWeekend);
15150
+ const movedParentCascade = disableConstraints && !originalTask.composite ? [updatedTask] : universalCascade(updatedTask, newStart, newEnd, normalizedTasks, businessDays, isCustomWeekend);
14845
15151
  if (onScheduleIntent) {
14846
15152
  onScheduleIntent({
14847
15153
  type: "move_task",
@@ -14865,7 +15171,8 @@ function TaskGanttChartInner(props, ref) {
14865
15171
  taskId: updatedTask.id,
14866
15172
  duration: durationEdit.duration,
14867
15173
  anchor: durationEdit.anchor,
14868
- taskType: updatedTask.type ?? "task"
15174
+ taskType: updatedTask.type ?? "task",
15175
+ ...isCompositeDetail ? { localOverride: true } : {}
14869
15176
  });
14870
15177
  } else {
14871
15178
  onTasksChange?.(cascadedTasks);
@@ -14877,7 +15184,8 @@ function TaskGanttChartInner(props, ref) {
14877
15184
  onScheduleIntent({
14878
15185
  type: "move_task",
14879
15186
  taskId: updatedTask.id,
14880
- startDate: newStart.toISOString().split("T")[0]
15187
+ startDate: newStart.toISOString().split("T")[0],
15188
+ ...isCompositeDetail ? { localOverride: true } : {}
14881
15189
  });
14882
15190
  } else {
14883
15191
  const anchor = startChanged ? "end" : "start";
@@ -14885,13 +15193,14 @@ function TaskGanttChartInner(props, ref) {
14885
15193
  type: "resize_task",
14886
15194
  taskId: updatedTask.id,
14887
15195
  anchor,
14888
- date: (startChanged ? newStart : newEnd).toISOString().split("T")[0]
15196
+ date: (startChanged ? newStart : newEnd).toISOString().split("T")[0],
15197
+ ...isCompositeDetail ? { localOverride: true } : {}
14889
15198
  });
14890
15199
  }
14891
15200
  } else {
14892
15201
  onTasksChange?.(cascadedTasks);
14893
15202
  }
14894
- }, [tasks, onTasksChange, onScheduleIntent, disableConstraints, editingTaskId, businessDays, isCustomWeekend, onSubtreeScaleResult]);
15203
+ }, [tasks, normalizedTasks, onTasksChange, onScheduleIntent, disableConstraints, editingTaskId, businessDays, isCustomWeekend, onSubtreeScaleResult]);
14895
15204
  const handleTaskDurationChange = (0, import_react19.useCallback)((task, duration) => {
14896
15205
  const updatedTask = {
14897
15206
  ...task,
@@ -15005,12 +15314,25 @@ function TaskGanttChartInner(props, ref) {
15005
15314
  if (scrollViewport.viewportHeight <= 0) {
15006
15315
  return Array.from({ length: totalTasks }, (_, index) => index);
15007
15316
  }
15008
- const viewportRows = Math.max(1, Math.ceil(scrollViewport.viewportHeight / effectiveRowHeight));
15009
- const rangeStart = Math.max(0, Math.floor(scrollViewport.scrollTop / effectiveRowHeight) - TASK_ROW_OVERSCAN);
15010
- const rangeEnd = Math.min(
15011
- totalTasks - 1,
15012
- rangeStart + viewportRows + TASK_ROW_OVERSCAN * 2 - 1
15013
- );
15317
+ let low = 0;
15318
+ let high = totalTasks;
15319
+ while (low < high) {
15320
+ const middle = low + high >>> 1;
15321
+ if (compositeRowLayout.tops[middle] + compositeRowLayout.heights[middle] < scrollViewport.scrollTop) low = middle + 1;
15322
+ else high = middle;
15323
+ }
15324
+ const first = Math.min(low, totalTasks - 1);
15325
+ const viewportBottom = scrollViewport.scrollTop + scrollViewport.viewportHeight;
15326
+ low = first;
15327
+ high = totalTasks;
15328
+ while (low < high) {
15329
+ const middle = low + high >>> 1;
15330
+ if (compositeRowLayout.tops[middle] < viewportBottom) low = middle + 1;
15331
+ else high = middle;
15332
+ }
15333
+ const last = Math.min(low, totalTasks - 1);
15334
+ const rangeStart = Math.max(0, first - TASK_ROW_OVERSCAN);
15335
+ const rangeEnd = Math.min(totalTasks - 1, last + TASK_ROW_OVERSCAN);
15014
15336
  const indices = /* @__PURE__ */ new Set();
15015
15337
  for (let index = rangeStart; index <= rangeEnd; index += 1) {
15016
15338
  indices.add(index);
@@ -15022,7 +15344,7 @@ function TaskGanttChartInner(props, ref) {
15022
15344
  }
15023
15345
  }
15024
15346
  return Array.from(indices).sort((left, right) => left - right);
15025
- }, [effectiveRowHeight, forceFullRenderForPrint, forcedRenderedTaskIds, scrollViewport, visibleTaskIndexMap, visibleTasks.length]);
15347
+ }, [compositeRowLayout, forceFullRenderForPrint, forcedRenderedTaskIds, scrollViewport, visibleTaskIndexMap, visibleTasks.length]);
15026
15348
  const visiblePlanFactDateIndices = (0, import_react19.useMemo)(() => {
15027
15349
  if (forceFullRenderForPrint) {
15028
15350
  return void 0;
@@ -15099,7 +15421,16 @@ function TaskGanttChartInner(props, ref) {
15099
15421
  }
15100
15422
  applyHoveredRows(taskId);
15101
15423
  }, [applyHoveredRows]);
15102
- const handleToggleCollapse = externalOnToggleCollapse ?? (0, import_react19.useCallback)((parentId) => {
15424
+ const handleToggleCollapse = externalOnToggleCollapse ?? ((parentId) => {
15425
+ if (normalizedTasks.some((task) => task.id === parentId && task.composite)) {
15426
+ setExpandedCompositeIds((prev) => {
15427
+ const next = new Set(prev);
15428
+ if (next.has(parentId)) next.delete(parentId);
15429
+ else next.add(parentId);
15430
+ return next;
15431
+ });
15432
+ return;
15433
+ }
15103
15434
  setInternalCollapsedParentIds((prev) => {
15104
15435
  const next = new Set(prev);
15105
15436
  if (next.has(parentId)) {
@@ -15109,7 +15440,14 @@ function TaskGanttChartInner(props, ref) {
15109
15440
  }
15110
15441
  return next;
15111
15442
  });
15112
- }, []);
15443
+ });
15444
+ const lastCompositeBarToggleRef = (0, import_react19.useRef)(null);
15445
+ const handleCompositeBarToggle = (taskId, source) => {
15446
+ const now = Date.now();
15447
+ if (source === "click" && lastCompositeBarToggleRef.current?.source === "release" && lastCompositeBarToggleRef.current.id === taskId && now - lastCompositeBarToggleRef.current.time < 150) return;
15448
+ lastCompositeBarToggleRef.current = { id: taskId, time: now, source };
15449
+ handleToggleCollapse(taskId);
15450
+ };
15113
15451
  const allParentIds = (0, import_react19.useMemo)(() => {
15114
15452
  const taskIds = new Set(normalizedTasks.map((task) => task.id));
15115
15453
  return new Set(normalizedTasks.map((task) => task.parentId).filter((parentId) => Boolean(parentId && taskIds.has(parentId))));
@@ -15117,11 +15455,13 @@ function TaskGanttChartInner(props, ref) {
15117
15455
  const handleCollapseAll = (0, import_react19.useCallback)(() => {
15118
15456
  if (externalCollapsedParentIds) return;
15119
15457
  setInternalCollapsedParentIds(allParentIds);
15458
+ setExpandedCompositeIds(/* @__PURE__ */ new Set());
15120
15459
  }, [allParentIds, externalCollapsedParentIds]);
15121
15460
  const handleExpandAll = (0, import_react19.useCallback)(() => {
15122
15461
  if (externalCollapsedParentIds) return;
15123
15462
  setInternalCollapsedParentIds(/* @__PURE__ */ new Set());
15124
- }, [externalCollapsedParentIds]);
15463
+ setExpandedCompositeIds(new Set(normalizedTasks.filter((task) => task.composite).map((task) => task.id)));
15464
+ }, [externalCollapsedParentIds, normalizedTasks]);
15125
15465
  const exportToPdf = (0, import_react19.useCallback)(async (options) => {
15126
15466
  const sourceContainer = containerRef.current;
15127
15467
  if (!sourceContainer || typeof window === "undefined" || typeof document === "undefined") {
@@ -15386,6 +15726,8 @@ function TaskGanttChartInner(props, ref) {
15386
15726
  {
15387
15727
  tasks: normalizedTasks,
15388
15728
  rowHeight: effectiveRowHeight,
15729
+ rowTops: compositeRowLayout.tops,
15730
+ rowHeights: compositeRowLayout.heights,
15389
15731
  headerHeight,
15390
15732
  taskListWidth,
15391
15733
  onTasksChange: handleTaskChange,
@@ -15580,6 +15922,8 @@ function TaskGanttChartInner(props, ref) {
15580
15922
  gridWidth: renderGridWidth,
15581
15923
  totalHeight: totalGridHeight,
15582
15924
  rowIndexByTaskId: visibleTaskIndexMap,
15925
+ rowTops: compositeRowLayout.tops,
15926
+ rowHeights: compositeRowLayout.heights,
15583
15927
  dragOverrides: dependencyOverrides,
15584
15928
  selectedDep: selectedChip,
15585
15929
  businessDays,
@@ -15666,10 +16010,10 @@ function TaskGanttChartInner(props, ref) {
15666
16010
  {
15667
16011
  style: {
15668
16012
  position: "absolute",
15669
- top: `${index * effectiveRowHeight}px`,
16013
+ top: `${compositeRowLayout.tops[index]}px`,
15670
16014
  left: 0,
15671
16015
  right: 0,
15672
- height: `${effectiveRowHeight}px`
16016
+ height: `${compositeRowLayout.heights[index]}px`
15673
16017
  },
15674
16018
  children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
15675
16019
  TaskRow_default,
@@ -15677,7 +16021,9 @@ function TaskGanttChartInner(props, ref) {
15677
16021
  task,
15678
16022
  monthStart,
15679
16023
  dayWidth,
15680
- rowHeight: effectiveRowHeight,
16024
+ rowHeight: compositeRowLayout.heights[index],
16025
+ compactDetail: compositeRowLayout.heights[index] < effectiveRowHeight,
16026
+ compositeParentColor: task.parentId ? compositeParentsById.get(task.parentId)?.color : void 0,
15681
16027
  onTasksChange: handleTaskChange,
15682
16028
  onDragStateChange: (state) => {
15683
16029
  if (state.isDragging) {
@@ -15714,7 +16060,10 @@ function TaskGanttChartInner(props, ref) {
15714
16060
  isDependencyDragActive: dependencyCreationDrag !== null,
15715
16061
  viewMode,
15716
16062
  showTaskDateLabels,
15717
- showTaskNames
16063
+ showTaskNames,
16064
+ showCompositeSegments,
16065
+ onCompositeToggle: task.composite ? (source) => handleCompositeBarToggle(task.id, source) : void 0,
16066
+ compositeExpanded: task.composite ? !collapsedParentIds.has(task.id) : void 0
15718
16067
  }
15719
16068
  )
15720
16069
  },
@@ -15727,6 +16076,7 @@ function TaskGanttChartInner(props, ref) {
15727
16076
  rowHeight: effectiveRowHeight,
15728
16077
  variant: "chart",
15729
16078
  startIndex: visibleTasks.length,
16079
+ startTop: compositeRowLayout.totalHeight,
15730
16080
  chartDayWidth: dayWidth,
15731
16081
  chartStartDayOffset: todayInRange ? Math.max(0, todayIndex + Math.floor(Number.isFinite(skeletonStartOffsetDays) ? skeletonStartOffsetDays : 0)) : 0,
15732
16082
  chartStartJitterDays: skeletonStartJitterDays