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.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) => {
@@ -4263,12 +4264,36 @@ var useTaskDrag = (options) => {
4263
4264
 
4264
4265
  // src/components/TaskRow/TaskRow.tsx
4265
4266
  import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
4267
+ var COMPOSITE_SEGMENT_CUT_MIN_PX = 10;
4268
+ var formatCompositePreviewDate = (date) => {
4269
+ const month = new Intl.DateTimeFormat("ru-RU", { month: "short", timeZone: "UTC" }).format(date).replace(/\.$/, "");
4270
+ const yearSuffix = date.getUTCFullYear() === (/* @__PURE__ */ new Date()).getFullYear() ? "" : ` ${String(date.getUTCFullYear()).slice(-2)}`;
4271
+ return `${date.getUTCDate()} ${month}${yearSuffix}`;
4272
+ };
4266
4273
  var arePropsEqual = (prevProps, nextProps) => {
4267
- 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;
4274
+ 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;
4268
4275
  };
4269
4276
  var TaskRow = React2.memo(
4270
- ({ 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 }) => {
4277
+ ({ 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 }) => {
4271
4278
  const defaultParentBarColor = "#782FC4";
4279
+ const [showCompositePreview, setShowCompositePreview] = useState2(false);
4280
+ const [compositePreviewPosition, setCompositePreviewPosition] = useState2(null);
4281
+ const pointerStart = useRef2(null);
4282
+ const suppressCompositeClick = useRef2(false);
4283
+ const compositeChildren = useMemo2(
4284
+ () => task.composite ? (allTasks ?? []).filter((child) => child.parentId === task.id) : [],
4285
+ [allTasks, task.composite, task.id]
4286
+ );
4287
+ useEffect2(() => {
4288
+ if (!showCompositePreview) return;
4289
+ const hide = () => setShowCompositePreview(false);
4290
+ window.addEventListener("scroll", hide, true);
4291
+ window.addEventListener("resize", hide);
4292
+ return () => {
4293
+ window.removeEventListener("scroll", hide, true);
4294
+ window.removeEventListener("resize", hide);
4295
+ };
4296
+ }, [showCompositePreview]);
4272
4297
  const { divider: taskDivider } = task;
4273
4298
  const normalizedTask = useMemo2(() => normalizeTaskDatesForType(task), [task]);
4274
4299
  const milestone = useMemo2(() => isMilestoneTask(normalizedTask), [normalizedTask]);
@@ -4285,6 +4310,7 @@ var TaskRow = React2.memo(
4285
4310
  const isParent = useMemo2(() => {
4286
4311
  return allTasks ? isTaskParent(task.id, allTasks) : false;
4287
4312
  }, [allTasks, task.id]);
4313
+ const isVisualParent = isParent && !task.composite;
4288
4314
  const childCount = useMemo2(() => {
4289
4315
  return allTasks ? getChildren(task.id, allTasks).length : 0;
4290
4316
  }, [allTasks, task.id]);
@@ -4330,19 +4356,19 @@ var TaskRow = React2.memo(
4330
4356
  if (isExpired) {
4331
4357
  return "color-mix(in srgb, var(--gantt-expired-color) 40%, black)";
4332
4358
  }
4333
- const baseColor = isParent ? task.color || defaultParentBarColor : task.color || "var(--gantt-task-bar-default-color)";
4359
+ const baseColor = isVisualParent ? task.color || defaultParentBarColor : task.color || "var(--gantt-task-bar-default-color)";
4334
4360
  return `color-mix(in srgb, ${baseColor} 40%, black)`;
4335
- }, [defaultParentBarColor, isCritical, isExpired, isParent, task.color]);
4361
+ }, [defaultParentBarColor, isCritical, isExpired, isVisualParent, task.color]);
4336
4362
  const barStyle = useMemo2(() => {
4337
4363
  const parentBarColor = task.color || defaultParentBarColor;
4338
4364
  if (isCritical && !isExpired) {
4339
4365
  const c = criticalBarColor;
4340
- if (isParent) {
4366
+ if (isVisualParent) {
4341
4367
  return { backgroundColor: c, "--gantt-parent-bar-color": c };
4342
4368
  }
4343
4369
  return { backgroundColor: c };
4344
4370
  }
4345
- if (isParent) {
4371
+ if (isVisualParent) {
4346
4372
  if (progressWidth >= 100) {
4347
4373
  const c = isExpired ? "color-mix(in srgb, var(--gantt-expired-color) 40%, black)" : `color-mix(in srgb, ${parentBarColor} 40%, black)`;
4348
4374
  return { backgroundColor: c, "--gantt-parent-bar-color": c };
@@ -4353,7 +4379,7 @@ var TaskRow = React2.memo(
4353
4379
  return { backgroundColor: progressColor };
4354
4380
  }
4355
4381
  return { backgroundColor: barColor };
4356
- }, [isCritical, defaultParentBarColor, isExpired, isParent, progressWidth, barColor, progressColor, task.color]);
4382
+ }, [isCritical, defaultParentBarColor, isExpired, isVisualParent, progressWidth, barColor, progressColor, task.color]);
4357
4383
  const handleDragEnd = (result) => {
4358
4384
  const updatedTask = {
4359
4385
  ...normalizedTask,
@@ -4427,6 +4453,35 @@ var TaskRow = React2.memo(
4427
4453
  }, [displayLeft, dayWidth, milestoneGeometry.size]);
4428
4454
  const visualLeft = milestone ? displayMilestoneGeometry.left : displayLeft;
4429
4455
  const visualWidth = milestone ? displayMilestoneGeometry.size : displayWidth;
4456
+ const compositeSegments = useMemo2(() => {
4457
+ if (!task.composite || width <= 0) return [];
4458
+ const laneEnds = [];
4459
+ const segments2 = compositeChildren.map((child) => {
4460
+ const segment = calculateTaskBar(parseUTCDate(child.startDate), parseUTCDate(child.endDate), monthStart, dayWidth);
4461
+ let lane = Math.max(0, laneEnds.length - 1);
4462
+ if (laneEnds.length > 0 && segment.left < laneEnds[lane]) {
4463
+ lane = laneEnds.length;
4464
+ }
4465
+ laneEnds[lane] = segment.left + segment.width;
4466
+ return {
4467
+ id: child.id,
4468
+ name: child.name,
4469
+ left: (segment.left - left) / width * 100,
4470
+ width: segment.width / width * 100,
4471
+ lane,
4472
+ pxLeft: segment.left - left,
4473
+ cut: false
4474
+ };
4475
+ });
4476
+ const lastCutByLane = [];
4477
+ for (const segment of [...segments2].sort((a, b) => a.pxLeft - b.pxLeft)) {
4478
+ segment.cut = segment.pxLeft >= 1 && segment.pxLeft - (lastCutByLane[segment.lane] ?? 0) >= COMPOSITE_SEGMENT_CUT_MIN_PX;
4479
+ if (segment.cut) lastCutByLane[segment.lane] = segment.pxLeft;
4480
+ }
4481
+ return segments2;
4482
+ }, [task.composite, compositeChildren, monthStart, dayWidth, left, width, displayLeft]);
4483
+ const compositeLaneCount = Math.max(1, ...compositeSegments.map((segment) => segment.lane + 1));
4484
+ const shouldRenderCompositeSegments = showCompositeSegmentsProp && task.composite && compositeSegments.length > 0 && visualWidth / compositeSegments.length >= 22;
4430
4485
  const shouldRenderBaseline = showBaseline && baselineGeometry !== null;
4431
4486
  const hasPreviewPosition = isDragging || effectiveOverridePosition !== void 0;
4432
4487
  const currentStartDate = hasPreviewPosition ? pixelsToDate(displayLeft, monthStart, dayWidth) : taskStartDate;
@@ -4444,17 +4499,20 @@ var TaskRow = React2.memo(
4444
4499
  if (lastDigit >= 2 && lastDigit <= 4) return `${count} \u0440\u0430\u0431\u043E\u0442\u044B`;
4445
4500
  return `${count} \u0440\u0430\u0431\u043E\u0442`;
4446
4501
  };
4447
- const estimatedTextWidth = isParent ? 120 : durationDays >= 10 ? 76 : 62;
4448
- const showProgressInside = !milestone && progressWidth > 0 && displayWidth > estimatedTextWidth;
4449
- const MIN_DURATION_WIDTH = isParent ? 80 : 50;
4450
- const showDurationInside = !milestone && durationDays >= 2 && displayWidth > MIN_DURATION_WIDTH;
4502
+ const estimatedTextWidth = isVisualParent ? 120 : durationDays >= 10 ? 76 : 62;
4503
+ const showProgressInside = !milestone && progressWidth > 0 && displayWidth > (compactDetail ? 28 : estimatedTextWidth);
4504
+ const MIN_DURATION_WIDTH = isVisualParent ? 80 : task.composite ? 92 : 50;
4505
+ const showDurationInside = !compactDetail && !milestone && durationDays >= 2 && displayWidth > MIN_DURATION_WIDTH;
4451
4506
  return /* @__PURE__ */ jsxs2(
4452
4507
  "div",
4453
4508
  {
4454
4509
  "data-filter-match": isFilterMatch ? "true" : "false",
4455
4510
  "data-gantt-task-row-id": task.id,
4456
- className: `gantt-tr-row ${isFilterMatch ? "gantt-tr-row-filter-match" : ""}`,
4457
- style: { height: `${rowHeight}px` },
4511
+ className: `gantt-tr-row ${compactDetail ? "gantt-tr-row-compositeDetail" : ""} ${isFilterMatch ? "gantt-tr-row-filter-match" : ""}`,
4512
+ style: {
4513
+ height: `${rowHeight}px`,
4514
+ "--gantt-tr-composite-detail-color": compositeParentColor ?? "var(--gantt-task-bar-default-color, #8b5cf6)"
4515
+ },
4458
4516
  children: [
4459
4517
  taskDivider === "top" && /* @__PURE__ */ jsx2("div", { className: "gantt-tr-divider gantt-tr-divider-top" }),
4460
4518
  /* @__PURE__ */ jsxs2("div", { className: "gantt-tr-taskContainer", children: [
@@ -4472,7 +4530,11 @@ var TaskRow = React2.memo(
4472
4530
  "div",
4473
4531
  {
4474
4532
  "data-taskbar": true,
4475
- 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" : ""}`,
4533
+ role: task.composite ? "button" : void 0,
4534
+ tabIndex: task.composite ? 0 : void 0,
4535
+ "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,
4536
+ "aria-expanded": task.composite ? compositeExpanded : void 0,
4537
+ 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" : ""}`,
4476
4538
  style: {
4477
4539
  left: `${visualLeft}px`,
4478
4540
  ...barStyle,
@@ -4482,14 +4544,57 @@ var TaskRow = React2.memo(
4482
4544
  padding: 0
4483
4545
  } : {
4484
4546
  width: `${visualWidth}px`,
4485
- height: isParent ? "var(--gantt-parent-bar-height, 14px)" : "var(--gantt-task-bar-height)"
4547
+ height: compactDetail ? "18px" : isVisualParent ? "var(--gantt-parent-bar-height, 14px)" : "var(--gantt-task-bar-height)"
4486
4548
  },
4487
4549
  cursor: dragHandleProps.style.cursor,
4488
4550
  userSelect: dragHandleProps.style.userSelect
4489
4551
  },
4490
4552
  onMouseDown: dragHandleProps.onMouseDown,
4491
4553
  onMouseMove: dragHandleProps.onMouseMove,
4492
- onMouseLeave: dragHandleProps.onMouseLeave,
4554
+ onMouseLeave: () => {
4555
+ dragHandleProps.onMouseLeave();
4556
+ setShowCompositePreview(false);
4557
+ },
4558
+ onMouseEnter: task.composite ? (event) => {
4559
+ const rect = event.currentTarget.getBoundingClientRect();
4560
+ const previewWidth = 220;
4561
+ const previewHeight = Math.min(340, 68 + Math.min(compositeChildren.length, 12) * 20);
4562
+ setCompositePreviewPosition({
4563
+ left: Math.max(8, Math.min(rect.left, window.innerWidth - previewWidth - 8)),
4564
+ top: rect.bottom + previewHeight + 8 < window.innerHeight ? rect.bottom + 6 : Math.max(8, rect.top - previewHeight - 6)
4565
+ });
4566
+ setShowCompositePreview(true);
4567
+ } : void 0,
4568
+ onMouseDownCapture: task.composite ? (event) => {
4569
+ suppressCompositeClick.current = Boolean(event.target.closest(".gantt-tr-resizeHandle"));
4570
+ pointerStart.current = { x: event.clientX, y: event.clientY };
4571
+ } : void 0,
4572
+ onMouseMoveCapture: task.composite ? (event) => {
4573
+ const start = pointerStart.current;
4574
+ if (start && Math.hypot(event.clientX - start.x, event.clientY - start.y) >= 4) {
4575
+ suppressCompositeClick.current = true;
4576
+ }
4577
+ } : void 0,
4578
+ onMouseUpCapture: task.composite ? (event) => {
4579
+ const start = pointerStart.current;
4580
+ if (start && Math.hypot(event.clientX - start.x, event.clientY - start.y) >= 4) {
4581
+ suppressCompositeClick.current = true;
4582
+ }
4583
+ pointerStart.current = null;
4584
+ } : void 0,
4585
+ onClick: task.composite ? () => {
4586
+ if (!isDragging && !suppressCompositeClick.current) {
4587
+ setShowCompositePreview(false);
4588
+ onCompositeToggle?.("click");
4589
+ }
4590
+ suppressCompositeClick.current = false;
4591
+ } : void 0,
4592
+ onKeyDown: task.composite ? (event) => {
4593
+ if (event.key === "Enter" || event.key === " ") {
4594
+ event.preventDefault();
4595
+ onCompositeToggle?.("keyboard");
4596
+ }
4597
+ } : void 0,
4493
4598
  children: [
4494
4599
  !milestone && progressWidth > 0 && progressWidth < 100 && /* @__PURE__ */ jsx2(
4495
4600
  "div",
@@ -4498,14 +4603,42 @@ var TaskRow = React2.memo(
4498
4603
  style: {
4499
4604
  width: `${progressWidth}%`,
4500
4605
  backgroundColor: progressColor,
4501
- ...isParent && {
4606
+ ...isVisualParent && {
4502
4607
  borderRadius: "var(--gantt-parent-bar-radius, 8px) 0 0 0"
4503
4608
  }
4504
4609
  }
4505
4610
  }
4506
4611
  ),
4507
4612
  !milestone && /* @__PURE__ */ jsx2("div", { className: "gantt-tr-resizeHandle gantt-tr-resizeHandleLeft" }),
4508
- showDurationInside && /* @__PURE__ */ jsx2("span", { className: "gantt-tr-taskDuration", children: isParent ? getChildCountLabel(childCount) : `${durationDays} \u0434` }),
4613
+ shouldRenderCompositeSegments && /* @__PURE__ */ jsxs2("span", { className: "gantt-tr-compositeSegments", "aria-hidden": "true", children: [
4614
+ compositeSegments.map((segment) => /* @__PURE__ */ jsx2(
4615
+ "span",
4616
+ {
4617
+ className: "gantt-tr-compositeSegment",
4618
+ style: {
4619
+ left: `${segment.left}%`,
4620
+ width: `${segment.width}%`,
4621
+ top: `${segment.lane / compositeLaneCount * 100}%`,
4622
+ height: `${100 / compositeLaneCount}%`
4623
+ }
4624
+ },
4625
+ segment.id
4626
+ )),
4627
+ compositeSegments.map((segment) => segment.cut ? /* @__PURE__ */ jsx2(
4628
+ "span",
4629
+ {
4630
+ className: "gantt-tr-compositeSegmentCut",
4631
+ style: {
4632
+ left: `${segment.left}%`,
4633
+ top: `${segment.lane / compositeLaneCount * 100}%`,
4634
+ height: `${100 / compositeLaneCount}%`
4635
+ }
4636
+ },
4637
+ `${segment.id}-cut`
4638
+ ) : null)
4639
+ ] }),
4640
+ 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" }) }) }),
4641
+ showDurationInside && /* @__PURE__ */ jsx2("span", { className: "gantt-tr-taskDuration", children: isVisualParent ? getChildCountLabel(childCount) : `${durationDays} \u0434` }),
4509
4642
  progressWidth > 0 && showProgressInside && /* @__PURE__ */ jsxs2("span", { className: "gantt-tr-progressText", children: [
4510
4643
  progressWidth,
4511
4644
  "%"
@@ -4514,6 +4647,41 @@ var TaskRow = React2.memo(
4514
4647
  ]
4515
4648
  }
4516
4649
  ),
4650
+ task.composite && showCompositePreview && compositePreviewPosition && compositeChildren.length > 0 && !isDragging && typeof document !== "undefined" && createPortal(
4651
+ /* @__PURE__ */ jsxs2(
4652
+ "div",
4653
+ {
4654
+ className: "gantt-tr-compositePreview",
4655
+ style: { left: `${compositePreviewPosition.left}px`, top: `${compositePreviewPosition.top}px` },
4656
+ role: "tooltip",
4657
+ children: [
4658
+ /* @__PURE__ */ jsxs2("div", { className: "gantt-tr-compositePreviewAxis", children: [
4659
+ /* @__PURE__ */ jsx2("span", { children: formatCompositePreviewDate(currentStartDate) }),
4660
+ /* @__PURE__ */ jsx2("span", { children: formatCompositePreviewDate(currentEndDate) })
4661
+ ] }),
4662
+ compositeChildren.slice(0, 12).map((child) => /* @__PURE__ */ jsxs2("div", { className: "gantt-tr-compositePreviewRow", children: [
4663
+ /* @__PURE__ */ jsx2("span", { children: child.name }),
4664
+ /* @__PURE__ */ jsx2("span", { className: "gantt-tr-compositePreviewTrack", children: /* @__PURE__ */ jsx2(
4665
+ "span",
4666
+ {
4667
+ className: "gantt-tr-compositePreviewBar",
4668
+ style: {
4669
+ left: `${Math.max(0, (parseUTCDate(child.startDate).getTime() - taskStartDate.getTime()) / (taskEndDate.getTime() - taskStartDate.getTime() + 864e5) * 100)}%`,
4670
+ width: `${Math.max(2, (parseUTCDate(child.endDate).getTime() - parseUTCDate(child.startDate).getTime() + 864e5) / (taskEndDate.getTime() - taskStartDate.getTime() + 864e5) * 100)}%`,
4671
+ backgroundColor: barColor
4672
+ }
4673
+ }
4674
+ ) })
4675
+ ] }, child.id)),
4676
+ compositeChildren.length > 12 && /* @__PURE__ */ jsxs2("div", { children: [
4677
+ "+",
4678
+ compositeChildren.length - 12
4679
+ ] })
4680
+ ]
4681
+ }
4682
+ ),
4683
+ document.body
4684
+ ),
4517
4685
  !disableDependencyEditing && onDependencyPortPointerDown && /* @__PURE__ */ jsxs2(Fragment, { children: [
4518
4686
  /* @__PURE__ */ jsx2(
4519
4687
  "button",
@@ -4522,7 +4690,7 @@ var TaskRow = React2.memo(
4522
4690
  "data-gantt-dependency-port": true,
4523
4691
  "data-task-id": task.id,
4524
4692
  "data-port-side": "left",
4525
- className: `gantt-tr-dependencyPort gantt-tr-dependencyPortLeft ${milestone ? "gantt-tr-dependencyPortMilestone" : ""} ${isParent ? "gantt-tr-dependencyPortParent" : ""} ${isDependencyDragActive ? "gantt-tr-dependencyPortDragActive" : ""}`,
4693
+ className: `gantt-tr-dependencyPort gantt-tr-dependencyPortLeft ${milestone ? "gantt-tr-dependencyPortMilestone" : ""} ${isVisualParent ? "gantt-tr-dependencyPortParent" : ""} ${isDependencyDragActive ? "gantt-tr-dependencyPortDragActive" : ""}`,
4526
4694
  style: { left: `${visualLeft - 24}px` },
4527
4695
  "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}`,
4528
4696
  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",
@@ -4536,7 +4704,7 @@ var TaskRow = React2.memo(
4536
4704
  "data-gantt-dependency-port": true,
4537
4705
  "data-task-id": task.id,
4538
4706
  "data-port-side": "right",
4539
- className: `gantt-tr-dependencyPort gantt-tr-dependencyPortRight ${milestone ? "gantt-tr-dependencyPortMilestone" : ""} ${isParent ? "gantt-tr-dependencyPortParent" : ""} ${isDependencyDragActive ? "gantt-tr-dependencyPortDragActive" : ""}`,
4707
+ className: `gantt-tr-dependencyPort gantt-tr-dependencyPortRight ${milestone ? "gantt-tr-dependencyPortMilestone" : ""} ${isVisualParent ? "gantt-tr-dependencyPortParent" : ""} ${isDependencyDragActive ? "gantt-tr-dependencyPortDragActive" : ""}`,
4540
4708
  style: { left: `${visualLeft + visualWidth}px` },
4541
4709
  "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}`,
4542
4710
  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",
@@ -4575,6 +4743,14 @@ var TaskRow = React2.memo(
4575
4743
  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" })
4576
4744
  }
4577
4745
  ),
4746
+ !showDurationInside && !milestone && /* @__PURE__ */ jsx2(
4747
+ "span",
4748
+ {
4749
+ className: `gantt-tr-externalDuration gantt-tr-durationBeforeBar${compactDetail ? " gantt-tr-compositeDuration" : ""}`,
4750
+ style: { left: `${visualLeft - 46}px`, color: isParent ? task.color || defaultParentBarColor : barColor },
4751
+ children: isVisualParent ? getChildCountLabel(childCount) : `${durationDays} \u0434`
4752
+ }
4753
+ ),
4578
4754
  /* @__PURE__ */ jsxs2(
4579
4755
  "div",
4580
4756
  {
@@ -4584,7 +4760,6 @@ var TaskRow = React2.memo(
4584
4760
  color: isParent ? task.color || defaultParentBarColor : barColor
4585
4761
  },
4586
4762
  children: [
4587
- !showDurationInside && !milestone && /* @__PURE__ */ jsx2("span", { className: "gantt-tr-externalDuration", children: isParent ? getChildCountLabel(childCount) : `${durationDays} \u0434` }),
4588
4763
  progressWidth > 0 && !showProgressInside && /* @__PURE__ */ jsxs2("span", { className: "gantt-tr-externalProgress", children: [
4589
4764
  progressWidth,
4590
4765
  "%"
@@ -4849,7 +5024,7 @@ var DragGuideLines = ({
4849
5024
  var DragGuideLines_default = DragGuideLines;
4850
5025
 
4851
5026
  // src/components/DependencyLines/DependencyLines.tsx
4852
- import React6, { useMemo as useMemo6, useState as useState2 } from "react";
5027
+ import React6, { useMemo as useMemo6, useState as useState3 } from "react";
4853
5028
  import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
4854
5029
  function isTaskHidden(taskId, collapsedParentIds, taskMap) {
4855
5030
  const task = taskMap.get(taskId);
@@ -4888,6 +5063,8 @@ var DependencyLines = React6.memo(({
4888
5063
  gridWidth,
4889
5064
  totalHeight,
4890
5065
  rowIndexByTaskId,
5066
+ rowTops,
5067
+ rowHeights,
4891
5068
  dragOverrides,
4892
5069
  selectedDep,
4893
5070
  criticalTaskIds,
@@ -4895,7 +5072,7 @@ var DependencyLines = React6.memo(({
4895
5072
  weekendPredicate,
4896
5073
  onDependencyClick
4897
5074
  }) => {
4898
- const [hoveredLineId, setHoveredLineId] = useState2(null);
5075
+ const [hoveredLineId, setHoveredLineId] = useState3(null);
4899
5076
  const tasksForPositions = allTasks ?? tasks;
4900
5077
  const { taskPositions, taskIndices, hiddenTaskIds } = useMemo6(() => {
4901
5078
  const positions = /* @__PURE__ */ new Map();
@@ -4911,7 +5088,8 @@ var DependencyLines = React6.memo(({
4911
5088
  left: computed.left,
4912
5089
  right: computed.right,
4913
5090
  centerX: computed.centerX,
4914
- rowTop: rowIndex * rowHeight,
5091
+ rowTop: rowTops?.[rowIndex] ?? rowIndex * rowHeight,
5092
+ rowHeight: rowHeights?.[rowIndex] ?? rowHeight,
4915
5093
  isVirtual: false
4916
5094
  });
4917
5095
  });
@@ -4931,6 +5109,7 @@ var DependencyLines = React6.memo(({
4931
5109
  right: computed.right,
4932
5110
  centerX: computed.centerX,
4933
5111
  rowTop: ancestorPosition.rowTop,
5112
+ rowHeight: ancestorPosition.rowHeight,
4934
5113
  isVirtual: true
4935
5114
  });
4936
5115
  }
@@ -4944,18 +5123,19 @@ var DependencyLines = React6.memo(({
4944
5123
  const override = dragOverrides?.get(task.id);
4945
5124
  const computed = resolveTaskHorizontalGeometry(task, monthStart, dayWidth, override);
4946
5125
  const rowIndex = rowIndexByTaskId?.get(task.id);
4947
- const rowTop = rowIndex !== void 0 ? rowIndex * rowHeight : 0;
5126
+ const rowTop = rowIndex !== void 0 ? rowTops?.[rowIndex] ?? rowIndex * rowHeight : 0;
4948
5127
  positions.set(task.id, {
4949
5128
  left: computed.left,
4950
5129
  right: computed.right,
4951
5130
  centerX: computed.centerX,
4952
5131
  rowTop,
5132
+ rowHeight: rowIndex !== void 0 ? rowHeights?.[rowIndex] ?? rowHeight : rowHeight,
4953
5133
  isVirtual: false
4954
5134
  });
4955
5135
  }
4956
5136
  }
4957
5137
  return { taskPositions: positions, taskIndices: indices, hiddenTaskIds: hidden };
4958
- }, [tasks, tasksForPositions, allTasks, collapsedParentIds, monthStart, dayWidth, rowHeight, dragOverrides, rowIndexByTaskId]);
5138
+ }, [tasks, tasksForPositions, allTasks, collapsedParentIds, monthStart, dayWidth, rowHeight, rowTops, rowHeights, dragOverrides, rowIndexByTaskId]);
4959
5139
  const tasksForCycleDetection = allTasks ?? tasks;
4960
5140
  const cycleInfo = useMemo6(() => {
4961
5141
  const result = detectCycles(tasksForCycleDetection);
@@ -4997,7 +5177,9 @@ var DependencyLines = React6.memo(({
4997
5177
  continue;
4998
5178
  }
4999
5179
  }
5000
- if (!renderedTaskIdSet.has(edge.predecessorId) && !renderedTaskIdSet.has(edge.successorId)) {
5180
+ const predecessorVisibleId = predecessor.isVirtual ? findVisibleAncestor(taskMap.get(edge.predecessorId), collapsedParentIds, taskMap)?.id : edge.predecessorId;
5181
+ const successorVisibleId = successor.isVirtual ? findVisibleAncestor(taskMap.get(edge.successorId), collapsedParentIds, taskMap)?.id : edge.successorId;
5182
+ if (!renderedTaskIdSet.has(predecessorVisibleId ?? "") && !renderedTaskIdSet.has(successorVisibleId ?? "")) {
5001
5183
  continue;
5002
5184
  }
5003
5185
  const isVirtual = predecessor.isVirtual || successor.isVirtual;
@@ -5011,9 +5193,9 @@ var DependencyLines = React6.memo(({
5011
5193
  let toY;
5012
5194
  if (reverseOrder) {
5013
5195
  fromY = predecessor.rowTop + 10;
5014
- toY = successor.rowTop + rowHeight - 6;
5196
+ toY = successor.rowTop + successor.rowHeight - 6;
5015
5197
  } else {
5016
- fromY = predecessor.rowTop + rowHeight - 10;
5198
+ fromY = predecessor.rowTop + predecessor.rowHeight - 10;
5017
5199
  toY = successor.rowTop + 6;
5018
5200
  }
5019
5201
  let fromX = predecessorTask && isMilestoneTask(predecessorTask) ? predecessor.centerX : edge.type === "SS" || edge.type === "SF" ? predecessor.left : predecessor.right;
@@ -5289,7 +5471,7 @@ var DependencyCreationOverlay = ({ drag, width, height }) => {
5289
5471
  };
5290
5472
 
5291
5473
  // src/components/TaskList/TaskList.tsx
5292
- import React13, { useMemo as useMemo9, useCallback as useCallback6, useState as useState7, useEffect as useEffect6, useRef as useRef7 } from "react";
5474
+ import React13, { useMemo as useMemo9, useCallback as useCallback6, useState as useState8, useEffect as useEffect7, useRef as useRef8 } from "react";
5293
5475
 
5294
5476
  // src/utils/taskListReorder.ts
5295
5477
  function getDescendantIds(taskId, tasks) {
@@ -5494,9 +5676,9 @@ var PopoverContent = ({
5494
5676
 
5495
5677
  // src/components/TaskList/TaskListRow.tsx
5496
5678
  import React10, {
5497
- useState as useState5,
5498
- useRef as useRef4,
5499
- useEffect as useEffect4,
5679
+ useState as useState6,
5680
+ useRef as useRef5,
5681
+ useEffect as useEffect5,
5500
5682
  useCallback as useCallback5,
5501
5683
  useMemo as useMemo8
5502
5684
  } from "react";
@@ -5519,14 +5701,14 @@ var Input = React7.forwardRef(
5519
5701
  Input.displayName = "Input";
5520
5702
 
5521
5703
  // src/components/ui/DatePicker.tsx
5522
- import { useState as useState4, useCallback as useCallback4, useEffect as useEffect3, useRef as useRef3 } from "react";
5704
+ import { useState as useState5, useCallback as useCallback4, useEffect as useEffect4, useRef as useRef4 } from "react";
5523
5705
  import { format as format3, isValid, parse, addDays, addMonths as addMonths2, addYears, subMonths as subMonths2, subYears, subDays } from "date-fns";
5524
5706
 
5525
5707
  // src/components/ui/Calendar.tsx
5526
5708
  import {
5527
- useState as useState3,
5528
- useRef as useRef2,
5529
- useEffect as useEffect2,
5709
+ useState as useState4,
5710
+ useRef as useRef3,
5711
+ useEffect as useEffect3,
5530
5712
  useCallback as useCallback3,
5531
5713
  useMemo as useMemo7
5532
5714
  } from "react";
@@ -5561,13 +5743,13 @@ var Calendar = ({
5561
5743
  disabled = false,
5562
5744
  isWeekend: isWeekendProp
5563
5745
  }) => {
5564
- const scrollRef = useRef2(null);
5746
+ const scrollRef = useRef3(null);
5565
5747
  const initialMonth = useMemo7(
5566
5748
  () => startOfMonth(initialDate ?? selected ?? /* @__PURE__ */ new Date()),
5567
5749
  // eslint-disable-next-line react-hooks/exhaustive-deps
5568
5750
  []
5569
5751
  );
5570
- const [months, setMonths] = useState3(() => [
5752
+ const [months, setMonths] = useState4(() => [
5571
5753
  subMonths(initialMonth, 1),
5572
5754
  initialMonth,
5573
5755
  addMonths(initialMonth, 1)
@@ -5581,7 +5763,7 @@ var Calendar = ({
5581
5763
  }
5582
5764
  });
5583
5765
  }, []);
5584
- useEffect2(() => {
5766
+ useEffect3(() => {
5585
5767
  const container = scrollRef.current;
5586
5768
  if (!container) return;
5587
5769
  const handleScroll = () => {
@@ -5599,7 +5781,7 @@ var Calendar = ({
5599
5781
  container.addEventListener("scroll", handleScroll);
5600
5782
  return () => container.removeEventListener("scroll", handleScroll);
5601
5783
  }, [loadMoreMonths]);
5602
- useEffect2(() => {
5784
+ useEffect3(() => {
5603
5785
  if (!scrollRef.current) return;
5604
5786
  const target = selected ?? initialDate ?? /* @__PURE__ */ new Date();
5605
5787
  const monthKey = format2(startOfMonth(target), "yyyy-MM");
@@ -5704,12 +5886,12 @@ var DatePicker = ({
5704
5886
  businessDays = true,
5705
5887
  footer
5706
5888
  }) => {
5707
- const [open, setOpen] = useState4(false);
5708
- const [inputValue, setInputValue] = useState4("");
5709
- const [draftDate, setDraftDate] = useState4(void 0);
5710
- const dateInputRef = useRef3(null);
5711
- const segIdxRef = useRef3(0);
5712
- const charPosRef = useRef3(0);
5889
+ const [open, setOpen] = useState5(false);
5890
+ const [inputValue, setInputValue] = useState5("");
5891
+ const [draftDate, setDraftDate] = useState5(void 0);
5892
+ const dateInputRef = useRef4(null);
5893
+ const segIdxRef = useRef4(0);
5894
+ const charPosRef = useRef4(0);
5713
5895
  const selectedDate = (() => {
5714
5896
  if (!value) return void 0;
5715
5897
  const d = /* @__PURE__ */ new Date(value + "T00:00:00Z");
@@ -5717,7 +5899,7 @@ var DatePicker = ({
5717
5899
  })();
5718
5900
  const activeDate = draftDate ?? selectedDate;
5719
5901
  const displayValue = selectedDate ? formatUTCDate(selectedDate, displayFormat) : placeholder;
5720
- useEffect3(() => {
5902
+ useEffect4(() => {
5721
5903
  if (value) {
5722
5904
  const d = /* @__PURE__ */ new Date(value + "T00:00:00Z");
5723
5905
  if (isValid(d)) {
@@ -5734,7 +5916,7 @@ var DatePicker = ({
5734
5916
  const seg = segments[idx] ?? segments[0];
5735
5917
  dateInputRef.current.setSelectionRange(seg.start, seg.end);
5736
5918
  }, []);
5737
- useEffect3(() => {
5919
+ useEffect4(() => {
5738
5920
  if (open) {
5739
5921
  setTimeout(() => {
5740
5922
  if (dateInputRef.current) {
@@ -6333,10 +6515,10 @@ var DepChip = ({
6333
6515
  businessDays = true,
6334
6516
  weekendPredicate
6335
6517
  }) => {
6336
- const [popoverOpen, setPopoverOpen] = useState5(false);
6518
+ const [popoverOpen, setPopoverOpen] = useState6(false);
6337
6519
  const lagAbs = Math.abs(lag ?? 0);
6338
- const [inputAbs, setInputAbs] = useState5(lagAbs === 0 ? "" : String(lagAbs));
6339
- useEffect4(() => {
6520
+ const [inputAbs, setInputAbs] = useState6(lagAbs === 0 ? "" : String(lagAbs));
6521
+ useEffect5(() => {
6340
6522
  const abs = Math.abs(lag ?? 0);
6341
6523
  setInputAbs(abs === 0 ? "" : String(abs));
6342
6524
  }, [lag]);
@@ -6715,13 +6897,14 @@ var TaskListRow = React10.memo(
6715
6897
  taskListMenuCommands = [],
6716
6898
  hideTaskListRowActions = false,
6717
6899
  rowClassName,
6900
+ compositeParentColor,
6718
6901
  getTaskListNamePrefixIcon,
6719
6902
  taskDateChangeMode = "preserve-duration",
6720
6903
  onTaskDateChangeModeChange,
6721
6904
  printDependencyLabels = false
6722
6905
  }) => {
6723
- const [editingColumnId, setEditingColumnId] = useState5(null);
6724
- const [editingColumnStartValue, setEditingColumnStartValue] = useState5(void 0);
6906
+ const [editingColumnId, setEditingColumnId] = useState6(null);
6907
+ const [editingColumnStartValue, setEditingColumnStartValue] = useState6(void 0);
6725
6908
  const editingName = editingColumnId === "name";
6726
6909
  const editingDuration = editingColumnId === "duration";
6727
6910
  const editingProgress = editingColumnId === "progress";
@@ -6752,27 +6935,27 @@ var TaskListRow = React10.memo(
6752
6935
  }, [columnWidthStyleMap]);
6753
6936
  const normalizedTask = useMemo8(() => normalizeTaskDatesForType(task), [task]);
6754
6937
  const isMilestone = useMemo8(() => isMilestoneTask(normalizedTask), [normalizedTask]);
6755
- const [nameValue, setNameValue] = useState5("");
6756
- const nameInputRef = useRef4(null);
6757
- const [durationValue, setDurationValue] = useState5(
6938
+ const [nameValue, setNameValue] = useState6("");
6939
+ const nameInputRef = useRef5(null);
6940
+ const [durationValue, setDurationValue] = useState6(
6758
6941
  () => getInclusiveDurationDays(normalizedTask.startDate, normalizedTask.endDate)
6759
6942
  );
6760
- const durationInputRef = useRef4(null);
6761
- const dependencySearchInputRef = useRef4(null);
6762
- const dependencySearchListRef = useRef4(null);
6763
- const [progressValue, setProgressValue] = useState5(0);
6764
- const progressInputRef = useRef4(null);
6765
- const [overflowOpen, setOverflowOpen] = useState5(false);
6766
- const [contextMenuOpen, setContextMenuOpen] = useState5(false);
6767
- const [colorMenuOpen, setColorMenuOpen] = useState5(false);
6768
- const nameConfirmedRef = useRef4(false);
6769
- const durationConfirmedRef = useRef4(false);
6770
- const progressConfirmedRef = useRef4(false);
6771
- const autoEditedForRef = useRef4(null);
6772
- const editTriggerRef = useRef4(
6943
+ const durationInputRef = useRef5(null);
6944
+ const dependencySearchInputRef = useRef5(null);
6945
+ const dependencySearchListRef = useRef5(null);
6946
+ const [progressValue, setProgressValue] = useState6(0);
6947
+ const progressInputRef = useRef5(null);
6948
+ const [overflowOpen, setOverflowOpen] = useState6(false);
6949
+ const [contextMenuOpen, setContextMenuOpen] = useState6(false);
6950
+ const [colorMenuOpen, setColorMenuOpen] = useState6(false);
6951
+ const nameConfirmedRef = useRef5(false);
6952
+ const durationConfirmedRef = useRef5(false);
6953
+ const progressConfirmedRef = useRef5(false);
6954
+ const autoEditedForRef = useRef5(null);
6955
+ const editTriggerRef = useRef5(
6773
6956
  "doubleclick"
6774
6957
  );
6775
- const rowRef = useRef4(null);
6958
+ const rowRef = useRef5(null);
6776
6959
  const isSelected = selectedTaskId === task.id;
6777
6960
  const isParent = useMemo8(
6778
6961
  () => isTaskParent(task.id, allTasks),
@@ -6799,13 +6982,42 @@ var TaskListRow = React10.memo(
6799
6982
  [businessDays, weekendPredicate]
6800
6983
  );
6801
6984
  const isCollapsed = collapsedParentIds.has(task.id);
6985
+ const [compositeIconMode, setCompositeIconMode] = useState6(
6986
+ () => isCollapsed ? "composite" : "down"
6987
+ );
6988
+ const compositeIconModeRef = useRef5(compositeIconMode);
6989
+ const [compositeIconAnimation, setCompositeIconAnimation] = useState6(null);
6990
+ const [compositeIconAnimationPhase, setCompositeIconAnimationPhase] = useState6(null);
6991
+ useEffect5(() => {
6992
+ const nextMode = isCollapsed ? "composite" : "down";
6993
+ if (nextMode === compositeIconModeRef.current) {
6994
+ setCompositeIconAnimation(null);
6995
+ setCompositeIconAnimationPhase(null);
6996
+ return;
6997
+ }
6998
+ setCompositeIconAnimation(isCollapsed ? "closing" : "opening");
6999
+ setCompositeIconAnimationPhase("outgoing");
7000
+ const swapTimeoutId = window.setTimeout(() => {
7001
+ compositeIconModeRef.current = nextMode;
7002
+ setCompositeIconMode(nextMode);
7003
+ setCompositeIconAnimationPhase("incoming");
7004
+ }, 120);
7005
+ const finishTimeoutId = window.setTimeout(() => {
7006
+ setCompositeIconAnimation(null);
7007
+ setCompositeIconAnimationPhase(null);
7008
+ }, 240);
7009
+ return () => {
7010
+ window.clearTimeout(swapTimeoutId);
7011
+ window.clearTimeout(finishTimeoutId);
7012
+ };
7013
+ }, [isCollapsed]);
6802
7014
  const getHierarchyLineColor = useCallback5((columnDepth) => {
6803
7015
  return columnDepth % 2 === 0 ? "#93c5fd" : "var(--gantt-hierarchy-line-color)";
6804
7016
  }, []);
6805
7017
  const isPicking = selectingPredecessorFor != null;
6806
7018
  const isSourceRow = isPicking && selectingPredecessorFor === task.id;
6807
- const [dependencySearchQuery, setDependencySearchQuery] = useState5("");
6808
- const [highlightedDependencyIndex, setHighlightedDependencyIndex] = useState5(0);
7019
+ const [dependencySearchQuery, setDependencySearchQuery] = useState6("");
7020
+ const [highlightedDependencyIndex, setHighlightedDependencyIndex] = useState6(0);
6809
7021
  const chips = useMemo8(() => {
6810
7022
  const taskById = new Map((allTasks ?? []).map((t) => [t.id, t]));
6811
7023
  return (task.dependencies ?? []).map((dep) => {
@@ -6842,7 +7054,7 @@ var TaskListRow = React10.memo(
6842
7054
  taskNumberMap,
6843
7055
  task.dependencies
6844
7056
  ]);
6845
- useEffect4(() => {
7057
+ useEffect5(() => {
6846
7058
  if (editingName && nameInputRef.current) {
6847
7059
  nameInputRef.current.focus();
6848
7060
  if (editTriggerRef.current === "keypress") {
@@ -6853,15 +7065,15 @@ var TaskListRow = React10.memo(
6853
7065
  }
6854
7066
  }
6855
7067
  }, [editingName]);
6856
- useEffect4(() => {
7068
+ useEffect5(() => {
6857
7069
  if (!isSourceRow && dependencySearchQuery !== "") {
6858
7070
  setDependencySearchQuery("");
6859
7071
  }
6860
7072
  }, [isSourceRow, dependencySearchQuery]);
6861
- useEffect4(() => {
7073
+ useEffect5(() => {
6862
7074
  setHighlightedDependencyIndex(0);
6863
7075
  }, [dependencySearchQuery, isSourceRow, dependencyPickMode]);
6864
- useEffect4(() => {
7076
+ useEffect5(() => {
6865
7077
  if (dependencySearchCandidates.length === 0) {
6866
7078
  setHighlightedDependencyIndex(0);
6867
7079
  return;
@@ -6870,13 +7082,13 @@ var TaskListRow = React10.memo(
6870
7082
  setHighlightedDependencyIndex(dependencySearchCandidates.length - 1);
6871
7083
  }
6872
7084
  }, [dependencySearchCandidates, highlightedDependencyIndex]);
6873
- useEffect4(() => {
7085
+ useEffect5(() => {
6874
7086
  if (isSourceRow && dependencySearchInputRef.current) {
6875
7087
  dependencySearchInputRef.current.focus();
6876
7088
  dependencySearchInputRef.current.select();
6877
7089
  }
6878
7090
  }, [isSourceRow, dependencyPickMode, activeLinkType]);
6879
- useEffect4(() => {
7091
+ useEffect5(() => {
6880
7092
  if (!isSourceRow || dependencySearchCandidates.length === 0) {
6881
7093
  return;
6882
7094
  }
@@ -6888,7 +7100,7 @@ var TaskListRow = React10.memo(
6888
7100
  block: "nearest"
6889
7101
  });
6890
7102
  }, [isSourceRow, highlightedDependencyIndex, dependencySearchCandidates]);
6891
- useEffect4(() => {
7103
+ useEffect5(() => {
6892
7104
  if (editingTaskId === task.id && !disableTaskNameEditing && autoEditedForRef.current !== editingTaskId) {
6893
7105
  autoEditedForRef.current = editingTaskId;
6894
7106
  nameConfirmedRef.current = false;
@@ -6907,6 +7119,7 @@ var TaskListRow = React10.memo(
6907
7119
  );
6908
7120
  const handleNameDoubleClick = useCallback5(
6909
7121
  (e) => {
7122
+ if (task.composite) return;
6910
7123
  if (disableTaskNameEditing) return;
6911
7124
  e.stopPropagation();
6912
7125
  nameConfirmedRef.current = false;
@@ -6914,7 +7127,7 @@ var TaskListRow = React10.memo(
6914
7127
  setNameValue(task.name);
6915
7128
  setEditingColumnId("name");
6916
7129
  },
6917
- [task.name, disableTaskNameEditing]
7130
+ [task.name, task.composite, disableTaskNameEditing]
6918
7131
  );
6919
7132
  const handleRowKeyDown = useCallback5(
6920
7133
  (e) => {
@@ -7069,16 +7282,16 @@ var TaskListRow = React10.memo(
7069
7282
  },
7070
7283
  [progressValue, task, onTasksChange, handleProgressCancel, allTasks]
7071
7284
  );
7072
- useEffect4(() => {
7285
+ useEffect5(() => {
7073
7286
  if (editingProgress && progressInputRef.current) {
7074
7287
  progressInputRef.current.focus();
7075
7288
  progressInputRef.current.select();
7076
7289
  }
7077
7290
  }, [editingProgress]);
7078
- useEffect4(() => {
7291
+ useEffect5(() => {
7079
7292
  setDurationValue(getDuration(normalizedTask.startDate, normalizedTask.endDate));
7080
7293
  }, [normalizedTask.startDate, normalizedTask.endDate, getDuration]);
7081
- useEffect4(() => {
7294
+ useEffect5(() => {
7082
7295
  if (editingDuration && durationInputRef.current) {
7083
7296
  durationInputRef.current.focus();
7084
7297
  durationInputRef.current.select();
@@ -7563,7 +7776,7 @@ var TaskListRow = React10.memo(
7563
7776
  ]
7564
7777
  }
7565
7778
  );
7566
- const nameTriggerPaddingLeft = isParent ? `${nestingDepth * 20 + 28}px` : nestingDepth > 0 ? `${nestingDepth * 20 + 8}px` : void 0;
7779
+ const nameTriggerPaddingLeft = isParent && !task.composite ? `${nestingDepth * 20 + 28}px` : nestingDepth > 0 ? `${nestingDepth * 20 + 8}px` : void 0;
7567
7780
  const nameInputPaddingLeft = nestingDepth > 0 ? `${nestingDepth * 20 + 8}px` : void 0;
7568
7781
  const nestedDropIndicatorLeft = (() => {
7569
7782
  const numberColumnWidth = resolvedColumns?.find((col) => col.id === "number")?.width ?? 40;
@@ -7661,36 +7874,35 @@ var TaskListRow = React10.memo(
7661
7874
  }
7662
7875
  )
7663
7876
  ] }),
7664
- isParent && !editingName && /* @__PURE__ */ jsxs10(Fragment3, { children: [
7665
- !isFilterHideMode && !isCollapsed && /* @__PURE__ */ jsx14(
7666
- "span",
7667
- {
7668
- style: {
7669
- position: "absolute",
7670
- left: `${nestingDepth * 20 + 9}px`,
7671
- top: `${rowHeight / 2 + 7}px`,
7672
- height: `${rowHeight / 2 - 7}px`,
7673
- width: "1.5px",
7674
- background: getHierarchyLineColor(nestingDepth),
7675
- borderRadius: "1px",
7676
- pointerEvents: "none"
7677
- }
7678
- }
7679
- ),
7680
- /* @__PURE__ */ jsx14(
7681
- "button",
7682
- {
7683
- type: "button",
7684
- className: `gantt-tl-collapse-btn ${isCollapsed ? "gantt-tl-collapse-btn-collapsed" : ""}`,
7685
- onClick: handleToggleCollapse,
7686
- style: { left: `${nestingDepth * 20 + 1}px` },
7687
- "aria-label": isCollapsed ? "Expand children" : "Collapse children",
7688
- children: /* @__PURE__ */ jsx14(ChevronRightIcon, {})
7877
+ task.composite && !isFilterHideMode && !isCollapsed && !editingName && /* @__PURE__ */ jsx14(
7878
+ "span",
7879
+ {
7880
+ "data-testid": "gantt-tl-parent-connector-tail",
7881
+ style: {
7882
+ position: "absolute",
7883
+ left: `${nestingDepth * 20 + 9}px`,
7884
+ top: `${rowHeight / 2 + 7}px`,
7885
+ height: `${rowHeight / 2 - 7}px`,
7886
+ width: "1.5px",
7887
+ background: getHierarchyLineColor(nestingDepth),
7888
+ borderRadius: "1px",
7889
+ pointerEvents: "none"
7689
7890
  }
7690
- )
7691
- ] })
7891
+ }
7892
+ ),
7893
+ isParent && !task.composite && !editingName && /* @__PURE__ */ jsx14(Fragment3, { children: /* @__PURE__ */ jsx14(
7894
+ "button",
7895
+ {
7896
+ type: "button",
7897
+ className: `gantt-tl-collapse-btn ${isCollapsed ? "gantt-tl-collapse-btn-collapsed" : ""}`,
7898
+ onClick: handleToggleCollapse,
7899
+ style: { left: `${nestingDepth * 20 - 2}px` },
7900
+ "aria-label": isCollapsed ? "Expand children" : "Collapse children",
7901
+ children: /* @__PURE__ */ jsx14(ChevronRightIcon, {})
7902
+ }
7903
+ ) })
7692
7904
  ] }),
7693
- !isChild && isParent && !editingName && /* @__PURE__ */ jsxs10(Fragment3, { children: [
7905
+ !isChild && isParent && !task.composite && !editingName && /* @__PURE__ */ jsxs10(Fragment3, { children: [
7694
7906
  !isFilterHideMode && !isCollapsed && /* @__PURE__ */ jsx14(
7695
7907
  "span",
7696
7908
  {
@@ -7713,7 +7925,7 @@ var TaskListRow = React10.memo(
7713
7925
  type: "button",
7714
7926
  className: `gantt-tl-collapse-btn ${isCollapsed ? "gantt-tl-collapse-btn-collapsed" : ""}`,
7715
7927
  onClick: handleToggleCollapse,
7716
- style: { left: `${nestingDepth * 20 + 1}px` },
7928
+ style: { left: `${nestingDepth * 20 - 2}px` },
7717
7929
  "aria-label": isCollapsed ? "Expand children" : "Collapse children",
7718
7930
  children: /* @__PURE__ */ jsx14(ChevronRightIcon, {})
7719
7931
  }
@@ -7732,27 +7944,57 @@ var TaskListRow = React10.memo(
7732
7944
  style: { paddingLeft: nameInputPaddingLeft },
7733
7945
  onClick: (e) => e.stopPropagation()
7734
7946
  }
7735
- ) : /* @__PURE__ */ jsxs10(
7736
- "button",
7737
- {
7738
- type: "button",
7739
- className: [
7740
- "gantt-tl-name-trigger",
7741
- disableTaskNameEditing ? "gantt-tl-name-locked" : ""
7742
- ].filter(Boolean).join(" "),
7743
- title: task.name,
7744
- onClick: handleNameClick,
7745
- onDoubleClick: handleNameDoubleClick,
7746
- style: {
7747
- paddingLeft: nameTriggerPaddingLeft,
7748
- paddingRight: task.color ? "20px" : void 0
7749
- },
7750
- children: [
7751
- taskListNamePrefixIcon ? /* @__PURE__ */ jsx14("span", { className: "gantt-tl-name-trigger-icon", "aria-hidden": "true", children: taskListNamePrefixIcon }) : null,
7752
- /* @__PURE__ */ jsx14("span", { className: "gantt-tl-name-trigger-text", children: task.name })
7753
- ]
7754
- }
7755
- ),
7947
+ ) : /* @__PURE__ */ jsxs10(Fragment3, { children: [
7948
+ task.composite && /* @__PURE__ */ jsx14(
7949
+ "button",
7950
+ {
7951
+ type: "button",
7952
+ className: "gantt-tl-compositeToggle",
7953
+ onClick: handleToggleCollapse,
7954
+ style: { left: `${nestingDepth * 20 - 3}px` },
7955
+ "aria-label": isCollapsed ? `\u0420\u0430\u0441\u043A\u0440\u044B\u0442\u044C ${task.name}` : `\u0421\u0432\u0435\u0440\u043D\u0443\u0442\u044C ${task.name}`,
7956
+ "aria-expanded": !isCollapsed,
7957
+ 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",
7958
+ children: /* @__PURE__ */ jsx14(
7959
+ "svg",
7960
+ {
7961
+ className: compositeIconAnimation && compositeIconAnimationPhase ? `gantt-tl-compositeGlyph-${compositeIconMode}-${compositeIconAnimation}-${compositeIconAnimationPhase}` : void 0,
7962
+ viewBox: "0 0 24 24",
7963
+ fill: "none",
7964
+ stroke: "currentColor",
7965
+ strokeWidth: "2",
7966
+ strokeLinecap: "round",
7967
+ strokeLinejoin: "round",
7968
+ "aria-hidden": "true",
7969
+ children: compositeIconMode === "composite" ? /* @__PURE__ */ jsxs10(Fragment3, { children: [
7970
+ /* @__PURE__ */ jsx14("path", { d: "M21 5H11" }),
7971
+ /* @__PURE__ */ jsx14("path", { d: "M21 12H11" }),
7972
+ /* @__PURE__ */ jsx14("path", { d: "M21 19H11" }),
7973
+ /* @__PURE__ */ jsx14("path", { d: "m3 8 4 4-4 4" })
7974
+ ] }) : /* @__PURE__ */ jsx14("path", { d: "m6 9 6 6 6-6" })
7975
+ }
7976
+ )
7977
+ }
7978
+ ),
7979
+ /* @__PURE__ */ jsxs10(
7980
+ "button",
7981
+ {
7982
+ type: "button",
7983
+ className: `gantt-tl-name-trigger${disableTaskNameEditing ? " gantt-tl-name-locked" : ""}`,
7984
+ title: task.name,
7985
+ onClick: handleNameClick,
7986
+ onDoubleClick: handleNameDoubleClick,
7987
+ style: {
7988
+ paddingLeft: task.composite ? `${nestingDepth * 20 + 30}px` : nameTriggerPaddingLeft,
7989
+ paddingRight: task.color ? "20px" : void 0
7990
+ },
7991
+ children: [
7992
+ taskListNamePrefixIcon ? /* @__PURE__ */ jsx14("span", { className: "gantt-tl-name-trigger-icon", "aria-hidden": "true", children: taskListNamePrefixIcon }) : null,
7993
+ /* @__PURE__ */ jsx14("span", { className: "gantt-tl-name-trigger-text", children: task.name })
7994
+ ]
7995
+ }
7996
+ )
7997
+ ] }),
7756
7998
  !editingName && task.color && /* @__PURE__ */ jsx14(
7757
7999
  "span",
7758
8000
  {
@@ -8369,8 +8611,9 @@ var TaskListRow = React10.memo(
8369
8611
  isDragOver && isNestedDropTarget ? "gantt-tl-row-drag-over-nested" : "",
8370
8612
  isDragOver && isDirectChildDropTarget ? "gantt-tl-row-drag-over-direct-child" : "",
8371
8613
  isChild ? "gantt-tl-row-child" : "",
8372
- isParent ? "gantt-tl-row-parent" : "",
8373
- isParent && fillParentRow ? "gantt-tl-row-parent-filled" : "",
8614
+ isParent && !task.composite ? "gantt-tl-row-parent" : "",
8615
+ task.composite ? "gantt-tl-row-composite" : "",
8616
+ isParent && !task.composite && fillParentRow ? "gantt-tl-row-parent-filled" : "",
8374
8617
  `gantt-tl-row-level-${rowFillLevel}`,
8375
8618
  isTotalRow ? "gantt-tl-row-total" : "",
8376
8619
  rowClassName ?? ""
@@ -8378,6 +8621,7 @@ var TaskListRow = React10.memo(
8378
8621
  style: {
8379
8622
  height: `${rowHeight}px`,
8380
8623
  position: "relative",
8624
+ "--gantt-tl-composite-detail-color": compositeParentColor ?? "var(--gantt-task-bar-default-color, #8b5cf6)",
8381
8625
  "--gantt-tl-nested-drop-left": nestedDropIndicatorLeft,
8382
8626
  "--gantt-tl-nested-inside-drop-left": nestedInsideDropIndicatorLeft
8383
8627
  },
@@ -8519,7 +8763,7 @@ var TaskListRow = React10.memo(
8519
8763
  TaskListRow.displayName = "TaskListRow";
8520
8764
 
8521
8765
  // src/components/TaskList/NewTaskRow.tsx
8522
- import { useState as useState6, useRef as useRef5, useEffect as useEffect5 } from "react";
8766
+ import { useState as useState7, useRef as useRef6, useEffect as useEffect6 } from "react";
8523
8767
  import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
8524
8768
  var NewTaskRow = ({
8525
8769
  rowHeight,
@@ -8527,12 +8771,12 @@ var NewTaskRow = ({
8527
8771
  onCancel,
8528
8772
  nestingDepth = 0
8529
8773
  }) => {
8530
- const [nameValue, setNameValue] = useState6("");
8531
- const inputRef = useRef5(null);
8532
- const confirmedRef = useRef5(false);
8774
+ const [nameValue, setNameValue] = useState7("");
8775
+ const inputRef = useRef6(null);
8776
+ const confirmedRef = useRef6(false);
8533
8777
  const inputInnerPadding = 4;
8534
8778
  const levelOffset = nestingDepth > 0 ? nestingDepth * 20 + 8 : 0;
8535
- useEffect5(() => {
8779
+ useEffect6(() => {
8536
8780
  if (inputRef.current) {
8537
8781
  inputRef.current.focus();
8538
8782
  inputRef.current.select();
@@ -8585,7 +8829,7 @@ var NewTaskRow = ({
8585
8829
  };
8586
8830
 
8587
8831
  // src/components/GenerationSkeleton/GenerationSkeleton.tsx
8588
- import { useRef as useRef6 } from "react";
8832
+ import { useRef as useRef7 } from "react";
8589
8833
  import { Fragment as Fragment4, jsx as jsx16 } from "react/jsx-runtime";
8590
8834
  var MAX_SKELETON_ROWS = 200;
8591
8835
  var SHIMMER_DURATION_MS = 1450;
@@ -8603,14 +8847,15 @@ var GenerationSkeletonRows = ({
8603
8847
  rowHeight,
8604
8848
  variant,
8605
8849
  startIndex = 0,
8850
+ startTop,
8606
8851
  taskListLayout,
8607
8852
  chartDayWidth = 40,
8608
8853
  chartStartDayOffset = 0,
8609
8854
  chartStartJitterDays = 0
8610
8855
  }) => {
8611
8856
  const safeCount = clampCount(count);
8612
- const previousCountRef = useRef6(null);
8613
- const shimmerDelayRef = useRef6("0ms");
8857
+ const previousCountRef = useRef7(null);
8858
+ const shimmerDelayRef = useRef7("0ms");
8614
8859
  if (previousCountRef.current !== safeCount) {
8615
8860
  previousCountRef.current = safeCount;
8616
8861
  shimmerDelayRef.current = `-${Date.now() % SHIMMER_DURATION_MS}ms`;
@@ -8643,7 +8888,7 @@ var GenerationSkeletonRows = ({
8643
8888
  "div",
8644
8889
  {
8645
8890
  className: "gantt-generation-skeleton-row",
8646
- style: { top: `${rowIndex * rowHeight}px`, height: `${rowHeight}px` },
8891
+ style: { top: `${(startTop ?? startIndex * rowHeight) + index * rowHeight}px`, height: `${rowHeight}px` },
8647
8892
  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 })
8648
8893
  },
8649
8894
  rowIndex
@@ -8881,8 +9126,8 @@ var SelectAllCheckbox = ({
8881
9126
  indeterminate,
8882
9127
  onChange
8883
9128
  }) => {
8884
- const ref = useRef7(null);
8885
- useEffect6(() => {
9129
+ const ref = useRef8(null);
9130
+ useEffect7(() => {
8886
9131
  if (ref.current) {
8887
9132
  ref.current.indeterminate = indeterminate;
8888
9133
  }
@@ -8903,6 +9148,8 @@ var SelectAllCheckbox = ({
8903
9148
  var TaskList = ({
8904
9149
  tasks,
8905
9150
  rowHeight,
9151
+ rowTops,
9152
+ rowHeights,
8906
9153
  headerHeight,
8907
9154
  taskListWidth,
8908
9155
  onTasksChange,
@@ -8961,14 +9208,14 @@ var TaskList = ({
8961
9208
  skeletonRowCount = 0
8962
9209
  }) => {
8963
9210
  const reorderDisabled = disableTaskListReorder || disableTaskDrag;
8964
- const [internalSelectedTaskIds, setInternalSelectedTaskIds] = useState7(/* @__PURE__ */ new Set());
8965
- const [activeCustomCell, setActiveCustomCell] = useState7(null);
8966
- const [columnWidthOverrides, setColumnWidthOverrides] = useState7(() => normalizeColumnWidthMap(taskListColumnWidths));
8967
- const [isColumnResizeMode, setIsColumnResizeMode] = useState7(false);
8968
- const columnResizeHeaderRef = useRef7(null);
8969
- const resizeStateRef = useRef7(null);
9211
+ const [internalSelectedTaskIds, setInternalSelectedTaskIds] = useState8(/* @__PURE__ */ new Set());
9212
+ const [activeCustomCell, setActiveCustomCell] = useState8(null);
9213
+ const [columnWidthOverrides, setColumnWidthOverrides] = useState8(() => normalizeColumnWidthMap(taskListColumnWidths));
9214
+ const [isColumnResizeMode, setIsColumnResizeMode] = useState8(false);
9215
+ const columnResizeHeaderRef = useRef8(null);
9216
+ const resizeStateRef = useRef8(null);
8970
9217
  const effectiveSelectedTaskIds = selectedTaskIds ?? internalSelectedTaskIds;
8971
- useEffect6(() => {
9218
+ useEffect7(() => {
8972
9219
  if (!isColumnResizeMode) {
8973
9220
  return;
8974
9221
  }
@@ -8990,7 +9237,7 @@ var TaskList = ({
8990
9237
  }
8991
9238
  onSelectedTaskIdsChange?.(nextSelectedTaskIds);
8992
9239
  }, [onSelectedTaskIdsChange, selectedTaskIds]);
8993
- const [internalCollapsedParentIds, setInternalCollapsedParentIds] = useState7(/* @__PURE__ */ new Set());
9240
+ const [internalCollapsedParentIds, setInternalCollapsedParentIds] = useState8(/* @__PURE__ */ new Set());
8994
9241
  const collapsedParentIds = externalCollapsedParentIds ?? internalCollapsedParentIds;
8995
9242
  const handleToggleCollapse = externalOnToggleCollapse ?? useCallback6((parentId) => {
8996
9243
  setInternalCollapsedParentIds((prev) => {
@@ -9030,14 +9277,22 @@ var TaskList = ({
9030
9277
  return tasks2;
9031
9278
  }, [orderedTasks, collapsedParentIds, filterMode, filteredTaskIds, isFilterActive, criticalPathMode, criticalTaskIds]);
9032
9279
  const totalHeight = useMemo9(
9033
- () => (visibleTasks.length + Math.max(0, Math.floor(skeletonRowCount))) * rowHeight,
9034
- [rowHeight, skeletonRowCount, visibleTasks.length]
9280
+ () => (rowTops && rowHeights && visibleTasks.length > 0 ? rowTops[visibleTasks.length - 1] + rowHeights[visibleTasks.length - 1] : visibleTasks.length * rowHeight) + Math.max(0, Math.floor(skeletonRowCount)) * rowHeight,
9281
+ [rowHeight, rowTops, rowHeights, skeletonRowCount, visibleTasks.length]
9035
9282
  );
9036
9283
  const visibleTaskNumberMap = useMemo9(
9037
9284
  () => Object.fromEntries(buildTaskNumberMap(visibleTasks)),
9038
9285
  [visibleTasks]
9039
9286
  );
9040
9287
  const visibleTaskIds = useMemo9(() => visibleTasks.map((task) => task.id), [visibleTasks]);
9288
+ const compositeParentIds = useMemo9(
9289
+ () => new Set(tasks.filter((task) => task.composite).map((task) => task.id)),
9290
+ [tasks]
9291
+ );
9292
+ const compositeParentsById = useMemo9(
9293
+ () => new Map(tasks.filter((task) => task.composite).map((task) => [task.id, task])),
9294
+ [tasks]
9295
+ );
9041
9296
  const renderedVisibleRowIndices = useMemo9(
9042
9297
  () => visibleRowIndices ?? visibleTasks.map((_, index) => index),
9043
9298
  [visibleRowIndices, visibleTasks]
@@ -9148,14 +9403,14 @@ var TaskList = ({
9148
9403
  const handleRowClick = useCallback6((taskId) => {
9149
9404
  onTaskSelect?.(taskId);
9150
9405
  }, [onTaskSelect]);
9151
- const [activeLinkType, setActiveLinkType] = useState7("FS");
9152
- const [selectingPredecessorFor, setSelectingPredecessorFor] = useState7(null);
9153
- const [dependencyPickMode, setDependencyPickMode] = useState7("successor");
9154
- const [typeMenuOpen, setTypeMenuOpen] = useState7(false);
9155
- const [dependencyError, setDependencyError] = useState7(null);
9156
- const overlayRef = useRef7(null);
9157
- const [selectedChip, setSelectedChip] = useState7(controlledSelectedChip ? { ...controlledSelectedChip, linkType: controlledSelectedChip.linkType } : null);
9158
- useEffect6(() => {
9406
+ const [activeLinkType, setActiveLinkType] = useState8("FS");
9407
+ const [selectingPredecessorFor, setSelectingPredecessorFor] = useState8(null);
9408
+ const [dependencyPickMode, setDependencyPickMode] = useState8("successor");
9409
+ const [typeMenuOpen, setTypeMenuOpen] = useState8(false);
9410
+ const [dependencyError, setDependencyError] = useState8(null);
9411
+ const overlayRef = useRef8(null);
9412
+ const [selectedChip, setSelectedChip] = useState8(controlledSelectedChip ? { ...controlledSelectedChip, linkType: controlledSelectedChip.linkType } : null);
9413
+ useEffect7(() => {
9159
9414
  if (controlledSelectedChip !== void 0) {
9160
9415
  setSelectedChip(controlledSelectedChip ? { ...controlledSelectedChip, linkType: controlledSelectedChip.linkType } : null);
9161
9416
  }
@@ -9164,7 +9419,7 @@ var TaskList = ({
9164
9419
  setSelectedChip(chip);
9165
9420
  onSelectedChipChange?.(chip);
9166
9421
  }, [onSelectedChipChange]);
9167
- useEffect6(() => {
9422
+ useEffect7(() => {
9168
9423
  if (!selectingPredecessorFor && !selectedChip && !selectedTaskId && !activeCustomCell) return;
9169
9424
  const pendingCanvasMouseDownRef = {
9170
9425
  container: null,
@@ -9346,13 +9601,13 @@ var TaskList = ({
9346
9601
  );
9347
9602
  onTasksChange?.([{ ...task, dependencies: updatedDeps }]);
9348
9603
  }, [tasks, onTasksChange]);
9349
- const [isCreating, setIsCreating] = useState7(false);
9350
- const [pendingInsert, setPendingInsert] = useState7(null);
9351
- const [draggingIndex, setDraggingIndex] = useState7(null);
9352
- const [dragOverTarget, setDragOverTarget] = useState7(null);
9353
- const [isBeforeFirstDropZoneActive, setIsBeforeFirstDropZoneActive] = useState7(false);
9354
- const dragOriginIndexRef = useRef7(null);
9355
- const dragTaskIdRef = useRef7(null);
9604
+ const [isCreating, setIsCreating] = useState8(false);
9605
+ const [pendingInsert, setPendingInsert] = useState8(null);
9606
+ const [draggingIndex, setDraggingIndex] = useState8(null);
9607
+ const [dragOverTarget, setDragOverTarget] = useState8(null);
9608
+ const [isBeforeFirstDropZoneActive, setIsBeforeFirstDropZoneActive] = useState8(false);
9609
+ const dragOriginIndexRef = useRef8(null);
9610
+ const dragTaskIdRef = useRef8(null);
9356
9611
  const clearDragState = useCallback6(() => {
9357
9612
  setDraggingIndex(null);
9358
9613
  setDragOverTarget(null);
@@ -9688,7 +9943,7 @@ var TaskList = ({
9688
9943
  () => createBuiltInColumns({ businessDays, enableTaskMultiSelect }),
9689
9944
  [businessDays, enableTaskMultiSelect]
9690
9945
  );
9691
- useEffect6(() => {
9946
+ useEffect7(() => {
9692
9947
  setColumnWidthOverrides(normalizeColumnWidthMap(taskListColumnWidths));
9693
9948
  }, [taskListColumnWidths]);
9694
9949
  const resolvedColumns = useMemo9(
@@ -9740,7 +9995,7 @@ var TaskList = ({
9740
9995
  return next;
9741
9996
  });
9742
9997
  }, [onTaskListColumnWidthsChange]);
9743
- useEffect6(() => {
9998
+ useEffect7(() => {
9744
9999
  const updateResizedColumn = (clientX) => {
9745
10000
  const resizeState = resizeStateRef.current;
9746
10001
  if (!resizeState) {
@@ -10029,7 +10284,7 @@ var TaskList = ({
10029
10284
  renderedVisibleTasks.map(({ task, index }) => {
10030
10285
  const previousVisibleTask = index > 0 ? visibleTasks[index - 1] : void 0;
10031
10286
  const canDemoteTask = index === 0 || !task.parentId || previousVisibleTask?.id !== task.parentId;
10032
- const topOffset = index * rowHeight + (hasVisiblePendingInsert && index > pendingInsertRowIndex ? rowHeight : 0);
10287
+ const topOffset = (rowTops?.[index] ?? index * rowHeight) + (hasVisiblePendingInsert && index > pendingInsertRowIndex ? rowHeight : 0);
10033
10288
  return /* @__PURE__ */ jsx17(
10034
10289
  "div",
10035
10290
  {
@@ -10046,7 +10301,7 @@ var TaskList = ({
10046
10301
  rowIndex: index,
10047
10302
  taskNumber: originalTaskNumberMap[task.id] || "",
10048
10303
  taskNumberMap: originalTaskNumberMap,
10049
- rowHeight,
10304
+ rowHeight: rowHeights?.[index] ?? rowHeight,
10050
10305
  onTasksChange,
10051
10306
  onDurationChange,
10052
10307
  selectedTaskId,
@@ -10103,7 +10358,8 @@ var TaskList = ({
10103
10358
  onActiveCustomCellChange: setActiveCustomCell,
10104
10359
  taskListMenuCommands,
10105
10360
  hideTaskListRowActions,
10106
- rowClassName: getTaskListRowClassName?.(task),
10361
+ rowClassName: [task.parentId && compositeParentIds.has(task.parentId) ? "gantt-tl-row-composite-detail" : "", getTaskListRowClassName?.(task)].filter(Boolean).join(" "),
10362
+ compositeParentColor: task.parentId ? compositeParentsById.get(task.parentId)?.color : void 0,
10107
10363
  getTaskListNamePrefixIcon,
10108
10364
  taskDateChangeMode,
10109
10365
  onTaskDateChangeModeChange,
@@ -10119,7 +10375,7 @@ var TaskList = ({
10119
10375
  {
10120
10376
  style: {
10121
10377
  position: "absolute",
10122
- top: `${(pendingInsertRowIndex + 1) * rowHeight}px`,
10378
+ top: `${rowTops?.[pendingInsertRowIndex + 1] ?? (pendingInsertRowIndex + 1) * rowHeight}px`,
10123
10379
  left: 0,
10124
10380
  right: 0
10125
10381
  },
@@ -10141,6 +10397,7 @@ var TaskList = ({
10141
10397
  rowHeight,
10142
10398
  variant: "task-list",
10143
10399
  startIndex: visibleTasks.length,
10400
+ startTop: totalHeight - Math.max(0, Math.floor(skeletonRowCount)) * rowHeight,
10144
10401
  taskListLayout: generationSkeletonTaskListLayout
10145
10402
  }
10146
10403
  )
@@ -10194,7 +10451,7 @@ var TaskList = ({
10194
10451
  };
10195
10452
 
10196
10453
  // src/components/ResourceTimelineChart/ResourceTimelineChart.tsx
10197
- import React14, { useCallback as useCallback8, useEffect as useEffect8, useMemo as useMemo10, useRef as useRef9, useState as useState9 } from "react";
10454
+ import React14, { useCallback as useCallback8, useEffect as useEffect9, useMemo as useMemo10, useRef as useRef10, useState as useState10 } from "react";
10198
10455
 
10199
10456
  // src/utils/resourceTimelineLayout.ts
10200
10457
  var isInvalidDate = (date) => Number.isNaN(date.getTime());
@@ -10396,7 +10653,7 @@ var layoutResourceTimelineItems = (resources, options) => {
10396
10653
  };
10397
10654
 
10398
10655
  // src/hooks/useResourceItemDrag.ts
10399
- import { useCallback as useCallback7, useEffect as useEffect7, useRef as useRef8, useState as useState8 } from "react";
10656
+ import { useCallback as useCallback7, useEffect as useEffect8, useRef as useRef9, useState as useState9 } from "react";
10400
10657
  var RESOURCE_CURSOR_STYLE_ID = "gantt-resource-global-drag-cursor-style";
10401
10658
  function ensureResourceGlobalCursorStyle() {
10402
10659
  if (typeof document === "undefined") return;
@@ -10508,15 +10765,15 @@ var useResourceItemDrag = ({
10508
10765
  weekendPredicate,
10509
10766
  onResourceItemMove
10510
10767
  }) => {
10511
- const activeDragRef = useRef8(null);
10512
- const rowsRef = useRef8(rows);
10513
- const onResourceItemMoveRef = useRef8(onResourceItemMove);
10514
- const rafRef = useRef8(null);
10515
- const [preview, setPreview] = useState8(null);
10516
- useEffect7(() => {
10768
+ const activeDragRef = useRef9(null);
10769
+ const rowsRef = useRef9(rows);
10770
+ const onResourceItemMoveRef = useRef9(onResourceItemMove);
10771
+ const rafRef = useRef9(null);
10772
+ const [preview, setPreview] = useState9(null);
10773
+ useEffect8(() => {
10517
10774
  rowsRef.current = rows;
10518
10775
  }, [rows]);
10519
- useEffect7(() => {
10776
+ useEffect8(() => {
10520
10777
  onResourceItemMoveRef.current = onResourceItemMove;
10521
10778
  }, [onResourceItemMove]);
10522
10779
  const clearRaf = useCallback7(() => {
@@ -10531,7 +10788,7 @@ var useResourceItemDrag = ({
10531
10788
  activeDragRef.current = null;
10532
10789
  setPreview(null);
10533
10790
  }, [clearRaf]);
10534
- useEffect7(() => {
10791
+ useEffect8(() => {
10535
10792
  const handleMouseMove = (event) => {
10536
10793
  const activeDrag = activeDragRef.current;
10537
10794
  if (!activeDrag || rafRef.current !== null) {
@@ -10864,13 +11121,13 @@ var ResourceHeader = ({
10864
11121
  onConflictBadgeClick,
10865
11122
  columnTemplate
10866
11123
  }) => {
10867
- const [menuOpen, setMenuOpen] = useState9(false);
10868
- const [typeMenuOpen, setTypeMenuOpen] = useState9(false);
10869
- const [scopeMenuOpen, setScopeMenuOpen] = useState9(false);
10870
- const [editingName, setEditingName] = useState9(false);
10871
- const [nameValue, setNameValue] = useState9(resource.name);
10872
- const nameInputRef = useRef9(null);
10873
- const nameConfirmedRef = useRef9(false);
11124
+ const [menuOpen, setMenuOpen] = useState10(false);
11125
+ const [typeMenuOpen, setTypeMenuOpen] = useState10(false);
11126
+ const [scopeMenuOpen, setScopeMenuOpen] = useState10(false);
11127
+ const [editingName, setEditingName] = useState10(false);
11128
+ const [nameValue, setNameValue] = useState10(resource.name);
11129
+ const nameInputRef = useRef10(null);
11130
+ const nameConfirmedRef = useRef10(false);
10874
11131
  const visibleCommands = useMemo10(
10875
11132
  () => menuCommands.filter((command) => command.isVisible?.(resource) ?? true),
10876
11133
  [menuCommands, resource]
@@ -10883,12 +11140,12 @@ var ResourceHeader = ({
10883
11140
  const applyResourcePatch = useCallback8((patch) => {
10884
11141
  onResourceChange?.({ ...resource, ...patch });
10885
11142
  }, [onResourceChange, resource]);
10886
- useEffect8(() => {
11143
+ useEffect9(() => {
10887
11144
  if (!editingName) {
10888
11145
  setNameValue(resource.name);
10889
11146
  }
10890
11147
  }, [editingName, resource.name]);
10891
- useEffect8(() => {
11148
+ useEffect9(() => {
10892
11149
  if (editingName && nameInputRef.current) {
10893
11150
  nameInputRef.current.focus();
10894
11151
  nameInputRef.current.select();
@@ -11129,10 +11386,10 @@ var ResourceHeader = ({
11129
11386
  );
11130
11387
  };
11131
11388
  var NewResourceRow = ({ height, onConfirm, onCancel, columnTemplate }) => {
11132
- const [nameValue, setNameValue] = useState9("");
11133
- const inputRef = useRef9(null);
11134
- const confirmedRef = useRef9(false);
11135
- useEffect8(() => {
11389
+ const [nameValue, setNameValue] = useState10("");
11390
+ const inputRef = useRef10(null);
11391
+ const confirmedRef = useRef10(false);
11392
+ useEffect9(() => {
11136
11393
  inputRef.current?.focus();
11137
11394
  inputRef.current?.select();
11138
11395
  }, []);
@@ -11217,18 +11474,18 @@ function ResourceTimelineChart({
11217
11474
  resourceTableColumnWidths,
11218
11475
  onResourceTableColumnWidthsChange
11219
11476
  }) {
11220
- const scrollContainerRef = useRef9(null);
11221
- const gridRef = useRef9(null);
11222
- const panStateRef = useRef9(null);
11223
- const resourceColumnResizeRef = useRef9(null);
11224
- const conflictNavigationIndexRef = useRef9(/* @__PURE__ */ new Map());
11225
- const resourceNavigationIndexRef = useRef9(/* @__PURE__ */ new Map());
11226
- const conflictHighlightTimeoutRef = useRef9(null);
11227
- const [isCreatingResource, setIsCreatingResource] = useState9(false);
11228
- const [creatingResourceGroupType, setCreatingResourceGroupType] = useState9(null);
11229
- const [activeConflictItemId, setActiveConflictItemId] = useState9(null);
11230
- const [resourceColumnHasRightShadow, setResourceColumnHasRightShadow] = useState9(false);
11231
- const [resourceColumnWidthOverrides, setResourceColumnWidthOverrides] = useState9(() => normalizeResourceColumnWidthMap(resourceTableColumnWidths));
11477
+ const scrollContainerRef = useRef10(null);
11478
+ const gridRef = useRef10(null);
11479
+ const panStateRef = useRef10(null);
11480
+ const resourceColumnResizeRef = useRef10(null);
11481
+ const conflictNavigationIndexRef = useRef10(/* @__PURE__ */ new Map());
11482
+ const resourceNavigationIndexRef = useRef10(/* @__PURE__ */ new Map());
11483
+ const conflictHighlightTimeoutRef = useRef10(null);
11484
+ const [isCreatingResource, setIsCreatingResource] = useState10(false);
11485
+ const [creatingResourceGroupType, setCreatingResourceGroupType] = useState10(null);
11486
+ const [activeConflictItemId, setActiveConflictItemId] = useState10(null);
11487
+ const [resourceColumnHasRightShadow, setResourceColumnHasRightShadow] = useState10(false);
11488
+ const [resourceColumnWidthOverrides, setResourceColumnWidthOverrides] = useState10(() => normalizeResourceColumnWidthMap(resourceTableColumnWidths));
11232
11489
  const validItems = useMemo10(() => collectValidItems(resources), [resources]);
11233
11490
  const dateRange = useMemo10(() => getMultiMonthDays(validItems), [validItems]);
11234
11491
  const monthStart = useMemo10(() => {
@@ -11240,7 +11497,7 @@ function ResourceTimelineChart({
11240
11497
  [customDays, isWeekend3]
11241
11498
  );
11242
11499
  const gridWidth = useMemo10(() => Math.round(dateRange.length * dayWidth), [dateRange.length, dayWidth]);
11243
- useEffect8(() => {
11500
+ useEffect9(() => {
11244
11501
  setResourceColumnWidthOverrides(normalizeResourceColumnWidthMap(resourceTableColumnWidths));
11245
11502
  }, [resourceTableColumnWidths]);
11246
11503
  const resolvedResourceColumnWidths = useMemo10(
@@ -11296,7 +11553,7 @@ function ResourceTimelineChart({
11296
11553
  );
11297
11554
  const canAddResource = enableAddResource && Boolean(onAddResource);
11298
11555
  const resourceAddRowHeight = laneHeight + DEFAULT_RESOURCE_ROW_GAP;
11299
- const [collapsedResourceGroups, setCollapsedResourceGroups] = useState9(/* @__PURE__ */ new Set());
11556
+ const [collapsedResourceGroups, setCollapsedResourceGroups] = useState10(/* @__PURE__ */ new Set());
11300
11557
  const updateResourceColumnWidth = useCallback8((columnId, width) => {
11301
11558
  setResourceColumnWidthOverrides((prev) => {
11302
11559
  if (prev[columnId] === width) {
@@ -11307,7 +11564,7 @@ function ResourceTimelineChart({
11307
11564
  return next;
11308
11565
  });
11309
11566
  }, [onResourceTableColumnWidthsChange]);
11310
- useEffect8(() => {
11567
+ useEffect9(() => {
11311
11568
  const handleMouseMove = (event) => {
11312
11569
  const resizeState = resourceColumnResizeRef.current;
11313
11570
  if (!resizeState) {
@@ -11576,7 +11833,7 @@ function ResourceTimelineChart({
11576
11833
  weekendPredicate,
11577
11834
  onResourceItemMove
11578
11835
  });
11579
- useEffect8(() => {
11836
+ useEffect9(() => {
11580
11837
  const container = scrollContainerRef.current;
11581
11838
  if (!container) return;
11582
11839
  const updateShadow = () => {
@@ -11616,7 +11873,7 @@ function ResourceTimelineChart({
11616
11873
  container.style.cursor = "grabbing";
11617
11874
  event.preventDefault();
11618
11875
  }, []);
11619
- useEffect8(() => {
11876
+ useEffect9(() => {
11620
11877
  const handlePanMove = (event) => {
11621
11878
  const pan = panStateRef.current;
11622
11879
  const container = scrollContainerRef.current;
@@ -11645,7 +11902,7 @@ function ResourceTimelineChart({
11645
11902
  window.removeEventListener("mouseup", handlePanEnd);
11646
11903
  };
11647
11904
  }, [allowVerticalPan]);
11648
- useEffect8(() => {
11905
+ useEffect9(() => {
11649
11906
  const nextNavigation = /* @__PURE__ */ new Map();
11650
11907
  for (const [resourceId, resourceItems] of itemsByResourceId.entries()) {
11651
11908
  if (resourceItems.length === 0) {
@@ -11655,7 +11912,7 @@ function ResourceTimelineChart({
11655
11912
  }
11656
11913
  resourceNavigationIndexRef.current = nextNavigation;
11657
11914
  }, [itemsByResourceId]);
11658
- useEffect8(() => {
11915
+ useEffect9(() => {
11659
11916
  return () => {
11660
11917
  if (conflictHighlightTimeoutRef.current) {
11661
11918
  window.clearTimeout(conflictHighlightTimeoutRef.current);
@@ -12075,7 +12332,7 @@ function ResourceTimelineChart({
12075
12332
  }
12076
12333
 
12077
12334
  // src/components/TableMatrix/TableMatrix.tsx
12078
- import { useLayoutEffect, useMemo as useMemo11, useRef as useRef10, useState as useState10 } from "react";
12335
+ import { useLayoutEffect, useMemo as useMemo11, useRef as useRef11, useState as useState11 } from "react";
12079
12336
  import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
12080
12337
  var AUTO_COLUMN_MIN_WIDTH = 72;
12081
12338
  var AUTO_COLUMN_MAX_WIDTH = 180;
@@ -12142,8 +12399,8 @@ function TableMatrix({
12142
12399
  filterMode = "highlight",
12143
12400
  visibleRowIndices
12144
12401
  }) {
12145
- const measureRef = useRef10(null);
12146
- const [measuredAutoWidths, setMeasuredAutoWidths] = useState10([]);
12402
+ const measureRef = useRef11(null);
12403
+ const [measuredAutoWidths, setMeasuredAutoWidths] = useState11([]);
12147
12404
  const hasAutoWidthColumns = useMemo11(
12148
12405
  () => columns.some((column) => typeof column.width !== "number"),
12149
12406
  [columns]
@@ -12443,7 +12700,7 @@ function TableMatrix({
12443
12700
  }
12444
12701
 
12445
12702
  // src/components/PlanFactMatrix/PlanFactMatrix.tsx
12446
- import React16, { useCallback as useCallback9, useEffect as useEffect9, useMemo as useMemo12, useRef as useRef11, useState as useState11 } from "react";
12703
+ import React16, { useCallback as useCallback9, useEffect as useEffect10, useMemo as useMemo12, useRef as useRef12, useState as useState12 } from "react";
12447
12704
  import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
12448
12705
  var DAY_MS5 = 24 * 60 * 60 * 1e3;
12449
12706
  function joinClasses2(...values) {
@@ -12535,9 +12792,9 @@ function PlanFactCellEditor({
12535
12792
  onCommitRange,
12536
12793
  onCancel
12537
12794
  }) {
12538
- const [draft, setDraft] = useState11(startValue ?? (value === void 0 ? "" : String(value)));
12539
- const inputRef = useRef11(null);
12540
- useEffect9(() => {
12795
+ const [draft, setDraft] = useState12(startValue ?? (value === void 0 ? "" : String(value)));
12796
+ const inputRef = useRef12(null);
12797
+ useEffect10(() => {
12541
12798
  inputRef.current?.focus();
12542
12799
  if (startValue === void 0) {
12543
12800
  inputRef.current?.select();
@@ -12890,18 +13147,18 @@ function PlanFactMatrix({
12890
13147
  todayDateIndex,
12891
13148
  isCustomWeekend
12892
13149
  }) {
12893
- const [activeCell, setActiveCell] = useState11(null);
12894
- const [editingCell, setEditingCell] = useState11(null);
12895
- const [selectedRange, setSelectedRange] = useState11(null);
12896
- const [fillRange, setFillRange] = useState11(null);
12897
- const [overflowTooltip, setOverflowTooltip] = useState11(null);
12898
- const isSelectingRef = useRef11(false);
12899
- const isFillDraggingRef = useRef11(false);
12900
- const didDragSelectRef = useRef11(false);
12901
- const pendingHoverCellRef = useRef11(null);
12902
- const hoverFrameRef = useRef11(null);
12903
- const rootRef = useRef11(null);
12904
- const bodyRef = useRef11(null);
13150
+ const [activeCell, setActiveCell] = useState12(null);
13151
+ const [editingCell, setEditingCell] = useState12(null);
13152
+ const [selectedRange, setSelectedRange] = useState12(null);
13153
+ const [fillRange, setFillRange] = useState12(null);
13154
+ const [overflowTooltip, setOverflowTooltip] = useState12(null);
13155
+ const isSelectingRef = useRef12(false);
13156
+ const isFillDraggingRef = useRef12(false);
13157
+ const didDragSelectRef = useRef12(false);
13158
+ const pendingHoverCellRef = useRef12(null);
13159
+ const hoverFrameRef = useRef12(null);
13160
+ const rootRef = useRef12(null);
13161
+ const bodyRef = useRef12(null);
12905
13162
  const totalWidth = dateRange.length * dayWidth;
12906
13163
  const subrowHeight = rowHeight / 2;
12907
13164
  const renderedRowIndices = useMemo12(
@@ -13307,7 +13564,7 @@ function PlanFactMatrix({
13307
13564
  onTaskSelect?.(nextCell.taskId);
13308
13565
  focusCell(selectedRange?.anchor ?? cell);
13309
13566
  }, [dateRange.length, focusCell, onTaskSelect, parentTaskIds, selectedRange, tasks]);
13310
- useEffect9(() => {
13567
+ useEffect10(() => {
13311
13568
  const endSelection = () => {
13312
13569
  let pendingFillRange = null;
13313
13570
  if (hoverFrameRef.current !== null) {
@@ -13330,12 +13587,12 @@ function PlanFactMatrix({
13330
13587
  window.removeEventListener("mouseup", endSelection);
13331
13588
  };
13332
13589
  }, [applyFillRange, flushPendingHoverCell]);
13333
- useEffect9(() => () => {
13590
+ useEffect10(() => () => {
13334
13591
  if (hoverFrameRef.current !== null) {
13335
13592
  window.cancelAnimationFrame(hoverFrameRef.current);
13336
13593
  }
13337
13594
  }, []);
13338
- useEffect9(() => {
13595
+ useEffect10(() => {
13339
13596
  const handleKeyDown = (event) => {
13340
13597
  if (event.key !== "Escape") return;
13341
13598
  clearSelection();
@@ -14111,6 +14368,7 @@ function TaskGanttChartInner(props, ref) {
14111
14368
  skeletonStartJitterDays = 0,
14112
14369
  showTaskDateLabels = true,
14113
14370
  showTaskNames = true,
14371
+ showCompositeSegments = true,
14114
14372
  timelineMarkers,
14115
14373
  additionalColumns,
14116
14374
  hiddenTaskListColumns,
@@ -14138,31 +14396,31 @@ function TaskGanttChartInner(props, ref) {
14138
14396
  const onMatrixCellClick = isTableMatrixMode ? props.onMatrixCellClick : void 0;
14139
14397
  const matrixDateOverlay = isTableMatrixMode ? props.matrixDateOverlay : void 0;
14140
14398
  const onPlanFactCellCommit = isPlanFactMode ? props.onPlanFactCellCommit : void 0;
14141
- const containerRef = useRef12(null);
14142
- const scrollContainerRef = useRef12(null);
14143
- const scrollContentRef = useRef12(null);
14144
- const clearSelectedTaskTimeoutRef = useRef12(null);
14145
- const hasAutoScrolledToTodayRef = useRef12(false);
14146
- const previewPositionStoreRef = useRef12(null);
14147
- const renderedTaskIdsRef = useRef12(/* @__PURE__ */ new Set());
14399
+ const containerRef = useRef13(null);
14400
+ const scrollContainerRef = useRef13(null);
14401
+ const scrollContentRef = useRef13(null);
14402
+ const clearSelectedTaskTimeoutRef = useRef13(null);
14403
+ const hasAutoScrolledToTodayRef = useRef13(false);
14404
+ const previewPositionStoreRef = useRef13(null);
14405
+ const renderedTaskIdsRef = useRef13(/* @__PURE__ */ new Set());
14148
14406
  if (previewPositionStoreRef.current === null) {
14149
14407
  previewPositionStoreRef.current = createTaskPreviewPositionStore();
14150
14408
  }
14151
14409
  const previewPositionStore = previewPositionStoreRef.current;
14152
- const [selectedTaskId, setSelectedTaskId] = useState12(null);
14153
- const [taskListHasRightShadow, setTaskListHasRightShadow] = useState12(false);
14154
- const [internalTaskDateChangeMode, setInternalTaskDateChangeMode] = useState12("preserve-duration");
14155
- const [scrollViewport, setScrollViewport] = useState12(() => ({
14410
+ const [selectedTaskId, setSelectedTaskId] = useState13(null);
14411
+ const [taskListHasRightShadow, setTaskListHasRightShadow] = useState13(false);
14412
+ const [internalTaskDateChangeMode, setInternalTaskDateChangeMode] = useState13("preserve-duration");
14413
+ const [scrollViewport, setScrollViewport] = useState13(() => ({
14156
14414
  scrollTop: 0,
14157
14415
  viewportHeight: getInitialScrollViewportHeight(containerHeight, headerHeight + 1)
14158
14416
  }));
14159
- const [forceFullRenderForPrint, setForceFullRenderForPrint] = useState12(false);
14160
- const [planFactDateWindow, setPlanFactDateWindow] = useState12({ start: 0, end: 0 });
14161
- const [selectedChip, setSelectedChip] = useState12(null);
14162
- const [dependencyCreationDrag, setDependencyCreationDrag] = useState12(null);
14163
- const dependencyCreationRef = useRef12(null);
14164
- const [dependencyCreationError, setDependencyCreationError] = useState12(null);
14165
- const [dependencyLineMenu, setDependencyLineMenu] = useState12(null);
14417
+ const [forceFullRenderForPrint, setForceFullRenderForPrint] = useState13(false);
14418
+ const [planFactDateWindow, setPlanFactDateWindow] = useState13({ start: 0, end: 0 });
14419
+ const [selectedChip, setSelectedChip] = useState13(null);
14420
+ const [dependencyCreationDrag, setDependencyCreationDrag] = useState13(null);
14421
+ const dependencyCreationRef = useRef13(null);
14422
+ const [dependencyCreationError, setDependencyCreationError] = useState13(null);
14423
+ const [dependencyLineMenu, setDependencyLineMenu] = useState13(null);
14166
14424
  const getDependencyPortPoint = useCallback10((element, chartRect) => {
14167
14425
  const rect = element.getBoundingClientRect();
14168
14426
  const side = element.getAttribute("data-port-side");
@@ -14196,7 +14454,7 @@ function TaskGanttChartInner(props, ref) {
14196
14454
  dependencyCreationRef.current = nextDrag;
14197
14455
  setDependencyCreationDrag(nextDrag);
14198
14456
  }, [disableDependencyEditing, getDependencyPortPoint]);
14199
- useEffect10(() => {
14457
+ useEffect11(() => {
14200
14458
  if (!dependencyCreationDrag) return;
14201
14459
  const handlePointerMove = (event) => {
14202
14460
  const activeDrag = dependencyCreationRef.current;
@@ -14280,12 +14538,12 @@ function TaskGanttChartInner(props, ref) {
14280
14538
  document.removeEventListener("keydown", handleKeyDown);
14281
14539
  };
14282
14540
  }, [businessDays, customDays, dependencyCreationDrag !== null, getDependencyPortPoint, getVisualDependencyType, isWeekend3, onTasksChange, tasks]);
14283
- useEffect10(() => {
14541
+ useEffect11(() => {
14284
14542
  if (!dependencyCreationError) return;
14285
14543
  const timeout = window.setTimeout(() => setDependencyCreationError(null), 3e3);
14286
14544
  return () => window.clearTimeout(timeout);
14287
14545
  }, [dependencyCreationError]);
14288
- useEffect10(() => {
14546
+ useEffect11(() => {
14289
14547
  if (!dependencyLineMenu) return;
14290
14548
  const closeMenu = (event) => {
14291
14549
  const target = event.target;
@@ -14323,26 +14581,55 @@ function TaskGanttChartInner(props, ref) {
14323
14581
  setSelectedChip(null);
14324
14582
  setDependencyLineMenu(null);
14325
14583
  }, [dependencyLineMenu, onTasksChange, tasks]);
14326
- const [activeTimelineTooltip, setActiveTimelineTooltip] = useState12(null);
14327
- const [internalCollapsedParentIds, setInternalCollapsedParentIds] = useState12(/* @__PURE__ */ new Set());
14328
- const collapsedParentIds = externalCollapsedParentIds ?? internalCollapsedParentIds;
14329
- const [editingTaskId, setEditingTaskId] = useState12(null);
14584
+ const [activeTimelineTooltip, setActiveTimelineTooltip] = useState13(null);
14585
+ const [internalCollapsedParentIds, setInternalCollapsedParentIds] = useState13(/* @__PURE__ */ new Set());
14586
+ const [expandedCompositeIds, setExpandedCompositeIds] = useState13(/* @__PURE__ */ new Set());
14587
+ const [editingTaskId, setEditingTaskId] = useState13(null);
14330
14588
  const taskDateChangeMode = externalTaskDateChangeMode ?? internalTaskDateChangeMode;
14331
14589
  const handleTaskDateChangeMode = externalOnTaskDateChangeModeChange ?? setInternalTaskDateChangeMode;
14332
14590
  const resolvedRowContentLines = isPlanFactMode ? Math.max(2, Math.floor(rowContentLines)) : Math.max(1, Math.floor(rowContentLines));
14333
14591
  const shouldFillParentRowsInTaskList = fillParentRowsInTaskList ?? isPlanFactMode;
14334
14592
  const effectiveRowHeight = useMemo13(
14335
- () => Math.max(rowHeight, 10 + resolvedRowContentLines * 18),
14336
- [resolvedRowContentLines, rowHeight]
14593
+ () => rowContentLines === 0 && !isPlanFactMode ? rowHeight : Math.max(rowHeight, 10 + resolvedRowContentLines * 18),
14594
+ [isPlanFactMode, resolvedRowContentLines, rowContentLines, rowHeight]
14337
14595
  );
14338
- const normalizedTasks = useMemo13(() => normalizeHierarchyTasks(tasks), [tasks]);
14596
+ const normalizedTasks = useMemo13(() => {
14597
+ const ordered = normalizeHierarchyTasks(tasks);
14598
+ const ranges = /* @__PURE__ */ new Map();
14599
+ for (const task of ordered) {
14600
+ if (!task.parentId) continue;
14601
+ const start = parseUTCDate(task.startDate).getTime();
14602
+ const end = parseUTCDate(task.endDate).getTime();
14603
+ const range = ranges.get(task.parentId);
14604
+ ranges.set(task.parentId, {
14605
+ start: Math.min(range?.start ?? start, start),
14606
+ end: Math.max(range?.end ?? end, end)
14607
+ });
14608
+ }
14609
+ return ordered.map((task) => {
14610
+ const range = task.composite ? ranges.get(task.id) : void 0;
14611
+ return range ? {
14612
+ ...task,
14613
+ startDate: new Date(range.start).toISOString().slice(0, 10),
14614
+ endDate: new Date(range.end).toISOString().slice(0, 10)
14615
+ } : task;
14616
+ });
14617
+ }, [tasks]);
14618
+ const collapsedParentIds = useMemo13(() => {
14619
+ if (externalCollapsedParentIds) return externalCollapsedParentIds;
14620
+ const ids = new Set(internalCollapsedParentIds);
14621
+ for (const task of normalizedTasks) {
14622
+ if (task.composite && !expandedCompositeIds.has(task.id)) ids.add(task.id);
14623
+ }
14624
+ return ids;
14625
+ }, [externalCollapsedParentIds, internalCollapsedParentIds, normalizedTasks, expandedCompositeIds]);
14339
14626
  const controlledScheduleIdentity = useMemo13(
14340
14627
  () => buildControlledScheduleIdentity(normalizedTasks),
14341
14628
  [normalizedTasks]
14342
14629
  );
14343
- const previousControlledScheduleIdentityRef = useRef12(null);
14630
+ const previousControlledScheduleIdentityRef = useRef13(null);
14344
14631
  const controlledScheduleChanged = previousControlledScheduleIdentityRef.current !== null && previousControlledScheduleIdentityRef.current !== controlledScheduleIdentity;
14345
- const [previewTasksById, setPreviewTasksById] = useState12(/* @__PURE__ */ new Map());
14632
+ const [previewTasksById, setPreviewTasksById] = useState13(/* @__PURE__ */ new Map());
14346
14633
  const previewNormalizedTasks = useMemo13(() => {
14347
14634
  if (controlledScheduleChanged || previewTasksById.size === 0) return normalizedTasks;
14348
14635
  return normalizedTasks.map((task) => previewTasksById.get(task.id) ?? task);
@@ -14382,8 +14669,8 @@ function TaskGanttChartInner(props, ref) {
14382
14669
  },
14383
14670
  [dateRangeTasks, explicitDateRange, isPlanFactMode]
14384
14671
  );
14385
- const [validationResult, setValidationResult] = useState12(null);
14386
- const [cascadeOverrides, setCascadeOverrides] = useState12(/* @__PURE__ */ new Map());
14672
+ const [validationResult, setValidationResult] = useState13(null);
14673
+ const [cascadeOverrides, setCascadeOverrides] = useState13(/* @__PURE__ */ new Map());
14387
14674
  const gridWidth = useMemo13(
14388
14675
  () => Math.round(dateRange.length * dayWidth),
14389
14676
  [dateRange.length, dayWidth]
@@ -14415,6 +14702,23 @@ function TaskGanttChartInner(props, ref) {
14415
14702
  }
14416
14703
  return tasks2;
14417
14704
  }, [normalizedTasks, collapsedParentIds, filterMode, taskFilter, criticalPathMode, criticalTaskIds]);
14705
+ const compositeRowLayout = useMemo13(() => {
14706
+ const compositeIds = new Set(normalizedTasks.filter((task) => task.composite).map((task) => task.id));
14707
+ const tops = [];
14708
+ const heights = [];
14709
+ let top = 0;
14710
+ for (const task of visibleTasks) {
14711
+ tops.push(top);
14712
+ const height = task.parentId && compositeIds.has(task.parentId) ? Math.min(effectiveRowHeight, 22) : effectiveRowHeight;
14713
+ heights.push(height);
14714
+ top += height;
14715
+ }
14716
+ return { tops, heights, totalHeight: top };
14717
+ }, [normalizedTasks, visibleTasks, effectiveRowHeight]);
14718
+ const compositeParentsById = useMemo13(
14719
+ () => new Map(normalizedTasks.filter((task) => task.composite).map((task) => [task.id, task])),
14720
+ [normalizedTasks]
14721
+ );
14418
14722
  const matchedTaskIds = useMemo13(() => {
14419
14723
  if (!taskFilter) return /* @__PURE__ */ new Set();
14420
14724
  return new Set(visibleTasks.filter(taskFilter).map((task) => task.id));
@@ -14436,8 +14740,8 @@ function TaskGanttChartInner(props, ref) {
14436
14740
  return mergedHighlightedTaskIds;
14437
14741
  }, [filterMode, highlightedTaskIds, matchedTaskIds, criticalPathMode, criticalTaskIds]);
14438
14742
  const totalGridHeight = useMemo13(
14439
- () => (visibleTasks.length + Math.max(0, Math.floor(skeletonRowCount))) * effectiveRowHeight,
14440
- [effectiveRowHeight, skeletonRowCount, visibleTasks.length]
14743
+ () => compositeRowLayout.totalHeight + Math.max(0, Math.floor(skeletonRowCount)) * effectiveRowHeight,
14744
+ [compositeRowLayout.totalHeight, effectiveRowHeight, skeletonRowCount]
14441
14745
  );
14442
14746
  const timelineHeaderHeight = headerHeight + 1;
14443
14747
  const tableBodyMinHeight = useMemo13(() => {
@@ -14475,7 +14779,7 @@ function TaskGanttChartInner(props, ref) {
14475
14779
  return markerDate >= rangeStartMs && markerDate <= rangeEndMs;
14476
14780
  });
14477
14781
  }, [dateRange, isTableMatrixMode, timelineMarkers]);
14478
- useEffect10(() => {
14782
+ useEffect11(() => {
14479
14783
  if (isTableMatrixMode) return;
14480
14784
  if (hasAutoScrolledToTodayRef.current) return;
14481
14785
  const container = scrollContainerRef.current;
@@ -14489,7 +14793,7 @@ function TaskGanttChartInner(props, ref) {
14489
14793
  container.scrollLeft = Math.max(0, scrollLeft);
14490
14794
  hasAutoScrolledToTodayRef.current = true;
14491
14795
  }, [dateRange, dayWidth, isTableMatrixMode]);
14492
- useEffect10(() => {
14796
+ useEffect11(() => {
14493
14797
  const container = scrollContainerRef.current;
14494
14798
  if (!container) return;
14495
14799
  let frameId = null;
@@ -14564,7 +14868,7 @@ function TaskGanttChartInner(props, ref) {
14564
14868
  const rowIndex = visibleTasks.findIndex((visibleTask) => visibleTask.id === taskId);
14565
14869
  if (rowIndex === -1) return;
14566
14870
  const paddedRowIndex = Math.max(0, rowIndex - SCROLL_TO_ROW_CONTEXT_ROWS);
14567
- container2.scrollTo({ top: Math.max(0, effectiveRowHeight * paddedRowIndex), behavior: "smooth" });
14871
+ container2.scrollTo({ top: Math.max(0, compositeRowLayout.tops[paddedRowIndex] ?? 0), behavior: "smooth" });
14568
14872
  setSelectedTaskId(taskId);
14569
14873
  return;
14570
14874
  }
@@ -14583,7 +14887,7 @@ function TaskGanttChartInner(props, ref) {
14583
14887
  const taskOffset = taskIndex * dayWidth;
14584
14888
  const scrollLeft = Math.round(taskOffset - dayWidth * 2);
14585
14889
  container.scrollTo({ left: Math.max(0, scrollLeft), behavior: "smooth" });
14586
- }, [dateRange, dayWidth, effectiveRowHeight, isTableMatrixMode, tasks, visibleTasks]);
14890
+ }, [dateRange, dayWidth, effectiveRowHeight, compositeRowLayout, isTableMatrixMode, tasks, visibleTasks]);
14587
14891
  const scrollToRow = useCallback10((taskId, options = {}) => {
14588
14892
  const container = scrollContainerRef.current;
14589
14893
  if (!container) return;
@@ -14592,7 +14896,7 @@ function TaskGanttChartInner(props, ref) {
14592
14896
  const rowIndex = visibleTasks.findIndex((visibleTask) => visibleTask.id === task.id);
14593
14897
  if (rowIndex === -1) return;
14594
14898
  const paddedRowIndex = Math.max(0, rowIndex - SCROLL_TO_ROW_CONTEXT_ROWS);
14595
- const scrollTop = Math.max(0, effectiveRowHeight * paddedRowIndex);
14899
+ const scrollTop = Math.max(0, compositeRowLayout.tops[paddedRowIndex] ?? 0);
14596
14900
  const {
14597
14901
  select = true,
14598
14902
  behavior = "smooth",
@@ -14612,10 +14916,10 @@ function TaskGanttChartInner(props, ref) {
14612
14916
  }
14613
14917
  }
14614
14918
  container.scrollTo({ top: scrollTop, behavior });
14615
- }, [effectiveRowHeight, tasks, visibleTasks]);
14616
- const [dragGuideLines, setDragGuideLines] = useState12(null);
14617
- const [draggedTaskOverride, setDraggedTaskOverride] = useState12(null);
14618
- useEffect10(() => {
14919
+ }, [effectiveRowHeight, compositeRowLayout, tasks, visibleTasks]);
14920
+ const [dragGuideLines, setDragGuideLines] = useState13(null);
14921
+ const [draggedTaskOverride, setDraggedTaskOverride] = useState13(null);
14922
+ useEffect11(() => {
14619
14923
  const previousIdentity = previousControlledScheduleIdentityRef.current;
14620
14924
  previousControlledScheduleIdentityRef.current = controlledScheduleIdentity;
14621
14925
  if (previousIdentity === null || previousIdentity === controlledScheduleIdentity) {
@@ -14627,21 +14931,21 @@ function TaskGanttChartInner(props, ref) {
14627
14931
  setDraggedTaskOverride((current) => current === null ? current : null);
14628
14932
  setDragGuideLines((current) => current === null ? current : null);
14629
14933
  }, [controlledScheduleIdentity, previewPositionStore]);
14630
- useEffect10(() => {
14934
+ useEffect11(() => {
14631
14935
  const result = validateDependencies(tasks);
14632
14936
  setValidationResult(result);
14633
14937
  onValidateDependencies?.(result);
14634
14938
  }, [tasks, onValidateDependencies]);
14635
- useEffect10(() => () => {
14939
+ useEffect11(() => () => {
14636
14940
  if (clearSelectedTaskTimeoutRef.current !== null) {
14637
14941
  window.clearTimeout(clearSelectedTaskTimeoutRef.current);
14638
14942
  }
14639
14943
  previewPositionStore.clear();
14640
14944
  }, [previewPositionStore]);
14641
14945
  const handleTaskChange = useCallback10((updatedTasks, durationEdit) => {
14642
- const updatedTask = updatedTasks[0];
14946
+ let updatedTask = updatedTasks[0];
14643
14947
  if (!updatedTask) return;
14644
- const originalTask = tasks.find((t) => t.id === updatedTask.id);
14948
+ const originalTask = normalizedTasks.find((t) => t.id === updatedTask.id);
14645
14949
  if (!originalTask) {
14646
14950
  onTasksChange?.(updatedTasks);
14647
14951
  if (editingTaskId === updatedTask.id) {
@@ -14654,6 +14958,8 @@ function TaskGanttChartInner(props, ref) {
14654
14958
  const newStart = new Date(updatedTask.startDate);
14655
14959
  const newEnd = new Date(updatedTask.endDate);
14656
14960
  const datesChanged = origStart.getTime() !== newStart.getTime() || origEnd.getTime() !== newEnd.getTime();
14961
+ const isCompositeDetail = datesChanged && tasks.some((task) => task.id === updatedTask.parentId && task.composite);
14962
+ if (isCompositeDetail) updatedTask = { ...updatedTask, synced: false };
14657
14963
  if (!datesChanged) {
14658
14964
  if (updatedTasks.length > 1) {
14659
14965
  onTasksChange?.(updatedTasks);
@@ -14715,7 +15021,7 @@ function TaskGanttChartInner(props, ref) {
14715
15021
  }
14716
15022
  return;
14717
15023
  }
14718
- const movedParentCascade = disableConstraints ? [updatedTask] : universalCascade(updatedTask, newStart, newEnd, tasks, businessDays, isCustomWeekend);
15024
+ const movedParentCascade = disableConstraints && !originalTask.composite ? [updatedTask] : universalCascade(updatedTask, newStart, newEnd, normalizedTasks, businessDays, isCustomWeekend);
14719
15025
  if (onScheduleIntent) {
14720
15026
  onScheduleIntent({
14721
15027
  type: "move_task",
@@ -14739,7 +15045,8 @@ function TaskGanttChartInner(props, ref) {
14739
15045
  taskId: updatedTask.id,
14740
15046
  duration: durationEdit.duration,
14741
15047
  anchor: durationEdit.anchor,
14742
- taskType: updatedTask.type ?? "task"
15048
+ taskType: updatedTask.type ?? "task",
15049
+ ...isCompositeDetail ? { localOverride: true } : {}
14743
15050
  });
14744
15051
  } else {
14745
15052
  onTasksChange?.(cascadedTasks);
@@ -14751,7 +15058,8 @@ function TaskGanttChartInner(props, ref) {
14751
15058
  onScheduleIntent({
14752
15059
  type: "move_task",
14753
15060
  taskId: updatedTask.id,
14754
- startDate: newStart.toISOString().split("T")[0]
15061
+ startDate: newStart.toISOString().split("T")[0],
15062
+ ...isCompositeDetail ? { localOverride: true } : {}
14755
15063
  });
14756
15064
  } else {
14757
15065
  const anchor = startChanged ? "end" : "start";
@@ -14759,13 +15067,14 @@ function TaskGanttChartInner(props, ref) {
14759
15067
  type: "resize_task",
14760
15068
  taskId: updatedTask.id,
14761
15069
  anchor,
14762
- date: (startChanged ? newStart : newEnd).toISOString().split("T")[0]
15070
+ date: (startChanged ? newStart : newEnd).toISOString().split("T")[0],
15071
+ ...isCompositeDetail ? { localOverride: true } : {}
14763
15072
  });
14764
15073
  }
14765
15074
  } else {
14766
15075
  onTasksChange?.(cascadedTasks);
14767
15076
  }
14768
- }, [tasks, onTasksChange, onScheduleIntent, disableConstraints, editingTaskId, businessDays, isCustomWeekend, onSubtreeScaleResult]);
15077
+ }, [tasks, normalizedTasks, onTasksChange, onScheduleIntent, disableConstraints, editingTaskId, businessDays, isCustomWeekend, onSubtreeScaleResult]);
14769
15078
  const handleTaskDurationChange = useCallback10((task, duration) => {
14770
15079
  const updatedTask = {
14771
15080
  ...task,
@@ -14879,12 +15188,25 @@ function TaskGanttChartInner(props, ref) {
14879
15188
  if (scrollViewport.viewportHeight <= 0) {
14880
15189
  return Array.from({ length: totalTasks }, (_, index) => index);
14881
15190
  }
14882
- const viewportRows = Math.max(1, Math.ceil(scrollViewport.viewportHeight / effectiveRowHeight));
14883
- const rangeStart = Math.max(0, Math.floor(scrollViewport.scrollTop / effectiveRowHeight) - TASK_ROW_OVERSCAN);
14884
- const rangeEnd = Math.min(
14885
- totalTasks - 1,
14886
- rangeStart + viewportRows + TASK_ROW_OVERSCAN * 2 - 1
14887
- );
15191
+ let low = 0;
15192
+ let high = totalTasks;
15193
+ while (low < high) {
15194
+ const middle = low + high >>> 1;
15195
+ if (compositeRowLayout.tops[middle] + compositeRowLayout.heights[middle] < scrollViewport.scrollTop) low = middle + 1;
15196
+ else high = middle;
15197
+ }
15198
+ const first = Math.min(low, totalTasks - 1);
15199
+ const viewportBottom = scrollViewport.scrollTop + scrollViewport.viewportHeight;
15200
+ low = first;
15201
+ high = totalTasks;
15202
+ while (low < high) {
15203
+ const middle = low + high >>> 1;
15204
+ if (compositeRowLayout.tops[middle] < viewportBottom) low = middle + 1;
15205
+ else high = middle;
15206
+ }
15207
+ const last = Math.min(low, totalTasks - 1);
15208
+ const rangeStart = Math.max(0, first - TASK_ROW_OVERSCAN);
15209
+ const rangeEnd = Math.min(totalTasks - 1, last + TASK_ROW_OVERSCAN);
14888
15210
  const indices = /* @__PURE__ */ new Set();
14889
15211
  for (let index = rangeStart; index <= rangeEnd; index += 1) {
14890
15212
  indices.add(index);
@@ -14896,7 +15218,7 @@ function TaskGanttChartInner(props, ref) {
14896
15218
  }
14897
15219
  }
14898
15220
  return Array.from(indices).sort((left, right) => left - right);
14899
- }, [effectiveRowHeight, forceFullRenderForPrint, forcedRenderedTaskIds, scrollViewport, visibleTaskIndexMap, visibleTasks.length]);
15221
+ }, [compositeRowLayout, forceFullRenderForPrint, forcedRenderedTaskIds, scrollViewport, visibleTaskIndexMap, visibleTasks.length]);
14900
15222
  const visiblePlanFactDateIndices = useMemo13(() => {
14901
15223
  if (forceFullRenderForPrint) {
14902
15224
  return void 0;
@@ -14933,7 +15255,7 @@ function TaskGanttChartInner(props, ref) {
14933
15255
  const handleTaskSelect = useCallback10((taskId) => {
14934
15256
  setSelectedTaskId(taskId);
14935
15257
  }, []);
14936
- const hoveredRowElementsRef = useRef12([]);
15258
+ const hoveredRowElementsRef = useRef13([]);
14937
15259
  const clearHoveredRows = useCallback10(() => {
14938
15260
  for (const element of hoveredRowElementsRef.current) {
14939
15261
  element.classList.remove("gantt-tl-row-hovered", "gantt-tr-row-hovered", "gantt-mx-row-hovered", "gantt-pf-row-hovered");
@@ -14973,7 +15295,16 @@ function TaskGanttChartInner(props, ref) {
14973
15295
  }
14974
15296
  applyHoveredRows(taskId);
14975
15297
  }, [applyHoveredRows]);
14976
- const handleToggleCollapse = externalOnToggleCollapse ?? useCallback10((parentId) => {
15298
+ const handleToggleCollapse = externalOnToggleCollapse ?? ((parentId) => {
15299
+ if (normalizedTasks.some((task) => task.id === parentId && task.composite)) {
15300
+ setExpandedCompositeIds((prev) => {
15301
+ const next = new Set(prev);
15302
+ if (next.has(parentId)) next.delete(parentId);
15303
+ else next.add(parentId);
15304
+ return next;
15305
+ });
15306
+ return;
15307
+ }
14977
15308
  setInternalCollapsedParentIds((prev) => {
14978
15309
  const next = new Set(prev);
14979
15310
  if (next.has(parentId)) {
@@ -14983,7 +15314,14 @@ function TaskGanttChartInner(props, ref) {
14983
15314
  }
14984
15315
  return next;
14985
15316
  });
14986
- }, []);
15317
+ });
15318
+ const lastCompositeBarToggleRef = useRef13(null);
15319
+ const handleCompositeBarToggle = (taskId, source) => {
15320
+ const now = Date.now();
15321
+ if (source === "click" && lastCompositeBarToggleRef.current?.source === "release" && lastCompositeBarToggleRef.current.id === taskId && now - lastCompositeBarToggleRef.current.time < 150) return;
15322
+ lastCompositeBarToggleRef.current = { id: taskId, time: now, source };
15323
+ handleToggleCollapse(taskId);
15324
+ };
14987
15325
  const allParentIds = useMemo13(() => {
14988
15326
  const taskIds = new Set(normalizedTasks.map((task) => task.id));
14989
15327
  return new Set(normalizedTasks.map((task) => task.parentId).filter((parentId) => Boolean(parentId && taskIds.has(parentId))));
@@ -14991,11 +15329,13 @@ function TaskGanttChartInner(props, ref) {
14991
15329
  const handleCollapseAll = useCallback10(() => {
14992
15330
  if (externalCollapsedParentIds) return;
14993
15331
  setInternalCollapsedParentIds(allParentIds);
15332
+ setExpandedCompositeIds(/* @__PURE__ */ new Set());
14994
15333
  }, [allParentIds, externalCollapsedParentIds]);
14995
15334
  const handleExpandAll = useCallback10(() => {
14996
15335
  if (externalCollapsedParentIds) return;
14997
15336
  setInternalCollapsedParentIds(/* @__PURE__ */ new Set());
14998
- }, [externalCollapsedParentIds]);
15337
+ setExpandedCompositeIds(new Set(normalizedTasks.filter((task) => task.composite).map((task) => task.id)));
15338
+ }, [externalCollapsedParentIds, normalizedTasks]);
14999
15339
  const exportToPdf = useCallback10(async (options) => {
15000
15340
  const sourceContainer = containerRef.current;
15001
15341
  if (!sourceContainer || typeof window === "undefined" || typeof document === "undefined") {
@@ -15167,7 +15507,7 @@ function TaskGanttChartInner(props, ref) {
15167
15507
  onTasksChange?.(changedTasks);
15168
15508
  }
15169
15509
  }, [tasks, onTasksChange, onUngroupTask]);
15170
- const panStateRef = useRef12(null);
15510
+ const panStateRef = useRef13(null);
15171
15511
  const handlePanStart = useCallback10((e) => {
15172
15512
  if (e.button !== 0) return;
15173
15513
  const target = e.target;
@@ -15192,7 +15532,7 @@ function TaskGanttChartInner(props, ref) {
15192
15532
  container.style.cursor = "grabbing";
15193
15533
  e.preventDefault();
15194
15534
  }, []);
15195
- useEffect10(() => {
15535
+ useEffect11(() => {
15196
15536
  const flushPanMove = () => {
15197
15537
  const pan = panStateRef.current;
15198
15538
  if (!pan?.active) return;
@@ -15260,6 +15600,8 @@ function TaskGanttChartInner(props, ref) {
15260
15600
  {
15261
15601
  tasks: normalizedTasks,
15262
15602
  rowHeight: effectiveRowHeight,
15603
+ rowTops: compositeRowLayout.tops,
15604
+ rowHeights: compositeRowLayout.heights,
15263
15605
  headerHeight,
15264
15606
  taskListWidth,
15265
15607
  onTasksChange: handleTaskChange,
@@ -15454,6 +15796,8 @@ function TaskGanttChartInner(props, ref) {
15454
15796
  gridWidth: renderGridWidth,
15455
15797
  totalHeight: totalGridHeight,
15456
15798
  rowIndexByTaskId: visibleTaskIndexMap,
15799
+ rowTops: compositeRowLayout.tops,
15800
+ rowHeights: compositeRowLayout.heights,
15457
15801
  dragOverrides: dependencyOverrides,
15458
15802
  selectedDep: selectedChip,
15459
15803
  businessDays,
@@ -15540,10 +15884,10 @@ function TaskGanttChartInner(props, ref) {
15540
15884
  {
15541
15885
  style: {
15542
15886
  position: "absolute",
15543
- top: `${index * effectiveRowHeight}px`,
15887
+ top: `${compositeRowLayout.tops[index]}px`,
15544
15888
  left: 0,
15545
15889
  right: 0,
15546
- height: `${effectiveRowHeight}px`
15890
+ height: `${compositeRowLayout.heights[index]}px`
15547
15891
  },
15548
15892
  children: /* @__PURE__ */ jsx21(
15549
15893
  TaskRow_default,
@@ -15551,7 +15895,9 @@ function TaskGanttChartInner(props, ref) {
15551
15895
  task,
15552
15896
  monthStart,
15553
15897
  dayWidth,
15554
- rowHeight: effectiveRowHeight,
15898
+ rowHeight: compositeRowLayout.heights[index],
15899
+ compactDetail: compositeRowLayout.heights[index] < effectiveRowHeight,
15900
+ compositeParentColor: task.parentId ? compositeParentsById.get(task.parentId)?.color : void 0,
15555
15901
  onTasksChange: handleTaskChange,
15556
15902
  onDragStateChange: (state) => {
15557
15903
  if (state.isDragging) {
@@ -15588,7 +15934,10 @@ function TaskGanttChartInner(props, ref) {
15588
15934
  isDependencyDragActive: dependencyCreationDrag !== null,
15589
15935
  viewMode,
15590
15936
  showTaskDateLabels,
15591
- showTaskNames
15937
+ showTaskNames,
15938
+ showCompositeSegments,
15939
+ onCompositeToggle: task.composite ? (source) => handleCompositeBarToggle(task.id, source) : void 0,
15940
+ compositeExpanded: task.composite ? !collapsedParentIds.has(task.id) : void 0
15592
15941
  }
15593
15942
  )
15594
15943
  },
@@ -15601,6 +15950,7 @@ function TaskGanttChartInner(props, ref) {
15601
15950
  rowHeight: effectiveRowHeight,
15602
15951
  variant: "chart",
15603
15952
  startIndex: visibleTasks.length,
15953
+ startTop: compositeRowLayout.totalHeight,
15604
15954
  chartDayWidth: dayWidth,
15605
15955
  chartStartDayOffset: todayInRange ? Math.max(0, todayIndex + Math.floor(Number.isFinite(skeletonStartOffsetDays) ? skeletonStartOffsetDays : 0)) : 0,
15606
15956
  chartStartJitterDays: skeletonStartJitterDays