gantt-renderer 0.3.0 → 0.5.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/CHANGELOG.md +12 -1
- package/dist/index.d.mts +63 -21
- package/dist/index.mjs +249 -75
- package/dist/index.mjs.map +1 -1
- package/dist/styles/gantt.css +17 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -30,6 +30,8 @@ const taskBase = {
|
|
|
30
30
|
parent: z.number().int().positive().optional(),
|
|
31
31
|
/** Optional CSS color value for the task bar. Overrides the default color assignment. */
|
|
32
32
|
color: z.string().optional(),
|
|
33
|
+
/** When `true`, the task bar cannot be dragged or resized. */
|
|
34
|
+
readonly: z.boolean().optional(),
|
|
33
35
|
/** Optional arbitrary metadata for consumer use. Preserved in the parsed output. */
|
|
34
36
|
data: z.record(z.string(), z.unknown()).optional()
|
|
35
37
|
};
|
|
@@ -86,6 +88,8 @@ const LinkSchema = z.object({
|
|
|
86
88
|
* @default 'FS'
|
|
87
89
|
*/
|
|
88
90
|
type: LinkTypeSchema.default("FS"),
|
|
91
|
+
/** When `true`, the link cannot be modified or deleted through the UI. */
|
|
92
|
+
readonly: z.boolean().optional(),
|
|
89
93
|
/** Optional arbitrary metadata for consumer use. Preserved in the parsed output. */
|
|
90
94
|
data: z.record(z.string(), z.unknown()).optional()
|
|
91
95
|
}).refine((l) => l.source !== l.target, {
|
|
@@ -1748,7 +1752,7 @@ function buildRow(row, selectedId, expandedIds, cbs, columns, locale) {
|
|
|
1748
1752
|
wrapper.addEventListener("keydown", (event) => {
|
|
1749
1753
|
if (event.key === "Enter" || event.key === " ") {
|
|
1750
1754
|
event.preventDefault();
|
|
1751
|
-
cbs.
|
|
1755
|
+
cbs.onTaskClick(row.id);
|
|
1752
1756
|
}
|
|
1753
1757
|
});
|
|
1754
1758
|
for (const column of visibleColumns(columns)) wrapper.append(buildCell(column, row, expandedIds, cbs, locale));
|
|
@@ -2056,6 +2060,7 @@ function toTask(node) {
|
|
|
2056
2060
|
startDate: node.startDate,
|
|
2057
2061
|
...node.parent === void 0 ? {} : { parent: node.parent },
|
|
2058
2062
|
...node.color === void 0 ? {} : { color: node.color },
|
|
2063
|
+
...node.readonly === void 0 ? {} : { readonly: node.readonly },
|
|
2059
2064
|
...node.data === void 0 ? {} : { data: node.data }
|
|
2060
2065
|
};
|
|
2061
2066
|
switch (node.kind) {
|
|
@@ -2098,7 +2103,7 @@ function attachDrag(barEl, resizeHandleEl, task, getMapper, cbs) {
|
|
|
2098
2103
|
try {
|
|
2099
2104
|
barEl.setPointerCapture(e.pointerId);
|
|
2100
2105
|
} catch {}
|
|
2101
|
-
cbs.
|
|
2106
|
+
cbs.onTaskClick?.(task.id);
|
|
2102
2107
|
const startX = e.clientX;
|
|
2103
2108
|
const originDate = parseDate(task.startDate);
|
|
2104
2109
|
const mapper = getMapper();
|
|
@@ -2115,7 +2120,7 @@ function attachDrag(barEl, resizeHandleEl, task, getMapper, cbs) {
|
|
|
2115
2120
|
window.removeEventListener("pointermove", onMove);
|
|
2116
2121
|
window.removeEventListener("pointerup", onUp);
|
|
2117
2122
|
barEl.style.cursor = "grab";
|
|
2118
|
-
cbs._onTaskMoveFinal?.({
|
|
2123
|
+
if (lastHours !== 0) cbs._onTaskMoveFinal?.({
|
|
2119
2124
|
id: task.id,
|
|
2120
2125
|
startDate: addHours(originDate, lastHours)
|
|
2121
2126
|
});
|
|
@@ -2186,7 +2191,7 @@ function attachProgressDrag(progressEl, barEl, task, _getMapper, cbs) {
|
|
|
2186
2191
|
if (e.button !== 0) return;
|
|
2187
2192
|
e.preventDefault();
|
|
2188
2193
|
e.stopPropagation();
|
|
2189
|
-
cbs.
|
|
2194
|
+
cbs.onTaskClick?.(task.id);
|
|
2190
2195
|
try {
|
|
2191
2196
|
progressEl.setPointerCapture(e.pointerId);
|
|
2192
2197
|
} catch {}
|
|
@@ -2231,7 +2236,7 @@ function attachProgressDrag(progressEl, barEl, task, _getMapper, cbs) {
|
|
|
2231
2236
|
*/
|
|
2232
2237
|
function attachMilestoneClick(diamondEl, taskId, cbs) {
|
|
2233
2238
|
function onClick() {
|
|
2234
|
-
cbs.
|
|
2239
|
+
cbs.onTaskClick?.(taskId);
|
|
2235
2240
|
}
|
|
2236
2241
|
function onDoubleClick(event) {
|
|
2237
2242
|
if (event.detail === 2) {
|
|
@@ -2361,11 +2366,16 @@ function createRightPaneRefs() {
|
|
|
2361
2366
|
scrollContainer.append(stripeContainer);
|
|
2362
2367
|
scrollContainer.append(absoluteLayer);
|
|
2363
2368
|
absoluteLayer.append(svgLayer);
|
|
2369
|
+
const tooltipEl = el("div");
|
|
2370
|
+
tooltipEl.className = "gantt-tooltip";
|
|
2371
|
+
tooltipEl.style.display = "none";
|
|
2372
|
+
scrollContainer.append(tooltipEl);
|
|
2364
2373
|
return {
|
|
2365
2374
|
scrollContainer,
|
|
2366
2375
|
stripeContainer,
|
|
2367
2376
|
absoluteLayer,
|
|
2368
2377
|
svgLayer,
|
|
2378
|
+
tooltipEl,
|
|
2369
2379
|
barRegistry: /* @__PURE__ */ new Map()
|
|
2370
2380
|
};
|
|
2371
2381
|
}
|
|
@@ -2410,8 +2420,9 @@ function renderSpecialDayBackgrounds(layer, beforeNode, state, contentHeight) {
|
|
|
2410
2420
|
cur = next;
|
|
2411
2421
|
}
|
|
2412
2422
|
}
|
|
2413
|
-
function renderBar(layer, svgLayer, task, layout, selectedId, registry, state, cbs) {
|
|
2423
|
+
function renderBar(layer, svgLayer, task, layout, selectedId, registry, state, cbs, tooltipEl) {
|
|
2414
2424
|
const selected = task.id === selectedId;
|
|
2425
|
+
const readonly = task.readonly === true;
|
|
2415
2426
|
const color = BAR_COLOR[layout.kind] ?? BAR_COLOR["task"];
|
|
2416
2427
|
const bar = el("div");
|
|
2417
2428
|
bar.className = `gantt-bar${selected ? " gantt-bar--selected gantt-shape--selected" : ""}`;
|
|
@@ -2423,7 +2434,7 @@ function renderBar(layer, svgLayer, task, layout, selectedId, registry, state, c
|
|
|
2423
2434
|
height: `${layout.height}px`,
|
|
2424
2435
|
...color === void 0 ? {} : { background: color },
|
|
2425
2436
|
borderRadius: layout.kind === "project" ? "3px" : "4px",
|
|
2426
|
-
cursor: "grab",
|
|
2437
|
+
cursor: readonly ? "pointer" : "grab",
|
|
2427
2438
|
userSelect: "none",
|
|
2428
2439
|
overflow: "hidden",
|
|
2429
2440
|
zIndex: selected ? "3" : "2",
|
|
@@ -2474,30 +2485,38 @@ function renderBar(layer, svgLayer, task, layout, selectedId, registry, state, c
|
|
|
2474
2485
|
bar.setAttribute("aria-label", ariaLabel(state.locale, "ariaTask", task.text));
|
|
2475
2486
|
bar.setAttribute("aria-pressed", String(selected));
|
|
2476
2487
|
bar.dataset["taskId"] = String(task.id);
|
|
2477
|
-
bar.addEventListener("click", () => {
|
|
2478
|
-
cbs.
|
|
2488
|
+
bar.addEventListener("click", (event) => {
|
|
2489
|
+
if (event.detail === 2) cbs.onTaskDoubleClick?.({
|
|
2490
|
+
id: task.id,
|
|
2491
|
+
task: toTask(task)
|
|
2492
|
+
});
|
|
2493
|
+
else cbs.onTaskClick?.(task.id);
|
|
2479
2494
|
});
|
|
2480
2495
|
bar.addEventListener("keydown", (event) => {
|
|
2481
2496
|
if (event.key === "Enter" || event.key === " ") {
|
|
2482
2497
|
event.preventDefault();
|
|
2483
|
-
cbs.
|
|
2498
|
+
cbs.onTaskClick?.(task.id);
|
|
2484
2499
|
}
|
|
2485
2500
|
});
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
+
let handle;
|
|
2502
|
+
let cleanupDrag;
|
|
2503
|
+
if (!readonly) {
|
|
2504
|
+
handle = el("div");
|
|
2505
|
+
handle.className = "gantt-resize-handle";
|
|
2506
|
+
css(handle, {
|
|
2507
|
+
position: "absolute",
|
|
2508
|
+
right: "0",
|
|
2509
|
+
top: "0",
|
|
2510
|
+
width: "8px",
|
|
2511
|
+
height: "100%",
|
|
2512
|
+
cursor: "ew-resize",
|
|
2513
|
+
zIndex: "1",
|
|
2514
|
+
touchAction: "none"
|
|
2515
|
+
});
|
|
2516
|
+
bar.append(handle);
|
|
2517
|
+
layer.insertBefore(bar, svgLayer);
|
|
2518
|
+
cleanupDrag = attachDrag(bar, handle, task, () => state.mapper, cbs);
|
|
2519
|
+
} else layer.insertBefore(bar, svgLayer);
|
|
2501
2520
|
let cleanupLinkHandles;
|
|
2502
2521
|
if (state.linkCreationEnabled) {
|
|
2503
2522
|
const barCenterY = layout.y + layout.height / 2;
|
|
@@ -2532,17 +2551,52 @@ function renderBar(layer, svgLayer, task, layout, selectedId, registry, state, c
|
|
|
2532
2551
|
bar.removeEventListener("mouseleave", onBarLeave);
|
|
2533
2552
|
};
|
|
2534
2553
|
}
|
|
2554
|
+
const onTooltipEnter = () => {
|
|
2555
|
+
const content = cbs.onTooltipText?.({
|
|
2556
|
+
id: task.id,
|
|
2557
|
+
task: toTask(task)
|
|
2558
|
+
});
|
|
2559
|
+
if (content && content.length > 0) {
|
|
2560
|
+
tooltipEl.innerHTML = content;
|
|
2561
|
+
tooltipEl.style.display = "";
|
|
2562
|
+
} else tooltipEl.style.display = "none";
|
|
2563
|
+
};
|
|
2564
|
+
const onTooltipMove = (e) => {
|
|
2565
|
+
const offsetX = 12;
|
|
2566
|
+
const offsetY = -8;
|
|
2567
|
+
let left = e.clientX + offsetX;
|
|
2568
|
+
let top = e.clientY + offsetY;
|
|
2569
|
+
const maxLeft = window.innerWidth - tooltipEl.offsetWidth - 4;
|
|
2570
|
+
const maxTop = window.innerHeight - tooltipEl.offsetHeight - 4;
|
|
2571
|
+
left = Math.max(4, Math.min(left, maxLeft));
|
|
2572
|
+
top = Math.max(4, Math.min(top, maxTop));
|
|
2573
|
+
tooltipEl.style.left = `${left}px`;
|
|
2574
|
+
tooltipEl.style.top = `${top}px`;
|
|
2575
|
+
};
|
|
2576
|
+
const onTooltipLeave = () => {
|
|
2577
|
+
tooltipEl.style.display = "none";
|
|
2578
|
+
};
|
|
2579
|
+
bar.addEventListener("mouseenter", onTooltipEnter);
|
|
2580
|
+
bar.addEventListener("mousemove", onTooltipMove);
|
|
2581
|
+
bar.addEventListener("mouseleave", onTooltipLeave);
|
|
2582
|
+
const cleanupTooltip = () => {
|
|
2583
|
+
bar.removeEventListener("mouseenter", onTooltipEnter);
|
|
2584
|
+
bar.removeEventListener("mousemove", onTooltipMove);
|
|
2585
|
+
bar.removeEventListener("mouseleave", onTooltipLeave);
|
|
2586
|
+
};
|
|
2535
2587
|
const entry = {
|
|
2536
2588
|
bar,
|
|
2537
|
-
resizeHandle: handle
|
|
2538
|
-
cleanupDrag
|
|
2589
|
+
resizeHandle: handle ?? el("div")
|
|
2539
2590
|
};
|
|
2591
|
+
if (cleanupDrag !== void 0) entry.cleanupDrag = cleanupDrag;
|
|
2540
2592
|
if (cleanupLinkHandles !== void 0) entry.cleanupLinkHandles = cleanupLinkHandles;
|
|
2541
2593
|
if (cleanupProgressDrag !== void 0) entry.cleanupProgressDrag = cleanupProgressDrag;
|
|
2594
|
+
if (cleanupTooltip !== void 0) entry.cleanupTooltip = cleanupTooltip;
|
|
2542
2595
|
registry.set(task.id, entry);
|
|
2543
2596
|
}
|
|
2544
|
-
function renderMilestone(layer, svgLayer, task, layout, selectedId, registry, cbs, state) {
|
|
2597
|
+
function renderMilestone(layer, svgLayer, task, layout, selectedId, registry, cbs, state, tooltipEl) {
|
|
2545
2598
|
const selected = task.id === selectedId;
|
|
2599
|
+
const readonly = task.readonly === true;
|
|
2546
2600
|
const size = MILESTONE_HALF * 2;
|
|
2547
2601
|
const diamond = el("div");
|
|
2548
2602
|
diamond.className = `gantt-milestone${selected ? " gantt-shape--selected" : ""}`;
|
|
@@ -2554,7 +2608,7 @@ function renderMilestone(layer, svgLayer, task, layout, selectedId, registry, cb
|
|
|
2554
2608
|
height: `${size}px`,
|
|
2555
2609
|
background: "var(--gantt-milestone)",
|
|
2556
2610
|
transform: "rotate(45deg)",
|
|
2557
|
-
cursor: "pointer",
|
|
2611
|
+
cursor: readonly ? "default" : "pointer",
|
|
2558
2612
|
zIndex: "4"
|
|
2559
2613
|
});
|
|
2560
2614
|
diamond.tabIndex = 0;
|
|
@@ -2565,7 +2619,7 @@ function renderMilestone(layer, svgLayer, task, layout, selectedId, registry, cb
|
|
|
2565
2619
|
diamond.addEventListener("keydown", (event) => {
|
|
2566
2620
|
if (event.key === "Enter" || event.key === " ") {
|
|
2567
2621
|
event.preventDefault();
|
|
2568
|
-
cbs.
|
|
2622
|
+
cbs.onTaskClick?.(task.id);
|
|
2569
2623
|
}
|
|
2570
2624
|
});
|
|
2571
2625
|
const labelEl = el("span");
|
|
@@ -2585,7 +2639,15 @@ function renderMilestone(layer, svgLayer, task, layout, selectedId, registry, cb
|
|
|
2585
2639
|
layer.insertBefore(diamond, svgLayer);
|
|
2586
2640
|
bindMilestoneTask(diamond, task);
|
|
2587
2641
|
const dummy = el("div");
|
|
2588
|
-
|
|
2642
|
+
let cleanupDrag;
|
|
2643
|
+
if (!readonly) cleanupDrag = attachMilestoneClick(diamond, task.id, cbs);
|
|
2644
|
+
else diamond.addEventListener("click", (event) => {
|
|
2645
|
+
if (event.detail === 2) cbs.onTaskDoubleClick?.({
|
|
2646
|
+
id: task.id,
|
|
2647
|
+
task: toTask(task)
|
|
2648
|
+
});
|
|
2649
|
+
else cbs.onTaskClick?.(task.id);
|
|
2650
|
+
});
|
|
2589
2651
|
let cleanupLinkHandles;
|
|
2590
2652
|
if (state.linkCreationEnabled) {
|
|
2591
2653
|
const diamondCenterY = layout.y + layout.height / 2;
|
|
@@ -2611,12 +2673,46 @@ function renderMilestone(layer, svgLayer, task, layout, selectedId, registry, cb
|
|
|
2611
2673
|
diamond.removeEventListener("mouseleave", onDiamondLeave);
|
|
2612
2674
|
};
|
|
2613
2675
|
}
|
|
2676
|
+
const onTooltipEnter = () => {
|
|
2677
|
+
const content = cbs.onTooltipText?.({
|
|
2678
|
+
id: task.id,
|
|
2679
|
+
task: toTask(task)
|
|
2680
|
+
});
|
|
2681
|
+
if (content && content.length > 0) {
|
|
2682
|
+
tooltipEl.innerHTML = content;
|
|
2683
|
+
tooltipEl.style.display = "";
|
|
2684
|
+
} else tooltipEl.style.display = "none";
|
|
2685
|
+
};
|
|
2686
|
+
const onTooltipMove = (e) => {
|
|
2687
|
+
const offsetX = 12;
|
|
2688
|
+
const offsetY = -8;
|
|
2689
|
+
let left = e.clientX + offsetX;
|
|
2690
|
+
let top = e.clientY + offsetY;
|
|
2691
|
+
const maxLeft = window.innerWidth - tooltipEl.offsetWidth - 4;
|
|
2692
|
+
const maxTop = window.innerHeight - tooltipEl.offsetHeight - 4;
|
|
2693
|
+
left = Math.max(4, Math.min(left, maxLeft));
|
|
2694
|
+
top = Math.max(4, Math.min(top, maxTop));
|
|
2695
|
+
tooltipEl.style.left = `${left}px`;
|
|
2696
|
+
tooltipEl.style.top = `${top}px`;
|
|
2697
|
+
};
|
|
2698
|
+
const onTooltipLeave = () => {
|
|
2699
|
+
tooltipEl.style.display = "none";
|
|
2700
|
+
};
|
|
2701
|
+
diamond.addEventListener("mouseenter", onTooltipEnter);
|
|
2702
|
+
diamond.addEventListener("mousemove", onTooltipMove);
|
|
2703
|
+
diamond.addEventListener("mouseleave", onTooltipLeave);
|
|
2704
|
+
const cleanupTooltip = () => {
|
|
2705
|
+
diamond.removeEventListener("mouseenter", onTooltipEnter);
|
|
2706
|
+
diamond.removeEventListener("mousemove", onTooltipMove);
|
|
2707
|
+
diamond.removeEventListener("mouseleave", onTooltipLeave);
|
|
2708
|
+
};
|
|
2614
2709
|
const entry = {
|
|
2615
2710
|
bar: diamond,
|
|
2616
|
-
resizeHandle: dummy
|
|
2617
|
-
cleanupDrag
|
|
2711
|
+
resizeHandle: dummy
|
|
2618
2712
|
};
|
|
2713
|
+
if (cleanupDrag !== void 0) entry.cleanupDrag = cleanupDrag;
|
|
2619
2714
|
if (cleanupLinkHandles !== void 0) entry.cleanupLinkHandles = cleanupLinkHandles;
|
|
2715
|
+
if (cleanupTooltip !== void 0) entry.cleanupTooltip = cleanupTooltip;
|
|
2620
2716
|
registry.set(task.id, entry);
|
|
2621
2717
|
}
|
|
2622
2718
|
/**
|
|
@@ -2665,7 +2761,7 @@ function renderRightPane(refs, state, cbs) {
|
|
|
2665
2761
|
for (const node of toRemove) absoluteLayer.removeChild(node);
|
|
2666
2762
|
hideGhostLine(svgLayer);
|
|
2667
2763
|
for (const { cleanupDrag, cleanupLinkHandles, cleanupProgressDrag } of barRegistry.values()) {
|
|
2668
|
-
cleanupDrag();
|
|
2764
|
+
cleanupDrag?.();
|
|
2669
2765
|
cleanupLinkHandles?.();
|
|
2670
2766
|
cleanupProgressDrag?.();
|
|
2671
2767
|
}
|
|
@@ -2708,8 +2804,8 @@ function renderRightPane(refs, state, cbs) {
|
|
|
2708
2804
|
for (const task of visibleRows) {
|
|
2709
2805
|
const layout = layouts.get(task.id);
|
|
2710
2806
|
if (layout === void 0) continue;
|
|
2711
|
-
if (layout.kind === "milestone") renderMilestone(absoluteLayer, svgLayer, task, layout, selectedId, barRegistry, cbs, state);
|
|
2712
|
-
else renderBar(absoluteLayer, svgLayer, task, layout, selectedId, barRegistry, state, cbs);
|
|
2807
|
+
if (layout.kind === "milestone") renderMilestone(absoluteLayer, svgLayer, task, layout, selectedId, barRegistry, cbs, state, refs.tooltipEl);
|
|
2808
|
+
else renderBar(absoluteLayer, svgLayer, task, layout, selectedId, barRegistry, state, cbs, refs.tooltipEl);
|
|
2713
2809
|
}
|
|
2714
2810
|
updateDependencyLayer(svgLayer, links.filter((link) => visibleTaskIds.has(link.sourceTaskId) && visibleTaskIds.has(link.targetTaskId)), totalWidth, contentHeight, selectedId, highlightLinkedDependenciesOnSelect, cbs);
|
|
2715
2811
|
}
|
|
@@ -2869,15 +2965,16 @@ var GanttChart = class {
|
|
|
2869
2965
|
/**
|
|
2870
2966
|
* Constructs a new chart, builds the DOM, and wires internal event handling.
|
|
2871
2967
|
* Data must be loaded via {@link update} before the chart renders.
|
|
2968
|
+
* Callbacks must be set via {@link setCallbacks} before user interactions are handled.
|
|
2872
2969
|
*
|
|
2873
2970
|
* @param container - The host `HTMLElement` the chart will be appended to.
|
|
2874
|
-
* @param opts - Configuration
|
|
2971
|
+
* @param opts - Configuration options.
|
|
2875
2972
|
*/
|
|
2876
|
-
constructor(container, opts = {}
|
|
2973
|
+
constructor(container, opts = {}) {
|
|
2877
2974
|
this.#container = container;
|
|
2878
2975
|
this.#scale = opts.scale ?? "day";
|
|
2879
2976
|
this.#opts = opts;
|
|
2880
|
-
this.#callbacks =
|
|
2977
|
+
this.#callbacks = {};
|
|
2881
2978
|
this.#taskIndex = /* @__PURE__ */ new Map();
|
|
2882
2979
|
this.#locale = resolveChartLocale(opts.locale);
|
|
2883
2980
|
this.#columns = opts.gridColumns ?? gridColumnDefaults(this.#locale);
|
|
@@ -2887,21 +2984,39 @@ var GanttChart = class {
|
|
|
2887
2984
|
this.#weekendDays = normalizeWeekendDays(opts.weekendDays ?? this.#locale.weekendDays);
|
|
2888
2985
|
this.#specialDaysByDate = buildSpecialDayIndex(opts.specialDays ?? []);
|
|
2889
2986
|
this.#expandedIds = /* @__PURE__ */ new Set();
|
|
2890
|
-
this.#cbs =
|
|
2891
|
-
|
|
2987
|
+
this.#cbs = this.#buildCallbackAdapter();
|
|
2988
|
+
this.#buildDom();
|
|
2989
|
+
this.#wireEvents();
|
|
2990
|
+
container.append(this.#root);
|
|
2991
|
+
this.#applyTheme();
|
|
2992
|
+
this.#applyResponsivePaneStyles();
|
|
2993
|
+
this.#setupResizeObserver();
|
|
2994
|
+
}
|
|
2995
|
+
#buildCallbackAdapter() {
|
|
2996
|
+
return {
|
|
2997
|
+
onTaskClick: (id) => {
|
|
2892
2998
|
if (this.#selectedId === id) return;
|
|
2893
2999
|
this.#selectedId = id;
|
|
2894
3000
|
if (this.#selectedId !== null) {
|
|
2895
3001
|
const task = this.#findTask(this.#selectedId);
|
|
2896
|
-
if (task !== void 0) this.#callbacks.
|
|
3002
|
+
if (task !== void 0) this.#callbacks.onTaskClick?.({
|
|
3003
|
+
task,
|
|
3004
|
+
instance: this
|
|
3005
|
+
});
|
|
2897
3006
|
}
|
|
2898
3007
|
this.#scheduleRender();
|
|
2899
3008
|
},
|
|
2900
3009
|
onTaskDoubleClick: (payload) => {
|
|
2901
|
-
this.#callbacks.onTaskDoubleClick?.({
|
|
3010
|
+
this.#callbacks.onTaskDoubleClick?.({
|
|
3011
|
+
task: payload.task,
|
|
3012
|
+
instance: this
|
|
3013
|
+
});
|
|
2902
3014
|
},
|
|
2903
3015
|
onTaskEditIntent: (payload) => {
|
|
2904
|
-
this.#callbacks.onTaskDoubleClick?.({
|
|
3016
|
+
this.#callbacks.onTaskDoubleClick?.({
|
|
3017
|
+
task: payload.task,
|
|
3018
|
+
instance: this
|
|
3019
|
+
});
|
|
2905
3020
|
},
|
|
2906
3021
|
onTaskMove: (payload) => {
|
|
2907
3022
|
if (!this.#dragOriginals.has(payload.id)) {
|
|
@@ -2912,13 +3027,23 @@ var GanttChart = class {
|
|
|
2912
3027
|
this.#patchTask(payload.id, { startDate: iso });
|
|
2913
3028
|
this.#scheduleRender();
|
|
2914
3029
|
},
|
|
2915
|
-
_onTaskMoveFinal: (payload) => {
|
|
3030
|
+
_onTaskMoveFinal: async (payload) => {
|
|
2916
3031
|
const task = this.#findTask(payload.id);
|
|
2917
3032
|
if (task !== void 0) {
|
|
2918
|
-
|
|
3033
|
+
const durationHours = task.kind !== "milestone" ? task.durationHours : 0;
|
|
3034
|
+
const newEndDate = addHours(payload.startDate, durationHours);
|
|
3035
|
+
const result = this.#callbacks.onTaskMove?.({
|
|
2919
3036
|
task,
|
|
2920
|
-
newStartDate: payload.startDate
|
|
2921
|
-
|
|
3037
|
+
newStartDate: payload.startDate,
|
|
3038
|
+
newEndDate,
|
|
3039
|
+
instance: this
|
|
3040
|
+
});
|
|
3041
|
+
if (result instanceof Promise) {
|
|
3042
|
+
if (!await result) {
|
|
3043
|
+
const original = this.#dragOriginals.get(payload.id);
|
|
3044
|
+
if (original !== void 0) this.#patchTask(payload.id, { startDate: original.startDate });
|
|
3045
|
+
}
|
|
3046
|
+
} else if (!result) {
|
|
2922
3047
|
const original = this.#dragOriginals.get(payload.id);
|
|
2923
3048
|
if (original !== void 0) this.#patchTask(payload.id, { startDate: original.startDate });
|
|
2924
3049
|
}
|
|
@@ -2935,13 +3060,24 @@ var GanttChart = class {
|
|
|
2935
3060
|
this.#patchTask(payload.id, { durationHours: payload.durationHours });
|
|
2936
3061
|
this.#scheduleRender();
|
|
2937
3062
|
},
|
|
2938
|
-
_onTaskResizeFinal: (payload) => {
|
|
3063
|
+
_onTaskResizeFinal: async (payload) => {
|
|
2939
3064
|
const task = this.#findTask(payload.id);
|
|
2940
3065
|
if (task !== void 0) {
|
|
2941
|
-
|
|
3066
|
+
const newStartDate = parseDate(task.startDate);
|
|
3067
|
+
const newEndDate = addHours(newStartDate, task.kind !== "milestone" ? task.durationHours : 0);
|
|
3068
|
+
const result = this.#callbacks.onTaskResize?.({
|
|
2942
3069
|
task,
|
|
2943
|
-
newDurationHours: payload.durationHours
|
|
2944
|
-
|
|
3070
|
+
newDurationHours: payload.durationHours,
|
|
3071
|
+
newStartDate,
|
|
3072
|
+
newEndDate,
|
|
3073
|
+
instance: this
|
|
3074
|
+
});
|
|
3075
|
+
if (result instanceof Promise) {
|
|
3076
|
+
if (!await result) {
|
|
3077
|
+
const original = this.#dragOriginals.get(payload.id);
|
|
3078
|
+
if (original !== void 0 && original.kind !== "milestone") this.#patchTask(payload.id, { durationHours: original.durationHours });
|
|
3079
|
+
}
|
|
3080
|
+
} else if (!result) {
|
|
2945
3081
|
const original = this.#dragOriginals.get(payload.id);
|
|
2946
3082
|
if (original !== void 0 && original.kind !== "milestone") this.#patchTask(payload.id, { durationHours: original.durationHours });
|
|
2947
3083
|
}
|
|
@@ -2958,13 +3094,20 @@ var GanttChart = class {
|
|
|
2958
3094
|
this.#patchTask(payload.id, { percentComplete: payload.percentComplete });
|
|
2959
3095
|
this.#scheduleRender();
|
|
2960
3096
|
},
|
|
2961
|
-
_onTaskProgressDragFinal: (payload) => {
|
|
3097
|
+
_onTaskProgressDragFinal: async (payload) => {
|
|
2962
3098
|
const task = this.#findTask(payload.id);
|
|
2963
3099
|
if (task !== void 0) {
|
|
2964
|
-
|
|
3100
|
+
const result = this.#callbacks.onProgressChange?.({
|
|
2965
3101
|
task,
|
|
2966
|
-
newPercentComplete: payload.percentComplete
|
|
2967
|
-
|
|
3102
|
+
newPercentComplete: payload.percentComplete,
|
|
3103
|
+
instance: this
|
|
3104
|
+
});
|
|
3105
|
+
if (result instanceof Promise) {
|
|
3106
|
+
if (!await result) {
|
|
3107
|
+
const original = this.#dragOriginals.get(payload.id);
|
|
3108
|
+
if (original !== void 0 && original.kind !== "milestone") this.#patchTask(payload.id, { percentComplete: original.percentComplete });
|
|
3109
|
+
}
|
|
3110
|
+
} else if (!result) {
|
|
2968
3111
|
const original = this.#dragOriginals.get(payload.id);
|
|
2969
3112
|
if (original !== void 0 && original.kind !== "milestone") this.#patchTask(payload.id, { percentComplete: original.percentComplete });
|
|
2970
3113
|
}
|
|
@@ -2975,13 +3118,22 @@ var GanttChart = class {
|
|
|
2975
3118
|
},
|
|
2976
3119
|
onTaskAdd: (parentId) => {
|
|
2977
3120
|
const parentTask = this.#findTask(parentId);
|
|
2978
|
-
if (parentTask !== void 0) this.#callbacks.onTaskAdd?.({
|
|
3121
|
+
if (parentTask !== void 0) this.#callbacks.onTaskAdd?.({
|
|
3122
|
+
parentTask,
|
|
3123
|
+
instance: this
|
|
3124
|
+
});
|
|
2979
3125
|
},
|
|
2980
3126
|
onLeftPaneWidthChange: (width) => {
|
|
2981
|
-
this.#callbacks.onLeftPaneWidthChange?.(
|
|
3127
|
+
this.#callbacks.onLeftPaneWidthChange?.({
|
|
3128
|
+
width,
|
|
3129
|
+
instance: this
|
|
3130
|
+
});
|
|
2982
3131
|
},
|
|
2983
3132
|
onGridColumnsChange: (updatedColumns) => {
|
|
2984
|
-
this.#callbacks.onGridColumnsChange?.(
|
|
3133
|
+
this.#callbacks.onGridColumnsChange?.({
|
|
3134
|
+
columns: updatedColumns,
|
|
3135
|
+
instance: this
|
|
3136
|
+
});
|
|
2985
3137
|
},
|
|
2986
3138
|
onLinkCreate: (payload) => {
|
|
2987
3139
|
const sourceTask = this.#findTask(payload.sourceTaskId);
|
|
@@ -2989,22 +3141,39 @@ var GanttChart = class {
|
|
|
2989
3141
|
if (sourceTask !== void 0 && targetTask !== void 0) this.#callbacks.onLinkCreate?.({
|
|
2990
3142
|
type: "FS",
|
|
2991
3143
|
sourceTask,
|
|
2992
|
-
targetTask
|
|
3144
|
+
targetTask,
|
|
3145
|
+
instance: this
|
|
2993
3146
|
});
|
|
2994
3147
|
},
|
|
2995
3148
|
onLinkClick: (payload) => {
|
|
2996
|
-
this.#callbacks.onLinkClick?.({
|
|
3149
|
+
this.#callbacks.onLinkClick?.({
|
|
3150
|
+
link: payload,
|
|
3151
|
+
instance: this
|
|
3152
|
+
});
|
|
2997
3153
|
},
|
|
2998
3154
|
onLinkDblClick: (payload) => {
|
|
2999
|
-
this.#callbacks.onLinkDblClick?.({
|
|
3000
|
-
|
|
3155
|
+
this.#callbacks.onLinkDblClick?.({
|
|
3156
|
+
link: payload,
|
|
3157
|
+
instance: this
|
|
3158
|
+
});
|
|
3159
|
+
},
|
|
3160
|
+
onTooltipText: (payload) => this.#callbacks.onTooltipText?.({
|
|
3161
|
+
task: payload.task,
|
|
3162
|
+
instance: this
|
|
3163
|
+
}) ?? null
|
|
3001
3164
|
};
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3165
|
+
}
|
|
3166
|
+
/**
|
|
3167
|
+
* Sets or replaces the chart's user-facing callbacks.
|
|
3168
|
+
* Does not trigger a re-render.
|
|
3169
|
+
*
|
|
3170
|
+
* @param cbs - The {@link GanttCallbacks} to register.
|
|
3171
|
+
* @throws {GanttError} When the instance has been destroyed.
|
|
3172
|
+
*/
|
|
3173
|
+
setCallbacks(cbs) {
|
|
3174
|
+
this.#assertAlive();
|
|
3175
|
+
this.#callbacks = cbs;
|
|
3176
|
+
this.#cbs = this.#buildCallbackAdapter();
|
|
3008
3177
|
}
|
|
3009
3178
|
/**
|
|
3010
3179
|
* Replaces the full dataset and re-renders.
|
|
@@ -3085,14 +3254,18 @@ var GanttChart = class {
|
|
|
3085
3254
|
* Programmatically selects or deselects a task.
|
|
3086
3255
|
*
|
|
3087
3256
|
* @param id - The task ID to select, or `null` to clear the selection.
|
|
3257
|
+
* @param fireCallback - Whether to fire the `onTaskClick` callback. Default `true`.
|
|
3088
3258
|
* @throws {GanttError} When the instance has been destroyed.
|
|
3089
3259
|
*/
|
|
3090
|
-
select(id) {
|
|
3260
|
+
select(id, fireCallback = true) {
|
|
3091
3261
|
this.#assertAlive();
|
|
3092
3262
|
if (id === null) this.#selectedId = null;
|
|
3093
3263
|
else {
|
|
3094
3264
|
const task = this.#input?.tasks.find((t) => t.id === id);
|
|
3095
|
-
if (task !== void 0) this.#callbacks.
|
|
3265
|
+
if (task !== void 0 && fireCallback) this.#callbacks.onTaskClick?.({
|
|
3266
|
+
task,
|
|
3267
|
+
instance: this
|
|
3268
|
+
});
|
|
3096
3269
|
this.#selectedId = id;
|
|
3097
3270
|
}
|
|
3098
3271
|
if (this.#rafPending && this.#rafId !== null) {
|
|
@@ -3145,10 +3318,11 @@ var GanttChart = class {
|
|
|
3145
3318
|
else window.removeEventListener("resize", this.#applyResponsivePaneStyles);
|
|
3146
3319
|
if (this.#rafId !== null) cancelAnimationFrame(this.#rafId);
|
|
3147
3320
|
this.#columnResizeCleanup();
|
|
3148
|
-
for (const { cleanupDrag, cleanupLinkHandles, cleanupProgressDrag } of this.#rightPaneRefs.barRegistry.values()) {
|
|
3149
|
-
cleanupDrag();
|
|
3321
|
+
for (const { cleanupDrag, cleanupLinkHandles, cleanupProgressDrag, cleanupTooltip } of this.#rightPaneRefs.barRegistry.values()) {
|
|
3322
|
+
cleanupDrag?.();
|
|
3150
3323
|
cleanupLinkHandles?.();
|
|
3151
3324
|
cleanupProgressDrag?.();
|
|
3325
|
+
cleanupTooltip?.();
|
|
3152
3326
|
}
|
|
3153
3327
|
clearChildren(this.#container);
|
|
3154
3328
|
}
|
|
@@ -3181,7 +3355,7 @@ var GanttChart = class {
|
|
|
3181
3355
|
id: payload.id,
|
|
3182
3356
|
atMs: now
|
|
3183
3357
|
};
|
|
3184
|
-
this.#cbs.
|
|
3358
|
+
this.#cbs.onTaskClick?.(payload.id);
|
|
3185
3359
|
};
|
|
3186
3360
|
#onScroll = () => {
|
|
3187
3361
|
({scrollTop: this.#scrollTop} = this.#scrollEl);
|
|
@@ -3265,7 +3439,7 @@ var GanttChart = class {
|
|
|
3265
3439
|
else this.#expandedIds.add(id);
|
|
3266
3440
|
this.#scheduleRender();
|
|
3267
3441
|
},
|
|
3268
|
-
|
|
3442
|
+
onTaskClick: (id) => this.#cbs.onTaskClick?.(id),
|
|
3269
3443
|
onRowClick: (payload) => {
|
|
3270
3444
|
this.#handleGridClick(payload);
|
|
3271
3445
|
},
|