@affino/datagrid-gantt 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,784 @@
1
+ import { addDataGridWorkingDays, resolveDataGridWorkingCalendar, snapDataGridDateToWorkingDay, startOfUtcDay, startOfUtcWeek, } from "./calendar.js";
2
+ import { DAY_MS, clampDataGridTimelineScrollLeft, resolveDataGridTimelineDateToPixel, resolveDataGridTimelineRange, resolveDataGridTimelineScrollLeftForDate, } from "./timeline.js";
3
+ const DEFAULT_GANTT_OPTIONS = {
4
+ startKey: "start",
5
+ endKey: "end",
6
+ baselineStartKey: null,
7
+ baselineEndKey: null,
8
+ progressKey: "progress",
9
+ dependencyKey: "dependencies",
10
+ labelKey: "task",
11
+ idKey: "id",
12
+ criticalKey: null,
13
+ computedCriticalPath: false,
14
+ paneWidth: 520,
15
+ pixelsPerDay: 24,
16
+ zoomLevel: "day",
17
+ timelineStart: null,
18
+ timelineEnd: null,
19
+ rangePaddingDays: 0,
20
+ workingCalendar: resolveDataGridWorkingCalendar(null),
21
+ rowBarHeight: 18,
22
+ minBarWidth: 6,
23
+ resizeHandleWidth: 8,
24
+ };
25
+ const PIXELS_PER_DAY_BY_ZOOM = {
26
+ day: 24,
27
+ week: 8,
28
+ month: 3,
29
+ };
30
+ function normalizeNumericOption(value, fallback, min) {
31
+ if (!Number.isFinite(value)) {
32
+ return fallback;
33
+ }
34
+ return Math.max(min, Number(value));
35
+ }
36
+ function readRowCell(row, key) {
37
+ if (!row || !key) {
38
+ return undefined;
39
+ }
40
+ const record = row.row;
41
+ return record[key];
42
+ }
43
+ function resolveTaskId(row, options) {
44
+ const explicit = readRowCell(row, options.idKey);
45
+ if (explicit != null && String(explicit).trim().length > 0) {
46
+ return String(explicit).trim();
47
+ }
48
+ if (row.rowId != null) {
49
+ return String(row.rowId);
50
+ }
51
+ return "";
52
+ }
53
+ function resolveLabel(row, options) {
54
+ var _a;
55
+ const explicit = readRowCell(row, options.labelKey);
56
+ if (explicit != null && String(explicit).trim().length > 0) {
57
+ return String(explicit);
58
+ }
59
+ if (row.kind === "group") {
60
+ const groupValue = (_a = row.groupMeta) === null || _a === void 0 ? void 0 : _a.groupValue;
61
+ if (groupValue != null && String(groupValue).trim().length > 0) {
62
+ return String(groupValue);
63
+ }
64
+ }
65
+ return resolveTaskId(row, options);
66
+ }
67
+ function createResolvedGanttRowSnapshotReader(options) {
68
+ const cache = new WeakMap();
69
+ return row => {
70
+ const cached = cache.get(row);
71
+ if (cached) {
72
+ return cached;
73
+ }
74
+ const snapshot = {
75
+ startMs: resolveDataGridGanttDateMs(readRowCell(row, options.startKey)),
76
+ endMs: resolveDataGridGanttDateMs(readRowCell(row, options.endKey)),
77
+ baselineStartMs: resolveDataGridGanttDateMs(readRowCell(row, options.baselineStartKey)),
78
+ baselineEndMs: resolveDataGridGanttDateMs(readRowCell(row, options.baselineEndKey)),
79
+ progress: resolveDataGridGanttProgress(readRowCell(row, options.progressKey)),
80
+ dependencies: resolveDataGridGanttDependencies(readRowCell(row, options.dependencyKey)),
81
+ dependencyRefs: resolveDataGridGanttDependencyRefs(readRowCell(row, options.dependencyKey)),
82
+ critical: Boolean(options.criticalKey
83
+ && readRowCell(row, options.criticalKey)),
84
+ taskId: resolveTaskId(row, options),
85
+ label: resolveLabel(row, options),
86
+ };
87
+ cache.set(row, snapshot);
88
+ return snapshot;
89
+ };
90
+ }
91
+ export function resolveDataGridGanttDateMs(value) {
92
+ if (value instanceof Date) {
93
+ const ms = value.getTime();
94
+ return Number.isFinite(ms) ? ms : null;
95
+ }
96
+ if (typeof value === "number") {
97
+ return Number.isFinite(value) ? value : null;
98
+ }
99
+ if (typeof value === "string" && value.trim().length > 0) {
100
+ const ms = Date.parse(value);
101
+ return Number.isFinite(ms) ? ms : null;
102
+ }
103
+ return null;
104
+ }
105
+ export function resolveDataGridGanttProgress(value) {
106
+ const numeric = typeof value === "number"
107
+ ? value
108
+ : (typeof value === "string" && value.trim().length > 0 ? Number(value) : NaN);
109
+ if (!Number.isFinite(numeric)) {
110
+ return 0;
111
+ }
112
+ const normalized = numeric > 1 ? numeric / 100 : numeric;
113
+ return Math.min(1, Math.max(0, normalized));
114
+ }
115
+ export function resolveDataGridGanttSnapDays(zoomLevel) {
116
+ return zoomLevel === "week" || zoomLevel === "month" ? 7 : 1;
117
+ }
118
+ export function snapDataGridGanttDateMs(dateMs, zoomLevel, workingCalendar) {
119
+ if (!Number.isFinite(dateMs)) {
120
+ return dateMs;
121
+ }
122
+ if (resolveDataGridGanttSnapDays(zoomLevel) === 7) {
123
+ return startOfUtcWeek(dateMs);
124
+ }
125
+ return workingCalendar
126
+ ? snapDataGridDateToWorkingDay(dateMs, workingCalendar)
127
+ : startOfUtcDay(dateMs);
128
+ }
129
+ export function snapDataGridGanttDayDelta(dayDelta, zoomLevel) {
130
+ if (!Number.isFinite(dayDelta) || dayDelta === 0) {
131
+ return 0;
132
+ }
133
+ const snapDays = resolveDataGridGanttSnapDays(zoomLevel);
134
+ if (snapDays === 1) {
135
+ return Math.round(dayDelta);
136
+ }
137
+ return Math.round(dayDelta / snapDays) * snapDays;
138
+ }
139
+ export function resolveDataGridGanttDependencies(value) {
140
+ if (Array.isArray(value)) {
141
+ return value
142
+ .map(entry => String(entry !== null && entry !== void 0 ? entry : "").trim())
143
+ .filter(entry => entry.length > 0);
144
+ }
145
+ if (typeof value === "string") {
146
+ return value
147
+ .split(",")
148
+ .map(entry => entry.trim())
149
+ .filter(entry => entry.length > 0);
150
+ }
151
+ return [];
152
+ }
153
+ function resolveDataGridGanttDependencyType(value) {
154
+ const normalized = value.trim().toUpperCase();
155
+ return normalized === "FS" || normalized === "SS" || normalized === "FF" || normalized === "SF"
156
+ ? normalized
157
+ : null;
158
+ }
159
+ function parseDataGridGanttDependencyRef(token) {
160
+ var _a, _b, _c, _d, _e, _f;
161
+ const trimmed = token.trim();
162
+ if (trimmed.length === 0) {
163
+ return null;
164
+ }
165
+ const delimitedMatch = trimmed.match(/^(.+?)(?::|->|\s+)(FS|SS|FF|SF)$/i);
166
+ if (delimitedMatch) {
167
+ const taskId = (_b = (_a = delimitedMatch[1]) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : "";
168
+ const type = resolveDataGridGanttDependencyType((_c = delimitedMatch[2]) !== null && _c !== void 0 ? _c : "");
169
+ if (taskId.length > 0 && type) {
170
+ return {
171
+ taskId,
172
+ type,
173
+ raw: trimmed,
174
+ };
175
+ }
176
+ }
177
+ const compactNumericMatch = trimmed.match(/^(\d+)(FS|SS|FF|SF)$/i);
178
+ if (compactNumericMatch) {
179
+ const taskId = (_e = (_d = compactNumericMatch[1]) === null || _d === void 0 ? void 0 : _d.trim()) !== null && _e !== void 0 ? _e : "";
180
+ const type = resolveDataGridGanttDependencyType((_f = compactNumericMatch[2]) !== null && _f !== void 0 ? _f : "");
181
+ if (taskId.length > 0 && type) {
182
+ return {
183
+ taskId,
184
+ type,
185
+ raw: trimmed,
186
+ };
187
+ }
188
+ }
189
+ return {
190
+ taskId: trimmed,
191
+ type: "FS",
192
+ raw: trimmed,
193
+ };
194
+ }
195
+ export function resolveDataGridGanttDependencyRefs(value) {
196
+ return resolveDataGridGanttDependencies(value)
197
+ .map(parseDataGridGanttDependencyRef)
198
+ .filter((dependencyRef) => dependencyRef !== null);
199
+ }
200
+ export function normalizeDataGridGanttOptions(input) {
201
+ var _a, _b;
202
+ if (input == null || input === false) {
203
+ return null;
204
+ }
205
+ if (input === true) {
206
+ return { ...DEFAULT_GANTT_OPTIONS };
207
+ }
208
+ const zoomLevel = input.zoomLevel === "week" || input.zoomLevel === "month"
209
+ ? input.zoomLevel
210
+ : "day";
211
+ return {
212
+ startKey: typeof input.startKey === "string" && input.startKey.length > 0
213
+ ? input.startKey
214
+ : DEFAULT_GANTT_OPTIONS.startKey,
215
+ endKey: typeof input.endKey === "string" && input.endKey.length > 0
216
+ ? input.endKey
217
+ : DEFAULT_GANTT_OPTIONS.endKey,
218
+ baselineStartKey: typeof input.baselineStartKey === "string" && input.baselineStartKey.length > 0
219
+ ? input.baselineStartKey
220
+ : DEFAULT_GANTT_OPTIONS.baselineStartKey,
221
+ baselineEndKey: typeof input.baselineEndKey === "string" && input.baselineEndKey.length > 0
222
+ ? input.baselineEndKey
223
+ : DEFAULT_GANTT_OPTIONS.baselineEndKey,
224
+ progressKey: typeof input.progressKey === "string" && input.progressKey.length > 0
225
+ ? input.progressKey
226
+ : DEFAULT_GANTT_OPTIONS.progressKey,
227
+ dependencyKey: typeof input.dependencyKey === "string" && input.dependencyKey.length > 0
228
+ ? input.dependencyKey
229
+ : DEFAULT_GANTT_OPTIONS.dependencyKey,
230
+ labelKey: typeof input.labelKey === "string" && input.labelKey.length > 0
231
+ ? input.labelKey
232
+ : DEFAULT_GANTT_OPTIONS.labelKey,
233
+ idKey: typeof input.idKey === "string" && input.idKey.length > 0
234
+ ? input.idKey
235
+ : DEFAULT_GANTT_OPTIONS.idKey,
236
+ criticalKey: typeof input.criticalKey === "string" && input.criticalKey.length > 0
237
+ ? input.criticalKey
238
+ : DEFAULT_GANTT_OPTIONS.criticalKey,
239
+ computedCriticalPath: input.computedCriticalPath === true,
240
+ paneWidth: normalizeNumericOption(input.paneWidth, DEFAULT_GANTT_OPTIONS.paneWidth, 280),
241
+ pixelsPerDay: normalizeNumericOption(input.pixelsPerDay, PIXELS_PER_DAY_BY_ZOOM[zoomLevel], 1),
242
+ zoomLevel,
243
+ timelineStart: (_a = input.timelineStart) !== null && _a !== void 0 ? _a : null,
244
+ timelineEnd: (_b = input.timelineEnd) !== null && _b !== void 0 ? _b : null,
245
+ rangePaddingDays: normalizeNumericOption(input.rangePaddingDays, DEFAULT_GANTT_OPTIONS.rangePaddingDays, 0),
246
+ workingCalendar: resolveDataGridWorkingCalendar(input.workingCalendar),
247
+ rowBarHeight: normalizeNumericOption(input.rowBarHeight, DEFAULT_GANTT_OPTIONS.rowBarHeight, 6),
248
+ minBarWidth: normalizeNumericOption(input.minBarWidth, DEFAULT_GANTT_OPTIONS.minBarWidth, 1),
249
+ resizeHandleWidth: normalizeNumericOption(input.resizeHandleWidth, DEFAULT_GANTT_OPTIONS.resizeHandleWidth, 2),
250
+ };
251
+ }
252
+ export function resolveDataGridGanttTimelineState(rows, options) {
253
+ return resolveDataGridGanttAnalysis(rows, options).timeline;
254
+ }
255
+ function resolveDataGridGanttCriticalTaskIdsFromTaskNodes(taskNodes, taskIdByRowId) {
256
+ var _a, _b, _c, _d, _e, _f, _g;
257
+ const successorIdsByTaskId = new Map();
258
+ const predecessorIdsByTaskId = new Map();
259
+ const indegreeByTaskId = new Map();
260
+ for (const taskId of taskNodes.keys()) {
261
+ successorIdsByTaskId.set(taskId, []);
262
+ predecessorIdsByTaskId.set(taskId, []);
263
+ indegreeByTaskId.set(taskId, 0);
264
+ }
265
+ for (const node of taskNodes.values()) {
266
+ const predecessors = predecessorIdsByTaskId.get(node.taskId);
267
+ const uniquePredecessors = new Set();
268
+ for (const dependency of node.dependencyRefs) {
269
+ if (dependency.type !== "FS") {
270
+ continue;
271
+ }
272
+ const dependencyId = dependency.taskId;
273
+ const predecessorTaskId = taskNodes.has(dependencyId)
274
+ ? dependencyId
275
+ : ((_a = taskIdByRowId.get(dependencyId)) !== null && _a !== void 0 ? _a : null);
276
+ if (!predecessorTaskId || predecessorTaskId === node.taskId || uniquePredecessors.has(predecessorTaskId)) {
277
+ continue;
278
+ }
279
+ uniquePredecessors.add(predecessorTaskId);
280
+ predecessors === null || predecessors === void 0 ? void 0 : predecessors.push(predecessorTaskId);
281
+ (_b = successorIdsByTaskId.get(predecessorTaskId)) === null || _b === void 0 ? void 0 : _b.push(node.taskId);
282
+ indegreeByTaskId.set(node.taskId, ((_c = indegreeByTaskId.get(node.taskId)) !== null && _c !== void 0 ? _c : 0) + 1);
283
+ }
284
+ }
285
+ const queue = Array.from(taskNodes.keys()).filter(taskId => { var _a; return ((_a = indegreeByTaskId.get(taskId)) !== null && _a !== void 0 ? _a : 0) === 0; });
286
+ const topologicalOrder = [];
287
+ for (let index = 0; index < queue.length; index += 1) {
288
+ const taskId = queue[index];
289
+ if (!taskId) {
290
+ continue;
291
+ }
292
+ topologicalOrder.push(taskId);
293
+ for (const successorTaskId of (_d = successorIdsByTaskId.get(taskId)) !== null && _d !== void 0 ? _d : []) {
294
+ const nextIndegree = ((_e = indegreeByTaskId.get(successorTaskId)) !== null && _e !== void 0 ? _e : 0) - 1;
295
+ indegreeByTaskId.set(successorTaskId, nextIndegree);
296
+ if (nextIndegree === 0) {
297
+ queue.push(successorTaskId);
298
+ }
299
+ }
300
+ }
301
+ if (topologicalOrder.length !== taskNodes.size) {
302
+ return new Set();
303
+ }
304
+ const earliestStartByTaskId = new Map();
305
+ const earliestFinishByTaskId = new Map();
306
+ let projectEndMs = Number.NEGATIVE_INFINITY;
307
+ for (const taskId of topologicalOrder) {
308
+ const node = taskNodes.get(taskId);
309
+ if (!node) {
310
+ continue;
311
+ }
312
+ const durationMs = Math.max(0, node.endMs - node.startMs);
313
+ const predecessorFinishMs = ((_f = predecessorIdsByTaskId.get(taskId)) !== null && _f !== void 0 ? _f : []).reduce((maxFinish, predecessorTaskId) => {
314
+ var _a;
315
+ return Math.max(maxFinish, (_a = earliestFinishByTaskId.get(predecessorTaskId)) !== null && _a !== void 0 ? _a : Number.NEGATIVE_INFINITY);
316
+ }, Number.NEGATIVE_INFINITY);
317
+ const earliestStartMs = Math.max(node.startMs, Number.isFinite(predecessorFinishMs) ? predecessorFinishMs : node.startMs);
318
+ const earliestFinishMs = earliestStartMs + durationMs;
319
+ earliestStartByTaskId.set(taskId, earliestStartMs);
320
+ earliestFinishByTaskId.set(taskId, earliestFinishMs);
321
+ projectEndMs = Math.max(projectEndMs, earliestFinishMs, node.endMs);
322
+ }
323
+ if (!Number.isFinite(projectEndMs)) {
324
+ return new Set();
325
+ }
326
+ const latestStartByTaskId = new Map();
327
+ for (let index = topologicalOrder.length - 1; index >= 0; index -= 1) {
328
+ const taskId = topologicalOrder[index];
329
+ const node = taskNodes.get(taskId);
330
+ if (!node) {
331
+ continue;
332
+ }
333
+ const durationMs = Math.max(0, node.endMs - node.startMs);
334
+ const successorStartMs = ((_g = successorIdsByTaskId.get(taskId)) !== null && _g !== void 0 ? _g : []).reduce((minStart, successorTaskId) => {
335
+ var _a;
336
+ return Math.min(minStart, (_a = latestStartByTaskId.get(successorTaskId)) !== null && _a !== void 0 ? _a : Number.POSITIVE_INFINITY);
337
+ }, Number.POSITIVE_INFINITY);
338
+ const latestFinishMs = Number.isFinite(successorStartMs) ? successorStartMs : projectEndMs;
339
+ latestStartByTaskId.set(taskId, latestFinishMs - durationMs);
340
+ }
341
+ const criticalTaskIds = new Set();
342
+ const slackEpsilonMs = 60 * 1000;
343
+ for (const taskId of topologicalOrder) {
344
+ const earliestStartMs = earliestStartByTaskId.get(taskId);
345
+ const latestStartMs = latestStartByTaskId.get(taskId);
346
+ if (earliestStartMs == null || latestStartMs == null) {
347
+ continue;
348
+ }
349
+ if (Math.abs(latestStartMs - earliestStartMs) <= slackEpsilonMs) {
350
+ criticalTaskIds.add(taskId);
351
+ }
352
+ }
353
+ return criticalTaskIds;
354
+ }
355
+ function resolveDataGridGanttAnalysisInternal(rows, options, computeCriticalPath) {
356
+ const readResolvedRow = createResolvedGanttRowSnapshotReader(options);
357
+ const explicitStartMs = resolveDataGridGanttDateMs(options.timelineStart);
358
+ const explicitEndMs = resolveDataGridGanttDateMs(options.timelineEnd);
359
+ let minMs = explicitStartMs;
360
+ let maxMs = explicitEndMs;
361
+ const taskNodes = new Map();
362
+ const taskIdByRowId = new Map();
363
+ if (explicitStartMs == null || explicitEndMs == null || computeCriticalPath) {
364
+ const count = rows.getCount();
365
+ for (let index = 0; index < count; index += 1) {
366
+ const row = rows.get(index);
367
+ if (!row) {
368
+ continue;
369
+ }
370
+ const resolvedRow = readResolvedRow(row);
371
+ const startMs = resolvedRow.startMs;
372
+ const endMs = resolvedRow.endMs;
373
+ if (startMs == null && endMs == null) {
374
+ continue;
375
+ }
376
+ const boundedStart = startMs !== null && startMs !== void 0 ? startMs : endMs;
377
+ const boundedEnd = endMs !== null && endMs !== void 0 ? endMs : startMs;
378
+ if (boundedStart == null || boundedEnd == null) {
379
+ continue;
380
+ }
381
+ if (explicitStartMs == null) {
382
+ minMs = minMs == null ? boundedStart : Math.min(minMs, boundedStart);
383
+ }
384
+ if (explicitEndMs == null) {
385
+ maxMs = maxMs == null ? boundedEnd : Math.max(maxMs, boundedEnd);
386
+ }
387
+ if (computeCriticalPath) {
388
+ if (row.kind === "group" || boundedEnd < boundedStart) {
389
+ continue;
390
+ }
391
+ const taskId = resolvedRow.taskId || (row.rowId == null ? String(index) : String(row.rowId));
392
+ const rowId = row.rowId == null ? String(index) : String(row.rowId);
393
+ taskNodes.set(taskId, {
394
+ taskId,
395
+ rowId,
396
+ startMs: boundedStart,
397
+ endMs: boundedEnd,
398
+ dependencyRefs: resolvedRow.dependencyRefs,
399
+ });
400
+ taskIdByRowId.set(rowId, taskId);
401
+ }
402
+ }
403
+ }
404
+ const range = resolveDataGridTimelineRange({
405
+ minTaskDateMs: minMs,
406
+ maxTaskDateMs: maxMs,
407
+ pixelsPerDay: options.pixelsPerDay,
408
+ rangePaddingDays: options.rangePaddingDays,
409
+ });
410
+ return {
411
+ timeline: {
412
+ startMs: range.startMs,
413
+ endMs: range.endMs,
414
+ pixelsPerDay: options.pixelsPerDay,
415
+ totalWidth: range.totalWidth,
416
+ zoomLevel: options.zoomLevel,
417
+ },
418
+ criticalTaskIds: computeCriticalPath
419
+ ? resolveDataGridGanttCriticalTaskIdsFromTaskNodes(taskNodes, taskIdByRowId)
420
+ : new Set(),
421
+ };
422
+ }
423
+ export function resolveDataGridGanttAnalysis(rows, options) {
424
+ return resolveDataGridGanttAnalysisInternal(rows, options, options.computedCriticalPath === true);
425
+ }
426
+ export function resolveDataGridGanttRangeFrame(range, timeline, minBarWidth, milestoneWidth) {
427
+ if (range.endMs <= range.startMs) {
428
+ const width = Math.max(minBarWidth, milestoneWidth !== null && milestoneWidth !== void 0 ? milestoneWidth : minBarWidth);
429
+ return {
430
+ x: resolveDataGridTimelineDateToPixel(range.startMs, timeline) - (width / 2),
431
+ width,
432
+ };
433
+ }
434
+ return {
435
+ x: resolveDataGridTimelineDateToPixel(range.startMs, timeline),
436
+ width: Math.max(minBarWidth, resolveDataGridTimelineDateToPixel(range.endMs, timeline)
437
+ - resolveDataGridTimelineDateToPixel(range.startMs, timeline)),
438
+ };
439
+ }
440
+ export function resolveDataGridGanttDateOffset(dateMs, timeline) {
441
+ return resolveDataGridTimelineDateToPixel(dateMs, timeline);
442
+ }
443
+ export function clampDataGridGanttScrollLeft(scrollLeft, totalWidth, viewportWidth) {
444
+ return clampDataGridTimelineScrollLeft(scrollLeft, totalWidth, viewportWidth);
445
+ }
446
+ export function resolveDataGridGanttScrollLeftForDate(input) {
447
+ return resolveDataGridTimelineScrollLeftForDate({
448
+ dateMs: input.dateMs,
449
+ timeline: input.timeline,
450
+ viewportWidth: input.viewportWidth,
451
+ align: input.align,
452
+ });
453
+ }
454
+ export function buildDataGridGanttVisibleBars(input) {
455
+ var _a, _b, _c, _d, _e, _f;
456
+ const readResolvedRow = createResolvedGanttRowSnapshotReader(input.options);
457
+ const bars = [];
458
+ const rowContexts = [];
459
+ let currentY = input.topSpacerHeight;
460
+ const rowBarHeight = input.options.rowBarHeight;
461
+ input.rows.forEach((row, rowOffset) => {
462
+ var _a, _b, _c;
463
+ const rowIndex = input.viewportRowStart + rowOffset;
464
+ const metric = (_a = input.rowMetrics) === null || _a === void 0 ? void 0 : _a[rowOffset];
465
+ const rowHeight = Math.max(1, (_b = metric === null || metric === void 0 ? void 0 : metric.height) !== null && _b !== void 0 ? _b : (input.resolveRowHeight(rowIndex) || input.baseRowHeight));
466
+ const rowTop = (_c = metric === null || metric === void 0 ? void 0 : metric.top) !== null && _c !== void 0 ? _c : currentY;
467
+ rowContexts.push({
468
+ row,
469
+ rowIndex,
470
+ rowId: row.rowId == null ? String(rowIndex) : String(row.rowId),
471
+ rowUpdateId: row.rowId == null ? rowIndex : row.rowId,
472
+ rowHeight,
473
+ y: (rowTop - input.scrollTop) + Math.max(0, (rowHeight - rowBarHeight) / 2),
474
+ resolvedRow: readResolvedRow(row),
475
+ });
476
+ currentY = rowTop + rowHeight;
477
+ });
478
+ const summaryRangesByRowId = new Map();
479
+ const activeGroups = [];
480
+ function absorbRangeIntoActiveGroups(startMs, endMs) {
481
+ for (const group of activeGroups) {
482
+ group.startMs = group.startMs == null ? startMs : Math.min(group.startMs, startMs);
483
+ group.endMs = group.endMs == null ? endMs : Math.max(group.endMs, endMs);
484
+ }
485
+ }
486
+ function closeGroupsAtLevel(level) {
487
+ while (activeGroups.length > 0) {
488
+ const current = activeGroups[activeGroups.length - 1];
489
+ if (!current || current.level < level) {
490
+ break;
491
+ }
492
+ activeGroups.pop();
493
+ if (current.startMs != null && current.endMs != null && current.endMs >= current.startMs) {
494
+ summaryRangesByRowId.set(current.rowId, {
495
+ startMs: current.startMs,
496
+ endMs: current.endMs,
497
+ });
498
+ }
499
+ }
500
+ }
501
+ for (const context of rowContexts) {
502
+ if (context.row.kind === "group") {
503
+ const level = Math.max(0, Math.trunc((_b = (_a = context.row.groupMeta) === null || _a === void 0 ? void 0 : _a.level) !== null && _b !== void 0 ? _b : 0));
504
+ closeGroupsAtLevel(level);
505
+ activeGroups.push({
506
+ rowId: context.rowId,
507
+ level,
508
+ startMs: null,
509
+ endMs: null,
510
+ });
511
+ const ownStartMs = context.resolvedRow.startMs;
512
+ const ownEndMs = context.resolvedRow.endMs;
513
+ if (ownStartMs != null && ownEndMs != null && ownEndMs >= ownStartMs) {
514
+ absorbRangeIntoActiveGroups(ownStartMs, ownEndMs);
515
+ }
516
+ continue;
517
+ }
518
+ const startMs = context.resolvedRow.startMs;
519
+ const endMs = context.resolvedRow.endMs;
520
+ if (startMs != null && endMs != null && endMs >= startMs) {
521
+ absorbRangeIntoActiveGroups(startMs, endMs);
522
+ }
523
+ }
524
+ closeGroupsAtLevel(0);
525
+ for (const context of rowContexts) {
526
+ const ownStartMs = context.resolvedRow.startMs;
527
+ const ownEndMs = context.resolvedRow.endMs;
528
+ const ownBaselineStartMs = context.resolvedRow.baselineStartMs;
529
+ const ownBaselineEndMs = context.resolvedRow.baselineEndMs;
530
+ const summaryRange = context.row.kind === "group"
531
+ ? summaryRangesByRowId.get(context.rowId)
532
+ : null;
533
+ const startMs = (_c = summaryRange === null || summaryRange === void 0 ? void 0 : summaryRange.startMs) !== null && _c !== void 0 ? _c : ownStartMs;
534
+ const endMs = (_d = summaryRange === null || summaryRange === void 0 ? void 0 : summaryRange.endMs) !== null && _d !== void 0 ? _d : ownEndMs;
535
+ if (startMs == null || endMs == null || endMs < startMs) {
536
+ continue;
537
+ }
538
+ const summary = context.row.kind === "group" && summaryRange != null;
539
+ const computedCritical = (_f = (_e = input.criticalTaskIds) === null || _e === void 0 ? void 0 : _e.has(context.resolvedRow.taskId || context.rowId)) !== null && _f !== void 0 ? _f : false;
540
+ const height = rowBarHeight;
541
+ const y = context.y;
542
+ if (y + height < 0 || y > input.viewportHeight) {
543
+ continue;
544
+ }
545
+ const milestone = !summary && endMs === startMs;
546
+ const frame = resolveDataGridGanttRangeFrame({ startMs, endMs }, input.timeline, input.options.minBarWidth, milestone ? height : undefined);
547
+ bars.push({
548
+ row: context.row,
549
+ rowId: context.rowId,
550
+ rowUpdateId: context.rowUpdateId,
551
+ taskId: context.resolvedRow.taskId || context.rowId,
552
+ rowIndex: context.rowIndex,
553
+ label: context.resolvedRow.label,
554
+ dependencies: summary ? [] : context.resolvedRow.dependencies,
555
+ dependencyRefs: summary ? [] : context.resolvedRow.dependencyRefs,
556
+ critical: summary ? false : (context.resolvedRow.critical || computedCritical),
557
+ criticalSource: summary
558
+ ? null
559
+ : (context.resolvedRow.critical ? "manual" : (computedCritical ? "computed" : null)),
560
+ milestone,
561
+ summary,
562
+ progress: summary ? 0 : context.resolvedRow.progress,
563
+ startMs,
564
+ endMs,
565
+ baselineStartMs: summary || ownBaselineStartMs == null || ownBaselineEndMs == null || ownBaselineEndMs < ownBaselineStartMs
566
+ ? null
567
+ : ownBaselineStartMs,
568
+ baselineEndMs: summary || ownBaselineStartMs == null || ownBaselineEndMs == null || ownBaselineEndMs < ownBaselineStartMs
569
+ ? null
570
+ : ownBaselineEndMs,
571
+ x: frame.x,
572
+ width: frame.width,
573
+ y,
574
+ height,
575
+ });
576
+ }
577
+ return bars;
578
+ }
579
+ export function resolveDataGridGanttCriticalTaskIds(rows, options) {
580
+ return resolveDataGridGanttAnalysisInternal(rows, options, true).criticalTaskIds;
581
+ }
582
+ export function buildDataGridGanttDependencyPaths(input) {
583
+ var _a, _b;
584
+ const barsByTaskId = new Map();
585
+ const barsByRowId = new Map();
586
+ for (const bar of input.bars) {
587
+ const taskId = bar.taskId.trim();
588
+ const rowId = bar.rowId.trim();
589
+ if (taskId.length > 0) {
590
+ barsByTaskId.set(taskId, bar);
591
+ }
592
+ if (rowId.length > 0) {
593
+ barsByRowId.set(rowId, bar);
594
+ }
595
+ }
596
+ const paths = [];
597
+ const resolveFrame = (_a = input.resolveFrame) !== null && _a !== void 0 ? _a : (bar => bar);
598
+ const minBendPx = Math.max(0, (_b = input.minBendPx) !== null && _b !== void 0 ? _b : 12);
599
+ const portStubPx = Math.max(4, Math.min(8, Math.round(minBendPx / 2)));
600
+ const compactGapThresholdPx = (portStubPx * 2) + minBendPx;
601
+ function resolveAnchorX(frame, side) {
602
+ return side === "start" ? frame.x : frame.x + frame.width;
603
+ }
604
+ function resolveLeadX(anchorX, side) {
605
+ return side === "start" ? anchorX - portStubPx : anchorX + portStubPx;
606
+ }
607
+ for (const targetBar of input.bars) {
608
+ const targetFrame = resolveFrame(targetBar);
609
+ targetBar.dependencyRefs.forEach((dependency, dependencyIndex) => {
610
+ var _a;
611
+ const dependencyTaskId = dependency.taskId;
612
+ const sourceBar = (_a = barsByTaskId.get(dependencyTaskId)) !== null && _a !== void 0 ? _a : barsByRowId.get(dependencyTaskId);
613
+ if (!sourceBar) {
614
+ return;
615
+ }
616
+ const sourceFrame = resolveFrame(sourceBar);
617
+ const sourceSide = dependency.type === "SS" || dependency.type === "SF" ? "start" : "end";
618
+ const targetSide = dependency.type === "FS" || dependency.type === "SS" ? "start" : "end";
619
+ const sourceX = resolveAnchorX(sourceFrame, sourceSide);
620
+ const targetX = resolveAnchorX(targetFrame, targetSide);
621
+ const sourceY = sourceFrame.y + (sourceFrame.height / 2);
622
+ const targetY = targetFrame.y + (targetFrame.height / 2);
623
+ const laneOffsetPx = minBendPx + ((dependencyIndex % 4) * 8);
624
+ const sourceLeadX = resolveLeadX(sourceX, sourceSide);
625
+ const targetApproachX = resolveLeadX(targetX, targetSide);
626
+ const points = (() => {
627
+ if (sourceSide === targetSide) {
628
+ const laneX = sourceSide === "start"
629
+ ? Math.min(sourceLeadX, targetApproachX) - laneOffsetPx
630
+ : Math.max(sourceLeadX, targetApproachX) + laneOffsetPx;
631
+ return [
632
+ { x: sourceX, y: sourceY },
633
+ { x: sourceLeadX, y: sourceY },
634
+ { x: laneX, y: sourceY },
635
+ { x: laneX, y: targetY },
636
+ { x: targetApproachX, y: targetY },
637
+ { x: targetX, y: targetY },
638
+ ];
639
+ }
640
+ if (sourceSide === "end" && targetSide === "start" && targetX >= sourceX) {
641
+ const gapPx = targetX - sourceX;
642
+ const laneX = gapPx <= compactGapThresholdPx
643
+ ? sourceLeadX
644
+ : Math.max(sourceLeadX, sourceX + Math.max(laneOffsetPx, (targetApproachX - sourceX) / 2));
645
+ return [
646
+ { x: sourceX, y: sourceY },
647
+ { x: sourceLeadX, y: sourceY },
648
+ { x: laneX, y: sourceY },
649
+ { x: laneX, y: targetY },
650
+ { x: targetApproachX, y: targetY },
651
+ { x: targetX, y: targetY },
652
+ ];
653
+ }
654
+ const detourX = sourceSide === "end"
655
+ ? Math.max(sourceFrame.x + sourceFrame.width, targetFrame.x + targetFrame.width) + laneOffsetPx
656
+ : Math.min(sourceFrame.x, targetFrame.x) - laneOffsetPx;
657
+ const laneY = sourceY + ((targetY - sourceY) / 2);
658
+ return [
659
+ { x: sourceX, y: sourceY },
660
+ { x: sourceLeadX, y: sourceY },
661
+ { x: detourX, y: sourceY },
662
+ { x: detourX, y: laneY },
663
+ { x: targetApproachX, y: laneY },
664
+ { x: targetApproachX, y: targetY },
665
+ { x: targetX, y: targetY },
666
+ ];
667
+ })();
668
+ paths.push({
669
+ dependencyTaskId,
670
+ dependencyType: dependency.type,
671
+ sourceBar,
672
+ targetBar,
673
+ points,
674
+ });
675
+ });
676
+ }
677
+ return paths;
678
+ }
679
+ export function hitTestDataGridGanttBar(bars, point, resizeHandleWidth) {
680
+ for (let index = bars.length - 1; index >= 0; index -= 1) {
681
+ const bar = bars[index];
682
+ if (!bar) {
683
+ continue;
684
+ }
685
+ if (point.x < bar.x
686
+ || point.x > bar.x + bar.width
687
+ || point.y < bar.y
688
+ || point.y > bar.y + bar.height) {
689
+ continue;
690
+ }
691
+ if (bar.summary) {
692
+ continue;
693
+ }
694
+ if (bar.milestone) {
695
+ return { bar, mode: "move" };
696
+ }
697
+ const distanceToStart = point.x - bar.x;
698
+ const distanceToEnd = (bar.x + bar.width) - point.x;
699
+ if (distanceToStart <= resizeHandleWidth) {
700
+ return { bar, mode: "resize-start" };
701
+ }
702
+ if (distanceToEnd <= resizeHandleWidth) {
703
+ return { bar, mode: "resize-end" };
704
+ }
705
+ return { bar, mode: "move" };
706
+ }
707
+ return null;
708
+ }
709
+ export function applyDataGridGanttDragDelta(range, mode, dayDelta, zoomLevel = "day", workingCalendar) {
710
+ const snappedDayDelta = snapDataGridGanttDayDelta(dayDelta, zoomLevel);
711
+ if (!Number.isFinite(snappedDayDelta) || snappedDayDelta === 0) {
712
+ return range;
713
+ }
714
+ if (workingCalendar && resolveDataGridGanttSnapDays(zoomLevel) === 1) {
715
+ if (mode === "move") {
716
+ return {
717
+ startMs: addDataGridWorkingDays(range.startMs, snappedDayDelta, workingCalendar),
718
+ endMs: addDataGridWorkingDays(range.endMs, snappedDayDelta, workingCalendar),
719
+ };
720
+ }
721
+ if (mode === "resize-start") {
722
+ const startMs = addDataGridWorkingDays(range.startMs, snappedDayDelta, workingCalendar);
723
+ return {
724
+ startMs: Math.min(startMs, addDataGridWorkingDays(range.endMs, -1, workingCalendar)),
725
+ endMs: range.endMs,
726
+ };
727
+ }
728
+ const endMs = addDataGridWorkingDays(range.endMs, snappedDayDelta, workingCalendar);
729
+ return {
730
+ startMs: range.startMs,
731
+ endMs: Math.max(addDataGridWorkingDays(range.startMs, 1, workingCalendar), endMs),
732
+ };
733
+ }
734
+ const deltaMs = snappedDayDelta * DAY_MS;
735
+ if (mode === "move") {
736
+ const durationMs = Math.max(0, range.endMs - range.startMs);
737
+ const startMs = snapDataGridGanttDateMs(range.startMs + deltaMs, zoomLevel, workingCalendar);
738
+ return {
739
+ startMs,
740
+ endMs: startMs + durationMs,
741
+ };
742
+ }
743
+ if (mode === "resize-start") {
744
+ const startMs = snapDataGridGanttDateMs(range.startMs + deltaMs, zoomLevel, workingCalendar);
745
+ return {
746
+ startMs: Math.min(startMs, range.endMs - DAY_MS),
747
+ endMs: range.endMs,
748
+ };
749
+ }
750
+ const endMs = snapDataGridGanttDateMs(range.endMs + deltaMs, zoomLevel, workingCalendar);
751
+ return {
752
+ startMs: range.startMs,
753
+ endMs: Math.max(range.startMs + DAY_MS, endMs),
754
+ };
755
+ }
756
+ export function buildDataGridGanttRowEditPatch(rowId, range, options) {
757
+ return {
758
+ rowId,
759
+ data: {
760
+ [options.startKey]: new Date(range.startMs),
761
+ [options.endKey]: new Date(range.endMs),
762
+ },
763
+ };
764
+ }
765
+ export function formatDataGridGanttDayLabel(dayMs, zoomLevel) {
766
+ const date = new Date(dayMs);
767
+ if (zoomLevel === "month") {
768
+ return date.toLocaleDateString(undefined, {
769
+ month: "short",
770
+ year: "numeric",
771
+ });
772
+ }
773
+ if (zoomLevel === "week") {
774
+ return date.toLocaleDateString(undefined, {
775
+ month: "short",
776
+ day: "numeric",
777
+ });
778
+ }
779
+ return date.toLocaleDateString(undefined, {
780
+ weekday: "short",
781
+ month: "short",
782
+ day: "numeric",
783
+ });
784
+ }