gantt-lib 0.135.9 → 0.136.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -3,7 +3,7 @@
3
3
  "use client";
4
4
 
5
5
  // src/components/GanttChart/GanttChart.tsx
6
- import { useMemo as useMemo13, useCallback as useCallback10, useRef as useRef12, useState as useState12, useEffect as useEffect10, useImperativeHandle, forwardRef } from "react";
6
+ import { useMemo as useMemo13, useCallback as useCallback10, useRef as useRef13, useState as useState13, useEffect as useEffect11, useImperativeHandle, forwardRef } from "react";
7
7
 
8
8
  // src/core/scheduling/dateMath.ts
9
9
  var DAY_MS = 24 * 60 * 60 * 1e3;
@@ -3155,7 +3155,8 @@ var TimeScaleHeader = ({
3155
3155
  var TimeScaleHeader_default = TimeScaleHeader;
3156
3156
 
3157
3157
  // src/components/TaskRow/TaskRow.tsx
3158
- import React2, { useCallback as useCallback2, useMemo as useMemo2, useSyncExternalStore } from "react";
3158
+ import React2, { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2, useSyncExternalStore } from "react";
3159
+ import { createPortal } from "react-dom";
3159
3160
 
3160
3161
  // src/utils/geometry.ts
3161
3162
  var getUTCDayDifference = (date1, date2) => {
@@ -4263,12 +4264,35 @@ 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 formatCompositePreviewDate = (date) => {
4268
+ const month = new Intl.DateTimeFormat("ru-RU", { month: "short", timeZone: "UTC" }).format(date).replace(/\.$/, "");
4269
+ const yearSuffix = date.getUTCFullYear() === (/* @__PURE__ */ new Date()).getFullYear() ? "" : ` ${String(date.getUTCFullYear()).slice(-2)}`;
4270
+ return `${date.getUTCDate()} ${month}${yearSuffix}`;
4271
+ };
4266
4272
  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;
4273
+ return prevProps.task.id === nextProps.task.id && prevProps.task.name === nextProps.task.name && prevProps.task.startDate === nextProps.task.startDate && prevProps.task.endDate === nextProps.task.endDate && prevProps.task.baselineStartDate === nextProps.task.baselineStartDate && prevProps.task.baselineEndDate === nextProps.task.baselineEndDate && prevProps.task.type === nextProps.task.type && prevProps.task.color === nextProps.task.color && prevProps.task.progress === nextProps.task.progress && prevProps.task.accepted === nextProps.task.accepted && prevProps.monthStart.getTime() === nextProps.monthStart.getTime() && prevProps.dayWidth === nextProps.dayWidth && prevProps.rowHeight === nextProps.rowHeight && prevProps.overridePosition?.left === nextProps.overridePosition?.left && prevProps.overridePosition?.width === nextProps.overridePosition?.width && prevProps.previewPositionStore === nextProps.previewPositionStore && prevProps.allTasks === nextProps.allTasks && prevProps.disableConstraints === nextProps.disableConstraints && prevProps.deferCascadePreview === nextProps.deferCascadePreview && prevProps.task.locked === nextProps.task.locked && prevProps.task.synced === nextProps.task.synced && prevProps.task.divider === nextProps.task.divider && prevProps.highlightExpiredTasks === nextProps.highlightExpiredTasks && prevProps.isCritical === nextProps.isCritical && prevProps.showBaseline === nextProps.showBaseline && prevProps.onCompositeToggle === nextProps.onCompositeToggle && prevProps.compactDetail === nextProps.compactDetail && prevProps.compositeParentColor === nextProps.compositeParentColor && prevProps.compositeExpanded === nextProps.compositeExpanded && prevProps.isFilterMatch === nextProps.isFilterMatch && prevProps.businessDays === nextProps.businessDays && prevProps.customDays === nextProps.customDays && prevProps.isWeekend === nextProps.isWeekend && prevProps.disableTaskDrag === nextProps.disableTaskDrag && prevProps.disableDependencyEditing === nextProps.disableDependencyEditing && prevProps.isDependencyDragActive === nextProps.isDependencyDragActive && prevProps.viewMode === nextProps.viewMode && prevProps.showTaskDateLabels === nextProps.showTaskDateLabels && prevProps.showTaskNames === nextProps.showTaskNames;
4268
4274
  };
4269
4275
  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 }) => {
4276
+ ({ task, monthStart, dayWidth, rowHeight, onTasksChange, onScheduleIntent, onDragStateChange, rowIndex, allTasks, enableAutoSchedule, disableConstraints, deferCascadePreview = false, overridePosition, previewPositionStore, onCascadeProgress, onCascade, divider, highlightExpiredTasks, isCritical = false, showBaseline = false, isFilterMatch = false, businessDays, customDays, isWeekend: isWeekend3, disableTaskDrag = false, disableDependencyEditing = false, onDependencyPortPointerDown, isDependencyDragActive = false, viewMode = "day", showTaskDateLabels = true, showTaskNames = true, onCompositeToggle, compactDetail = false, compositeParentColor, compositeExpanded = false }) => {
4271
4277
  const defaultParentBarColor = "#782FC4";
4278
+ const [showCompositePreview, setShowCompositePreview] = useState2(false);
4279
+ const [compositePreviewPosition, setCompositePreviewPosition] = useState2(null);
4280
+ const pointerStart = useRef2(null);
4281
+ const suppressCompositeClick = useRef2(false);
4282
+ const compositeChildren = useMemo2(
4283
+ () => task.composite ? (allTasks ?? []).filter((child) => child.parentId === task.id) : [],
4284
+ [allTasks, task.composite, task.id]
4285
+ );
4286
+ useEffect2(() => {
4287
+ if (!showCompositePreview) return;
4288
+ const hide = () => setShowCompositePreview(false);
4289
+ window.addEventListener("scroll", hide, true);
4290
+ window.addEventListener("resize", hide);
4291
+ return () => {
4292
+ window.removeEventListener("scroll", hide, true);
4293
+ window.removeEventListener("resize", hide);
4294
+ };
4295
+ }, [showCompositePreview]);
4272
4296
  const { divider: taskDivider } = task;
4273
4297
  const normalizedTask = useMemo2(() => normalizeTaskDatesForType(task), [task]);
4274
4298
  const milestone = useMemo2(() => isMilestoneTask(normalizedTask), [normalizedTask]);
@@ -4285,6 +4309,7 @@ var TaskRow = React2.memo(
4285
4309
  const isParent = useMemo2(() => {
4286
4310
  return allTasks ? isTaskParent(task.id, allTasks) : false;
4287
4311
  }, [allTasks, task.id]);
4312
+ const isVisualParent = isParent && !task.composite;
4288
4313
  const childCount = useMemo2(() => {
4289
4314
  return allTasks ? getChildren(task.id, allTasks).length : 0;
4290
4315
  }, [allTasks, task.id]);
@@ -4330,19 +4355,19 @@ var TaskRow = React2.memo(
4330
4355
  if (isExpired) {
4331
4356
  return "color-mix(in srgb, var(--gantt-expired-color) 40%, black)";
4332
4357
  }
4333
- const baseColor = isParent ? task.color || defaultParentBarColor : task.color || "var(--gantt-task-bar-default-color)";
4358
+ const baseColor = isVisualParent ? task.color || defaultParentBarColor : task.color || "var(--gantt-task-bar-default-color)";
4334
4359
  return `color-mix(in srgb, ${baseColor} 40%, black)`;
4335
- }, [defaultParentBarColor, isCritical, isExpired, isParent, task.color]);
4360
+ }, [defaultParentBarColor, isCritical, isExpired, isVisualParent, task.color]);
4336
4361
  const barStyle = useMemo2(() => {
4337
4362
  const parentBarColor = task.color || defaultParentBarColor;
4338
4363
  if (isCritical && !isExpired) {
4339
4364
  const c = criticalBarColor;
4340
- if (isParent) {
4365
+ if (isVisualParent) {
4341
4366
  return { backgroundColor: c, "--gantt-parent-bar-color": c };
4342
4367
  }
4343
4368
  return { backgroundColor: c };
4344
4369
  }
4345
- if (isParent) {
4370
+ if (isVisualParent) {
4346
4371
  if (progressWidth >= 100) {
4347
4372
  const c = isExpired ? "color-mix(in srgb, var(--gantt-expired-color) 40%, black)" : `color-mix(in srgb, ${parentBarColor} 40%, black)`;
4348
4373
  return { backgroundColor: c, "--gantt-parent-bar-color": c };
@@ -4353,7 +4378,7 @@ var TaskRow = React2.memo(
4353
4378
  return { backgroundColor: progressColor };
4354
4379
  }
4355
4380
  return { backgroundColor: barColor };
4356
- }, [isCritical, defaultParentBarColor, isExpired, isParent, progressWidth, barColor, progressColor, task.color]);
4381
+ }, [isCritical, defaultParentBarColor, isExpired, isVisualParent, progressWidth, barColor, progressColor, task.color]);
4357
4382
  const handleDragEnd = (result) => {
4358
4383
  const updatedTask = {
4359
4384
  ...normalizedTask,
@@ -4427,6 +4452,25 @@ var TaskRow = React2.memo(
4427
4452
  }, [displayLeft, dayWidth, milestoneGeometry.size]);
4428
4453
  const visualLeft = milestone ? displayMilestoneGeometry.left : displayLeft;
4429
4454
  const visualWidth = milestone ? displayMilestoneGeometry.size : displayWidth;
4455
+ const compositeSegments = useMemo2(() => {
4456
+ if (!task.composite || width <= 0) return [];
4457
+ const laneEnds = [];
4458
+ return compositeChildren.map((child) => {
4459
+ const segment = calculateTaskBar(parseUTCDate(child.startDate), parseUTCDate(child.endDate), monthStart, dayWidth);
4460
+ let lane = laneEnds.findIndex((end) => end <= segment.left);
4461
+ if (lane < 0) lane = laneEnds.length;
4462
+ laneEnds[lane] = segment.left + segment.width;
4463
+ return {
4464
+ id: child.id,
4465
+ name: child.name,
4466
+ left: (segment.left - left) / width * 100,
4467
+ width: segment.width / width * 100,
4468
+ lane
4469
+ };
4470
+ });
4471
+ }, [task.composite, compositeChildren, monthStart, dayWidth, left, width, displayLeft]);
4472
+ const compositeLaneCount = Math.max(1, ...compositeSegments.map((segment) => segment.lane + 1));
4473
+ const showCompositeSegments = task.composite && compositeSegments.length > 0 && visualWidth / compositeSegments.length >= 22;
4430
4474
  const shouldRenderBaseline = showBaseline && baselineGeometry !== null;
4431
4475
  const hasPreviewPosition = isDragging || effectiveOverridePosition !== void 0;
4432
4476
  const currentStartDate = hasPreviewPosition ? pixelsToDate(displayLeft, monthStart, dayWidth) : taskStartDate;
@@ -4444,17 +4488,20 @@ var TaskRow = React2.memo(
4444
4488
  if (lastDigit >= 2 && lastDigit <= 4) return `${count} \u0440\u0430\u0431\u043E\u0442\u044B`;
4445
4489
  return `${count} \u0440\u0430\u0431\u043E\u0442`;
4446
4490
  };
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;
4491
+ const estimatedTextWidth = isVisualParent ? 120 : durationDays >= 10 ? 76 : 62;
4492
+ const showProgressInside = !milestone && progressWidth > 0 && displayWidth > (compactDetail ? 28 : estimatedTextWidth);
4493
+ const MIN_DURATION_WIDTH = isVisualParent ? 80 : task.composite ? 92 : 50;
4494
+ const showDurationInside = !compactDetail && !milestone && durationDays >= 2 && displayWidth > MIN_DURATION_WIDTH;
4451
4495
  return /* @__PURE__ */ jsxs2(
4452
4496
  "div",
4453
4497
  {
4454
4498
  "data-filter-match": isFilterMatch ? "true" : "false",
4455
4499
  "data-gantt-task-row-id": task.id,
4456
- className: `gantt-tr-row ${isFilterMatch ? "gantt-tr-row-filter-match" : ""}`,
4457
- style: { height: `${rowHeight}px` },
4500
+ className: `gantt-tr-row ${compactDetail ? "gantt-tr-row-compositeDetail" : ""} ${isFilterMatch ? "gantt-tr-row-filter-match" : ""}`,
4501
+ style: {
4502
+ height: `${rowHeight}px`,
4503
+ "--gantt-tr-composite-detail-color": compositeParentColor ?? "var(--gantt-task-bar-default-color, #8b5cf6)"
4504
+ },
4458
4505
  children: [
4459
4506
  taskDivider === "top" && /* @__PURE__ */ jsx2("div", { className: "gantt-tr-divider gantt-tr-divider-top" }),
4460
4507
  /* @__PURE__ */ jsxs2("div", { className: "gantt-tr-taskContainer", children: [
@@ -4472,7 +4519,11 @@ var TaskRow = React2.memo(
4472
4519
  "div",
4473
4520
  {
4474
4521
  "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" : ""}`,
4522
+ role: task.composite ? "button" : void 0,
4523
+ tabIndex: task.composite ? 0 : void 0,
4524
+ "aria-label": task.composite ? `${task.name}: ${compositeExpanded ? "\u0441\u0432\u0435\u0440\u043D\u0443\u0442\u044C" : "\u0440\u0430\u0441\u043A\u0440\u044B\u0442\u044C"} \u0434\u0435\u0442\u0430\u043B\u0438` : void 0,
4525
+ "aria-expanded": task.composite ? compositeExpanded : void 0,
4526
+ className: `gantt-tr-taskBar ${isDragging ? "gantt-tr-dragging" : ""} ${task.locked ? "gantt-tr-locked" : ""} ${task.synced === false ? "gantt-tr-unsynced" : ""} ${isVisualParent ? "gantt-tr-parentBar" : ""} ${task.composite ? "gantt-tr-compositeBar" : ""} ${milestone ? "gantt-tr-milestone" : ""} ${isCritical ? "gantt-tr-critical" : ""}`,
4476
4527
  style: {
4477
4528
  left: `${visualLeft}px`,
4478
4529
  ...barStyle,
@@ -4482,14 +4533,57 @@ var TaskRow = React2.memo(
4482
4533
  padding: 0
4483
4534
  } : {
4484
4535
  width: `${visualWidth}px`,
4485
- height: isParent ? "var(--gantt-parent-bar-height, 14px)" : "var(--gantt-task-bar-height)"
4536
+ height: compactDetail ? "18px" : isVisualParent ? "var(--gantt-parent-bar-height, 14px)" : "var(--gantt-task-bar-height)"
4486
4537
  },
4487
4538
  cursor: dragHandleProps.style.cursor,
4488
4539
  userSelect: dragHandleProps.style.userSelect
4489
4540
  },
4490
4541
  onMouseDown: dragHandleProps.onMouseDown,
4491
4542
  onMouseMove: dragHandleProps.onMouseMove,
4492
- onMouseLeave: dragHandleProps.onMouseLeave,
4543
+ onMouseLeave: () => {
4544
+ dragHandleProps.onMouseLeave();
4545
+ setShowCompositePreview(false);
4546
+ },
4547
+ onMouseEnter: task.composite ? (event) => {
4548
+ const rect = event.currentTarget.getBoundingClientRect();
4549
+ const previewWidth = 220;
4550
+ const previewHeight = Math.min(340, 68 + Math.min(compositeChildren.length, 12) * 20);
4551
+ setCompositePreviewPosition({
4552
+ left: Math.max(8, Math.min(rect.left, window.innerWidth - previewWidth - 8)),
4553
+ top: rect.bottom + previewHeight + 8 < window.innerHeight ? rect.bottom + 6 : Math.max(8, rect.top - previewHeight - 6)
4554
+ });
4555
+ setShowCompositePreview(true);
4556
+ } : void 0,
4557
+ onMouseDownCapture: task.composite ? (event) => {
4558
+ suppressCompositeClick.current = Boolean(event.target.closest(".gantt-tr-resizeHandle"));
4559
+ pointerStart.current = { x: event.clientX, y: event.clientY };
4560
+ } : void 0,
4561
+ onMouseMoveCapture: task.composite ? (event) => {
4562
+ const start = pointerStart.current;
4563
+ if (start && Math.hypot(event.clientX - start.x, event.clientY - start.y) >= 4) {
4564
+ suppressCompositeClick.current = true;
4565
+ }
4566
+ } : void 0,
4567
+ onMouseUpCapture: task.composite ? (event) => {
4568
+ const start = pointerStart.current;
4569
+ if (start && Math.hypot(event.clientX - start.x, event.clientY - start.y) >= 4) {
4570
+ suppressCompositeClick.current = true;
4571
+ }
4572
+ pointerStart.current = null;
4573
+ } : void 0,
4574
+ onClick: task.composite ? () => {
4575
+ if (!isDragging && !suppressCompositeClick.current) {
4576
+ setShowCompositePreview(false);
4577
+ onCompositeToggle?.("click");
4578
+ }
4579
+ suppressCompositeClick.current = false;
4580
+ } : void 0,
4581
+ onKeyDown: task.composite ? (event) => {
4582
+ if (event.key === "Enter" || event.key === " ") {
4583
+ event.preventDefault();
4584
+ onCompositeToggle?.("keyboard");
4585
+ }
4586
+ } : void 0,
4493
4587
  children: [
4494
4588
  !milestone && progressWidth > 0 && progressWidth < 100 && /* @__PURE__ */ jsx2(
4495
4589
  "div",
@@ -4498,14 +4592,28 @@ var TaskRow = React2.memo(
4498
4592
  style: {
4499
4593
  width: `${progressWidth}%`,
4500
4594
  backgroundColor: progressColor,
4501
- ...isParent && {
4595
+ ...isVisualParent && {
4502
4596
  borderRadius: "var(--gantt-parent-bar-radius, 8px) 0 0 0"
4503
4597
  }
4504
4598
  }
4505
4599
  }
4506
4600
  ),
4507
4601
  !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` }),
4602
+ showCompositeSegments && /* @__PURE__ */ jsx2("span", { className: "gantt-tr-compositeSegments", "aria-hidden": "true", children: compositeSegments.map((segment) => /* @__PURE__ */ jsx2(
4603
+ "span",
4604
+ {
4605
+ className: "gantt-tr-compositeSegment",
4606
+ style: {
4607
+ left: `${segment.left}%`,
4608
+ width: `${segment.width}%`,
4609
+ top: `${segment.lane / compositeLaneCount * 100}%`,
4610
+ height: `${100 / compositeLaneCount}%`
4611
+ }
4612
+ },
4613
+ segment.id
4614
+ )) }),
4615
+ task.composite && /* @__PURE__ */ jsx2("span", { className: "gantt-tr-compositeHoverChevron", "aria-hidden": "true", children: /* @__PURE__ */ jsx2("svg", { className: `gantt-tr-compositeChevron${compositeExpanded ? " gantt-tr-compositeChevron-open" : ""}`, viewBox: "0 0 20 20", children: /* @__PURE__ */ jsx2("path", { d: "m7 4 6 6-6 6", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }),
4616
+ showDurationInside && /* @__PURE__ */ jsx2("span", { className: "gantt-tr-taskDuration", children: isVisualParent ? getChildCountLabel(childCount) : `${durationDays} \u0434` }),
4509
4617
  progressWidth > 0 && showProgressInside && /* @__PURE__ */ jsxs2("span", { className: "gantt-tr-progressText", children: [
4510
4618
  progressWidth,
4511
4619
  "%"
@@ -4514,6 +4622,41 @@ var TaskRow = React2.memo(
4514
4622
  ]
4515
4623
  }
4516
4624
  ),
4625
+ task.composite && showCompositePreview && compositePreviewPosition && compositeChildren.length > 0 && !isDragging && typeof document !== "undefined" && createPortal(
4626
+ /* @__PURE__ */ jsxs2(
4627
+ "div",
4628
+ {
4629
+ className: "gantt-tr-compositePreview",
4630
+ style: { left: `${compositePreviewPosition.left}px`, top: `${compositePreviewPosition.top}px` },
4631
+ role: "tooltip",
4632
+ children: [
4633
+ /* @__PURE__ */ jsxs2("div", { className: "gantt-tr-compositePreviewAxis", children: [
4634
+ /* @__PURE__ */ jsx2("span", { children: formatCompositePreviewDate(currentStartDate) }),
4635
+ /* @__PURE__ */ jsx2("span", { children: formatCompositePreviewDate(currentEndDate) })
4636
+ ] }),
4637
+ compositeChildren.slice(0, 12).map((child) => /* @__PURE__ */ jsxs2("div", { className: "gantt-tr-compositePreviewRow", children: [
4638
+ /* @__PURE__ */ jsx2("span", { children: child.name }),
4639
+ /* @__PURE__ */ jsx2("span", { className: "gantt-tr-compositePreviewTrack", children: /* @__PURE__ */ jsx2(
4640
+ "span",
4641
+ {
4642
+ className: "gantt-tr-compositePreviewBar",
4643
+ style: {
4644
+ left: `${Math.max(0, (parseUTCDate(child.startDate).getTime() - taskStartDate.getTime()) / (taskEndDate.getTime() - taskStartDate.getTime() + 864e5) * 100)}%`,
4645
+ width: `${Math.max(2, (parseUTCDate(child.endDate).getTime() - parseUTCDate(child.startDate).getTime() + 864e5) / (taskEndDate.getTime() - taskStartDate.getTime() + 864e5) * 100)}%`,
4646
+ backgroundColor: barColor
4647
+ }
4648
+ }
4649
+ ) })
4650
+ ] }, child.id)),
4651
+ compositeChildren.length > 12 && /* @__PURE__ */ jsxs2("div", { children: [
4652
+ "+",
4653
+ compositeChildren.length - 12
4654
+ ] })
4655
+ ]
4656
+ }
4657
+ ),
4658
+ document.body
4659
+ ),
4517
4660
  !disableDependencyEditing && onDependencyPortPointerDown && /* @__PURE__ */ jsxs2(Fragment, { children: [
4518
4661
  /* @__PURE__ */ jsx2(
4519
4662
  "button",
@@ -4522,7 +4665,7 @@ var TaskRow = React2.memo(
4522
4665
  "data-gantt-dependency-port": true,
4523
4666
  "data-task-id": task.id,
4524
4667
  "data-port-side": "left",
4525
- className: `gantt-tr-dependencyPort gantt-tr-dependencyPortLeft ${milestone ? "gantt-tr-dependencyPortMilestone" : ""} ${isParent ? "gantt-tr-dependencyPortParent" : ""} ${isDependencyDragActive ? "gantt-tr-dependencyPortDragActive" : ""}`,
4668
+ className: `gantt-tr-dependencyPort gantt-tr-dependencyPortLeft ${milestone ? "gantt-tr-dependencyPortMilestone" : ""} ${isVisualParent ? "gantt-tr-dependencyPortParent" : ""} ${isDependencyDragActive ? "gantt-tr-dependencyPortDragActive" : ""}`,
4526
4669
  style: { left: `${visualLeft - 24}px` },
4527
4670
  "aria-label": `\u041D\u0430\u0447\u0430\u0442\u044C \u0441\u0432\u044F\u0437\u044C \u043E\u0442 \u043B\u0435\u0432\u043E\u0433\u043E \u043A\u0440\u0430\u044F: ${task.name}`,
4528
4671
  title: "\u041F\u043E\u0442\u044F\u043D\u0438\u0442\u0435 \u043A \u043A\u0440\u0430\u044E \u0434\u0440\u0443\u0433\u043E\u0439 \u043F\u043E\u043B\u043E\u0441\u044B, \u0447\u0442\u043E\u0431\u044B \u0441\u043E\u0437\u0434\u0430\u0442\u044C \u0441\u0432\u044F\u0437\u044C",
@@ -4536,7 +4679,7 @@ var TaskRow = React2.memo(
4536
4679
  "data-gantt-dependency-port": true,
4537
4680
  "data-task-id": task.id,
4538
4681
  "data-port-side": "right",
4539
- className: `gantt-tr-dependencyPort gantt-tr-dependencyPortRight ${milestone ? "gantt-tr-dependencyPortMilestone" : ""} ${isParent ? "gantt-tr-dependencyPortParent" : ""} ${isDependencyDragActive ? "gantt-tr-dependencyPortDragActive" : ""}`,
4682
+ className: `gantt-tr-dependencyPort gantt-tr-dependencyPortRight ${milestone ? "gantt-tr-dependencyPortMilestone" : ""} ${isVisualParent ? "gantt-tr-dependencyPortParent" : ""} ${isDependencyDragActive ? "gantt-tr-dependencyPortDragActive" : ""}`,
4540
4683
  style: { left: `${visualLeft + visualWidth}px` },
4541
4684
  "aria-label": `\u041D\u0430\u0447\u0430\u0442\u044C \u0441\u0432\u044F\u0437\u044C \u043E\u0442 \u043F\u0440\u0430\u0432\u043E\u0433\u043E \u043A\u0440\u0430\u044F: ${task.name}`,
4542
4685
  title: "\u041F\u043E\u0442\u044F\u043D\u0438\u0442\u0435 \u043A \u043A\u0440\u0430\u044E \u0434\u0440\u0443\u0433\u043E\u0439 \u043F\u043E\u043B\u043E\u0441\u044B, \u0447\u0442\u043E\u0431\u044B \u0441\u043E\u0437\u0434\u0430\u0442\u044C \u0441\u0432\u044F\u0437\u044C",
@@ -4575,6 +4718,14 @@ var TaskRow = React2.memo(
4575
4718
  children: /* @__PURE__ */ jsx2("path", { d: "M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zM12 17c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z" })
4576
4719
  }
4577
4720
  ),
4721
+ !showDurationInside && !milestone && /* @__PURE__ */ jsx2(
4722
+ "span",
4723
+ {
4724
+ className: `gantt-tr-externalDuration gantt-tr-durationBeforeBar${compactDetail ? " gantt-tr-compositeDuration" : ""}`,
4725
+ style: { left: `${visualLeft - 46}px`, color: isParent ? task.color || defaultParentBarColor : barColor },
4726
+ children: isVisualParent ? getChildCountLabel(childCount) : `${durationDays} \u0434`
4727
+ }
4728
+ ),
4578
4729
  /* @__PURE__ */ jsxs2(
4579
4730
  "div",
4580
4731
  {
@@ -4584,7 +4735,6 @@ var TaskRow = React2.memo(
4584
4735
  color: isParent ? task.color || defaultParentBarColor : barColor
4585
4736
  },
4586
4737
  children: [
4587
- !showDurationInside && !milestone && /* @__PURE__ */ jsx2("span", { className: "gantt-tr-externalDuration", children: isParent ? getChildCountLabel(childCount) : `${durationDays} \u0434` }),
4588
4738
  progressWidth > 0 && !showProgressInside && /* @__PURE__ */ jsxs2("span", { className: "gantt-tr-externalProgress", children: [
4589
4739
  progressWidth,
4590
4740
  "%"
@@ -4849,7 +4999,7 @@ var DragGuideLines = ({
4849
4999
  var DragGuideLines_default = DragGuideLines;
4850
5000
 
4851
5001
  // src/components/DependencyLines/DependencyLines.tsx
4852
- import React6, { useMemo as useMemo6, useState as useState2 } from "react";
5002
+ import React6, { useMemo as useMemo6, useState as useState3 } from "react";
4853
5003
  import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
4854
5004
  function isTaskHidden(taskId, collapsedParentIds, taskMap) {
4855
5005
  const task = taskMap.get(taskId);
@@ -4888,6 +5038,8 @@ var DependencyLines = React6.memo(({
4888
5038
  gridWidth,
4889
5039
  totalHeight,
4890
5040
  rowIndexByTaskId,
5041
+ rowTops,
5042
+ rowHeights,
4891
5043
  dragOverrides,
4892
5044
  selectedDep,
4893
5045
  criticalTaskIds,
@@ -4895,7 +5047,7 @@ var DependencyLines = React6.memo(({
4895
5047
  weekendPredicate,
4896
5048
  onDependencyClick
4897
5049
  }) => {
4898
- const [hoveredLineId, setHoveredLineId] = useState2(null);
5050
+ const [hoveredLineId, setHoveredLineId] = useState3(null);
4899
5051
  const tasksForPositions = allTasks ?? tasks;
4900
5052
  const { taskPositions, taskIndices, hiddenTaskIds } = useMemo6(() => {
4901
5053
  const positions = /* @__PURE__ */ new Map();
@@ -4911,7 +5063,8 @@ var DependencyLines = React6.memo(({
4911
5063
  left: computed.left,
4912
5064
  right: computed.right,
4913
5065
  centerX: computed.centerX,
4914
- rowTop: rowIndex * rowHeight,
5066
+ rowTop: rowTops?.[rowIndex] ?? rowIndex * rowHeight,
5067
+ rowHeight: rowHeights?.[rowIndex] ?? rowHeight,
4915
5068
  isVirtual: false
4916
5069
  });
4917
5070
  });
@@ -4931,6 +5084,7 @@ var DependencyLines = React6.memo(({
4931
5084
  right: computed.right,
4932
5085
  centerX: computed.centerX,
4933
5086
  rowTop: ancestorPosition.rowTop,
5087
+ rowHeight: ancestorPosition.rowHeight,
4934
5088
  isVirtual: true
4935
5089
  });
4936
5090
  }
@@ -4944,18 +5098,19 @@ var DependencyLines = React6.memo(({
4944
5098
  const override = dragOverrides?.get(task.id);
4945
5099
  const computed = resolveTaskHorizontalGeometry(task, monthStart, dayWidth, override);
4946
5100
  const rowIndex = rowIndexByTaskId?.get(task.id);
4947
- const rowTop = rowIndex !== void 0 ? rowIndex * rowHeight : 0;
5101
+ const rowTop = rowIndex !== void 0 ? rowTops?.[rowIndex] ?? rowIndex * rowHeight : 0;
4948
5102
  positions.set(task.id, {
4949
5103
  left: computed.left,
4950
5104
  right: computed.right,
4951
5105
  centerX: computed.centerX,
4952
5106
  rowTop,
5107
+ rowHeight: rowIndex !== void 0 ? rowHeights?.[rowIndex] ?? rowHeight : rowHeight,
4953
5108
  isVirtual: false
4954
5109
  });
4955
5110
  }
4956
5111
  }
4957
5112
  return { taskPositions: positions, taskIndices: indices, hiddenTaskIds: hidden };
4958
- }, [tasks, tasksForPositions, allTasks, collapsedParentIds, monthStart, dayWidth, rowHeight, dragOverrides, rowIndexByTaskId]);
5113
+ }, [tasks, tasksForPositions, allTasks, collapsedParentIds, monthStart, dayWidth, rowHeight, rowTops, rowHeights, dragOverrides, rowIndexByTaskId]);
4959
5114
  const tasksForCycleDetection = allTasks ?? tasks;
4960
5115
  const cycleInfo = useMemo6(() => {
4961
5116
  const result = detectCycles(tasksForCycleDetection);
@@ -4997,7 +5152,9 @@ var DependencyLines = React6.memo(({
4997
5152
  continue;
4998
5153
  }
4999
5154
  }
5000
- if (!renderedTaskIdSet.has(edge.predecessorId) && !renderedTaskIdSet.has(edge.successorId)) {
5155
+ const predecessorVisibleId = predecessor.isVirtual ? findVisibleAncestor(taskMap.get(edge.predecessorId), collapsedParentIds, taskMap)?.id : edge.predecessorId;
5156
+ const successorVisibleId = successor.isVirtual ? findVisibleAncestor(taskMap.get(edge.successorId), collapsedParentIds, taskMap)?.id : edge.successorId;
5157
+ if (!renderedTaskIdSet.has(predecessorVisibleId ?? "") && !renderedTaskIdSet.has(successorVisibleId ?? "")) {
5001
5158
  continue;
5002
5159
  }
5003
5160
  const isVirtual = predecessor.isVirtual || successor.isVirtual;
@@ -5011,9 +5168,9 @@ var DependencyLines = React6.memo(({
5011
5168
  let toY;
5012
5169
  if (reverseOrder) {
5013
5170
  fromY = predecessor.rowTop + 10;
5014
- toY = successor.rowTop + rowHeight - 6;
5171
+ toY = successor.rowTop + successor.rowHeight - 6;
5015
5172
  } else {
5016
- fromY = predecessor.rowTop + rowHeight - 10;
5173
+ fromY = predecessor.rowTop + predecessor.rowHeight - 10;
5017
5174
  toY = successor.rowTop + 6;
5018
5175
  }
5019
5176
  let fromX = predecessorTask && isMilestoneTask(predecessorTask) ? predecessor.centerX : edge.type === "SS" || edge.type === "SF" ? predecessor.left : predecessor.right;
@@ -5289,7 +5446,7 @@ var DependencyCreationOverlay = ({ drag, width, height }) => {
5289
5446
  };
5290
5447
 
5291
5448
  // 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";
5449
+ import React13, { useMemo as useMemo9, useCallback as useCallback6, useState as useState8, useEffect as useEffect7, useRef as useRef8 } from "react";
5293
5450
 
5294
5451
  // src/utils/taskListReorder.ts
5295
5452
  function getDescendantIds(taskId, tasks) {
@@ -5494,9 +5651,9 @@ var PopoverContent = ({
5494
5651
 
5495
5652
  // src/components/TaskList/TaskListRow.tsx
5496
5653
  import React10, {
5497
- useState as useState5,
5498
- useRef as useRef4,
5499
- useEffect as useEffect4,
5654
+ useState as useState6,
5655
+ useRef as useRef5,
5656
+ useEffect as useEffect5,
5500
5657
  useCallback as useCallback5,
5501
5658
  useMemo as useMemo8
5502
5659
  } from "react";
@@ -5519,14 +5676,14 @@ var Input = React7.forwardRef(
5519
5676
  Input.displayName = "Input";
5520
5677
 
5521
5678
  // src/components/ui/DatePicker.tsx
5522
- import { useState as useState4, useCallback as useCallback4, useEffect as useEffect3, useRef as useRef3 } from "react";
5679
+ import { useState as useState5, useCallback as useCallback4, useEffect as useEffect4, useRef as useRef4 } from "react";
5523
5680
  import { format as format3, isValid, parse, addDays, addMonths as addMonths2, addYears, subMonths as subMonths2, subYears, subDays } from "date-fns";
5524
5681
 
5525
5682
  // src/components/ui/Calendar.tsx
5526
5683
  import {
5527
- useState as useState3,
5528
- useRef as useRef2,
5529
- useEffect as useEffect2,
5684
+ useState as useState4,
5685
+ useRef as useRef3,
5686
+ useEffect as useEffect3,
5530
5687
  useCallback as useCallback3,
5531
5688
  useMemo as useMemo7
5532
5689
  } from "react";
@@ -5561,13 +5718,13 @@ var Calendar = ({
5561
5718
  disabled = false,
5562
5719
  isWeekend: isWeekendProp
5563
5720
  }) => {
5564
- const scrollRef = useRef2(null);
5721
+ const scrollRef = useRef3(null);
5565
5722
  const initialMonth = useMemo7(
5566
5723
  () => startOfMonth(initialDate ?? selected ?? /* @__PURE__ */ new Date()),
5567
5724
  // eslint-disable-next-line react-hooks/exhaustive-deps
5568
5725
  []
5569
5726
  );
5570
- const [months, setMonths] = useState3(() => [
5727
+ const [months, setMonths] = useState4(() => [
5571
5728
  subMonths(initialMonth, 1),
5572
5729
  initialMonth,
5573
5730
  addMonths(initialMonth, 1)
@@ -5581,7 +5738,7 @@ var Calendar = ({
5581
5738
  }
5582
5739
  });
5583
5740
  }, []);
5584
- useEffect2(() => {
5741
+ useEffect3(() => {
5585
5742
  const container = scrollRef.current;
5586
5743
  if (!container) return;
5587
5744
  const handleScroll = () => {
@@ -5599,7 +5756,7 @@ var Calendar = ({
5599
5756
  container.addEventListener("scroll", handleScroll);
5600
5757
  return () => container.removeEventListener("scroll", handleScroll);
5601
5758
  }, [loadMoreMonths]);
5602
- useEffect2(() => {
5759
+ useEffect3(() => {
5603
5760
  if (!scrollRef.current) return;
5604
5761
  const target = selected ?? initialDate ?? /* @__PURE__ */ new Date();
5605
5762
  const monthKey = format2(startOfMonth(target), "yyyy-MM");
@@ -5704,12 +5861,12 @@ var DatePicker = ({
5704
5861
  businessDays = true,
5705
5862
  footer
5706
5863
  }) => {
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);
5864
+ const [open, setOpen] = useState5(false);
5865
+ const [inputValue, setInputValue] = useState5("");
5866
+ const [draftDate, setDraftDate] = useState5(void 0);
5867
+ const dateInputRef = useRef4(null);
5868
+ const segIdxRef = useRef4(0);
5869
+ const charPosRef = useRef4(0);
5713
5870
  const selectedDate = (() => {
5714
5871
  if (!value) return void 0;
5715
5872
  const d = /* @__PURE__ */ new Date(value + "T00:00:00Z");
@@ -5717,7 +5874,7 @@ var DatePicker = ({
5717
5874
  })();
5718
5875
  const activeDate = draftDate ?? selectedDate;
5719
5876
  const displayValue = selectedDate ? formatUTCDate(selectedDate, displayFormat) : placeholder;
5720
- useEffect3(() => {
5877
+ useEffect4(() => {
5721
5878
  if (value) {
5722
5879
  const d = /* @__PURE__ */ new Date(value + "T00:00:00Z");
5723
5880
  if (isValid(d)) {
@@ -5734,7 +5891,7 @@ var DatePicker = ({
5734
5891
  const seg = segments[idx] ?? segments[0];
5735
5892
  dateInputRef.current.setSelectionRange(seg.start, seg.end);
5736
5893
  }, []);
5737
- useEffect3(() => {
5894
+ useEffect4(() => {
5738
5895
  if (open) {
5739
5896
  setTimeout(() => {
5740
5897
  if (dateInputRef.current) {
@@ -6333,10 +6490,10 @@ var DepChip = ({
6333
6490
  businessDays = true,
6334
6491
  weekendPredicate
6335
6492
  }) => {
6336
- const [popoverOpen, setPopoverOpen] = useState5(false);
6493
+ const [popoverOpen, setPopoverOpen] = useState6(false);
6337
6494
  const lagAbs = Math.abs(lag ?? 0);
6338
- const [inputAbs, setInputAbs] = useState5(lagAbs === 0 ? "" : String(lagAbs));
6339
- useEffect4(() => {
6495
+ const [inputAbs, setInputAbs] = useState6(lagAbs === 0 ? "" : String(lagAbs));
6496
+ useEffect5(() => {
6340
6497
  const abs = Math.abs(lag ?? 0);
6341
6498
  setInputAbs(abs === 0 ? "" : String(abs));
6342
6499
  }, [lag]);
@@ -6715,13 +6872,14 @@ var TaskListRow = React10.memo(
6715
6872
  taskListMenuCommands = [],
6716
6873
  hideTaskListRowActions = false,
6717
6874
  rowClassName,
6875
+ compositeParentColor,
6718
6876
  getTaskListNamePrefixIcon,
6719
6877
  taskDateChangeMode = "preserve-duration",
6720
6878
  onTaskDateChangeModeChange,
6721
6879
  printDependencyLabels = false
6722
6880
  }) => {
6723
- const [editingColumnId, setEditingColumnId] = useState5(null);
6724
- const [editingColumnStartValue, setEditingColumnStartValue] = useState5(void 0);
6881
+ const [editingColumnId, setEditingColumnId] = useState6(null);
6882
+ const [editingColumnStartValue, setEditingColumnStartValue] = useState6(void 0);
6725
6883
  const editingName = editingColumnId === "name";
6726
6884
  const editingDuration = editingColumnId === "duration";
6727
6885
  const editingProgress = editingColumnId === "progress";
@@ -6752,27 +6910,27 @@ var TaskListRow = React10.memo(
6752
6910
  }, [columnWidthStyleMap]);
6753
6911
  const normalizedTask = useMemo8(() => normalizeTaskDatesForType(task), [task]);
6754
6912
  const isMilestone = useMemo8(() => isMilestoneTask(normalizedTask), [normalizedTask]);
6755
- const [nameValue, setNameValue] = useState5("");
6756
- const nameInputRef = useRef4(null);
6757
- const [durationValue, setDurationValue] = useState5(
6913
+ const [nameValue, setNameValue] = useState6("");
6914
+ const nameInputRef = useRef5(null);
6915
+ const [durationValue, setDurationValue] = useState6(
6758
6916
  () => getInclusiveDurationDays(normalizedTask.startDate, normalizedTask.endDate)
6759
6917
  );
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(
6918
+ const durationInputRef = useRef5(null);
6919
+ const dependencySearchInputRef = useRef5(null);
6920
+ const dependencySearchListRef = useRef5(null);
6921
+ const [progressValue, setProgressValue] = useState6(0);
6922
+ const progressInputRef = useRef5(null);
6923
+ const [overflowOpen, setOverflowOpen] = useState6(false);
6924
+ const [contextMenuOpen, setContextMenuOpen] = useState6(false);
6925
+ const [colorMenuOpen, setColorMenuOpen] = useState6(false);
6926
+ const nameConfirmedRef = useRef5(false);
6927
+ const durationConfirmedRef = useRef5(false);
6928
+ const progressConfirmedRef = useRef5(false);
6929
+ const autoEditedForRef = useRef5(null);
6930
+ const editTriggerRef = useRef5(
6773
6931
  "doubleclick"
6774
6932
  );
6775
- const rowRef = useRef4(null);
6933
+ const rowRef = useRef5(null);
6776
6934
  const isSelected = selectedTaskId === task.id;
6777
6935
  const isParent = useMemo8(
6778
6936
  () => isTaskParent(task.id, allTasks),
@@ -6799,13 +6957,42 @@ var TaskListRow = React10.memo(
6799
6957
  [businessDays, weekendPredicate]
6800
6958
  );
6801
6959
  const isCollapsed = collapsedParentIds.has(task.id);
6960
+ const [compositeIconMode, setCompositeIconMode] = useState6(
6961
+ () => isCollapsed ? "composite" : "down"
6962
+ );
6963
+ const compositeIconModeRef = useRef5(compositeIconMode);
6964
+ const [compositeIconAnimation, setCompositeIconAnimation] = useState6(null);
6965
+ const [compositeIconAnimationPhase, setCompositeIconAnimationPhase] = useState6(null);
6966
+ useEffect5(() => {
6967
+ const nextMode = isCollapsed ? "composite" : "down";
6968
+ if (nextMode === compositeIconModeRef.current) {
6969
+ setCompositeIconAnimation(null);
6970
+ setCompositeIconAnimationPhase(null);
6971
+ return;
6972
+ }
6973
+ setCompositeIconAnimation(isCollapsed ? "closing" : "opening");
6974
+ setCompositeIconAnimationPhase("outgoing");
6975
+ const swapTimeoutId = window.setTimeout(() => {
6976
+ compositeIconModeRef.current = nextMode;
6977
+ setCompositeIconMode(nextMode);
6978
+ setCompositeIconAnimationPhase("incoming");
6979
+ }, 120);
6980
+ const finishTimeoutId = window.setTimeout(() => {
6981
+ setCompositeIconAnimation(null);
6982
+ setCompositeIconAnimationPhase(null);
6983
+ }, 240);
6984
+ return () => {
6985
+ window.clearTimeout(swapTimeoutId);
6986
+ window.clearTimeout(finishTimeoutId);
6987
+ };
6988
+ }, [isCollapsed]);
6802
6989
  const getHierarchyLineColor = useCallback5((columnDepth) => {
6803
6990
  return columnDepth % 2 === 0 ? "#93c5fd" : "var(--gantt-hierarchy-line-color)";
6804
6991
  }, []);
6805
6992
  const isPicking = selectingPredecessorFor != null;
6806
6993
  const isSourceRow = isPicking && selectingPredecessorFor === task.id;
6807
- const [dependencySearchQuery, setDependencySearchQuery] = useState5("");
6808
- const [highlightedDependencyIndex, setHighlightedDependencyIndex] = useState5(0);
6994
+ const [dependencySearchQuery, setDependencySearchQuery] = useState6("");
6995
+ const [highlightedDependencyIndex, setHighlightedDependencyIndex] = useState6(0);
6809
6996
  const chips = useMemo8(() => {
6810
6997
  const taskById = new Map((allTasks ?? []).map((t) => [t.id, t]));
6811
6998
  return (task.dependencies ?? []).map((dep) => {
@@ -6842,7 +7029,7 @@ var TaskListRow = React10.memo(
6842
7029
  taskNumberMap,
6843
7030
  task.dependencies
6844
7031
  ]);
6845
- useEffect4(() => {
7032
+ useEffect5(() => {
6846
7033
  if (editingName && nameInputRef.current) {
6847
7034
  nameInputRef.current.focus();
6848
7035
  if (editTriggerRef.current === "keypress") {
@@ -6853,15 +7040,15 @@ var TaskListRow = React10.memo(
6853
7040
  }
6854
7041
  }
6855
7042
  }, [editingName]);
6856
- useEffect4(() => {
7043
+ useEffect5(() => {
6857
7044
  if (!isSourceRow && dependencySearchQuery !== "") {
6858
7045
  setDependencySearchQuery("");
6859
7046
  }
6860
7047
  }, [isSourceRow, dependencySearchQuery]);
6861
- useEffect4(() => {
7048
+ useEffect5(() => {
6862
7049
  setHighlightedDependencyIndex(0);
6863
7050
  }, [dependencySearchQuery, isSourceRow, dependencyPickMode]);
6864
- useEffect4(() => {
7051
+ useEffect5(() => {
6865
7052
  if (dependencySearchCandidates.length === 0) {
6866
7053
  setHighlightedDependencyIndex(0);
6867
7054
  return;
@@ -6870,13 +7057,13 @@ var TaskListRow = React10.memo(
6870
7057
  setHighlightedDependencyIndex(dependencySearchCandidates.length - 1);
6871
7058
  }
6872
7059
  }, [dependencySearchCandidates, highlightedDependencyIndex]);
6873
- useEffect4(() => {
7060
+ useEffect5(() => {
6874
7061
  if (isSourceRow && dependencySearchInputRef.current) {
6875
7062
  dependencySearchInputRef.current.focus();
6876
7063
  dependencySearchInputRef.current.select();
6877
7064
  }
6878
7065
  }, [isSourceRow, dependencyPickMode, activeLinkType]);
6879
- useEffect4(() => {
7066
+ useEffect5(() => {
6880
7067
  if (!isSourceRow || dependencySearchCandidates.length === 0) {
6881
7068
  return;
6882
7069
  }
@@ -6888,7 +7075,7 @@ var TaskListRow = React10.memo(
6888
7075
  block: "nearest"
6889
7076
  });
6890
7077
  }, [isSourceRow, highlightedDependencyIndex, dependencySearchCandidates]);
6891
- useEffect4(() => {
7078
+ useEffect5(() => {
6892
7079
  if (editingTaskId === task.id && !disableTaskNameEditing && autoEditedForRef.current !== editingTaskId) {
6893
7080
  autoEditedForRef.current = editingTaskId;
6894
7081
  nameConfirmedRef.current = false;
@@ -6907,6 +7094,7 @@ var TaskListRow = React10.memo(
6907
7094
  );
6908
7095
  const handleNameDoubleClick = useCallback5(
6909
7096
  (e) => {
7097
+ if (task.composite) return;
6910
7098
  if (disableTaskNameEditing) return;
6911
7099
  e.stopPropagation();
6912
7100
  nameConfirmedRef.current = false;
@@ -6914,7 +7102,7 @@ var TaskListRow = React10.memo(
6914
7102
  setNameValue(task.name);
6915
7103
  setEditingColumnId("name");
6916
7104
  },
6917
- [task.name, disableTaskNameEditing]
7105
+ [task.name, task.composite, disableTaskNameEditing]
6918
7106
  );
6919
7107
  const handleRowKeyDown = useCallback5(
6920
7108
  (e) => {
@@ -7069,16 +7257,16 @@ var TaskListRow = React10.memo(
7069
7257
  },
7070
7258
  [progressValue, task, onTasksChange, handleProgressCancel, allTasks]
7071
7259
  );
7072
- useEffect4(() => {
7260
+ useEffect5(() => {
7073
7261
  if (editingProgress && progressInputRef.current) {
7074
7262
  progressInputRef.current.focus();
7075
7263
  progressInputRef.current.select();
7076
7264
  }
7077
7265
  }, [editingProgress]);
7078
- useEffect4(() => {
7266
+ useEffect5(() => {
7079
7267
  setDurationValue(getDuration(normalizedTask.startDate, normalizedTask.endDate));
7080
7268
  }, [normalizedTask.startDate, normalizedTask.endDate, getDuration]);
7081
- useEffect4(() => {
7269
+ useEffect5(() => {
7082
7270
  if (editingDuration && durationInputRef.current) {
7083
7271
  durationInputRef.current.focus();
7084
7272
  durationInputRef.current.select();
@@ -7563,7 +7751,7 @@ var TaskListRow = React10.memo(
7563
7751
  ]
7564
7752
  }
7565
7753
  );
7566
- const nameTriggerPaddingLeft = isParent ? `${nestingDepth * 20 + 28}px` : nestingDepth > 0 ? `${nestingDepth * 20 + 8}px` : void 0;
7754
+ const nameTriggerPaddingLeft = isParent && !task.composite ? `${nestingDepth * 20 + 28}px` : nestingDepth > 0 ? `${nestingDepth * 20 + 8}px` : void 0;
7567
7755
  const nameInputPaddingLeft = nestingDepth > 0 ? `${nestingDepth * 20 + 8}px` : void 0;
7568
7756
  const nestedDropIndicatorLeft = (() => {
7569
7757
  const numberColumnWidth = resolvedColumns?.find((col) => col.id === "number")?.width ?? 40;
@@ -7661,36 +7849,35 @@ var TaskListRow = React10.memo(
7661
7849
  }
7662
7850
  )
7663
7851
  ] }),
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, {})
7852
+ task.composite && !isFilterHideMode && !isCollapsed && !editingName && /* @__PURE__ */ jsx14(
7853
+ "span",
7854
+ {
7855
+ "data-testid": "gantt-tl-parent-connector-tail",
7856
+ style: {
7857
+ position: "absolute",
7858
+ left: `${nestingDepth * 20 + 9}px`,
7859
+ top: `${rowHeight / 2 + 7}px`,
7860
+ height: `${rowHeight / 2 - 7}px`,
7861
+ width: "1.5px",
7862
+ background: getHierarchyLineColor(nestingDepth),
7863
+ borderRadius: "1px",
7864
+ pointerEvents: "none"
7689
7865
  }
7690
- )
7691
- ] })
7866
+ }
7867
+ ),
7868
+ isParent && !task.composite && !editingName && /* @__PURE__ */ jsx14(Fragment3, { children: /* @__PURE__ */ jsx14(
7869
+ "button",
7870
+ {
7871
+ type: "button",
7872
+ className: `gantt-tl-collapse-btn ${isCollapsed ? "gantt-tl-collapse-btn-collapsed" : ""}`,
7873
+ onClick: handleToggleCollapse,
7874
+ style: { left: `${nestingDepth * 20 - 2}px` },
7875
+ "aria-label": isCollapsed ? "Expand children" : "Collapse children",
7876
+ children: /* @__PURE__ */ jsx14(ChevronRightIcon, {})
7877
+ }
7878
+ ) })
7692
7879
  ] }),
7693
- !isChild && isParent && !editingName && /* @__PURE__ */ jsxs10(Fragment3, { children: [
7880
+ !isChild && isParent && !task.composite && !editingName && /* @__PURE__ */ jsxs10(Fragment3, { children: [
7694
7881
  !isFilterHideMode && !isCollapsed && /* @__PURE__ */ jsx14(
7695
7882
  "span",
7696
7883
  {
@@ -7713,7 +7900,7 @@ var TaskListRow = React10.memo(
7713
7900
  type: "button",
7714
7901
  className: `gantt-tl-collapse-btn ${isCollapsed ? "gantt-tl-collapse-btn-collapsed" : ""}`,
7715
7902
  onClick: handleToggleCollapse,
7716
- style: { left: `${nestingDepth * 20 + 1}px` },
7903
+ style: { left: `${nestingDepth * 20 - 2}px` },
7717
7904
  "aria-label": isCollapsed ? "Expand children" : "Collapse children",
7718
7905
  children: /* @__PURE__ */ jsx14(ChevronRightIcon, {})
7719
7906
  }
@@ -7732,27 +7919,57 @@ var TaskListRow = React10.memo(
7732
7919
  style: { paddingLeft: nameInputPaddingLeft },
7733
7920
  onClick: (e) => e.stopPropagation()
7734
7921
  }
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
- ),
7922
+ ) : /* @__PURE__ */ jsxs10(Fragment3, { children: [
7923
+ task.composite && /* @__PURE__ */ jsx14(
7924
+ "button",
7925
+ {
7926
+ type: "button",
7927
+ className: "gantt-tl-compositeToggle",
7928
+ onClick: handleToggleCollapse,
7929
+ style: { left: `${nestingDepth * 20 - 3}px` },
7930
+ "aria-label": isCollapsed ? `\u0420\u0430\u0441\u043A\u0440\u044B\u0442\u044C ${task.name}` : `\u0421\u0432\u0435\u0440\u043D\u0443\u0442\u044C ${task.name}`,
7931
+ "aria-expanded": !isCollapsed,
7932
+ title: isCollapsed ? "\u0420\u0430\u0441\u043A\u0440\u044B\u0442\u044C \u044D\u0442\u0430\u0436\u0438" : "\u0421\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u044D\u0442\u0430\u0436\u0438",
7933
+ children: /* @__PURE__ */ jsx14(
7934
+ "svg",
7935
+ {
7936
+ className: compositeIconAnimation && compositeIconAnimationPhase ? `gantt-tl-compositeGlyph-${compositeIconMode}-${compositeIconAnimation}-${compositeIconAnimationPhase}` : void 0,
7937
+ viewBox: "0 0 24 24",
7938
+ fill: "none",
7939
+ stroke: "currentColor",
7940
+ strokeWidth: "2",
7941
+ strokeLinecap: "round",
7942
+ strokeLinejoin: "round",
7943
+ "aria-hidden": "true",
7944
+ children: compositeIconMode === "composite" ? /* @__PURE__ */ jsxs10(Fragment3, { children: [
7945
+ /* @__PURE__ */ jsx14("path", { d: "M21 5H11" }),
7946
+ /* @__PURE__ */ jsx14("path", { d: "M21 12H11" }),
7947
+ /* @__PURE__ */ jsx14("path", { d: "M21 19H11" }),
7948
+ /* @__PURE__ */ jsx14("path", { d: "m3 8 4 4-4 4" })
7949
+ ] }) : /* @__PURE__ */ jsx14("path", { d: "m6 9 6 6 6-6" })
7950
+ }
7951
+ )
7952
+ }
7953
+ ),
7954
+ /* @__PURE__ */ jsxs10(
7955
+ "button",
7956
+ {
7957
+ type: "button",
7958
+ className: `gantt-tl-name-trigger${disableTaskNameEditing ? " gantt-tl-name-locked" : ""}`,
7959
+ title: task.name,
7960
+ onClick: handleNameClick,
7961
+ onDoubleClick: handleNameDoubleClick,
7962
+ style: {
7963
+ paddingLeft: task.composite ? `${nestingDepth * 20 + 30}px` : nameTriggerPaddingLeft,
7964
+ paddingRight: task.color ? "20px" : void 0
7965
+ },
7966
+ children: [
7967
+ taskListNamePrefixIcon ? /* @__PURE__ */ jsx14("span", { className: "gantt-tl-name-trigger-icon", "aria-hidden": "true", children: taskListNamePrefixIcon }) : null,
7968
+ /* @__PURE__ */ jsx14("span", { className: "gantt-tl-name-trigger-text", children: task.name })
7969
+ ]
7970
+ }
7971
+ )
7972
+ ] }),
7756
7973
  !editingName && task.color && /* @__PURE__ */ jsx14(
7757
7974
  "span",
7758
7975
  {
@@ -8369,8 +8586,9 @@ var TaskListRow = React10.memo(
8369
8586
  isDragOver && isNestedDropTarget ? "gantt-tl-row-drag-over-nested" : "",
8370
8587
  isDragOver && isDirectChildDropTarget ? "gantt-tl-row-drag-over-direct-child" : "",
8371
8588
  isChild ? "gantt-tl-row-child" : "",
8372
- isParent ? "gantt-tl-row-parent" : "",
8373
- isParent && fillParentRow ? "gantt-tl-row-parent-filled" : "",
8589
+ isParent && !task.composite ? "gantt-tl-row-parent" : "",
8590
+ task.composite ? "gantt-tl-row-composite" : "",
8591
+ isParent && !task.composite && fillParentRow ? "gantt-tl-row-parent-filled" : "",
8374
8592
  `gantt-tl-row-level-${rowFillLevel}`,
8375
8593
  isTotalRow ? "gantt-tl-row-total" : "",
8376
8594
  rowClassName ?? ""
@@ -8378,6 +8596,7 @@ var TaskListRow = React10.memo(
8378
8596
  style: {
8379
8597
  height: `${rowHeight}px`,
8380
8598
  position: "relative",
8599
+ "--gantt-tl-composite-detail-color": compositeParentColor ?? "var(--gantt-task-bar-default-color, #8b5cf6)",
8381
8600
  "--gantt-tl-nested-drop-left": nestedDropIndicatorLeft,
8382
8601
  "--gantt-tl-nested-inside-drop-left": nestedInsideDropIndicatorLeft
8383
8602
  },
@@ -8519,7 +8738,7 @@ var TaskListRow = React10.memo(
8519
8738
  TaskListRow.displayName = "TaskListRow";
8520
8739
 
8521
8740
  // src/components/TaskList/NewTaskRow.tsx
8522
- import { useState as useState6, useRef as useRef5, useEffect as useEffect5 } from "react";
8741
+ import { useState as useState7, useRef as useRef6, useEffect as useEffect6 } from "react";
8523
8742
  import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
8524
8743
  var NewTaskRow = ({
8525
8744
  rowHeight,
@@ -8527,12 +8746,12 @@ var NewTaskRow = ({
8527
8746
  onCancel,
8528
8747
  nestingDepth = 0
8529
8748
  }) => {
8530
- const [nameValue, setNameValue] = useState6("");
8531
- const inputRef = useRef5(null);
8532
- const confirmedRef = useRef5(false);
8749
+ const [nameValue, setNameValue] = useState7("");
8750
+ const inputRef = useRef6(null);
8751
+ const confirmedRef = useRef6(false);
8533
8752
  const inputInnerPadding = 4;
8534
8753
  const levelOffset = nestingDepth > 0 ? nestingDepth * 20 + 8 : 0;
8535
- useEffect5(() => {
8754
+ useEffect6(() => {
8536
8755
  if (inputRef.current) {
8537
8756
  inputRef.current.focus();
8538
8757
  inputRef.current.select();
@@ -8585,7 +8804,7 @@ var NewTaskRow = ({
8585
8804
  };
8586
8805
 
8587
8806
  // src/components/GenerationSkeleton/GenerationSkeleton.tsx
8588
- import { useRef as useRef6 } from "react";
8807
+ import { useRef as useRef7 } from "react";
8589
8808
  import { Fragment as Fragment4, jsx as jsx16 } from "react/jsx-runtime";
8590
8809
  var MAX_SKELETON_ROWS = 200;
8591
8810
  var SHIMMER_DURATION_MS = 1450;
@@ -8603,14 +8822,15 @@ var GenerationSkeletonRows = ({
8603
8822
  rowHeight,
8604
8823
  variant,
8605
8824
  startIndex = 0,
8825
+ startTop,
8606
8826
  taskListLayout,
8607
8827
  chartDayWidth = 40,
8608
8828
  chartStartDayOffset = 0,
8609
8829
  chartStartJitterDays = 0
8610
8830
  }) => {
8611
8831
  const safeCount = clampCount(count);
8612
- const previousCountRef = useRef6(null);
8613
- const shimmerDelayRef = useRef6("0ms");
8832
+ const previousCountRef = useRef7(null);
8833
+ const shimmerDelayRef = useRef7("0ms");
8614
8834
  if (previousCountRef.current !== safeCount) {
8615
8835
  previousCountRef.current = safeCount;
8616
8836
  shimmerDelayRef.current = `-${Date.now() % SHIMMER_DURATION_MS}ms`;
@@ -8643,7 +8863,7 @@ var GenerationSkeletonRows = ({
8643
8863
  "div",
8644
8864
  {
8645
8865
  className: "gantt-generation-skeleton-row",
8646
- style: { top: `${rowIndex * rowHeight}px`, height: `${rowHeight}px` },
8866
+ style: { top: `${(startTop ?? startIndex * rowHeight) + index * rowHeight}px`, height: `${rowHeight}px` },
8647
8867
  children: variant === "task-list" ? /* @__PURE__ */ jsx16(Fragment4, { children: /* @__PURE__ */ jsx16("span", { className: `gantt-generation-skeleton-label ${shortShape ? "gantt-generation-skeleton-label-short" : ""} ${mediumShape ? "gantt-generation-skeleton-label-medium" : ""}` }) }) : /* @__PURE__ */ jsx16("span", { className: `gantt-generation-skeleton-bar ${parentShape ? "gantt-generation-skeleton-bar-parent" : ""} ${shortShape ? "gantt-generation-skeleton-bar-short" : ""}`, style: chartBarStyle })
8648
8868
  },
8649
8869
  rowIndex
@@ -8881,8 +9101,8 @@ var SelectAllCheckbox = ({
8881
9101
  indeterminate,
8882
9102
  onChange
8883
9103
  }) => {
8884
- const ref = useRef7(null);
8885
- useEffect6(() => {
9104
+ const ref = useRef8(null);
9105
+ useEffect7(() => {
8886
9106
  if (ref.current) {
8887
9107
  ref.current.indeterminate = indeterminate;
8888
9108
  }
@@ -8903,6 +9123,8 @@ var SelectAllCheckbox = ({
8903
9123
  var TaskList = ({
8904
9124
  tasks,
8905
9125
  rowHeight,
9126
+ rowTops,
9127
+ rowHeights,
8906
9128
  headerHeight,
8907
9129
  taskListWidth,
8908
9130
  onTasksChange,
@@ -8961,14 +9183,14 @@ var TaskList = ({
8961
9183
  skeletonRowCount = 0
8962
9184
  }) => {
8963
9185
  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);
9186
+ const [internalSelectedTaskIds, setInternalSelectedTaskIds] = useState8(/* @__PURE__ */ new Set());
9187
+ const [activeCustomCell, setActiveCustomCell] = useState8(null);
9188
+ const [columnWidthOverrides, setColumnWidthOverrides] = useState8(() => normalizeColumnWidthMap(taskListColumnWidths));
9189
+ const [isColumnResizeMode, setIsColumnResizeMode] = useState8(false);
9190
+ const columnResizeHeaderRef = useRef8(null);
9191
+ const resizeStateRef = useRef8(null);
8970
9192
  const effectiveSelectedTaskIds = selectedTaskIds ?? internalSelectedTaskIds;
8971
- useEffect6(() => {
9193
+ useEffect7(() => {
8972
9194
  if (!isColumnResizeMode) {
8973
9195
  return;
8974
9196
  }
@@ -8990,7 +9212,7 @@ var TaskList = ({
8990
9212
  }
8991
9213
  onSelectedTaskIdsChange?.(nextSelectedTaskIds);
8992
9214
  }, [onSelectedTaskIdsChange, selectedTaskIds]);
8993
- const [internalCollapsedParentIds, setInternalCollapsedParentIds] = useState7(/* @__PURE__ */ new Set());
9215
+ const [internalCollapsedParentIds, setInternalCollapsedParentIds] = useState8(/* @__PURE__ */ new Set());
8994
9216
  const collapsedParentIds = externalCollapsedParentIds ?? internalCollapsedParentIds;
8995
9217
  const handleToggleCollapse = externalOnToggleCollapse ?? useCallback6((parentId) => {
8996
9218
  setInternalCollapsedParentIds((prev) => {
@@ -9030,14 +9252,22 @@ var TaskList = ({
9030
9252
  return tasks2;
9031
9253
  }, [orderedTasks, collapsedParentIds, filterMode, filteredTaskIds, isFilterActive, criticalPathMode, criticalTaskIds]);
9032
9254
  const totalHeight = useMemo9(
9033
- () => (visibleTasks.length + Math.max(0, Math.floor(skeletonRowCount))) * rowHeight,
9034
- [rowHeight, skeletonRowCount, visibleTasks.length]
9255
+ () => (rowTops && rowHeights && visibleTasks.length > 0 ? rowTops[visibleTasks.length - 1] + rowHeights[visibleTasks.length - 1] : visibleTasks.length * rowHeight) + Math.max(0, Math.floor(skeletonRowCount)) * rowHeight,
9256
+ [rowHeight, rowTops, rowHeights, skeletonRowCount, visibleTasks.length]
9035
9257
  );
9036
9258
  const visibleTaskNumberMap = useMemo9(
9037
9259
  () => Object.fromEntries(buildTaskNumberMap(visibleTasks)),
9038
9260
  [visibleTasks]
9039
9261
  );
9040
9262
  const visibleTaskIds = useMemo9(() => visibleTasks.map((task) => task.id), [visibleTasks]);
9263
+ const compositeParentIds = useMemo9(
9264
+ () => new Set(tasks.filter((task) => task.composite).map((task) => task.id)),
9265
+ [tasks]
9266
+ );
9267
+ const compositeParentsById = useMemo9(
9268
+ () => new Map(tasks.filter((task) => task.composite).map((task) => [task.id, task])),
9269
+ [tasks]
9270
+ );
9041
9271
  const renderedVisibleRowIndices = useMemo9(
9042
9272
  () => visibleRowIndices ?? visibleTasks.map((_, index) => index),
9043
9273
  [visibleRowIndices, visibleTasks]
@@ -9148,14 +9378,14 @@ var TaskList = ({
9148
9378
  const handleRowClick = useCallback6((taskId) => {
9149
9379
  onTaskSelect?.(taskId);
9150
9380
  }, [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(() => {
9381
+ const [activeLinkType, setActiveLinkType] = useState8("FS");
9382
+ const [selectingPredecessorFor, setSelectingPredecessorFor] = useState8(null);
9383
+ const [dependencyPickMode, setDependencyPickMode] = useState8("successor");
9384
+ const [typeMenuOpen, setTypeMenuOpen] = useState8(false);
9385
+ const [dependencyError, setDependencyError] = useState8(null);
9386
+ const overlayRef = useRef8(null);
9387
+ const [selectedChip, setSelectedChip] = useState8(controlledSelectedChip ? { ...controlledSelectedChip, linkType: controlledSelectedChip.linkType } : null);
9388
+ useEffect7(() => {
9159
9389
  if (controlledSelectedChip !== void 0) {
9160
9390
  setSelectedChip(controlledSelectedChip ? { ...controlledSelectedChip, linkType: controlledSelectedChip.linkType } : null);
9161
9391
  }
@@ -9164,7 +9394,7 @@ var TaskList = ({
9164
9394
  setSelectedChip(chip);
9165
9395
  onSelectedChipChange?.(chip);
9166
9396
  }, [onSelectedChipChange]);
9167
- useEffect6(() => {
9397
+ useEffect7(() => {
9168
9398
  if (!selectingPredecessorFor && !selectedChip && !selectedTaskId && !activeCustomCell) return;
9169
9399
  const pendingCanvasMouseDownRef = {
9170
9400
  container: null,
@@ -9346,13 +9576,13 @@ var TaskList = ({
9346
9576
  );
9347
9577
  onTasksChange?.([{ ...task, dependencies: updatedDeps }]);
9348
9578
  }, [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);
9579
+ const [isCreating, setIsCreating] = useState8(false);
9580
+ const [pendingInsert, setPendingInsert] = useState8(null);
9581
+ const [draggingIndex, setDraggingIndex] = useState8(null);
9582
+ const [dragOverTarget, setDragOverTarget] = useState8(null);
9583
+ const [isBeforeFirstDropZoneActive, setIsBeforeFirstDropZoneActive] = useState8(false);
9584
+ const dragOriginIndexRef = useRef8(null);
9585
+ const dragTaskIdRef = useRef8(null);
9356
9586
  const clearDragState = useCallback6(() => {
9357
9587
  setDraggingIndex(null);
9358
9588
  setDragOverTarget(null);
@@ -9688,7 +9918,7 @@ var TaskList = ({
9688
9918
  () => createBuiltInColumns({ businessDays, enableTaskMultiSelect }),
9689
9919
  [businessDays, enableTaskMultiSelect]
9690
9920
  );
9691
- useEffect6(() => {
9921
+ useEffect7(() => {
9692
9922
  setColumnWidthOverrides(normalizeColumnWidthMap(taskListColumnWidths));
9693
9923
  }, [taskListColumnWidths]);
9694
9924
  const resolvedColumns = useMemo9(
@@ -9740,7 +9970,7 @@ var TaskList = ({
9740
9970
  return next;
9741
9971
  });
9742
9972
  }, [onTaskListColumnWidthsChange]);
9743
- useEffect6(() => {
9973
+ useEffect7(() => {
9744
9974
  const updateResizedColumn = (clientX) => {
9745
9975
  const resizeState = resizeStateRef.current;
9746
9976
  if (!resizeState) {
@@ -10029,7 +10259,7 @@ var TaskList = ({
10029
10259
  renderedVisibleTasks.map(({ task, index }) => {
10030
10260
  const previousVisibleTask = index > 0 ? visibleTasks[index - 1] : void 0;
10031
10261
  const canDemoteTask = index === 0 || !task.parentId || previousVisibleTask?.id !== task.parentId;
10032
- const topOffset = index * rowHeight + (hasVisiblePendingInsert && index > pendingInsertRowIndex ? rowHeight : 0);
10262
+ const topOffset = (rowTops?.[index] ?? index * rowHeight) + (hasVisiblePendingInsert && index > pendingInsertRowIndex ? rowHeight : 0);
10033
10263
  return /* @__PURE__ */ jsx17(
10034
10264
  "div",
10035
10265
  {
@@ -10046,7 +10276,7 @@ var TaskList = ({
10046
10276
  rowIndex: index,
10047
10277
  taskNumber: originalTaskNumberMap[task.id] || "",
10048
10278
  taskNumberMap: originalTaskNumberMap,
10049
- rowHeight,
10279
+ rowHeight: rowHeights?.[index] ?? rowHeight,
10050
10280
  onTasksChange,
10051
10281
  onDurationChange,
10052
10282
  selectedTaskId,
@@ -10103,7 +10333,8 @@ var TaskList = ({
10103
10333
  onActiveCustomCellChange: setActiveCustomCell,
10104
10334
  taskListMenuCommands,
10105
10335
  hideTaskListRowActions,
10106
- rowClassName: getTaskListRowClassName?.(task),
10336
+ rowClassName: [task.parentId && compositeParentIds.has(task.parentId) ? "gantt-tl-row-composite-detail" : "", getTaskListRowClassName?.(task)].filter(Boolean).join(" "),
10337
+ compositeParentColor: task.parentId ? compositeParentsById.get(task.parentId)?.color : void 0,
10107
10338
  getTaskListNamePrefixIcon,
10108
10339
  taskDateChangeMode,
10109
10340
  onTaskDateChangeModeChange,
@@ -10119,7 +10350,7 @@ var TaskList = ({
10119
10350
  {
10120
10351
  style: {
10121
10352
  position: "absolute",
10122
- top: `${(pendingInsertRowIndex + 1) * rowHeight}px`,
10353
+ top: `${rowTops?.[pendingInsertRowIndex + 1] ?? (pendingInsertRowIndex + 1) * rowHeight}px`,
10123
10354
  left: 0,
10124
10355
  right: 0
10125
10356
  },
@@ -10141,6 +10372,7 @@ var TaskList = ({
10141
10372
  rowHeight,
10142
10373
  variant: "task-list",
10143
10374
  startIndex: visibleTasks.length,
10375
+ startTop: totalHeight - Math.max(0, Math.floor(skeletonRowCount)) * rowHeight,
10144
10376
  taskListLayout: generationSkeletonTaskListLayout
10145
10377
  }
10146
10378
  )
@@ -10194,7 +10426,7 @@ var TaskList = ({
10194
10426
  };
10195
10427
 
10196
10428
  // 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";
10429
+ import React14, { useCallback as useCallback8, useEffect as useEffect9, useMemo as useMemo10, useRef as useRef10, useState as useState10 } from "react";
10198
10430
 
10199
10431
  // src/utils/resourceTimelineLayout.ts
10200
10432
  var isInvalidDate = (date) => Number.isNaN(date.getTime());
@@ -10396,7 +10628,7 @@ var layoutResourceTimelineItems = (resources, options) => {
10396
10628
  };
10397
10629
 
10398
10630
  // src/hooks/useResourceItemDrag.ts
10399
- import { useCallback as useCallback7, useEffect as useEffect7, useRef as useRef8, useState as useState8 } from "react";
10631
+ import { useCallback as useCallback7, useEffect as useEffect8, useRef as useRef9, useState as useState9 } from "react";
10400
10632
  var RESOURCE_CURSOR_STYLE_ID = "gantt-resource-global-drag-cursor-style";
10401
10633
  function ensureResourceGlobalCursorStyle() {
10402
10634
  if (typeof document === "undefined") return;
@@ -10508,15 +10740,15 @@ var useResourceItemDrag = ({
10508
10740
  weekendPredicate,
10509
10741
  onResourceItemMove
10510
10742
  }) => {
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(() => {
10743
+ const activeDragRef = useRef9(null);
10744
+ const rowsRef = useRef9(rows);
10745
+ const onResourceItemMoveRef = useRef9(onResourceItemMove);
10746
+ const rafRef = useRef9(null);
10747
+ const [preview, setPreview] = useState9(null);
10748
+ useEffect8(() => {
10517
10749
  rowsRef.current = rows;
10518
10750
  }, [rows]);
10519
- useEffect7(() => {
10751
+ useEffect8(() => {
10520
10752
  onResourceItemMoveRef.current = onResourceItemMove;
10521
10753
  }, [onResourceItemMove]);
10522
10754
  const clearRaf = useCallback7(() => {
@@ -10531,7 +10763,7 @@ var useResourceItemDrag = ({
10531
10763
  activeDragRef.current = null;
10532
10764
  setPreview(null);
10533
10765
  }, [clearRaf]);
10534
- useEffect7(() => {
10766
+ useEffect8(() => {
10535
10767
  const handleMouseMove = (event) => {
10536
10768
  const activeDrag = activeDragRef.current;
10537
10769
  if (!activeDrag || rafRef.current !== null) {
@@ -10864,13 +11096,13 @@ var ResourceHeader = ({
10864
11096
  onConflictBadgeClick,
10865
11097
  columnTemplate
10866
11098
  }) => {
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);
11099
+ const [menuOpen, setMenuOpen] = useState10(false);
11100
+ const [typeMenuOpen, setTypeMenuOpen] = useState10(false);
11101
+ const [scopeMenuOpen, setScopeMenuOpen] = useState10(false);
11102
+ const [editingName, setEditingName] = useState10(false);
11103
+ const [nameValue, setNameValue] = useState10(resource.name);
11104
+ const nameInputRef = useRef10(null);
11105
+ const nameConfirmedRef = useRef10(false);
10874
11106
  const visibleCommands = useMemo10(
10875
11107
  () => menuCommands.filter((command) => command.isVisible?.(resource) ?? true),
10876
11108
  [menuCommands, resource]
@@ -10883,12 +11115,12 @@ var ResourceHeader = ({
10883
11115
  const applyResourcePatch = useCallback8((patch) => {
10884
11116
  onResourceChange?.({ ...resource, ...patch });
10885
11117
  }, [onResourceChange, resource]);
10886
- useEffect8(() => {
11118
+ useEffect9(() => {
10887
11119
  if (!editingName) {
10888
11120
  setNameValue(resource.name);
10889
11121
  }
10890
11122
  }, [editingName, resource.name]);
10891
- useEffect8(() => {
11123
+ useEffect9(() => {
10892
11124
  if (editingName && nameInputRef.current) {
10893
11125
  nameInputRef.current.focus();
10894
11126
  nameInputRef.current.select();
@@ -11129,10 +11361,10 @@ var ResourceHeader = ({
11129
11361
  );
11130
11362
  };
11131
11363
  var NewResourceRow = ({ height, onConfirm, onCancel, columnTemplate }) => {
11132
- const [nameValue, setNameValue] = useState9("");
11133
- const inputRef = useRef9(null);
11134
- const confirmedRef = useRef9(false);
11135
- useEffect8(() => {
11364
+ const [nameValue, setNameValue] = useState10("");
11365
+ const inputRef = useRef10(null);
11366
+ const confirmedRef = useRef10(false);
11367
+ useEffect9(() => {
11136
11368
  inputRef.current?.focus();
11137
11369
  inputRef.current?.select();
11138
11370
  }, []);
@@ -11217,18 +11449,18 @@ function ResourceTimelineChart({
11217
11449
  resourceTableColumnWidths,
11218
11450
  onResourceTableColumnWidthsChange
11219
11451
  }) {
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));
11452
+ const scrollContainerRef = useRef10(null);
11453
+ const gridRef = useRef10(null);
11454
+ const panStateRef = useRef10(null);
11455
+ const resourceColumnResizeRef = useRef10(null);
11456
+ const conflictNavigationIndexRef = useRef10(/* @__PURE__ */ new Map());
11457
+ const resourceNavigationIndexRef = useRef10(/* @__PURE__ */ new Map());
11458
+ const conflictHighlightTimeoutRef = useRef10(null);
11459
+ const [isCreatingResource, setIsCreatingResource] = useState10(false);
11460
+ const [creatingResourceGroupType, setCreatingResourceGroupType] = useState10(null);
11461
+ const [activeConflictItemId, setActiveConflictItemId] = useState10(null);
11462
+ const [resourceColumnHasRightShadow, setResourceColumnHasRightShadow] = useState10(false);
11463
+ const [resourceColumnWidthOverrides, setResourceColumnWidthOverrides] = useState10(() => normalizeResourceColumnWidthMap(resourceTableColumnWidths));
11232
11464
  const validItems = useMemo10(() => collectValidItems(resources), [resources]);
11233
11465
  const dateRange = useMemo10(() => getMultiMonthDays(validItems), [validItems]);
11234
11466
  const monthStart = useMemo10(() => {
@@ -11240,7 +11472,7 @@ function ResourceTimelineChart({
11240
11472
  [customDays, isWeekend3]
11241
11473
  );
11242
11474
  const gridWidth = useMemo10(() => Math.round(dateRange.length * dayWidth), [dateRange.length, dayWidth]);
11243
- useEffect8(() => {
11475
+ useEffect9(() => {
11244
11476
  setResourceColumnWidthOverrides(normalizeResourceColumnWidthMap(resourceTableColumnWidths));
11245
11477
  }, [resourceTableColumnWidths]);
11246
11478
  const resolvedResourceColumnWidths = useMemo10(
@@ -11296,7 +11528,7 @@ function ResourceTimelineChart({
11296
11528
  );
11297
11529
  const canAddResource = enableAddResource && Boolean(onAddResource);
11298
11530
  const resourceAddRowHeight = laneHeight + DEFAULT_RESOURCE_ROW_GAP;
11299
- const [collapsedResourceGroups, setCollapsedResourceGroups] = useState9(/* @__PURE__ */ new Set());
11531
+ const [collapsedResourceGroups, setCollapsedResourceGroups] = useState10(/* @__PURE__ */ new Set());
11300
11532
  const updateResourceColumnWidth = useCallback8((columnId, width) => {
11301
11533
  setResourceColumnWidthOverrides((prev) => {
11302
11534
  if (prev[columnId] === width) {
@@ -11307,7 +11539,7 @@ function ResourceTimelineChart({
11307
11539
  return next;
11308
11540
  });
11309
11541
  }, [onResourceTableColumnWidthsChange]);
11310
- useEffect8(() => {
11542
+ useEffect9(() => {
11311
11543
  const handleMouseMove = (event) => {
11312
11544
  const resizeState = resourceColumnResizeRef.current;
11313
11545
  if (!resizeState) {
@@ -11576,7 +11808,7 @@ function ResourceTimelineChart({
11576
11808
  weekendPredicate,
11577
11809
  onResourceItemMove
11578
11810
  });
11579
- useEffect8(() => {
11811
+ useEffect9(() => {
11580
11812
  const container = scrollContainerRef.current;
11581
11813
  if (!container) return;
11582
11814
  const updateShadow = () => {
@@ -11616,7 +11848,7 @@ function ResourceTimelineChart({
11616
11848
  container.style.cursor = "grabbing";
11617
11849
  event.preventDefault();
11618
11850
  }, []);
11619
- useEffect8(() => {
11851
+ useEffect9(() => {
11620
11852
  const handlePanMove = (event) => {
11621
11853
  const pan = panStateRef.current;
11622
11854
  const container = scrollContainerRef.current;
@@ -11645,7 +11877,7 @@ function ResourceTimelineChart({
11645
11877
  window.removeEventListener("mouseup", handlePanEnd);
11646
11878
  };
11647
11879
  }, [allowVerticalPan]);
11648
- useEffect8(() => {
11880
+ useEffect9(() => {
11649
11881
  const nextNavigation = /* @__PURE__ */ new Map();
11650
11882
  for (const [resourceId, resourceItems] of itemsByResourceId.entries()) {
11651
11883
  if (resourceItems.length === 0) {
@@ -11655,7 +11887,7 @@ function ResourceTimelineChart({
11655
11887
  }
11656
11888
  resourceNavigationIndexRef.current = nextNavigation;
11657
11889
  }, [itemsByResourceId]);
11658
- useEffect8(() => {
11890
+ useEffect9(() => {
11659
11891
  return () => {
11660
11892
  if (conflictHighlightTimeoutRef.current) {
11661
11893
  window.clearTimeout(conflictHighlightTimeoutRef.current);
@@ -12075,7 +12307,7 @@ function ResourceTimelineChart({
12075
12307
  }
12076
12308
 
12077
12309
  // src/components/TableMatrix/TableMatrix.tsx
12078
- import { useLayoutEffect, useMemo as useMemo11, useRef as useRef10, useState as useState10 } from "react";
12310
+ import { useLayoutEffect, useMemo as useMemo11, useRef as useRef11, useState as useState11 } from "react";
12079
12311
  import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
12080
12312
  var AUTO_COLUMN_MIN_WIDTH = 72;
12081
12313
  var AUTO_COLUMN_MAX_WIDTH = 180;
@@ -12142,8 +12374,8 @@ function TableMatrix({
12142
12374
  filterMode = "highlight",
12143
12375
  visibleRowIndices
12144
12376
  }) {
12145
- const measureRef = useRef10(null);
12146
- const [measuredAutoWidths, setMeasuredAutoWidths] = useState10([]);
12377
+ const measureRef = useRef11(null);
12378
+ const [measuredAutoWidths, setMeasuredAutoWidths] = useState11([]);
12147
12379
  const hasAutoWidthColumns = useMemo11(
12148
12380
  () => columns.some((column) => typeof column.width !== "number"),
12149
12381
  [columns]
@@ -12443,7 +12675,7 @@ function TableMatrix({
12443
12675
  }
12444
12676
 
12445
12677
  // 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";
12678
+ import React16, { useCallback as useCallback9, useEffect as useEffect10, useMemo as useMemo12, useRef as useRef12, useState as useState12 } from "react";
12447
12679
  import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
12448
12680
  var DAY_MS5 = 24 * 60 * 60 * 1e3;
12449
12681
  function joinClasses2(...values) {
@@ -12535,9 +12767,9 @@ function PlanFactCellEditor({
12535
12767
  onCommitRange,
12536
12768
  onCancel
12537
12769
  }) {
12538
- const [draft, setDraft] = useState11(startValue ?? (value === void 0 ? "" : String(value)));
12539
- const inputRef = useRef11(null);
12540
- useEffect9(() => {
12770
+ const [draft, setDraft] = useState12(startValue ?? (value === void 0 ? "" : String(value)));
12771
+ const inputRef = useRef12(null);
12772
+ useEffect10(() => {
12541
12773
  inputRef.current?.focus();
12542
12774
  if (startValue === void 0) {
12543
12775
  inputRef.current?.select();
@@ -12890,18 +13122,18 @@ function PlanFactMatrix({
12890
13122
  todayDateIndex,
12891
13123
  isCustomWeekend
12892
13124
  }) {
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);
13125
+ const [activeCell, setActiveCell] = useState12(null);
13126
+ const [editingCell, setEditingCell] = useState12(null);
13127
+ const [selectedRange, setSelectedRange] = useState12(null);
13128
+ const [fillRange, setFillRange] = useState12(null);
13129
+ const [overflowTooltip, setOverflowTooltip] = useState12(null);
13130
+ const isSelectingRef = useRef12(false);
13131
+ const isFillDraggingRef = useRef12(false);
13132
+ const didDragSelectRef = useRef12(false);
13133
+ const pendingHoverCellRef = useRef12(null);
13134
+ const hoverFrameRef = useRef12(null);
13135
+ const rootRef = useRef12(null);
13136
+ const bodyRef = useRef12(null);
12905
13137
  const totalWidth = dateRange.length * dayWidth;
12906
13138
  const subrowHeight = rowHeight / 2;
12907
13139
  const renderedRowIndices = useMemo12(
@@ -13307,7 +13539,7 @@ function PlanFactMatrix({
13307
13539
  onTaskSelect?.(nextCell.taskId);
13308
13540
  focusCell(selectedRange?.anchor ?? cell);
13309
13541
  }, [dateRange.length, focusCell, onTaskSelect, parentTaskIds, selectedRange, tasks]);
13310
- useEffect9(() => {
13542
+ useEffect10(() => {
13311
13543
  const endSelection = () => {
13312
13544
  let pendingFillRange = null;
13313
13545
  if (hoverFrameRef.current !== null) {
@@ -13330,12 +13562,12 @@ function PlanFactMatrix({
13330
13562
  window.removeEventListener("mouseup", endSelection);
13331
13563
  };
13332
13564
  }, [applyFillRange, flushPendingHoverCell]);
13333
- useEffect9(() => () => {
13565
+ useEffect10(() => () => {
13334
13566
  if (hoverFrameRef.current !== null) {
13335
13567
  window.cancelAnimationFrame(hoverFrameRef.current);
13336
13568
  }
13337
13569
  }, []);
13338
- useEffect9(() => {
13570
+ useEffect10(() => {
13339
13571
  const handleKeyDown = (event) => {
13340
13572
  if (event.key !== "Escape") return;
13341
13573
  clearSelection();
@@ -14138,31 +14370,31 @@ function TaskGanttChartInner(props, ref) {
14138
14370
  const onMatrixCellClick = isTableMatrixMode ? props.onMatrixCellClick : void 0;
14139
14371
  const matrixDateOverlay = isTableMatrixMode ? props.matrixDateOverlay : void 0;
14140
14372
  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());
14373
+ const containerRef = useRef13(null);
14374
+ const scrollContainerRef = useRef13(null);
14375
+ const scrollContentRef = useRef13(null);
14376
+ const clearSelectedTaskTimeoutRef = useRef13(null);
14377
+ const hasAutoScrolledToTodayRef = useRef13(false);
14378
+ const previewPositionStoreRef = useRef13(null);
14379
+ const renderedTaskIdsRef = useRef13(/* @__PURE__ */ new Set());
14148
14380
  if (previewPositionStoreRef.current === null) {
14149
14381
  previewPositionStoreRef.current = createTaskPreviewPositionStore();
14150
14382
  }
14151
14383
  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(() => ({
14384
+ const [selectedTaskId, setSelectedTaskId] = useState13(null);
14385
+ const [taskListHasRightShadow, setTaskListHasRightShadow] = useState13(false);
14386
+ const [internalTaskDateChangeMode, setInternalTaskDateChangeMode] = useState13("preserve-duration");
14387
+ const [scrollViewport, setScrollViewport] = useState13(() => ({
14156
14388
  scrollTop: 0,
14157
14389
  viewportHeight: getInitialScrollViewportHeight(containerHeight, headerHeight + 1)
14158
14390
  }));
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);
14391
+ const [forceFullRenderForPrint, setForceFullRenderForPrint] = useState13(false);
14392
+ const [planFactDateWindow, setPlanFactDateWindow] = useState13({ start: 0, end: 0 });
14393
+ const [selectedChip, setSelectedChip] = useState13(null);
14394
+ const [dependencyCreationDrag, setDependencyCreationDrag] = useState13(null);
14395
+ const dependencyCreationRef = useRef13(null);
14396
+ const [dependencyCreationError, setDependencyCreationError] = useState13(null);
14397
+ const [dependencyLineMenu, setDependencyLineMenu] = useState13(null);
14166
14398
  const getDependencyPortPoint = useCallback10((element, chartRect) => {
14167
14399
  const rect = element.getBoundingClientRect();
14168
14400
  const side = element.getAttribute("data-port-side");
@@ -14196,7 +14428,7 @@ function TaskGanttChartInner(props, ref) {
14196
14428
  dependencyCreationRef.current = nextDrag;
14197
14429
  setDependencyCreationDrag(nextDrag);
14198
14430
  }, [disableDependencyEditing, getDependencyPortPoint]);
14199
- useEffect10(() => {
14431
+ useEffect11(() => {
14200
14432
  if (!dependencyCreationDrag) return;
14201
14433
  const handlePointerMove = (event) => {
14202
14434
  const activeDrag = dependencyCreationRef.current;
@@ -14280,12 +14512,12 @@ function TaskGanttChartInner(props, ref) {
14280
14512
  document.removeEventListener("keydown", handleKeyDown);
14281
14513
  };
14282
14514
  }, [businessDays, customDays, dependencyCreationDrag !== null, getDependencyPortPoint, getVisualDependencyType, isWeekend3, onTasksChange, tasks]);
14283
- useEffect10(() => {
14515
+ useEffect11(() => {
14284
14516
  if (!dependencyCreationError) return;
14285
14517
  const timeout = window.setTimeout(() => setDependencyCreationError(null), 3e3);
14286
14518
  return () => window.clearTimeout(timeout);
14287
14519
  }, [dependencyCreationError]);
14288
- useEffect10(() => {
14520
+ useEffect11(() => {
14289
14521
  if (!dependencyLineMenu) return;
14290
14522
  const closeMenu = (event) => {
14291
14523
  const target = event.target;
@@ -14323,10 +14555,10 @@ function TaskGanttChartInner(props, ref) {
14323
14555
  setSelectedChip(null);
14324
14556
  setDependencyLineMenu(null);
14325
14557
  }, [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);
14558
+ const [activeTimelineTooltip, setActiveTimelineTooltip] = useState13(null);
14559
+ const [internalCollapsedParentIds, setInternalCollapsedParentIds] = useState13(/* @__PURE__ */ new Set());
14560
+ const [expandedCompositeIds, setExpandedCompositeIds] = useState13(/* @__PURE__ */ new Set());
14561
+ const [editingTaskId, setEditingTaskId] = useState13(null);
14330
14562
  const taskDateChangeMode = externalTaskDateChangeMode ?? internalTaskDateChangeMode;
14331
14563
  const handleTaskDateChangeMode = externalOnTaskDateChangeModeChange ?? setInternalTaskDateChangeMode;
14332
14564
  const resolvedRowContentLines = isPlanFactMode ? Math.max(2, Math.floor(rowContentLines)) : Math.max(1, Math.floor(rowContentLines));
@@ -14335,14 +14567,43 @@ function TaskGanttChartInner(props, ref) {
14335
14567
  () => Math.max(rowHeight, 10 + resolvedRowContentLines * 18),
14336
14568
  [resolvedRowContentLines, rowHeight]
14337
14569
  );
14338
- const normalizedTasks = useMemo13(() => normalizeHierarchyTasks(tasks), [tasks]);
14570
+ const normalizedTasks = useMemo13(() => {
14571
+ const ordered = normalizeHierarchyTasks(tasks);
14572
+ const ranges = /* @__PURE__ */ new Map();
14573
+ for (const task of ordered) {
14574
+ if (!task.parentId) continue;
14575
+ const start = parseUTCDate(task.startDate).getTime();
14576
+ const end = parseUTCDate(task.endDate).getTime();
14577
+ const range = ranges.get(task.parentId);
14578
+ ranges.set(task.parentId, {
14579
+ start: Math.min(range?.start ?? start, start),
14580
+ end: Math.max(range?.end ?? end, end)
14581
+ });
14582
+ }
14583
+ return ordered.map((task) => {
14584
+ const range = task.composite ? ranges.get(task.id) : void 0;
14585
+ return range ? {
14586
+ ...task,
14587
+ startDate: new Date(range.start).toISOString().slice(0, 10),
14588
+ endDate: new Date(range.end).toISOString().slice(0, 10)
14589
+ } : task;
14590
+ });
14591
+ }, [tasks]);
14592
+ const collapsedParentIds = useMemo13(() => {
14593
+ if (externalCollapsedParentIds) return externalCollapsedParentIds;
14594
+ const ids = new Set(internalCollapsedParentIds);
14595
+ for (const task of normalizedTasks) {
14596
+ if (task.composite && !expandedCompositeIds.has(task.id)) ids.add(task.id);
14597
+ }
14598
+ return ids;
14599
+ }, [externalCollapsedParentIds, internalCollapsedParentIds, normalizedTasks, expandedCompositeIds]);
14339
14600
  const controlledScheduleIdentity = useMemo13(
14340
14601
  () => buildControlledScheduleIdentity(normalizedTasks),
14341
14602
  [normalizedTasks]
14342
14603
  );
14343
- const previousControlledScheduleIdentityRef = useRef12(null);
14604
+ const previousControlledScheduleIdentityRef = useRef13(null);
14344
14605
  const controlledScheduleChanged = previousControlledScheduleIdentityRef.current !== null && previousControlledScheduleIdentityRef.current !== controlledScheduleIdentity;
14345
- const [previewTasksById, setPreviewTasksById] = useState12(/* @__PURE__ */ new Map());
14606
+ const [previewTasksById, setPreviewTasksById] = useState13(/* @__PURE__ */ new Map());
14346
14607
  const previewNormalizedTasks = useMemo13(() => {
14347
14608
  if (controlledScheduleChanged || previewTasksById.size === 0) return normalizedTasks;
14348
14609
  return normalizedTasks.map((task) => previewTasksById.get(task.id) ?? task);
@@ -14382,8 +14643,8 @@ function TaskGanttChartInner(props, ref) {
14382
14643
  },
14383
14644
  [dateRangeTasks, explicitDateRange, isPlanFactMode]
14384
14645
  );
14385
- const [validationResult, setValidationResult] = useState12(null);
14386
- const [cascadeOverrides, setCascadeOverrides] = useState12(/* @__PURE__ */ new Map());
14646
+ const [validationResult, setValidationResult] = useState13(null);
14647
+ const [cascadeOverrides, setCascadeOverrides] = useState13(/* @__PURE__ */ new Map());
14387
14648
  const gridWidth = useMemo13(
14388
14649
  () => Math.round(dateRange.length * dayWidth),
14389
14650
  [dateRange.length, dayWidth]
@@ -14415,6 +14676,23 @@ function TaskGanttChartInner(props, ref) {
14415
14676
  }
14416
14677
  return tasks2;
14417
14678
  }, [normalizedTasks, collapsedParentIds, filterMode, taskFilter, criticalPathMode, criticalTaskIds]);
14679
+ const compositeRowLayout = useMemo13(() => {
14680
+ const compositeIds = new Set(normalizedTasks.filter((task) => task.composite).map((task) => task.id));
14681
+ const tops = [];
14682
+ const heights = [];
14683
+ let top = 0;
14684
+ for (const task of visibleTasks) {
14685
+ tops.push(top);
14686
+ const height = task.parentId && compositeIds.has(task.parentId) ? Math.min(effectiveRowHeight, 22) : effectiveRowHeight;
14687
+ heights.push(height);
14688
+ top += height;
14689
+ }
14690
+ return { tops, heights, totalHeight: top };
14691
+ }, [normalizedTasks, visibleTasks, effectiveRowHeight]);
14692
+ const compositeParentsById = useMemo13(
14693
+ () => new Map(normalizedTasks.filter((task) => task.composite).map((task) => [task.id, task])),
14694
+ [normalizedTasks]
14695
+ );
14418
14696
  const matchedTaskIds = useMemo13(() => {
14419
14697
  if (!taskFilter) return /* @__PURE__ */ new Set();
14420
14698
  return new Set(visibleTasks.filter(taskFilter).map((task) => task.id));
@@ -14436,8 +14714,8 @@ function TaskGanttChartInner(props, ref) {
14436
14714
  return mergedHighlightedTaskIds;
14437
14715
  }, [filterMode, highlightedTaskIds, matchedTaskIds, criticalPathMode, criticalTaskIds]);
14438
14716
  const totalGridHeight = useMemo13(
14439
- () => (visibleTasks.length + Math.max(0, Math.floor(skeletonRowCount))) * effectiveRowHeight,
14440
- [effectiveRowHeight, skeletonRowCount, visibleTasks.length]
14717
+ () => compositeRowLayout.totalHeight + Math.max(0, Math.floor(skeletonRowCount)) * effectiveRowHeight,
14718
+ [compositeRowLayout.totalHeight, effectiveRowHeight, skeletonRowCount]
14441
14719
  );
14442
14720
  const timelineHeaderHeight = headerHeight + 1;
14443
14721
  const tableBodyMinHeight = useMemo13(() => {
@@ -14475,7 +14753,7 @@ function TaskGanttChartInner(props, ref) {
14475
14753
  return markerDate >= rangeStartMs && markerDate <= rangeEndMs;
14476
14754
  });
14477
14755
  }, [dateRange, isTableMatrixMode, timelineMarkers]);
14478
- useEffect10(() => {
14756
+ useEffect11(() => {
14479
14757
  if (isTableMatrixMode) return;
14480
14758
  if (hasAutoScrolledToTodayRef.current) return;
14481
14759
  const container = scrollContainerRef.current;
@@ -14489,7 +14767,7 @@ function TaskGanttChartInner(props, ref) {
14489
14767
  container.scrollLeft = Math.max(0, scrollLeft);
14490
14768
  hasAutoScrolledToTodayRef.current = true;
14491
14769
  }, [dateRange, dayWidth, isTableMatrixMode]);
14492
- useEffect10(() => {
14770
+ useEffect11(() => {
14493
14771
  const container = scrollContainerRef.current;
14494
14772
  if (!container) return;
14495
14773
  let frameId = null;
@@ -14564,7 +14842,7 @@ function TaskGanttChartInner(props, ref) {
14564
14842
  const rowIndex = visibleTasks.findIndex((visibleTask) => visibleTask.id === taskId);
14565
14843
  if (rowIndex === -1) return;
14566
14844
  const paddedRowIndex = Math.max(0, rowIndex - SCROLL_TO_ROW_CONTEXT_ROWS);
14567
- container2.scrollTo({ top: Math.max(0, effectiveRowHeight * paddedRowIndex), behavior: "smooth" });
14845
+ container2.scrollTo({ top: Math.max(0, compositeRowLayout.tops[paddedRowIndex] ?? 0), behavior: "smooth" });
14568
14846
  setSelectedTaskId(taskId);
14569
14847
  return;
14570
14848
  }
@@ -14583,7 +14861,7 @@ function TaskGanttChartInner(props, ref) {
14583
14861
  const taskOffset = taskIndex * dayWidth;
14584
14862
  const scrollLeft = Math.round(taskOffset - dayWidth * 2);
14585
14863
  container.scrollTo({ left: Math.max(0, scrollLeft), behavior: "smooth" });
14586
- }, [dateRange, dayWidth, effectiveRowHeight, isTableMatrixMode, tasks, visibleTasks]);
14864
+ }, [dateRange, dayWidth, effectiveRowHeight, compositeRowLayout, isTableMatrixMode, tasks, visibleTasks]);
14587
14865
  const scrollToRow = useCallback10((taskId, options = {}) => {
14588
14866
  const container = scrollContainerRef.current;
14589
14867
  if (!container) return;
@@ -14592,7 +14870,7 @@ function TaskGanttChartInner(props, ref) {
14592
14870
  const rowIndex = visibleTasks.findIndex((visibleTask) => visibleTask.id === task.id);
14593
14871
  if (rowIndex === -1) return;
14594
14872
  const paddedRowIndex = Math.max(0, rowIndex - SCROLL_TO_ROW_CONTEXT_ROWS);
14595
- const scrollTop = Math.max(0, effectiveRowHeight * paddedRowIndex);
14873
+ const scrollTop = Math.max(0, compositeRowLayout.tops[paddedRowIndex] ?? 0);
14596
14874
  const {
14597
14875
  select = true,
14598
14876
  behavior = "smooth",
@@ -14612,10 +14890,10 @@ function TaskGanttChartInner(props, ref) {
14612
14890
  }
14613
14891
  }
14614
14892
  container.scrollTo({ top: scrollTop, behavior });
14615
- }, [effectiveRowHeight, tasks, visibleTasks]);
14616
- const [dragGuideLines, setDragGuideLines] = useState12(null);
14617
- const [draggedTaskOverride, setDraggedTaskOverride] = useState12(null);
14618
- useEffect10(() => {
14893
+ }, [effectiveRowHeight, compositeRowLayout, tasks, visibleTasks]);
14894
+ const [dragGuideLines, setDragGuideLines] = useState13(null);
14895
+ const [draggedTaskOverride, setDraggedTaskOverride] = useState13(null);
14896
+ useEffect11(() => {
14619
14897
  const previousIdentity = previousControlledScheduleIdentityRef.current;
14620
14898
  previousControlledScheduleIdentityRef.current = controlledScheduleIdentity;
14621
14899
  if (previousIdentity === null || previousIdentity === controlledScheduleIdentity) {
@@ -14627,21 +14905,21 @@ function TaskGanttChartInner(props, ref) {
14627
14905
  setDraggedTaskOverride((current) => current === null ? current : null);
14628
14906
  setDragGuideLines((current) => current === null ? current : null);
14629
14907
  }, [controlledScheduleIdentity, previewPositionStore]);
14630
- useEffect10(() => {
14908
+ useEffect11(() => {
14631
14909
  const result = validateDependencies(tasks);
14632
14910
  setValidationResult(result);
14633
14911
  onValidateDependencies?.(result);
14634
14912
  }, [tasks, onValidateDependencies]);
14635
- useEffect10(() => () => {
14913
+ useEffect11(() => () => {
14636
14914
  if (clearSelectedTaskTimeoutRef.current !== null) {
14637
14915
  window.clearTimeout(clearSelectedTaskTimeoutRef.current);
14638
14916
  }
14639
14917
  previewPositionStore.clear();
14640
14918
  }, [previewPositionStore]);
14641
14919
  const handleTaskChange = useCallback10((updatedTasks, durationEdit) => {
14642
- const updatedTask = updatedTasks[0];
14920
+ let updatedTask = updatedTasks[0];
14643
14921
  if (!updatedTask) return;
14644
- const originalTask = tasks.find((t) => t.id === updatedTask.id);
14922
+ const originalTask = normalizedTasks.find((t) => t.id === updatedTask.id);
14645
14923
  if (!originalTask) {
14646
14924
  onTasksChange?.(updatedTasks);
14647
14925
  if (editingTaskId === updatedTask.id) {
@@ -14654,6 +14932,8 @@ function TaskGanttChartInner(props, ref) {
14654
14932
  const newStart = new Date(updatedTask.startDate);
14655
14933
  const newEnd = new Date(updatedTask.endDate);
14656
14934
  const datesChanged = origStart.getTime() !== newStart.getTime() || origEnd.getTime() !== newEnd.getTime();
14935
+ const isCompositeDetail = datesChanged && tasks.some((task) => task.id === updatedTask.parentId && task.composite);
14936
+ if (isCompositeDetail) updatedTask = { ...updatedTask, synced: false };
14657
14937
  if (!datesChanged) {
14658
14938
  if (updatedTasks.length > 1) {
14659
14939
  onTasksChange?.(updatedTasks);
@@ -14715,7 +14995,7 @@ function TaskGanttChartInner(props, ref) {
14715
14995
  }
14716
14996
  return;
14717
14997
  }
14718
- const movedParentCascade = disableConstraints ? [updatedTask] : universalCascade(updatedTask, newStart, newEnd, tasks, businessDays, isCustomWeekend);
14998
+ const movedParentCascade = disableConstraints && !originalTask.composite ? [updatedTask] : universalCascade(updatedTask, newStart, newEnd, normalizedTasks, businessDays, isCustomWeekend);
14719
14999
  if (onScheduleIntent) {
14720
15000
  onScheduleIntent({
14721
15001
  type: "move_task",
@@ -14739,7 +15019,8 @@ function TaskGanttChartInner(props, ref) {
14739
15019
  taskId: updatedTask.id,
14740
15020
  duration: durationEdit.duration,
14741
15021
  anchor: durationEdit.anchor,
14742
- taskType: updatedTask.type ?? "task"
15022
+ taskType: updatedTask.type ?? "task",
15023
+ ...isCompositeDetail ? { localOverride: true } : {}
14743
15024
  });
14744
15025
  } else {
14745
15026
  onTasksChange?.(cascadedTasks);
@@ -14751,7 +15032,8 @@ function TaskGanttChartInner(props, ref) {
14751
15032
  onScheduleIntent({
14752
15033
  type: "move_task",
14753
15034
  taskId: updatedTask.id,
14754
- startDate: newStart.toISOString().split("T")[0]
15035
+ startDate: newStart.toISOString().split("T")[0],
15036
+ ...isCompositeDetail ? { localOverride: true } : {}
14755
15037
  });
14756
15038
  } else {
14757
15039
  const anchor = startChanged ? "end" : "start";
@@ -14759,13 +15041,14 @@ function TaskGanttChartInner(props, ref) {
14759
15041
  type: "resize_task",
14760
15042
  taskId: updatedTask.id,
14761
15043
  anchor,
14762
- date: (startChanged ? newStart : newEnd).toISOString().split("T")[0]
15044
+ date: (startChanged ? newStart : newEnd).toISOString().split("T")[0],
15045
+ ...isCompositeDetail ? { localOverride: true } : {}
14763
15046
  });
14764
15047
  }
14765
15048
  } else {
14766
15049
  onTasksChange?.(cascadedTasks);
14767
15050
  }
14768
- }, [tasks, onTasksChange, onScheduleIntent, disableConstraints, editingTaskId, businessDays, isCustomWeekend, onSubtreeScaleResult]);
15051
+ }, [tasks, normalizedTasks, onTasksChange, onScheduleIntent, disableConstraints, editingTaskId, businessDays, isCustomWeekend, onSubtreeScaleResult]);
14769
15052
  const handleTaskDurationChange = useCallback10((task, duration) => {
14770
15053
  const updatedTask = {
14771
15054
  ...task,
@@ -14879,12 +15162,25 @@ function TaskGanttChartInner(props, ref) {
14879
15162
  if (scrollViewport.viewportHeight <= 0) {
14880
15163
  return Array.from({ length: totalTasks }, (_, index) => index);
14881
15164
  }
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
- );
15165
+ let low = 0;
15166
+ let high = totalTasks;
15167
+ while (low < high) {
15168
+ const middle = low + high >>> 1;
15169
+ if (compositeRowLayout.tops[middle] + compositeRowLayout.heights[middle] < scrollViewport.scrollTop) low = middle + 1;
15170
+ else high = middle;
15171
+ }
15172
+ const first = Math.min(low, totalTasks - 1);
15173
+ const viewportBottom = scrollViewport.scrollTop + scrollViewport.viewportHeight;
15174
+ low = first;
15175
+ high = totalTasks;
15176
+ while (low < high) {
15177
+ const middle = low + high >>> 1;
15178
+ if (compositeRowLayout.tops[middle] < viewportBottom) low = middle + 1;
15179
+ else high = middle;
15180
+ }
15181
+ const last = Math.min(low, totalTasks - 1);
15182
+ const rangeStart = Math.max(0, first - TASK_ROW_OVERSCAN);
15183
+ const rangeEnd = Math.min(totalTasks - 1, last + TASK_ROW_OVERSCAN);
14888
15184
  const indices = /* @__PURE__ */ new Set();
14889
15185
  for (let index = rangeStart; index <= rangeEnd; index += 1) {
14890
15186
  indices.add(index);
@@ -14896,7 +15192,7 @@ function TaskGanttChartInner(props, ref) {
14896
15192
  }
14897
15193
  }
14898
15194
  return Array.from(indices).sort((left, right) => left - right);
14899
- }, [effectiveRowHeight, forceFullRenderForPrint, forcedRenderedTaskIds, scrollViewport, visibleTaskIndexMap, visibleTasks.length]);
15195
+ }, [compositeRowLayout, forceFullRenderForPrint, forcedRenderedTaskIds, scrollViewport, visibleTaskIndexMap, visibleTasks.length]);
14900
15196
  const visiblePlanFactDateIndices = useMemo13(() => {
14901
15197
  if (forceFullRenderForPrint) {
14902
15198
  return void 0;
@@ -14933,7 +15229,7 @@ function TaskGanttChartInner(props, ref) {
14933
15229
  const handleTaskSelect = useCallback10((taskId) => {
14934
15230
  setSelectedTaskId(taskId);
14935
15231
  }, []);
14936
- const hoveredRowElementsRef = useRef12([]);
15232
+ const hoveredRowElementsRef = useRef13([]);
14937
15233
  const clearHoveredRows = useCallback10(() => {
14938
15234
  for (const element of hoveredRowElementsRef.current) {
14939
15235
  element.classList.remove("gantt-tl-row-hovered", "gantt-tr-row-hovered", "gantt-mx-row-hovered", "gantt-pf-row-hovered");
@@ -14973,7 +15269,16 @@ function TaskGanttChartInner(props, ref) {
14973
15269
  }
14974
15270
  applyHoveredRows(taskId);
14975
15271
  }, [applyHoveredRows]);
14976
- const handleToggleCollapse = externalOnToggleCollapse ?? useCallback10((parentId) => {
15272
+ const handleToggleCollapse = externalOnToggleCollapse ?? ((parentId) => {
15273
+ if (normalizedTasks.some((task) => task.id === parentId && task.composite)) {
15274
+ setExpandedCompositeIds((prev) => {
15275
+ const next = new Set(prev);
15276
+ if (next.has(parentId)) next.delete(parentId);
15277
+ else next.add(parentId);
15278
+ return next;
15279
+ });
15280
+ return;
15281
+ }
14977
15282
  setInternalCollapsedParentIds((prev) => {
14978
15283
  const next = new Set(prev);
14979
15284
  if (next.has(parentId)) {
@@ -14983,7 +15288,14 @@ function TaskGanttChartInner(props, ref) {
14983
15288
  }
14984
15289
  return next;
14985
15290
  });
14986
- }, []);
15291
+ });
15292
+ const lastCompositeBarToggleRef = useRef13(null);
15293
+ const handleCompositeBarToggle = (taskId, source) => {
15294
+ const now = Date.now();
15295
+ if (source === "click" && lastCompositeBarToggleRef.current?.source === "release" && lastCompositeBarToggleRef.current.id === taskId && now - lastCompositeBarToggleRef.current.time < 150) return;
15296
+ lastCompositeBarToggleRef.current = { id: taskId, time: now, source };
15297
+ handleToggleCollapse(taskId);
15298
+ };
14987
15299
  const allParentIds = useMemo13(() => {
14988
15300
  const taskIds = new Set(normalizedTasks.map((task) => task.id));
14989
15301
  return new Set(normalizedTasks.map((task) => task.parentId).filter((parentId) => Boolean(parentId && taskIds.has(parentId))));
@@ -14991,11 +15303,13 @@ function TaskGanttChartInner(props, ref) {
14991
15303
  const handleCollapseAll = useCallback10(() => {
14992
15304
  if (externalCollapsedParentIds) return;
14993
15305
  setInternalCollapsedParentIds(allParentIds);
15306
+ setExpandedCompositeIds(/* @__PURE__ */ new Set());
14994
15307
  }, [allParentIds, externalCollapsedParentIds]);
14995
15308
  const handleExpandAll = useCallback10(() => {
14996
15309
  if (externalCollapsedParentIds) return;
14997
15310
  setInternalCollapsedParentIds(/* @__PURE__ */ new Set());
14998
- }, [externalCollapsedParentIds]);
15311
+ setExpandedCompositeIds(new Set(normalizedTasks.filter((task) => task.composite).map((task) => task.id)));
15312
+ }, [externalCollapsedParentIds, normalizedTasks]);
14999
15313
  const exportToPdf = useCallback10(async (options) => {
15000
15314
  const sourceContainer = containerRef.current;
15001
15315
  if (!sourceContainer || typeof window === "undefined" || typeof document === "undefined") {
@@ -15167,7 +15481,7 @@ function TaskGanttChartInner(props, ref) {
15167
15481
  onTasksChange?.(changedTasks);
15168
15482
  }
15169
15483
  }, [tasks, onTasksChange, onUngroupTask]);
15170
- const panStateRef = useRef12(null);
15484
+ const panStateRef = useRef13(null);
15171
15485
  const handlePanStart = useCallback10((e) => {
15172
15486
  if (e.button !== 0) return;
15173
15487
  const target = e.target;
@@ -15192,7 +15506,7 @@ function TaskGanttChartInner(props, ref) {
15192
15506
  container.style.cursor = "grabbing";
15193
15507
  e.preventDefault();
15194
15508
  }, []);
15195
- useEffect10(() => {
15509
+ useEffect11(() => {
15196
15510
  const flushPanMove = () => {
15197
15511
  const pan = panStateRef.current;
15198
15512
  if (!pan?.active) return;
@@ -15260,6 +15574,8 @@ function TaskGanttChartInner(props, ref) {
15260
15574
  {
15261
15575
  tasks: normalizedTasks,
15262
15576
  rowHeight: effectiveRowHeight,
15577
+ rowTops: compositeRowLayout.tops,
15578
+ rowHeights: compositeRowLayout.heights,
15263
15579
  headerHeight,
15264
15580
  taskListWidth,
15265
15581
  onTasksChange: handleTaskChange,
@@ -15454,6 +15770,8 @@ function TaskGanttChartInner(props, ref) {
15454
15770
  gridWidth: renderGridWidth,
15455
15771
  totalHeight: totalGridHeight,
15456
15772
  rowIndexByTaskId: visibleTaskIndexMap,
15773
+ rowTops: compositeRowLayout.tops,
15774
+ rowHeights: compositeRowLayout.heights,
15457
15775
  dragOverrides: dependencyOverrides,
15458
15776
  selectedDep: selectedChip,
15459
15777
  businessDays,
@@ -15540,10 +15858,10 @@ function TaskGanttChartInner(props, ref) {
15540
15858
  {
15541
15859
  style: {
15542
15860
  position: "absolute",
15543
- top: `${index * effectiveRowHeight}px`,
15861
+ top: `${compositeRowLayout.tops[index]}px`,
15544
15862
  left: 0,
15545
15863
  right: 0,
15546
- height: `${effectiveRowHeight}px`
15864
+ height: `${compositeRowLayout.heights[index]}px`
15547
15865
  },
15548
15866
  children: /* @__PURE__ */ jsx21(
15549
15867
  TaskRow_default,
@@ -15551,7 +15869,9 @@ function TaskGanttChartInner(props, ref) {
15551
15869
  task,
15552
15870
  monthStart,
15553
15871
  dayWidth,
15554
- rowHeight: effectiveRowHeight,
15872
+ rowHeight: compositeRowLayout.heights[index],
15873
+ compactDetail: compositeRowLayout.heights[index] < effectiveRowHeight,
15874
+ compositeParentColor: task.parentId ? compositeParentsById.get(task.parentId)?.color : void 0,
15555
15875
  onTasksChange: handleTaskChange,
15556
15876
  onDragStateChange: (state) => {
15557
15877
  if (state.isDragging) {
@@ -15588,7 +15908,9 @@ function TaskGanttChartInner(props, ref) {
15588
15908
  isDependencyDragActive: dependencyCreationDrag !== null,
15589
15909
  viewMode,
15590
15910
  showTaskDateLabels,
15591
- showTaskNames
15911
+ showTaskNames,
15912
+ onCompositeToggle: task.composite ? (source) => handleCompositeBarToggle(task.id, source) : void 0,
15913
+ compositeExpanded: task.composite ? !collapsedParentIds.has(task.id) : void 0
15592
15914
  }
15593
15915
  )
15594
15916
  },
@@ -15601,6 +15923,7 @@ function TaskGanttChartInner(props, ref) {
15601
15923
  rowHeight: effectiveRowHeight,
15602
15924
  variant: "chart",
15603
15925
  startIndex: visibleTasks.length,
15926
+ startTop: compositeRowLayout.totalHeight,
15604
15927
  chartDayWidth: dayWidth,
15605
15928
  chartStartDayOffset: todayInRange ? Math.max(0, todayIndex + Math.floor(Number.isFinite(skeletonStartOffsetDays) ? skeletonStartOffsetDays : 0)) : 0,
15606
15929
  chartStartJitterDays: skeletonStartJitterDays