@editora/ui-core 0.1.23 → 0.1.25
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/README.md +215 -5
- package/dist/components/gantt-schedule.d.ts +79 -0
- package/dist/components/gantt-schedule.d.ts.map +1 -0
- package/dist/components/ui-gantt.d.ts +186 -2
- package/dist/components/ui-gantt.d.ts.map +1 -1
- package/dist/components/ui-sortable.d.ts +209 -12
- package/dist/components/ui-sortable.d.ts.map +1 -1
- package/dist/gantt.cjs.js +519 -104
- package/dist/gantt.esm.js +1876 -371
- package/dist/sortable.cjs.js +504 -369
- package/dist/sortable.d.ts +1 -1
- package/dist/sortable.d.ts.map +1 -1
- package/dist/sortable.esm.js +1758 -1168
- package/package.json +1 -1
package/dist/gantt.esm.js
CHANGED
|
@@ -1,11 +1,138 @@
|
|
|
1
|
-
import { E as
|
|
2
|
-
|
|
1
|
+
import { E as Se } from "./ElementBase-BeZT54Hj.mjs";
|
|
2
|
+
function O(n, t) {
|
|
3
|
+
const e = new Date(n);
|
|
4
|
+
return e.setDate(e.getDate() + t), e.setHours(0, 0, 0, 0), e.getTime();
|
|
5
|
+
}
|
|
6
|
+
function wt(n, t) {
|
|
7
|
+
return Math.round((t - n) / 864e5);
|
|
8
|
+
}
|
|
9
|
+
function le(n) {
|
|
10
|
+
const t = new Set(n.map((r) => r.id)), e = /* @__PURE__ */ new Map();
|
|
11
|
+
for (const r of n) {
|
|
12
|
+
if (!r.parent || !t.has(r.parent) || r.parent === r.id) continue;
|
|
13
|
+
const i = e.get(r.parent) || [];
|
|
14
|
+
i.push(r), e.set(r.parent, i);
|
|
15
|
+
}
|
|
16
|
+
return e;
|
|
17
|
+
}
|
|
18
|
+
function de(n, t) {
|
|
19
|
+
const e = le(n), r = [], i = /* @__PURE__ */ new Set([t]), a = [t];
|
|
20
|
+
for (; a.length; )
|
|
21
|
+
for (const s of e.get(a.shift()) || [])
|
|
22
|
+
i.has(s.id) || (i.add(s.id), r.push(s), a.push(s.id));
|
|
23
|
+
return r;
|
|
24
|
+
}
|
|
25
|
+
function Ee(n) {
|
|
26
|
+
const t = le(n), e = /* @__PURE__ */ new Map(), r = /* @__PURE__ */ new Set(), i = (a) => {
|
|
27
|
+
const s = e.get(a.id);
|
|
28
|
+
if (s) return s;
|
|
29
|
+
const o = { start: a.start, end: a.end, progress: a.progress }, c = t.get(a.id);
|
|
30
|
+
if (!c?.length || r.has(a.id)) return o;
|
|
31
|
+
r.add(a.id);
|
|
32
|
+
let h = 1 / 0, d = -1 / 0, g = 0, u = 0;
|
|
33
|
+
for (const m of c) {
|
|
34
|
+
const p = i(m);
|
|
35
|
+
if (h = Math.min(h, p.start), d = Math.max(d, p.end), p.progress != null) {
|
|
36
|
+
const k = Math.max(1, wt(p.start, p.end));
|
|
37
|
+
g += k, u += p.progress * k;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
r.delete(a.id);
|
|
41
|
+
const b = { start: h, end: d, progress: g ? u / g : void 0 };
|
|
42
|
+
return e.set(a.id, b), b;
|
|
43
|
+
};
|
|
44
|
+
for (const a of n) i(a);
|
|
45
|
+
return e;
|
|
46
|
+
}
|
|
47
|
+
function Bt(n, t) {
|
|
48
|
+
const e = n.type === "e2s" || n.type === "e2e" ? t.end : t.start;
|
|
49
|
+
return { side: n.type === "e2s" || n.type === "s2s" ? "start" : "end", min: O(e, n.lag) };
|
|
50
|
+
}
|
|
51
|
+
function Te(n, t, e) {
|
|
52
|
+
const r = Bt(n, t);
|
|
53
|
+
return (r.side === "start" ? e.start : e.end) >= r.min;
|
|
54
|
+
}
|
|
55
|
+
function Ae(n, t) {
|
|
56
|
+
const e = new Map(n.map((i) => [i.id, i])), r = /* @__PURE__ */ new Set();
|
|
57
|
+
for (const i of t) {
|
|
58
|
+
const a = e.get(i.source), s = e.get(i.target);
|
|
59
|
+
a && s && !Te(i, a, s) && r.add(i.id);
|
|
60
|
+
}
|
|
61
|
+
return r;
|
|
62
|
+
}
|
|
63
|
+
function Le(n, t, e, r = !1) {
|
|
64
|
+
const i = new Map(n.map((u) => [u.id, { start: u.start, end: u.end }]));
|
|
65
|
+
for (const [u, b] of e) i.has(u) && i.set(u, { ...b });
|
|
66
|
+
const a = new Map(n.map((u) => [u.id, u])), s = /* @__PURE__ */ new Map();
|
|
67
|
+
for (const u of t) {
|
|
68
|
+
const b = s.get(u.source) || [];
|
|
69
|
+
b.push(u), s.set(u.source, b);
|
|
70
|
+
}
|
|
71
|
+
const o = /* @__PURE__ */ new Set(), c = /* @__PURE__ */ new Map(), h = [...e.keys()], d = (u, b) => {
|
|
72
|
+
const m = i.get(u);
|
|
73
|
+
m && (i.set(u, { start: O(m.start, b), end: O(m.end, b) }), o.add(u));
|
|
74
|
+
};
|
|
75
|
+
for (; h.length; ) {
|
|
76
|
+
const u = h.shift(), b = i.get(u);
|
|
77
|
+
if (b)
|
|
78
|
+
for (const m of s.get(u) || []) {
|
|
79
|
+
const p = a.get(m.target), k = i.get(m.target);
|
|
80
|
+
if (!p || !k || p.readonly) continue;
|
|
81
|
+
const y = Bt(m, b), w = y.side === "start" ? k.start : k.end;
|
|
82
|
+
if (w >= y.min) continue;
|
|
83
|
+
const x = (c.get(m.target) || 0) + 1;
|
|
84
|
+
if (x > n.length) continue;
|
|
85
|
+
c.set(m.target, x);
|
|
86
|
+
const M = wt(w, y.min);
|
|
87
|
+
if (d(m.target, M), h.push(m.target), r)
|
|
88
|
+
for (const T of de(n, m.target))
|
|
89
|
+
T.readonly || (d(T.id, M), h.push(T.id));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const g = [];
|
|
93
|
+
for (const u of o) {
|
|
94
|
+
const b = i.get(u), m = e.get(u);
|
|
95
|
+
(!m || m.start !== b.start || m.end !== b.end) && g.push({ id: u, ...b });
|
|
96
|
+
}
|
|
97
|
+
return g;
|
|
98
|
+
}
|
|
99
|
+
function Ie(n, t) {
|
|
100
|
+
const e = new Set(n.map((p) => p.parent).filter((p) => !!p)), r = new Map(n.filter((p) => !e.has(p.id)).map((p) => [p.id, p])), i = t.filter((p) => r.has(p.source) && r.has(p.target) && p.source !== p.target);
|
|
101
|
+
if (!i.length) return /* @__PURE__ */ new Set();
|
|
102
|
+
const a = (p) => wt(p.start, p.end), s = Math.max(...[...r.values()].map((p) => p.end)), o = /* @__PURE__ */ new Map(), c = /* @__PURE__ */ new Map();
|
|
103
|
+
for (const p of i)
|
|
104
|
+
o.set(p.source, [...o.get(p.source) || [], p]), c.set(p.target, [...c.get(p.target) || [], p]);
|
|
105
|
+
const h = new Map([...r.keys()].map((p) => [p, s])), d = new Map([...r.keys()].map((p) => [p, (o.get(p) || []).length])), g = [...r.keys()].filter((p) => d.get(p) === 0), u = /* @__PURE__ */ new Set();
|
|
106
|
+
for (; g.length; ) {
|
|
107
|
+
const p = g.shift();
|
|
108
|
+
u.add(p);
|
|
109
|
+
const k = r.get(p), y = h.get(p), w = O(y, -a(k));
|
|
110
|
+
for (const x of c.get(p) || []) {
|
|
111
|
+
const M = r.get(x.source), T = -x.lag, z = x.type === "e2s" ? O(w, T) : x.type === "s2s" ? O(O(w, T), a(M)) : x.type === "e2e" ? O(y, T) : O(O(y, T), a(M));
|
|
112
|
+
h.set(x.source, Math.min(h.get(x.source), z));
|
|
113
|
+
const U = d.get(x.source) - 1;
|
|
114
|
+
d.set(x.source, U), U === 0 && g.push(x.source);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const b = /* @__PURE__ */ new Set();
|
|
118
|
+
for (const p of i) {
|
|
119
|
+
const k = r.get(p.source), y = r.get(p.target), w = Bt(p, k);
|
|
120
|
+
(w.side === "start" ? y.start : y.end) === w.min && b.add(p.target);
|
|
121
|
+
}
|
|
122
|
+
const m = /* @__PURE__ */ new Set();
|
|
123
|
+
for (const p of u) {
|
|
124
|
+
const k = r.get(p), y = (o.get(p) || []).length + (c.get(p) || []).length > 0, w = wt(k.end, h.get(p));
|
|
125
|
+
y && w <= 0 && (!(c.get(p) || []).length || b.has(p)) && m.add(p);
|
|
126
|
+
}
|
|
127
|
+
return m;
|
|
128
|
+
}
|
|
129
|
+
const D = 1440 * 60 * 1e3, S = 42, N = 62, Tt = 376, ce = "minmax(170px, 1fr) 82px 80px 44px", ze = 640, Ce = 148, De = 2, vt = 28, Fe = 15, he = Fe * Math.SQRT2 / 2, Jt = {
|
|
3
130
|
day: 36,
|
|
4
131
|
week: 13,
|
|
5
132
|
month: 4.4,
|
|
6
133
|
quarter: 2.2,
|
|
7
134
|
year: 1.15
|
|
8
|
-
},
|
|
135
|
+
}, Gt = `
|
|
9
136
|
:host {
|
|
10
137
|
--ui-gantt-bg: color-mix(in srgb, var(--ui-color-surface, #ffffff) 96%, transparent);
|
|
11
138
|
--ui-gantt-panel: var(--ui-color-surface, #ffffff);
|
|
@@ -28,6 +155,7 @@ const I = 1440 * 60 * 1e3, y = 42, j = 62, J = 376, N = 28, At = 15, vt = At * M
|
|
|
28
155
|
}
|
|
29
156
|
|
|
30
157
|
.frame {
|
|
158
|
+
container: ui-gantt / inline-size;
|
|
31
159
|
border: 1px solid var(--ui-gantt-border);
|
|
32
160
|
border-radius: 12px;
|
|
33
161
|
background: var(--ui-gantt-bg);
|
|
@@ -67,7 +195,9 @@ const I = 1440 * 60 * 1e3, y = 42, j = 62, J = 376, N = 28, At = 15, vt = At * M
|
|
|
67
195
|
|
|
68
196
|
.control,
|
|
69
197
|
.select,
|
|
70
|
-
.zoom-button
|
|
198
|
+
.zoom-button,
|
|
199
|
+
.today-button,
|
|
200
|
+
.add-button {
|
|
71
201
|
border: 1px solid var(--ui-gantt-border);
|
|
72
202
|
background: var(--ui-gantt-panel);
|
|
73
203
|
color: var(--ui-gantt-text);
|
|
@@ -87,11 +217,18 @@ const I = 1440 * 60 * 1e3, y = 42, j = 62, J = 376, N = 28, At = 15, vt = At * M
|
|
|
87
217
|
min-inline-size: 118px;
|
|
88
218
|
}
|
|
89
219
|
|
|
90
|
-
.zoom-button
|
|
220
|
+
.zoom-button,
|
|
221
|
+
.today-button,
|
|
222
|
+
.add-button {
|
|
91
223
|
padding: 0 9px;
|
|
92
224
|
cursor: pointer;
|
|
93
225
|
}
|
|
94
226
|
|
|
227
|
+
.today-button:disabled {
|
|
228
|
+
opacity: 0.45;
|
|
229
|
+
cursor: default;
|
|
230
|
+
}
|
|
231
|
+
|
|
95
232
|
.zoom-button[aria-pressed="true"] {
|
|
96
233
|
border-color: color-mix(in srgb, var(--ui-gantt-accent) 46%, var(--ui-gantt-border));
|
|
97
234
|
background: color-mix(in srgb, var(--ui-gantt-accent) 10%, var(--ui-gantt-panel));
|
|
@@ -103,19 +240,67 @@ const I = 1440 * 60 * 1e3, y = 42, j = 62, J = 376, N = 28, At = 15, vt = At * M
|
|
|
103
240
|
max-block-size: var(--ui-gantt-max-height, 620px);
|
|
104
241
|
}
|
|
105
242
|
|
|
243
|
+
/* fill: the chart takes the height of its container (which needs one): the toolbar keeps its height and the
|
|
244
|
+
scroll area takes the rest, however tall. */
|
|
245
|
+
:host([fill]) {
|
|
246
|
+
block-size: 100%;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
:host([fill]) .frame {
|
|
250
|
+
display: flex;
|
|
251
|
+
flex-direction: column;
|
|
252
|
+
block-size: 100%;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
:host([fill]) .toolbar {
|
|
256
|
+
flex: none;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
:host([fill]) .viewport {
|
|
260
|
+
flex: 1 1 0;
|
|
261
|
+
min-block-size: 0;
|
|
262
|
+
max-block-size: none;
|
|
263
|
+
}
|
|
264
|
+
|
|
106
265
|
.surface {
|
|
266
|
+
--gantt-grid-w: ${Tt}px;
|
|
267
|
+
--gantt-cols: ${ce};
|
|
107
268
|
display: grid;
|
|
108
|
-
grid-template-columns:
|
|
269
|
+
grid-template-columns: var(--gantt-grid-w) minmax(640px, 1fr);
|
|
109
270
|
min-inline-size: 100%;
|
|
110
271
|
}
|
|
111
272
|
|
|
273
|
+
/* The task grid is sticky, so at 376px it covers a phone-sized chart completely and no bar can be
|
|
274
|
+
reached. In a narrow host keep only the task name column and give the timeline the room. */
|
|
275
|
+
@container ui-gantt (max-width: 640px) {
|
|
276
|
+
.surface {
|
|
277
|
+
--gantt-grid-w: 148px;
|
|
278
|
+
--gantt-cols: minmax(0, 1fr);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.splitter-rail {
|
|
282
|
+
display: none;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* !important: the sortable header wrappers carry an inline display: contents. */
|
|
286
|
+
.grid-head > :nth-child(n + 2),
|
|
287
|
+
.grid-cell:nth-child(n + 2) {
|
|
288
|
+
display: none !important;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.grid-cell.task-name {
|
|
292
|
+
padding-inline-end: 6px;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
112
296
|
.grid-head,
|
|
113
297
|
.timeline-head {
|
|
114
298
|
position: sticky;
|
|
115
299
|
top: 0;
|
|
116
300
|
z-index: 4;
|
|
117
301
|
min-block-size: 62px;
|
|
118
|
-
|
|
302
|
+
/* Opaque: the rows scroll underneath, and at 96% they showed through as a ghost of the text. */
|
|
303
|
+
background: var(--ui-gantt-panel);
|
|
119
304
|
border-bottom: 1px solid var(--ui-gantt-border);
|
|
120
305
|
}
|
|
121
306
|
|
|
@@ -134,7 +319,7 @@ const I = 1440 * 60 * 1e3, y = 42, j = 62, J = 376, N = 28, At = 15, vt = At * M
|
|
|
134
319
|
left: 0;
|
|
135
320
|
z-index: 5;
|
|
136
321
|
display: grid;
|
|
137
|
-
grid-template-columns:
|
|
322
|
+
grid-template-columns: var(--gantt-cols);
|
|
138
323
|
border-right: 1px solid var(--ui-gantt-border);
|
|
139
324
|
}
|
|
140
325
|
|
|
@@ -190,15 +375,79 @@ const I = 1440 * 60 * 1e3, y = 42, j = 62, J = 376, N = 28, At = 15, vt = At * M
|
|
|
190
375
|
min-block-size: 62px;
|
|
191
376
|
}
|
|
192
377
|
|
|
378
|
+
.band {
|
|
379
|
+
position: absolute;
|
|
380
|
+
inset-inline: 0;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.band.top {
|
|
384
|
+
inset-block-start: 0;
|
|
385
|
+
block-size: 26px;
|
|
386
|
+
border-bottom: 1px solid var(--ui-gantt-grid);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.band.bottom {
|
|
390
|
+
inset-block-start: 26px;
|
|
391
|
+
block-size: 36px;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.scale[data-zoom="year"] .band.bottom {
|
|
395
|
+
inset-block-start: 0;
|
|
396
|
+
block-size: 62px;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
.band-cell,
|
|
193
400
|
.tick {
|
|
194
401
|
position: absolute;
|
|
195
402
|
inset-block: 0;
|
|
403
|
+
display: flex;
|
|
404
|
+
align-items: center;
|
|
405
|
+
overflow: hidden;
|
|
196
406
|
border-left: 1px solid var(--ui-gantt-grid);
|
|
197
|
-
padding:
|
|
407
|
+
padding: 0 8px;
|
|
198
408
|
color: var(--ui-gantt-muted);
|
|
199
409
|
font-size: 11px;
|
|
200
410
|
font-weight: 650;
|
|
201
411
|
white-space: nowrap;
|
|
412
|
+
text-overflow: ellipsis;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.band-cell {
|
|
416
|
+
color: var(--ui-gantt-text);
|
|
417
|
+
font-weight: 700;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/* Labels sit at the top of their row so the today pill has the bottom of it to itself. */
|
|
421
|
+
.band.bottom .tick {
|
|
422
|
+
align-items: flex-start;
|
|
423
|
+
padding-block-start: 6px;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.scale[data-zoom="year"] .tick {
|
|
427
|
+
padding-block-start: 9px;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.tick.day {
|
|
431
|
+
flex-direction: column;
|
|
432
|
+
justify-content: flex-start;
|
|
433
|
+
gap: 1px;
|
|
434
|
+
padding: 3px 0 0;
|
|
435
|
+
line-height: 1.15;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.tick .dow {
|
|
439
|
+
font-size: 9px;
|
|
440
|
+
font-weight: 600;
|
|
441
|
+
opacity: 0.8;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.tick .num {
|
|
445
|
+
font-variant-numeric: tabular-nums;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.tick.today-cell {
|
|
449
|
+
color: var(--ui-gantt-accent);
|
|
450
|
+
background: color-mix(in srgb, var(--ui-gantt-accent) 9%, transparent);
|
|
202
451
|
}
|
|
203
452
|
|
|
204
453
|
.grid-body {
|
|
@@ -211,24 +460,165 @@ const I = 1440 * 60 * 1e3, y = 42, j = 62, J = 376, N = 28, At = 15, vt = At * M
|
|
|
211
460
|
|
|
212
461
|
.timeline-body {
|
|
213
462
|
position: relative;
|
|
463
|
+
/* grid lines can run a scrollbar's width past the track; clip them, and keep y visible for drag tips */
|
|
464
|
+
overflow-x: clip;
|
|
214
465
|
background:
|
|
215
466
|
linear-gradient(to right, var(--ui-gantt-grid) 1px, transparent 1px) 0 0 / var(--day-width) 100%,
|
|
216
467
|
color-mix(in srgb, var(--ui-gantt-panel) 98%, transparent);
|
|
217
468
|
}
|
|
218
469
|
|
|
470
|
+
/* The task list can be resized: a handle on its right edge, in the header and down the body. They are grid items
|
|
471
|
+
of their own, in the cells of the header and the body, so the sticky grid underneath stays a plain treegrid. */
|
|
472
|
+
.splitter-rail {
|
|
473
|
+
position: sticky;
|
|
474
|
+
left: 0;
|
|
475
|
+
grid-column: 1;
|
|
476
|
+
z-index: 4;
|
|
477
|
+
pointer-events: none;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
.splitter-rail.head {
|
|
481
|
+
grid-row: 1;
|
|
482
|
+
top: 0;
|
|
483
|
+
z-index: 6;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.splitter-rail.body {
|
|
487
|
+
grid-row: 2;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
.splitter {
|
|
491
|
+
position: absolute;
|
|
492
|
+
inset-block: 0;
|
|
493
|
+
inset-inline-end: -4px;
|
|
494
|
+
inline-size: 8px;
|
|
495
|
+
cursor: col-resize;
|
|
496
|
+
pointer-events: auto;
|
|
497
|
+
touch-action: none;
|
|
498
|
+
outline: 0;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
.splitter::after {
|
|
502
|
+
content: "";
|
|
503
|
+
position: absolute;
|
|
504
|
+
inset-block: 0;
|
|
505
|
+
inset-inline-start: 3px;
|
|
506
|
+
inline-size: 2px;
|
|
507
|
+
background: transparent;
|
|
508
|
+
transition: background 120ms ease;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.splitter:hover::after,
|
|
512
|
+
.splitter.dragging::after,
|
|
513
|
+
.splitter:focus-visible::after {
|
|
514
|
+
background: var(--ui-gantt-accent);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
.timeline-body[data-zoom="month"],
|
|
518
|
+
.timeline-body[data-zoom="quarter"],
|
|
519
|
+
.timeline-body[data-zoom="year"] {
|
|
520
|
+
background: color-mix(in srgb, var(--ui-gantt-panel) 98%, transparent);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/* Non-working days: a tint behind the rows, a warmer one for a holiday. */
|
|
524
|
+
.off-day {
|
|
525
|
+
position: absolute;
|
|
526
|
+
inset-block: 0;
|
|
527
|
+
background: color-mix(in srgb, var(--ui-gantt-muted) 11%, transparent);
|
|
528
|
+
pointer-events: none;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
.off-day.holiday {
|
|
532
|
+
background: color-mix(in srgb, var(--ui-color-warning, #d97706) 15%, transparent);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.tick.off {
|
|
536
|
+
background: color-mix(in srgb, var(--ui-gantt-muted) 9%, transparent);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/* The dots beside a bar that start a link (the linkable attribute), shown with the row's hover or focus. */
|
|
540
|
+
.connector {
|
|
541
|
+
position: absolute;
|
|
542
|
+
top: 15px;
|
|
543
|
+
inline-size: 12px;
|
|
544
|
+
block-size: 12px;
|
|
545
|
+
border: 2px solid var(--ui-gantt-accent);
|
|
546
|
+
border-radius: 50%;
|
|
547
|
+
background: var(--ui-gantt-panel);
|
|
548
|
+
cursor: crosshair;
|
|
549
|
+
opacity: 0;
|
|
550
|
+
z-index: 3;
|
|
551
|
+
touch-action: none;
|
|
552
|
+
transition: opacity 120ms ease;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
.timeline-row:hover .connector,
|
|
556
|
+
.timeline-row:focus-within .connector,
|
|
557
|
+
.frame[data-linking] .connector {
|
|
558
|
+
opacity: 1;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
.link-draft {
|
|
562
|
+
fill: none;
|
|
563
|
+
stroke: var(--ui-gantt-accent);
|
|
564
|
+
stroke-width: 2;
|
|
565
|
+
stroke-dasharray: 5 3;
|
|
566
|
+
stroke-linecap: round;
|
|
567
|
+
pointer-events: none;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.bar.link-target {
|
|
571
|
+
outline: 2px solid var(--ui-gantt-accent);
|
|
572
|
+
outline-offset: 2px;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.bar.link-target[data-link-side="start"] {
|
|
576
|
+
box-shadow: inset 7px 0 0 var(--ui-gantt-accent);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.bar.link-target[data-link-side="end"] {
|
|
580
|
+
box-shadow: inset -7px 0 0 var(--ui-gantt-accent);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
.links path.selected:not(.link-hit) {
|
|
584
|
+
stroke: var(--ui-gantt-accent);
|
|
585
|
+
stroke-width: 2.4;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
.links path.link-hit {
|
|
589
|
+
outline: 0;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
.unit-line {
|
|
593
|
+
position: absolute;
|
|
594
|
+
inset-block: 0;
|
|
595
|
+
inline-size: 1px;
|
|
596
|
+
background: color-mix(in srgb, var(--ui-gantt-border) 90%, transparent);
|
|
597
|
+
pointer-events: none;
|
|
598
|
+
}
|
|
599
|
+
|
|
219
600
|
.grid-row,
|
|
220
601
|
.timeline-row {
|
|
221
|
-
block-size: ${
|
|
602
|
+
block-size: ${S}px;
|
|
222
603
|
border-bottom: 1px solid color-mix(in srgb, var(--ui-gantt-border) 56%, transparent);
|
|
223
604
|
}
|
|
224
605
|
|
|
225
606
|
.grid-row {
|
|
226
607
|
display: grid;
|
|
227
|
-
grid-template-columns:
|
|
608
|
+
grid-template-columns: var(--gantt-cols);
|
|
228
609
|
color: var(--ui-gantt-text);
|
|
229
610
|
font-size: 12px;
|
|
230
611
|
}
|
|
231
612
|
|
|
613
|
+
.grid-row {
|
|
614
|
+
outline: 0;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
.grid-row:focus-visible {
|
|
618
|
+
outline: 2px solid var(--ui-gantt-accent);
|
|
619
|
+
outline-offset: -2px;
|
|
620
|
+
}
|
|
621
|
+
|
|
232
622
|
.grid-row.selected,
|
|
233
623
|
.timeline-row.selected {
|
|
234
624
|
background: var(--ui-gantt-selected);
|
|
@@ -258,6 +648,20 @@ const I = 1440 * 60 * 1e3, y = 42, j = 62, J = 376, N = 28, At = 15, vt = At * M
|
|
|
258
648
|
color: var(--ui-gantt-accent);
|
|
259
649
|
}
|
|
260
650
|
|
|
651
|
+
.rename {
|
|
652
|
+
flex: 1 1 auto;
|
|
653
|
+
min-inline-size: 0;
|
|
654
|
+
block-size: 26px;
|
|
655
|
+
padding: 0 6px;
|
|
656
|
+
border: 1px solid var(--ui-gantt-accent);
|
|
657
|
+
border-radius: 6px;
|
|
658
|
+
background: var(--ui-gantt-panel);
|
|
659
|
+
color: var(--ui-gantt-text);
|
|
660
|
+
font: inherit;
|
|
661
|
+
font-weight: 650;
|
|
662
|
+
outline: 0;
|
|
663
|
+
}
|
|
664
|
+
|
|
261
665
|
.name-text {
|
|
262
666
|
overflow: hidden;
|
|
263
667
|
text-overflow: ellipsis;
|
|
@@ -328,13 +732,15 @@ const I = 1440 * 60 * 1e3, y = 42, j = 62, J = 376, N = 28, At = 15, vt = At * M
|
|
|
328
732
|
overflow: visible;
|
|
329
733
|
}
|
|
330
734
|
|
|
331
|
-
|
|
735
|
+
/* A split bar shows the progress of each part, inside the part, and not one fill across the gaps. */
|
|
736
|
+
.bar.split > .progress {
|
|
332
737
|
display: none;
|
|
333
738
|
}
|
|
334
739
|
|
|
335
740
|
.split-segment {
|
|
336
741
|
position: absolute;
|
|
337
742
|
inset-block: 0;
|
|
743
|
+
overflow: hidden;
|
|
338
744
|
border-radius: 4px;
|
|
339
745
|
background:
|
|
340
746
|
linear-gradient(180deg, color-mix(in srgb, #ffffff 22%, transparent), transparent 42%),
|
|
@@ -408,13 +814,33 @@ const I = 1440 * 60 * 1e3, y = 42, j = 62, J = 376, N = 28, At = 15, vt = At * M
|
|
|
408
814
|
display: none;
|
|
409
815
|
}
|
|
410
816
|
|
|
817
|
+
/* The done part of a bar: a solid, darker shade of the bar's tone with a light edge where it stops, so
|
|
818
|
+
the amount is readable from the bar itself and not only from the % column. */
|
|
411
819
|
.progress {
|
|
412
820
|
position: absolute;
|
|
413
821
|
inset-block: 0;
|
|
414
822
|
inset-inline-start: 0;
|
|
415
|
-
background: linear-gradient(90deg, color-mix(in srgb, #000 22%, transparent), transparent);
|
|
416
|
-
opacity: 0.3;
|
|
417
823
|
inline-size: var(--progress, 0%);
|
|
824
|
+
background:
|
|
825
|
+
linear-gradient(180deg, color-mix(in srgb, #ffffff 16%, transparent), transparent 46%),
|
|
826
|
+
color-mix(in srgb, var(--tone, var(--ui-gantt-accent)) 58%, #0b1220);
|
|
827
|
+
box-shadow: inset -2px 0 0 color-mix(in srgb, #ffffff 62%, transparent);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
.bar.complete .progress,
|
|
831
|
+
.progress.full {
|
|
832
|
+
box-shadow: none;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
.bar.summary .progress {
|
|
836
|
+
background: color-mix(in srgb, #0b1220 58%, transparent);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
:host([bar-variant="soft"]) .bar.task .progress {
|
|
840
|
+
background:
|
|
841
|
+
linear-gradient(180deg, color-mix(in srgb, #ffffff 24%, transparent), transparent 46%),
|
|
842
|
+
color-mix(in srgb, var(--tone, var(--ui-gantt-accent)) 62%, var(--ui-gantt-panel));
|
|
843
|
+
box-shadow: inset -2px 0 0 color-mix(in srgb, var(--tone, var(--ui-gantt-accent)) 85%, transparent);
|
|
418
844
|
}
|
|
419
845
|
|
|
420
846
|
.handle {
|
|
@@ -459,28 +885,75 @@ const I = 1440 * 60 * 1e3, y = 42, j = 62, J = 376, N = 28, At = 15, vt = At * M
|
|
|
459
885
|
opacity: 1;
|
|
460
886
|
}
|
|
461
887
|
|
|
462
|
-
|
|
888
|
+
/* A finger needs the 24px minimum target size, and a touch screen has no hover to reveal the handles:
|
|
889
|
+
task bars grow from 18 to 24px tall (centred on the same line, so links and baselines still meet them),
|
|
890
|
+
the resize handles from 14 to 24px wide and always show their grip. A very short bar keeps a middle to
|
|
891
|
+
grab by giving each handle at most 35% of it. */
|
|
892
|
+
@media (pointer: coarse) {
|
|
893
|
+
.bar.task {
|
|
894
|
+
block-size: 24px;
|
|
895
|
+
top: 9px;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
.handle {
|
|
899
|
+
inline-size: min(24px, 35%);
|
|
900
|
+
opacity: 1;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
.handle.start::after {
|
|
904
|
+
inset-inline-start: 8px;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
.handle.end::after {
|
|
908
|
+
inset-inline-end: 8px;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
.connector {
|
|
912
|
+
opacity: 1;
|
|
913
|
+
inline-size: 20px;
|
|
914
|
+
block-size: 20px;
|
|
915
|
+
top: 11px;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/* One tooltip for the whole chart, outside the bars (which clip their children), placed by script. It
|
|
920
|
+
repeats the bar's accessible name, so it is hidden from assistive technology. */
|
|
921
|
+
.bar-tip {
|
|
463
922
|
position: absolute;
|
|
464
|
-
|
|
465
|
-
inset-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
padding:
|
|
469
|
-
border-radius:
|
|
923
|
+
inset-block-start: 0;
|
|
924
|
+
inset-inline-start: 0;
|
|
925
|
+
z-index: 7;
|
|
926
|
+
max-inline-size: 280px;
|
|
927
|
+
padding: 6px 9px;
|
|
928
|
+
border-radius: 8px;
|
|
470
929
|
background: #0f172a;
|
|
471
930
|
color: #f8fafc;
|
|
472
931
|
font-size: 11px;
|
|
473
|
-
|
|
474
|
-
|
|
932
|
+
line-height: 1.4;
|
|
933
|
+
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.28);
|
|
475
934
|
pointer-events: none;
|
|
476
935
|
opacity: 0;
|
|
936
|
+
visibility: hidden;
|
|
937
|
+
transition: opacity 90ms ease;
|
|
477
938
|
}
|
|
478
939
|
|
|
479
|
-
.bar
|
|
940
|
+
.bar-tip[data-open] {
|
|
480
941
|
opacity: 1;
|
|
942
|
+
visibility: visible;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
.bar-tip strong {
|
|
946
|
+
display: block;
|
|
947
|
+
font-weight: 750;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
.bar-tip span {
|
|
951
|
+
display: block;
|
|
952
|
+
opacity: 0.88;
|
|
481
953
|
}
|
|
482
954
|
|
|
483
|
-
:host([bar-variant="soft"]) .bar.task
|
|
955
|
+
:host([bar-variant="soft"]) .bar.task,
|
|
956
|
+
:host([bar-variant="soft"]) .split-segment {
|
|
484
957
|
background:
|
|
485
958
|
linear-gradient(180deg, color-mix(in srgb, #ffffff 38%, transparent), transparent 46%),
|
|
486
959
|
color-mix(in srgb, var(--tone, var(--ui-gantt-accent)) 22%, var(--ui-gantt-panel));
|
|
@@ -499,7 +972,8 @@ const I = 1440 * 60 * 1e3, y = 42, j = 62, J = 376, N = 28, At = 15, vt = At * M
|
|
|
499
972
|
0 7px 16px color-mix(in srgb, var(--tone, var(--ui-gantt-accent)) 16%, transparent);
|
|
500
973
|
}
|
|
501
974
|
|
|
502
|
-
:host([bar-variant="striped"]) .bar.task
|
|
975
|
+
:host([bar-variant="striped"]) .bar.task,
|
|
976
|
+
:host([bar-variant="striped"]) .split-segment {
|
|
503
977
|
background:
|
|
504
978
|
repeating-linear-gradient(135deg, color-mix(in srgb, #ffffff 24%, transparent) 0 7px, transparent 7px 14px),
|
|
505
979
|
var(--tone, var(--ui-gantt-accent));
|
|
@@ -511,7 +985,8 @@ const I = 1440 * 60 * 1e3, y = 42, j = 62, J = 376, N = 28, At = 15, vt = At * M
|
|
|
511
985
|
var(--tone, var(--ui-gantt-accent));
|
|
512
986
|
}
|
|
513
987
|
|
|
514
|
-
:host([bar-variant="outline"]) .bar.task
|
|
988
|
+
:host([bar-variant="outline"]) .bar.task,
|
|
989
|
+
:host([bar-variant="outline"]) .split-segment {
|
|
515
990
|
background: color-mix(in srgb, var(--tone, var(--ui-gantt-accent)) 8%, var(--ui-gantt-panel));
|
|
516
991
|
box-shadow:
|
|
517
992
|
inset 0 0 0 1.5px color-mix(in srgb, var(--tone, var(--ui-gantt-accent)) 72%, transparent),
|
|
@@ -519,8 +994,8 @@ const I = 1440 * 60 * 1e3, y = 42, j = 62, J = 376, N = 28, At = 15, vt = At * M
|
|
|
519
994
|
}
|
|
520
995
|
|
|
521
996
|
:host([bar-variant="outline"]) .bar.task .progress {
|
|
522
|
-
background: color-mix(in srgb, var(--tone, var(--ui-gantt-accent))
|
|
523
|
-
|
|
997
|
+
background: color-mix(in srgb, var(--tone, var(--ui-gantt-accent)) 42%, transparent);
|
|
998
|
+
box-shadow: inset -2px 0 0 color-mix(in srgb, var(--tone, var(--ui-gantt-accent)) 85%, transparent);
|
|
524
999
|
}
|
|
525
1000
|
|
|
526
1001
|
:host([bar-variant="outline"]) .milestone-shape {
|
|
@@ -530,7 +1005,8 @@ const I = 1440 * 60 * 1e3, y = 42, j = 62, J = 376, N = 28, At = 15, vt = At * M
|
|
|
530
1005
|
0 7px 16px color-mix(in srgb, var(--tone, var(--ui-gantt-accent)) 10%, transparent);
|
|
531
1006
|
}
|
|
532
1007
|
|
|
533
|
-
:host([bar-variant="glass"]) .bar.task
|
|
1008
|
+
:host([bar-variant="glass"]) .bar.task,
|
|
1009
|
+
:host([bar-variant="glass"]) .split-segment {
|
|
534
1010
|
background:
|
|
535
1011
|
linear-gradient(180deg, color-mix(in srgb, #ffffff 50%, transparent), color-mix(in srgb, #ffffff 10%, transparent)),
|
|
536
1012
|
color-mix(in srgb, var(--tone, var(--ui-gantt-accent)) 72%, transparent);
|
|
@@ -548,6 +1024,19 @@ const I = 1440 * 60 * 1e3, y = 42, j = 62, J = 376, N = 28, At = 15, vt = At * M
|
|
|
548
1024
|
backdrop-filter: blur(8px);
|
|
549
1025
|
}
|
|
550
1026
|
|
|
1027
|
+
/* The parts of a split bar carry the variant skin; the bar around them stays empty so the gaps are gaps. */
|
|
1028
|
+
:host([bar-variant]) .bar.split {
|
|
1029
|
+
background: transparent;
|
|
1030
|
+
box-shadow: none;
|
|
1031
|
+
backdrop-filter: none;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
/* After the variant skins, which draw their own edge: a finished bar or part has no edge to show. */
|
|
1035
|
+
:host([bar-variant]) .bar.complete .progress,
|
|
1036
|
+
:host([bar-variant]) .bar .progress.full {
|
|
1037
|
+
box-shadow: none;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
551
1040
|
.today {
|
|
552
1041
|
position: absolute;
|
|
553
1042
|
inset-block: 0;
|
|
@@ -557,14 +1046,22 @@ const I = 1440 * 60 * 1e3, y = 42, j = 62, J = 376, N = 28, At = 15, vt = At * M
|
|
|
557
1046
|
pointer-events: none;
|
|
558
1047
|
}
|
|
559
1048
|
|
|
560
|
-
|
|
561
|
-
|
|
1049
|
+
/* In the header, under the unit labels (which sit at the top of their row), centred on the today line
|
|
1050
|
+
and kept inside the track at either end. */
|
|
1051
|
+
.today-pill {
|
|
562
1052
|
position: absolute;
|
|
563
|
-
inset-block-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
1053
|
+
inset-block-end: 3px;
|
|
1054
|
+
z-index: 3;
|
|
1055
|
+
transform: translateX(-50%);
|
|
1056
|
+
padding: 0 6px;
|
|
1057
|
+
block-size: 14px;
|
|
1058
|
+
border-radius: 7px;
|
|
1059
|
+
background: var(--ui-color-danger, #dc2626);
|
|
1060
|
+
color: #ffffff;
|
|
1061
|
+
font-size: 10px;
|
|
567
1062
|
font-weight: 750;
|
|
1063
|
+
line-height: 14px;
|
|
1064
|
+
pointer-events: none;
|
|
568
1065
|
}
|
|
569
1066
|
|
|
570
1067
|
.links {
|
|
@@ -597,6 +1094,18 @@ const I = 1440 * 60 * 1e3, y = 42, j = 62, J = 376, N = 28, At = 15, vt = At * M
|
|
|
597
1094
|
pointer-events: none;
|
|
598
1095
|
}
|
|
599
1096
|
|
|
1097
|
+
/* A dependency whose target starts or ends earlier than the link allows. */
|
|
1098
|
+
.links path.violated:not(.link-hit) {
|
|
1099
|
+
stroke: var(--ui-color-danger, #dc2626);
|
|
1100
|
+
stroke-width: 1.8;
|
|
1101
|
+
stroke-dasharray: 5 3;
|
|
1102
|
+
marker-end: url(#ui-gantt-arrow-bad);
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
.links marker#ui-gantt-arrow-bad path {
|
|
1106
|
+
fill: var(--ui-color-danger, #dc2626);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
600
1109
|
.links marker path {
|
|
601
1110
|
fill: color-mix(in srgb, var(--ui-gantt-muted) 82%, transparent);
|
|
602
1111
|
stroke: none;
|
|
@@ -627,26 +1136,46 @@ const I = 1440 * 60 * 1e3, y = 42, j = 62, J = 376, N = 28, At = 15, vt = At * M
|
|
|
627
1136
|
display: none;
|
|
628
1137
|
}
|
|
629
1138
|
|
|
630
|
-
@media (
|
|
631
|
-
.
|
|
632
|
-
|
|
1139
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1140
|
+
.bar,
|
|
1141
|
+
.progress {
|
|
1142
|
+
transition: none !important;
|
|
633
1143
|
}
|
|
1144
|
+
}
|
|
634
1145
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
1146
|
+
/* Print: the whole chart, not a scroll area. Controls and handles are for the screen; the sticky headers stay put on the
|
|
1147
|
+
page; colours print as they are. A wide timeline is cut at the page edge, so print with the fit zoom or use exportSvg(). */
|
|
1148
|
+
@media print {
|
|
1149
|
+
.frame {
|
|
1150
|
+
box-shadow: none;
|
|
1151
|
+
-webkit-print-color-adjust: exact;
|
|
1152
|
+
print-color-adjust: exact;
|
|
638
1153
|
}
|
|
639
1154
|
|
|
640
|
-
.
|
|
641
|
-
.
|
|
642
|
-
|
|
1155
|
+
.controls,
|
|
1156
|
+
.splitter-rail,
|
|
1157
|
+
.connector,
|
|
1158
|
+
.bar-tip,
|
|
1159
|
+
.handle {
|
|
1160
|
+
display: none !important;
|
|
643
1161
|
}
|
|
644
|
-
}
|
|
645
1162
|
|
|
646
|
-
|
|
647
|
-
.
|
|
648
|
-
|
|
649
|
-
|
|
1163
|
+
.viewport,
|
|
1164
|
+
:host([fill]) .viewport {
|
|
1165
|
+
overflow: visible;
|
|
1166
|
+
max-block-size: none;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
.grid-head,
|
|
1170
|
+
.timeline-head,
|
|
1171
|
+
.grid-body,
|
|
1172
|
+
.splitter-rail.head {
|
|
1173
|
+
position: static;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
.grid-row,
|
|
1177
|
+
.timeline-row {
|
|
1178
|
+
break-inside: avoid;
|
|
650
1179
|
}
|
|
651
1180
|
}
|
|
652
1181
|
|
|
@@ -669,10 +1198,20 @@ const I = 1440 * 60 * 1e3, y = 42, j = 62, J = 376, N = 28, At = 15, vt = At * M
|
|
|
669
1198
|
.bar,
|
|
670
1199
|
.select,
|
|
671
1200
|
.control,
|
|
672
|
-
.zoom-button
|
|
1201
|
+
.zoom-button,
|
|
1202
|
+
.today-button,
|
|
1203
|
+
.add-button {
|
|
673
1204
|
forced-color-adjust: none;
|
|
674
1205
|
box-shadow: none;
|
|
675
1206
|
}
|
|
1207
|
+
|
|
1208
|
+
.bar-tip {
|
|
1209
|
+
forced-color-adjust: none;
|
|
1210
|
+
background: Canvas;
|
|
1211
|
+
color: CanvasText;
|
|
1212
|
+
border: 1px solid CanvasText;
|
|
1213
|
+
box-shadow: none;
|
|
1214
|
+
}
|
|
676
1215
|
}
|
|
677
1216
|
`, mt = {
|
|
678
1217
|
default: "var(--ui-color-primary, #2563eb)",
|
|
@@ -681,63 +1220,72 @@ const I = 1440 * 60 * 1e3, y = 42, j = 62, J = 376, N = 28, At = 15, vt = At * M
|
|
|
681
1220
|
warning: "var(--ui-color-warning, #b45309)",
|
|
682
1221
|
danger: "var(--ui-color-danger, #dc2626)"
|
|
683
1222
|
};
|
|
684
|
-
function
|
|
685
|
-
return String(
|
|
1223
|
+
function v(n) {
|
|
1224
|
+
return String(n ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
686
1225
|
}
|
|
687
|
-
function
|
|
688
|
-
if (
|
|
689
|
-
const
|
|
690
|
-
return Number.isFinite(
|
|
691
|
-
}
|
|
692
|
-
const t = String(
|
|
693
|
-
if (
|
|
694
|
-
const
|
|
695
|
-
return Number.isFinite(
|
|
696
|
-
}
|
|
697
|
-
const
|
|
698
|
-
return Number.isFinite(
|
|
1226
|
+
function $(n) {
|
|
1227
|
+
if (n instanceof Date) {
|
|
1228
|
+
const i = n.getTime();
|
|
1229
|
+
return Number.isFinite(i) ? Y(i) : null;
|
|
1230
|
+
}
|
|
1231
|
+
const t = String(n || "").trim(), e = /^(\d{4})-(\d{2})-(\d{2})(?:[T\s].*)?$/.exec(t);
|
|
1232
|
+
if (e) {
|
|
1233
|
+
const i = Number(e[1]), a = Number(e[2]), s = Number(e[3]), c = new Date(i, a - 1, s).getTime();
|
|
1234
|
+
return Number.isFinite(c) ? Y(c) : null;
|
|
1235
|
+
}
|
|
1236
|
+
const r = Date.parse(t);
|
|
1237
|
+
return Number.isFinite(r) ? Y(r) : null;
|
|
699
1238
|
}
|
|
700
|
-
function
|
|
701
|
-
const t = new Date(
|
|
1239
|
+
function Y(n) {
|
|
1240
|
+
const t = new Date(n);
|
|
702
1241
|
return t.setHours(0, 0, 0, 0), t.getTime();
|
|
703
1242
|
}
|
|
704
|
-
function
|
|
705
|
-
return
|
|
1243
|
+
function A(n, t) {
|
|
1244
|
+
return Y(n + t * D);
|
|
706
1245
|
}
|
|
707
|
-
function
|
|
708
|
-
const t = new Date(
|
|
709
|
-
return `${
|
|
1246
|
+
function W(n) {
|
|
1247
|
+
const t = new Date(n), e = t.getFullYear(), r = String(t.getMonth() + 1).padStart(2, "0"), i = String(t.getDate()).padStart(2, "0");
|
|
1248
|
+
return `${e}-${r}-${i}`;
|
|
710
1249
|
}
|
|
711
|
-
function
|
|
712
|
-
return
|
|
1250
|
+
function ht(n) {
|
|
1251
|
+
return Z({ month: "short", day: "numeric" }).format(new Date(n));
|
|
713
1252
|
}
|
|
714
|
-
function
|
|
715
|
-
return
|
|
1253
|
+
function Re(n) {
|
|
1254
|
+
return n === "day" || n === "month" || n === "quarter" || n === "year" || n === "fit" ? n : "week";
|
|
716
1255
|
}
|
|
717
|
-
|
|
718
|
-
|
|
1256
|
+
const qe = 0.2, Ne = 48, We = 680, Be = 160, He = 7;
|
|
1257
|
+
function Oe(n) {
|
|
1258
|
+
return n >= 22 ? "day" : n * 7 >= 44 ? "week" : n * 30 >= 44 ? "month" : n * 91 >= 44 ? "quarter" : "year";
|
|
719
1259
|
}
|
|
720
|
-
function
|
|
721
|
-
return
|
|
1260
|
+
function bt(n) {
|
|
1261
|
+
return n === "start" || n === "end" || n === "label" || n === "progress" ? n : "none";
|
|
722
1262
|
}
|
|
723
|
-
function
|
|
724
|
-
|
|
1263
|
+
function Pe(n) {
|
|
1264
|
+
return n === "soft" || n === "striped" || n === "outline" || n === "glass" ? n : "solid";
|
|
1265
|
+
}
|
|
1266
|
+
const Qt = new Date(1970, 0, 1).getTime();
|
|
1267
|
+
function _e(n, t, e = !1) {
|
|
1268
|
+
if (!n) return [];
|
|
725
1269
|
try {
|
|
726
|
-
const
|
|
727
|
-
return Array.isArray(
|
|
728
|
-
if (!
|
|
729
|
-
const s =
|
|
730
|
-
let
|
|
731
|
-
|
|
732
|
-
|
|
1270
|
+
const r = JSON.parse(n);
|
|
1271
|
+
return Array.isArray(r) ? r.map((i, a) => {
|
|
1272
|
+
if (!i || typeof i != "object") return null;
|
|
1273
|
+
const s = i, o = String(s.label || s.text || "").trim();
|
|
1274
|
+
let c = $(s.start);
|
|
1275
|
+
const h = Number(s.duration);
|
|
1276
|
+
let d = $(s.end);
|
|
1277
|
+
d == null && c != null && Number.isFinite(h) && (d = A(c, Math.max(0, h)));
|
|
1278
|
+
const g = e && c == null && s.type === "summary";
|
|
1279
|
+
if (g && (c = Qt, d = A(Qt, 1)), !o || c == null) return null;
|
|
1280
|
+
const u = s.type === "summary" || s.type === "milestone" ? s.type : "task", b = u === "milestone" ? c : Math.max(d ?? A(c, 1), A(c, 1)), m = String(s.id || `task-${a + 1}`), p = t.get(m) || {};
|
|
733
1281
|
return {
|
|
734
|
-
id:
|
|
735
|
-
label:
|
|
736
|
-
start:
|
|
737
|
-
end:
|
|
1282
|
+
id: m,
|
|
1283
|
+
label: p.label || o,
|
|
1284
|
+
start: p.start || W(c),
|
|
1285
|
+
end: p.end || W(b),
|
|
738
1286
|
progress: Number.isFinite(Number(s.progress)) ? Math.max(0, Math.min(100, Number(s.progress))) : void 0,
|
|
739
1287
|
tone: typeof s.tone == "string" ? s.tone : void 0,
|
|
740
|
-
type:
|
|
1288
|
+
type: u,
|
|
741
1289
|
parent: s.parent != null ? String(s.parent) : void 0,
|
|
742
1290
|
open: s.open !== !1,
|
|
743
1291
|
assignee: s.assignee != null ? String(s.assignee) : s.owner != null ? String(s.owner) : void 0,
|
|
@@ -745,6 +1293,7 @@ function Dt(a, t) {
|
|
|
745
1293
|
baselineStart: s.baselineStart != null ? String(s.baselineStart) : void 0,
|
|
746
1294
|
baselineEnd: s.baselineEnd != null ? String(s.baselineEnd) : void 0,
|
|
747
1295
|
critical: s.critical === !0,
|
|
1296
|
+
undated: g,
|
|
748
1297
|
segments: Array.isArray(s.segments) ? s.segments : void 0
|
|
749
1298
|
};
|
|
750
1299
|
}).filter(Boolean) : [];
|
|
@@ -752,397 +1301,1353 @@ function Dt(a, t) {
|
|
|
752
1301
|
return [];
|
|
753
1302
|
}
|
|
754
1303
|
}
|
|
755
|
-
function
|
|
756
|
-
if (!
|
|
1304
|
+
function Zt(n) {
|
|
1305
|
+
if (!n) return [];
|
|
757
1306
|
try {
|
|
758
|
-
const t = JSON.parse(
|
|
759
|
-
return Array.isArray(t) ? t.map((
|
|
760
|
-
if (!
|
|
761
|
-
const
|
|
762
|
-
if (!
|
|
763
|
-
const o =
|
|
764
|
-
return { id: String(
|
|
1307
|
+
const t = JSON.parse(n);
|
|
1308
|
+
return Array.isArray(t) ? t.map((e, r) => {
|
|
1309
|
+
if (!e || typeof e != "object") return null;
|
|
1310
|
+
const i = e, a = String(i.source || "").trim(), s = String(i.target || "").trim();
|
|
1311
|
+
if (!a || !s) return null;
|
|
1312
|
+
const o = i.type === "s2s" || i.type === "e2e" || i.type === "s2e" ? i.type : "e2s", c = Math.round(Number(i.lag));
|
|
1313
|
+
return { id: String(i.id || `link-${r + 1}`), source: a, target: s, type: o, lag: Number.isFinite(c) ? c : 0 };
|
|
765
1314
|
}).filter(Boolean) : [];
|
|
766
1315
|
} catch {
|
|
767
1316
|
return [];
|
|
768
1317
|
}
|
|
769
1318
|
}
|
|
770
|
-
function
|
|
771
|
-
return t === "none" ?
|
|
772
|
-
if (t === "label") return
|
|
773
|
-
if (t === "progress") return Number(
|
|
774
|
-
const
|
|
775
|
-
return
|
|
1319
|
+
function je(n, t) {
|
|
1320
|
+
return t === "none" ? n : [...n].sort((e, r) => {
|
|
1321
|
+
if (t === "label") return e.label.localeCompare(r.label);
|
|
1322
|
+
if (t === "progress") return Number(r.progress ?? 0) - Number(e.progress ?? 0);
|
|
1323
|
+
const i = $(t === "start" ? e.start : e.end) ?? 0, a = $(t === "start" ? r.start : r.end) ?? 0;
|
|
1324
|
+
return i - a;
|
|
776
1325
|
});
|
|
777
1326
|
}
|
|
778
|
-
function
|
|
779
|
-
const
|
|
780
|
-
|
|
781
|
-
const g = d.parent &&
|
|
782
|
-
|
|
1327
|
+
function Ve(n, t, e, r) {
|
|
1328
|
+
const i = /* @__PURE__ */ new Map(), a = new Map(n.map((d) => [d.id, d]));
|
|
1329
|
+
n.forEach((d) => {
|
|
1330
|
+
const g = d.parent && a.has(d.parent) ? d.parent : "__root__", u = i.get(g) || [];
|
|
1331
|
+
u.push(d), i.set(g, u);
|
|
783
1332
|
});
|
|
784
|
-
const
|
|
785
|
-
|
|
786
|
-
if (!o(
|
|
787
|
-
const
|
|
788
|
-
if (
|
|
789
|
-
const
|
|
790
|
-
|
|
1333
|
+
const s = e.trim().toLowerCase(), o = (d) => !s || d.label.toLowerCase().includes(s) || (d.assignee || "").toLowerCase().includes(s) ? !0 : (i.get(d.id) || []).some(o), c = [], h = (d, g) => {
|
|
1334
|
+
je(i.get(d) || [], r).forEach((u) => {
|
|
1335
|
+
if (!o(u)) return;
|
|
1336
|
+
const b = $(u.start), m = $(u.end);
|
|
1337
|
+
if (b == null || m == null) return;
|
|
1338
|
+
const p = (i.get(u.id) || []).length > 0;
|
|
1339
|
+
c.push({ ...u, depth: g, hasChildren: p, startMs: b, endMs: m }), p && !t.has(u.id) && h(u.id, g + 1);
|
|
791
1340
|
});
|
|
792
1341
|
};
|
|
793
|
-
return
|
|
1342
|
+
return h("__root__", 0), c;
|
|
1343
|
+
}
|
|
1344
|
+
function Xe() {
|
|
1345
|
+
try {
|
|
1346
|
+
const n = Z({}).resolvedOptions().locale, t = new Intl.Locale(n), e = typeof t.getWeekInfo == "function" ? t.getWeekInfo() : t.weekInfo;
|
|
1347
|
+
if (e && Number.isInteger(e.firstDay)) return e.firstDay % 7;
|
|
1348
|
+
} catch {
|
|
1349
|
+
}
|
|
1350
|
+
return 1;
|
|
1351
|
+
}
|
|
1352
|
+
function Ft(n) {
|
|
1353
|
+
const t = n == null || n === "" ? NaN : Number(n);
|
|
1354
|
+
return Number.isInteger(t) && t >= 0 && t <= 6 ? t : Xe();
|
|
1355
|
+
}
|
|
1356
|
+
function ue(n, t, e) {
|
|
1357
|
+
const r = new Date(n);
|
|
1358
|
+
return r.setHours(0, 0, 0, 0), t === "week" ? r.setDate(r.getDate() - (r.getDay() - e + 7) % 7) : t === "month" ? r.setDate(1) : t === "quarter" ? r.setMonth(Math.floor(r.getMonth() / 3) * 3, 1) : t === "year" && r.setMonth(0, 1), r.getTime();
|
|
1359
|
+
}
|
|
1360
|
+
function pe(n, t) {
|
|
1361
|
+
const e = new Date(n);
|
|
1362
|
+
return t === "day" ? e.setDate(e.getDate() + 1) : t === "week" ? e.setDate(e.getDate() + 7) : t === "month" ? e.setMonth(e.getMonth() + 1) : t === "quarter" ? e.setMonth(e.getMonth() + 3) : e.setFullYear(e.getFullYear() + 1), e.getTime();
|
|
1363
|
+
}
|
|
1364
|
+
function kt(n, t, e, r, i, a, s) {
|
|
1365
|
+
const o = [], c = e + Math.max(0, a - 480) / i * D, h = a + s + 960;
|
|
1366
|
+
let d = ue(Math.max(e, c), n, t);
|
|
1367
|
+
for (; d < r; ) {
|
|
1368
|
+
const g = pe(d, n), u = Math.max(d, e), b = Math.min(g, r), m = (u - e) / D * i;
|
|
1369
|
+
if (m > h) break;
|
|
1370
|
+
b > u && o.push({ left: m, width: (b - u) / D * i, start: d, label: "" }), d = g;
|
|
1371
|
+
}
|
|
1372
|
+
return o;
|
|
1373
|
+
}
|
|
1374
|
+
const Ye = { weekdays: /* @__PURE__ */ new Set(), holidays: /* @__PURE__ */ new Map() };
|
|
1375
|
+
function te(n, t) {
|
|
1376
|
+
const e = /* @__PURE__ */ new Set();
|
|
1377
|
+
if (n != null) {
|
|
1378
|
+
const i = n.trim();
|
|
1379
|
+
if (i === "")
|
|
1380
|
+
e.add(0), e.add(6);
|
|
1381
|
+
else {
|
|
1382
|
+
let a = i.split(",");
|
|
1383
|
+
try {
|
|
1384
|
+
const s = JSON.parse(i);
|
|
1385
|
+
Array.isArray(s) ? a = s : typeof s == "number" && (a = [s]);
|
|
1386
|
+
} catch {
|
|
1387
|
+
}
|
|
1388
|
+
for (const s of a) {
|
|
1389
|
+
const o = Number(typeof s == "string" ? s.trim() : s);
|
|
1390
|
+
Number.isInteger(o) && o >= 0 && o <= 6 && e.add(o);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
const r = /* @__PURE__ */ new Map();
|
|
1395
|
+
if (t)
|
|
1396
|
+
try {
|
|
1397
|
+
const i = JSON.parse(t);
|
|
1398
|
+
if (Array.isArray(i))
|
|
1399
|
+
for (const a of i) {
|
|
1400
|
+
const s = a && typeof a == "object" ? a : null, o = $(s ? s.date : a);
|
|
1401
|
+
o != null && r.set(o, s && typeof s.label == "string" ? s.label : "");
|
|
1402
|
+
}
|
|
1403
|
+
} catch {
|
|
1404
|
+
}
|
|
1405
|
+
return { weekdays: e, holidays: r };
|
|
1406
|
+
}
|
|
1407
|
+
function ge(n, t) {
|
|
1408
|
+
return t.holidays.has(n) || t.weekdays.has(new Date(n).getDay());
|
|
1409
|
+
}
|
|
1410
|
+
const fe = 8;
|
|
1411
|
+
function Ue(n, t, e, r, i, a) {
|
|
1412
|
+
if (e < fe || !r.weekdays.size && !r.holidays.size) return "";
|
|
1413
|
+
const s = Math.max(0, Math.floor(Math.max(0, i - 480) / e)), o = Math.min(Math.ceil((t - n) / D), Math.ceil((i + a + 960) / e)), c = [];
|
|
1414
|
+
let h = -1, d = "";
|
|
1415
|
+
const g = (u) => {
|
|
1416
|
+
h >= 0 && c.push(`<span class="off-day${d}" style="left:${(h * e).toFixed(2)}px;width:${((u - h) * e).toFixed(2)}px"></span>`), h = -1;
|
|
1417
|
+
};
|
|
1418
|
+
for (let u = s; u < o; u += 1) {
|
|
1419
|
+
const b = O(n, u), m = r.holidays.has(b) ? " holiday" : r.weekdays.has(new Date(b).getDay()) ? "" : null;
|
|
1420
|
+
(m === null || h >= 0 && m !== d) && g(u), m !== null && h < 0 && (h = u, d = m);
|
|
1421
|
+
}
|
|
1422
|
+
return g(o), c.join("");
|
|
1423
|
+
}
|
|
1424
|
+
const It = 36;
|
|
1425
|
+
function Ke(n, t, e, r, i, a = 0, s = Number.POSITIVE_INFINITY, o = Ye) {
|
|
1426
|
+
const c = Y(Date.now()), h = Z({ month: "long", year: "numeric" }), d = Z({ month: "short" }), g = Z({ weekday: "narrow" }), u = (y, w, x, M = "") => `<span class="${y}" style="left:${w.left.toFixed(2)}px;width:${w.width.toFixed(2)}px"${M ? ` title="${v(M)}"` : ""}>${x}</span>`, m = kt(r, i, n, t, e, a, s).map((y) => {
|
|
1427
|
+
const w = new Date(y.start), x = y.width >= It;
|
|
1428
|
+
if (r === "day") {
|
|
1429
|
+
const M = y.start === c, T = o.holidays.get(y.start);
|
|
1430
|
+
return u(
|
|
1431
|
+
`tick day${M ? " today-cell" : ""}${ge(y.start, o) ? " off" : ""}`,
|
|
1432
|
+
y,
|
|
1433
|
+
`<span class="dow">${v(g.format(w))}</span><span class="num">${w.getDate()}</span>`,
|
|
1434
|
+
T || ""
|
|
1435
|
+
);
|
|
1436
|
+
}
|
|
1437
|
+
return r === "week" ? u("tick", y, x ? `<span class="num">${w.getDate()}</span>` : "", ht(y.start)) : r === "month" ? u("tick", y, x ? v(d.format(w)) : "", h.format(w)) : r === "quarter" ? u("tick", y, x ? `Q${Math.floor(w.getMonth() / 3) + 1}` : "", `Q${Math.floor(w.getMonth() / 3) + 1} ${w.getFullYear()}`) : u("tick", y, x ? String(w.getFullYear()) : "");
|
|
1438
|
+
}).join("");
|
|
1439
|
+
if (r === "year") return `<div class="band bottom">${m}</div>`;
|
|
1440
|
+
const p = r === "day" || r === "week" ? "month" : "year";
|
|
1441
|
+
return `<div class="band top">${kt(p, i, n, t, e, a, s).map((y) => {
|
|
1442
|
+
const w = new Date(y.start), x = p === "month" ? h.format(w) : String(w.getFullYear()), M = y.width < It ? "" : p === "month" && y.width < 120 ? d.format(w) : x;
|
|
1443
|
+
return u("band-cell", y, v(M), x);
|
|
1444
|
+
}).join("")}</div><div class="band bottom">${m}</div>`;
|
|
794
1445
|
}
|
|
795
|
-
function
|
|
796
|
-
const
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
1446
|
+
function ee(n) {
|
|
1447
|
+
const t = /* @__PURE__ */ new Set();
|
|
1448
|
+
if (!n) return t;
|
|
1449
|
+
try {
|
|
1450
|
+
const e = JSON.parse(n);
|
|
1451
|
+
Array.isArray(e) && e.forEach((r, i) => {
|
|
1452
|
+
r && typeof r == "object" && t.add(String(r.id || `task-${i + 1}`));
|
|
1453
|
+
});
|
|
1454
|
+
} catch {
|
|
1455
|
+
}
|
|
1456
|
+
return t;
|
|
1457
|
+
}
|
|
1458
|
+
function Je(n, t) {
|
|
1459
|
+
for (const e of n) if (t.has(e)) return !0;
|
|
1460
|
+
return !1;
|
|
1461
|
+
}
|
|
1462
|
+
function Ge(n, t, e, r, i, a, s) {
|
|
1463
|
+
return r === "day" ? "" : kt(r, i, n, t, e, a, s).filter((o) => o.start >= n).map((o) => `<span class="unit-line" style="left:${o.left.toFixed(2)}px"></span>`).join("");
|
|
1464
|
+
}
|
|
1465
|
+
function Qe(n, t) {
|
|
1466
|
+
const e = (i) => Math.max(0, Math.min(100, i));
|
|
1467
|
+
let r = n.reduce((i, a) => a.progress == null ? i + a.duration : i, 0) * (e(t) / 100);
|
|
1468
|
+
return n.map((i) => {
|
|
1469
|
+
if (i.progress != null) return e(i.progress);
|
|
1470
|
+
const a = Math.min(i.duration, r);
|
|
1471
|
+
return r -= a, i.duration > 0 ? a / i.duration * 100 : 0;
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
function Ze(n, t) {
|
|
1475
|
+
const e = Math.max(D, n.endMs - n.startMs), r = [];
|
|
1476
|
+
for (const a of n.segments || []) {
|
|
1477
|
+
const s = $(a.start), o = $(a.end);
|
|
1478
|
+
if (s == null || o == null) continue;
|
|
1479
|
+
const c = Math.max(n.startMs, s), h = Math.min(n.endMs, o);
|
|
1480
|
+
if (h <= c) continue;
|
|
1481
|
+
const d = Math.max(0, Math.min(100, (c - n.startMs) / e * 100)), g = Math.min(100 - d, Math.max(1, (h - c) / e * 100));
|
|
1482
|
+
if (g <= 0) continue;
|
|
1483
|
+
const u = Number(a.progress);
|
|
1484
|
+
r.push({
|
|
1485
|
+
left: d,
|
|
1486
|
+
width: g,
|
|
1487
|
+
duration: h - c,
|
|
1488
|
+
progress: a.progress != null && Number.isFinite(u) ? u : void 0
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
const i = Qe(r, t);
|
|
1492
|
+
return r.map(
|
|
1493
|
+
(a, s) => `<span class="split-segment" style="left:${a.left.toFixed(2)}%;width:${a.width.toFixed(2)}%"><span class="progress${i[s] >= 100 ? " full" : ""}" style="--progress:${i[s].toFixed(2)}%"></span></span>`
|
|
1494
|
+
).join("");
|
|
1495
|
+
}
|
|
1496
|
+
function ot(n) {
|
|
1497
|
+
return typeof CSS < "u" && typeof CSS.escape == "function" ? CSS.escape(n) : n.replace(/["\\]/g, "\\$&");
|
|
1498
|
+
}
|
|
1499
|
+
function Nt(n) {
|
|
1500
|
+
return `.grid-row[data-task-id="${ot(n)}"]`;
|
|
1501
|
+
}
|
|
1502
|
+
function At(n) {
|
|
1503
|
+
return `.bar[data-task-id="${ot(n)}"]`;
|
|
1504
|
+
}
|
|
1505
|
+
function tr(n) {
|
|
1506
|
+
return n.classList.contains("control") ? ".control" : n.classList.contains("select") ? ".select" : n.classList.contains("zoom-button") && n.dataset.zoom ? `.zoom-button[data-zoom="${ot(n.dataset.zoom)}"]` : n.classList.contains("today-button") ? ".today-button" : n.classList.contains("add-button") ? ".add-button" : n.classList.contains("rename") ? ".rename" : n.classList.contains("head-button") && n.dataset.sort ? `.head-button[data-sort="${ot(n.dataset.sort)}"]` : n.classList.contains("splitter") && n.getAttribute("role") === "separator" ? '.splitter[role="separator"]' : n.classList.contains("link-hit") && n.dataset.linkId ? `.link-hit[data-link-id="${ot(n.dataset.linkId)}"]` : n.classList.contains("bar") && n.dataset.taskId ? At(n.dataset.taskId) : n.classList.contains("grid-row") && n.dataset.taskId ? Nt(n.dataset.taskId) : n.classList.contains("expander") && n.dataset.taskId ? `.expander[data-task-id="${ot(n.dataset.taskId)}"]` : null;
|
|
1507
|
+
}
|
|
1508
|
+
const Et = {
|
|
1509
|
+
title: "Project schedule",
|
|
1510
|
+
tasksCount: "{shown}/{total} tasks",
|
|
1511
|
+
filterPlaceholder: "Filter tasks",
|
|
1512
|
+
filterLabel: "Filter tasks",
|
|
1513
|
+
sortLabel: "Sort tasks",
|
|
1514
|
+
sortManual: "Manual",
|
|
1515
|
+
sortStart: "Start date",
|
|
1516
|
+
sortEnd: "End date",
|
|
1517
|
+
sortName: "Name",
|
|
1518
|
+
sortProgress: "Progress",
|
|
1519
|
+
zoomDay: "day",
|
|
1520
|
+
zoomWeek: "week",
|
|
1521
|
+
zoomMonth: "month",
|
|
1522
|
+
zoomQuarter: "quarter",
|
|
1523
|
+
zoomYear: "year",
|
|
1524
|
+
zoomFit: "fit",
|
|
1525
|
+
zoomFitTitle: "Fit the whole project to the width",
|
|
1526
|
+
columnTask: "Task",
|
|
1527
|
+
columnOwner: "Owner",
|
|
1528
|
+
columnDates: "Dates",
|
|
1529
|
+
columnProgress: "%",
|
|
1530
|
+
columnStart: "Start",
|
|
1531
|
+
columnEnd: "End",
|
|
1532
|
+
columnDuration: "Days",
|
|
1533
|
+
splitter: "Resize the task list",
|
|
1534
|
+
scrollToToday: "Scroll to today",
|
|
1535
|
+
addTask: "Add task",
|
|
1536
|
+
rename: "Rename task",
|
|
1537
|
+
linkViolated: "dependency not met",
|
|
1538
|
+
empty: "No tasks in this range",
|
|
1539
|
+
noMatches: "No matching tasks",
|
|
1540
|
+
expand: "Expand",
|
|
1541
|
+
collapse: "Collapse",
|
|
1542
|
+
timeline: "Timeline",
|
|
1543
|
+
today: "Today",
|
|
1544
|
+
summary: "summary",
|
|
1545
|
+
milestone: "milestone",
|
|
1546
|
+
to: "to",
|
|
1547
|
+
complete: "complete",
|
|
1548
|
+
assigned: "assigned to",
|
|
1549
|
+
critical: "critical"
|
|
1550
|
+
}, Rt = {
|
|
1551
|
+
label: { title: "columnTask", sort: "label", width: 170 },
|
|
1552
|
+
assignee: { title: "columnOwner", width: 82 },
|
|
1553
|
+
dates: { title: "columnDates", sort: "start", width: 80 },
|
|
1554
|
+
start: { title: "columnStart", sort: "start", width: 74 },
|
|
1555
|
+
end: { title: "columnEnd", sort: "end", width: 74 },
|
|
1556
|
+
duration: { title: "columnDuration", width: 56 },
|
|
1557
|
+
progress: { title: "columnProgress", sort: "progress", width: 44 }
|
|
1558
|
+
}, er = ["label", "assignee", "dates", "progress"], re = 96;
|
|
1559
|
+
function rr(n) {
|
|
1560
|
+
let t = er;
|
|
1561
|
+
if (n)
|
|
1562
|
+
try {
|
|
1563
|
+
const a = JSON.parse(n);
|
|
1564
|
+
Array.isArray(a) && a.length && (t = a);
|
|
1565
|
+
} catch {
|
|
1566
|
+
}
|
|
1567
|
+
const e = [], r = /* @__PURE__ */ new Set();
|
|
1568
|
+
for (const a of t) {
|
|
1569
|
+
const s = a && typeof a == "object" ? a : null;
|
|
1570
|
+
let o = typeof a == "string" ? a : s ? s.key : null;
|
|
1571
|
+
if (o === "owner" && (o = "assignee"), typeof o != "string" || !(o in Rt) || r.has(o)) continue;
|
|
1572
|
+
r.add(o);
|
|
1573
|
+
const c = Rt[o], h = s ? Number(s.width) : NaN;
|
|
1574
|
+
e.push({
|
|
1575
|
+
key: o,
|
|
1576
|
+
title: c.title,
|
|
1577
|
+
custom: s && typeof s.label == "string" && s.label ? s.label : void 0,
|
|
1578
|
+
sort: c.sort,
|
|
1579
|
+
width: Number.isFinite(h) && h >= 36 ? Math.min(Math.round(h), 400) : c.width
|
|
1580
|
+
});
|
|
800
1581
|
}
|
|
801
|
-
|
|
1582
|
+
const i = e.findIndex((a) => a.key === "label");
|
|
1583
|
+
return i > 0 && e.unshift(e.splice(i, 1)[0]), i < 0 && e.unshift({ key: "label", title: "columnTask", sort: "label", width: Rt.label.width }), e;
|
|
802
1584
|
}
|
|
803
|
-
function
|
|
804
|
-
|
|
1585
|
+
function ir(n, t) {
|
|
1586
|
+
const e = n.slice(1).reduce((a, s) => a + s.width, 0), r = t != null ? re : n[0].width, i = e + re;
|
|
1587
|
+
return {
|
|
1588
|
+
width: t != null ? Math.max(Math.round(t), i) : e + n[0].width,
|
|
1589
|
+
min: i,
|
|
1590
|
+
template: n.map((a, s) => s === 0 ? `minmax(${r}px, 1fr)` : `${a.width}px`).join(" ")
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
let Lt;
|
|
1594
|
+
function Z(n) {
|
|
1595
|
+
try {
|
|
1596
|
+
return new Intl.DateTimeFormat(Lt, n);
|
|
1597
|
+
} catch {
|
|
1598
|
+
return new Intl.DateTimeFormat(void 0, n);
|
|
1599
|
+
}
|
|
805
1600
|
}
|
|
806
|
-
function
|
|
807
|
-
const
|
|
808
|
-
return
|
|
1601
|
+
function nr(n, t, e, r) {
|
|
1602
|
+
const i = e.format(new Date(n.startMs)), a = t === "milestone" ? i : `${i} ${r.to} ${e.format(new Date(n.endMs))}`, s = t === "summary" || t === "milestone" ? r[t] : "", o = [];
|
|
1603
|
+
return n.progress != null && t !== "milestone" && o.push(`${Math.round(n.progress)}% ${r.complete}`), n.assignee && o.push(`${r.assigned} ${n.assignee}`), n.critical && o.push(r.critical), {
|
|
1604
|
+
name: [n.label, s, a, ...o].filter(Boolean).join(", "),
|
|
1605
|
+
title: n.label,
|
|
1606
|
+
kindWord: s,
|
|
1607
|
+
when: [s, a].filter(Boolean).join(" · "),
|
|
1608
|
+
more: o.join(" · ")
|
|
1609
|
+
};
|
|
809
1610
|
}
|
|
810
|
-
function
|
|
811
|
-
return
|
|
1611
|
+
function sr(n, t) {
|
|
1612
|
+
return { day: t.zoomDay, week: t.zoomWeek, month: t.zoomMonth, quarter: t.zoomQuarter, year: t.zoomYear, fit: t.zoomFit }[n];
|
|
812
1613
|
}
|
|
813
|
-
function
|
|
814
|
-
|
|
1614
|
+
function ar(n) {
|
|
1615
|
+
const t = (n || "").trim();
|
|
1616
|
+
return t === "none" || /^\d*\.?\d+(px|em|rem|vh|dvh|svh|lvh|vw|%|ch)$/.test(t) ? t : null;
|
|
815
1617
|
}
|
|
816
|
-
function
|
|
817
|
-
return
|
|
1618
|
+
function Wt(n) {
|
|
1619
|
+
return { id: n.id, source: n.source, target: n.target, type: n.type || "e2s", lag: n.lag ?? 0 };
|
|
818
1620
|
}
|
|
819
|
-
function
|
|
820
|
-
return
|
|
1621
|
+
function ie(n) {
|
|
1622
|
+
return n === "auto" || n === "off" ? n : "warn";
|
|
821
1623
|
}
|
|
822
|
-
function
|
|
823
|
-
const
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
1624
|
+
function yt(n) {
|
|
1625
|
+
const t = $(n.start), e = $(n.end);
|
|
1626
|
+
return t == null || e == null ? null : { id: n.id, start: t, end: e, parent: n.parent, type: n.type, readonly: n.readonly, progress: n.progress };
|
|
1627
|
+
}
|
|
1628
|
+
function or(n) {
|
|
1629
|
+
const t = Ee(n.map(yt).filter((e) => e != null));
|
|
1630
|
+
return n.map((e) => {
|
|
1631
|
+
const r = t.get(e.id);
|
|
1632
|
+
return r ? { ...e, undated: !1, start: W(r.start), end: W(r.end), progress: e.progress ?? r.progress } : e;
|
|
1633
|
+
}).filter((e) => !e.undated);
|
|
1634
|
+
}
|
|
1635
|
+
function lr(n, t) {
|
|
1636
|
+
const e = Ie(n.map(yt).filter((r) => r != null), t.map(Wt));
|
|
1637
|
+
return e.size ? n.map((r) => e.has(r.id) && !r.critical ? { ...r, critical: !0 } : r) : n;
|
|
1638
|
+
}
|
|
1639
|
+
function qt(n) {
|
|
1640
|
+
if (!n) return [];
|
|
1641
|
+
try {
|
|
1642
|
+
const t = JSON.parse(n);
|
|
1643
|
+
return Array.isArray(t) ? t : [];
|
|
1644
|
+
} catch {
|
|
1645
|
+
return [];
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
function xt(n) {
|
|
1649
|
+
return n instanceof Date ? Number.isFinite(n.getTime()) ? W(n.getTime()) : void 0 : n;
|
|
1650
|
+
}
|
|
1651
|
+
function ne(n, t, e, r) {
|
|
1652
|
+
if (!Array.isArray(e)) {
|
|
1653
|
+
n.removeAttribute(t);
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
const i = r ? e.map((a) => {
|
|
1657
|
+
const s = { ...a };
|
|
1658
|
+
for (const o of ["start", "end", "baselineStart", "baselineEnd"]) o in s && (s[o] = xt(s[o]));
|
|
1659
|
+
return Array.isArray(s.segments) && (s.segments = s.segments.map((o) => ({ ...o, start: xt(o.start), end: xt(o.end) }))), s;
|
|
1660
|
+
}) : e;
|
|
1661
|
+
try {
|
|
1662
|
+
n.setAttribute(t, JSON.stringify(i));
|
|
1663
|
+
} catch {
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
function dr(n) {
|
|
1667
|
+
const t = n == null || n === "" ? NaN : Number(n);
|
|
1668
|
+
return Number.isFinite(t) && t > 0 ? t : null;
|
|
1669
|
+
}
|
|
1670
|
+
function ut(n) {
|
|
1671
|
+
return n.type === "summary" || n.hasChildren ? "summary" : n.type === "milestone" || n.startMs === n.endMs ? "milestone" : "task";
|
|
1672
|
+
}
|
|
1673
|
+
function se(n) {
|
|
1674
|
+
const t = ut(n);
|
|
1675
|
+
return t === "summary" ? 17 + 9 / 2 : t === "milestone" ? 7 + vt / 2 : 12 + 18 / 2;
|
|
1676
|
+
}
|
|
1677
|
+
function cr(n, t) {
|
|
1678
|
+
return ut(n) === "milestone" ? t(n.startMs) - he : t(n.startMs);
|
|
1679
|
+
}
|
|
1680
|
+
function hr(n, t) {
|
|
1681
|
+
return ut(n) === "milestone" ? t(n.startMs) + he : t(n.endMs);
|
|
1682
|
+
}
|
|
1683
|
+
function ae(n, t, e) {
|
|
1684
|
+
return t === "start" ? cr(n, e) : hr(n, e);
|
|
1685
|
+
}
|
|
1686
|
+
function ur(n = "e2s") {
|
|
1687
|
+
return n === "s2s" ? { source: "start", target: "start" } : n === "e2e" ? { source: "end", target: "end" } : n === "s2e" ? { source: "start", target: "end" } : { source: "end", target: "start" };
|
|
1688
|
+
}
|
|
1689
|
+
function oe(n, t, e, r, i, a) {
|
|
1690
|
+
const s = ur(i), o = s.source === "start" ? -1 : 1, c = s.target === "start" ? 1 : -1, h = ae(n, s.source, a), d = ae(e, s.target, a), g = t * S + se(n), u = r * S + se(e), b = 22, m = h + o * b, p = d - c * b, k = 26;
|
|
1691
|
+
let y = (m + p) / 2;
|
|
1692
|
+
if (o === c) {
|
|
1693
|
+
const M = o === 1 ? Math.max(m, p, h, d) + k : Math.min(m, p, h, d) - k;
|
|
1694
|
+
y = (o === 1 ? p > m + k : p < m - k) ? y : M;
|
|
828
1695
|
} else
|
|
829
|
-
(o === 1 ?
|
|
830
|
-
const
|
|
831
|
-
return [[
|
|
1696
|
+
(o === 1 ? p < m : p > m) && (y = o === 1 ? Math.max(m, h, d) + k : Math.min(m, h, d) - k);
|
|
1697
|
+
const w = Math.abs(u - g) < 1 ? s.target === "start" ? u - 16 : u + 16 : u > g ? u - S / 2 + 3 : u + S / 2 - 3;
|
|
1698
|
+
return [[h, g], [m, g], [y, g], [y, w], [p, w], [p, u], [d, u]].map(([M, T], z) => `${z === 0 ? "M" : "L"} ${M.toFixed(1)} ${T.toFixed(1)}`).join(" ");
|
|
832
1699
|
}
|
|
833
|
-
class
|
|
1700
|
+
class pr extends Se {
|
|
834
1701
|
constructor() {
|
|
835
|
-
super(...arguments), this.collapsed = /* @__PURE__ */ new Set(), this.overrides = /* @__PURE__ */ new Map(), this.drag = null, this.selectedId = null, this.internalFilter = "", this.viewportTop = 0, this.viewportLeft = 0, this.viewportWidth = 1800, this.viewportHeight = 620, this.renderedFirstRow = 0, this.renderedLastRow = 0, this.scrollFrame = null, this.overridesVersion = 0, this.taskCacheRaw = null, this.taskCacheVersion = -1, this.taskCache = [], this.linkCacheRaw = null, this.linkCache = [], this.visibleCacheKey = "", this.visibleCache = [];
|
|
1702
|
+
super(...arguments), this.focusRequest = null, this.collapsed = /* @__PURE__ */ new Set(), this.seededIds = /* @__PURE__ */ new Set(), this.overrides = /* @__PURE__ */ new Map(), this.drag = null, this.selectedId = null, this.internalFilter = "", this.viewportTop = 0, this.viewportLeft = 0, this.viewportWidth = 1800, this.hostWidth = 0, this.renderedLeft = 0, this.smoothTarget = null, this.selectedLinkId = null, this.linking = null, this.editingId = null, this.editingValue = "", this.suppressBlur = !1, this.layoutInfo = { min: 0, dayWidth: 13, gridWidth: Tt, rangeEnd: 0 }, this.visibleIds = [], this.initialScrollPending = !0, this.pendingReveal = null, this.hostHeight = 0, this.virtualizedNow = !1, this.gridBounds = { min: 0, max: 0, width: Tt }, this.descriptions = /* @__PURE__ */ new Map(), this.tipTimer = null, this.pointerFocus = !1, this.scrollbarWidth = 0, this.dayWidthNow = Jt.week, this.resizeObserver = null, this.viewportHeight = 620, this.renderedFirstRow = 0, this.renderedLastRow = 0, this.scrollFrame = null, this.overridesVersion = 0, this.taskCacheRaw = null, this.taskCacheVersion = -1, this.taskCacheAuto = !1, this.taskCacheCritical = null, this.taskCache = [], this.linkCacheRaw = null, this.linkCache = [], this.visibleCacheKey = "", this.visibleCache = [], this.labelsRaw = null, this.labelsCache = Et;
|
|
836
1703
|
}
|
|
837
1704
|
static get observedAttributes() {
|
|
838
|
-
return ["tasks", "links", "zoom", "filter", "sort", "bar-variant", "variant", "readonly", "show-toolbar", "show-today", "headless"];
|
|
1705
|
+
return ["tasks", "links", "zoom", "filter", "sort", "bar-variant", "variant", "readonly", "show-toolbar", "show-today", "headless", "week-start", "labels", "locale", "columns", "grid-width", "fill", "max-height", "snap", "creatable", "auto-summary", "cascade", "schedule", "critical-path", "non-working-days", "holidays", "linkable"];
|
|
1706
|
+
}
|
|
1707
|
+
connectedCallback() {
|
|
1708
|
+
for (const t of ["tasks", "links", "holidays"])
|
|
1709
|
+
if (Object.prototype.hasOwnProperty.call(this, t)) {
|
|
1710
|
+
const e = this[t];
|
|
1711
|
+
delete this[t], this[t] = e;
|
|
1712
|
+
}
|
|
1713
|
+
super.connectedCallback(), !(typeof ResizeObserver > "u" || this.resizeObserver) && (this.resizeObserver = new ResizeObserver((t) => {
|
|
1714
|
+
const e = t[t.length - 1]?.contentRect, r = e?.width ?? 0, i = e?.height ?? 0, a = Math.abs(r - this.hostWidth) >= 1, s = Math.abs(i - this.hostHeight) >= 1 && (this.hasAttribute("fill") || this.virtualizedNow);
|
|
1715
|
+
this.hostHeight = i, !(!a && !s) && (a && (this.hostWidth = Math.round(r)), this.captureViewport(), this.requestRender());
|
|
1716
|
+
}), this.resizeObserver.observe(this));
|
|
1717
|
+
}
|
|
1718
|
+
disconnectedCallback() {
|
|
1719
|
+
this.resizeObserver?.disconnect(), this.resizeObserver = null, this.tipTimer != null && window.clearTimeout(this.tipTimer), this.tipTimer = null, super.disconnectedCallback();
|
|
1720
|
+
}
|
|
1721
|
+
/** The labels: the defaults with whatever the `labels` attribute (a JSON object) overrides. */
|
|
1722
|
+
labelSet() {
|
|
1723
|
+
const t = this.getAttribute("labels");
|
|
1724
|
+
if (t === this.labelsRaw) return this.labelsCache;
|
|
1725
|
+
if (this.labelsRaw = t, this.labelsCache = Et, t)
|
|
1726
|
+
try {
|
|
1727
|
+
const e = JSON.parse(t);
|
|
1728
|
+
if (e && typeof e == "object") {
|
|
1729
|
+
const r = { ...Et };
|
|
1730
|
+
for (const [i, a] of Object.entries(e))
|
|
1731
|
+
i in Et && typeof a == "string" && (r[i] = a);
|
|
1732
|
+
this.labelsCache = r;
|
|
1733
|
+
}
|
|
1734
|
+
} catch {
|
|
1735
|
+
}
|
|
1736
|
+
return this.labelsCache;
|
|
839
1737
|
}
|
|
840
1738
|
render() {
|
|
841
|
-
|
|
842
|
-
|
|
1739
|
+
this.suppressBlur = !0, queueMicrotask(() => {
|
|
1740
|
+
this.suppressBlur = !1;
|
|
1741
|
+
}), Lt = this.getAttribute("locale") || void 0;
|
|
1742
|
+
const t = this.labelSet(), e = this.getTasks(), r = this.getLinks(), i = Re(this.getAttribute("zoom")), a = bt(this.getAttribute("sort")), s = Pe(this.getAttribute("bar-variant")), o = this.getAttribute("filter") ?? this.internalFilter, c = this.getVisibleTasks(e, o, a), h = this.hasAttribute("readonly"), d = this.hasAttribute("creatable") && !h, g = this.hasAttribute("cascade"), u = this.hasAttribute("linkable") && !h, b = this.getAttribute("show-toolbar") !== "false", m = this.getAttribute("show-today") !== "false";
|
|
1743
|
+
if (!e.length) {
|
|
843
1744
|
this.setContent(`
|
|
844
|
-
<style>${
|
|
1745
|
+
<style>${Gt}</style>
|
|
845
1746
|
<section class="frame" part="frame">
|
|
846
|
-
<div class="empty" part="empty"
|
|
1747
|
+
<div class="empty" part="empty">${v(t.empty)}</div>
|
|
847
1748
|
</section>
|
|
848
1749
|
`);
|
|
849
1750
|
return;
|
|
850
1751
|
}
|
|
851
|
-
const
|
|
852
|
-
this.
|
|
853
|
-
const
|
|
854
|
-
|
|
1752
|
+
const p = e.map((l) => $(l.start)).filter((l) => l != null), k = e.map((l) => $(l.end)).filter((l) => l != null), y = A(Math.min(...p), -2), w = A(Math.max(...k), 3), x = Math.max(7, Math.ceil((w - y) / D));
|
|
1753
|
+
this.hostWidth = this.clientWidth || this.hostWidth;
|
|
1754
|
+
const M = this.hostWidth - De;
|
|
1755
|
+
this.hostHeight = this.clientHeight || this.hostHeight;
|
|
1756
|
+
const T = ar(this.getAttribute("max-height")), z = this.hostWidth > 0 && M <= ze, U = rr(this.getAttribute("columns")), P = ir(U, dr(this.getAttribute("grid-width"))), lt = z ? Ce : P.width;
|
|
1757
|
+
this.gridBounds = { min: P.min, max: Math.max(P.min, this.hostWidth > 0 ? M - 240 : 1200), width: P.width };
|
|
1758
|
+
const st = i === "fit", R = this.hostWidth > 0 ? M - lt - this.scrollbarWidth : We, q = st ? Math.max(qe, Math.min(Ne, R / x)) : Jt[i], K = st ? Oe(q) : i;
|
|
1759
|
+
this.dayWidthNow = q;
|
|
1760
|
+
const J = Math.max(st ? Be : 680, x * q), H = Math.max(c.length * S, S), V = this.getAttribute("virtualized") !== "false" && c.length > 240;
|
|
1761
|
+
this.virtualizedNow = V;
|
|
1762
|
+
const et = Math.max(20, Math.ceil(Math.max(420, this.viewportHeight - N) / S)), f = 160;
|
|
1763
|
+
this.viewportTop = Math.min(this.viewportTop, Math.max(0, H + N - this.viewportHeight));
|
|
1764
|
+
const E = Math.max(0, this.viewportTop - N), L = Math.max(0, Math.floor(E / S)), _ = Math.max(180, et * 4), G = V ? Math.max(0, Math.floor(L / _) * _ - f) : 0, at = V ? Math.min(c.length, G + et + f * 2 + _) : c.length, dt = c.slice(G, at), rt = G * S, it = Math.max(0, (c.length - at) * S);
|
|
1765
|
+
this.renderedFirstRow = G, this.renderedLastRow = at;
|
|
1766
|
+
const $t = Z({ dateStyle: "medium" }), pt = /* @__PURE__ */ new Map(), F = new Map(c.map((l, B) => [l.id, { task: l, index: B }])), C = new Set(dt.map((l) => l.id)), tt = this.selectedId != null && C.has(this.selectedId) ? this.selectedId : dt[0]?.id, nt = (l) => (l - y) / D * q, me = dt.map((l, B) => {
|
|
1767
|
+
const I = G + B, Q = this.selectedId === l.id ? " selected" : "", X = ut(l), ct = (ft) => {
|
|
1768
|
+
switch (ft.key) {
|
|
1769
|
+
case "label":
|
|
1770
|
+
return `<div class="grid-cell task-name" role="gridcell" style="padding-left:${10 + l.depth * 18}px">
|
|
1771
|
+
${l.hasChildren ? `<button class="expander" data-action="toggle" data-task-id="${v(l.id)}" aria-label="${v(this.collapsed.has(l.id) ? t.expand : t.collapse)} ${v(l.label)}">${this.collapsed.has(l.id) ? "›" : "⌄"}</button>` : '<span style="inline-size:22px"></span>'}
|
|
1772
|
+
${this.editingId === l.id ? `<input class="rename" value="${v(this.editingValue)}" aria-label="${v(t.rename)}" />` : `<span class="name-text" title="${v(l.label)}">${v(l.label)}</span>`}
|
|
1773
|
+
</div>`;
|
|
1774
|
+
case "assignee":
|
|
1775
|
+
return `<div class="grid-cell badge" role="gridcell">${v(l.assignee || X)}</div>`;
|
|
1776
|
+
case "dates":
|
|
1777
|
+
return `<div class="grid-cell badge" role="gridcell">${v(X === "milestone" ? ht(l.startMs) : `${ht(l.startMs)} - ${ht(l.endMs)}`)}</div>`;
|
|
1778
|
+
case "start":
|
|
1779
|
+
return `<div class="grid-cell badge" role="gridcell">${v(ht(l.startMs))}</div>`;
|
|
1780
|
+
case "end":
|
|
1781
|
+
return `<div class="grid-cell badge" role="gridcell">${v(X === "milestone" ? "-" : ht(l.endMs))}</div>`;
|
|
1782
|
+
case "duration":
|
|
1783
|
+
return `<div class="grid-cell badge" role="gridcell">${X === "milestone" ? "-" : Math.max(0, Math.round((l.endMs - l.startMs) / D))}</div>`;
|
|
1784
|
+
default:
|
|
1785
|
+
return `<div class="grid-cell badge" role="gridcell">${l.progress == null ? "-" : `${Math.round(l.progress)}%`}</div>`;
|
|
1786
|
+
}
|
|
1787
|
+
};
|
|
855
1788
|
return `
|
|
856
|
-
<div class="grid-row${
|
|
857
|
-
|
|
858
|
-
${r.hasChildren ? `<button class="expander" data-action="toggle" data-task-id="${b(r.id)}" aria-label="${this.collapsed.has(r.id) ? "Expand" : "Collapse"} ${b(r.label)}">${this.collapsed.has(r.id) ? "›" : "⌄"}</button>` : '<span style="inline-size:22px"></span>'}
|
|
859
|
-
<span class="name-text" title="${b(r.label)}">${b(r.label)}</span>
|
|
860
|
-
</div>
|
|
861
|
-
<div class="grid-cell badge" role="gridcell">${b(r.assignee || D)}</div>
|
|
862
|
-
<div class="grid-cell badge" role="gridcell">${b(U)}</div>
|
|
863
|
-
<div class="grid-cell badge" role="gridcell">${b(G)}</div>
|
|
1789
|
+
<div class="grid-row${Q}" part="grid-row" data-task-id="${v(l.id)}" role="row" tabindex="${l.id === tt ? 0 : -1}" aria-rowindex="${I + 2}" aria-level="${l.depth + 1}" ${Q ? 'aria-selected="true"' : ""} ${l.hasChildren ? `aria-expanded="${!this.collapsed.has(l.id)}"` : ""}>
|
|
1790
|
+
${U.map(ct).join("")}
|
|
864
1791
|
</div>
|
|
865
1792
|
`;
|
|
866
|
-
}).join(""),
|
|
867
|
-
const
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
const dt = Math.max(I, r.endMs - r.startMs), Z = Math.max(r.startMs, ot), ct = Math.min(r.endMs, lt);
|
|
871
|
-
if (ct <= Z) return "";
|
|
872
|
-
const pt = Math.max(0, Math.min(100, (Z - r.startMs) / dt * 100)), Tt = (ct - Z) / dt * 100, ut = Math.min(100 - pt, Math.max(1, Tt));
|
|
873
|
-
return ut <= 0 ? "" : `<span class="split-segment" style="left:${pt.toFixed(2)}%;width:${ut.toFixed(2)}%"></span>`;
|
|
874
|
-
}).join("") : "";
|
|
1793
|
+
}).join(""), be = dt.map((l, B) => {
|
|
1794
|
+
const I = ut(l), Q = nr(l, I, $t, t);
|
|
1795
|
+
pt.set(l.id, Q);
|
|
1796
|
+
const X = this.selectedId === l.id ? " selected" : "", ct = l.id === tt, ft = l.critical ? mt.danger : mt[l.tone || (I === "summary" ? "info" : "default")] || mt.default, Mt = I === "milestone" ? nt(l.startMs) - vt / 2 : nt(l.startMs), Me = nt(I === "milestone" ? l.startMs : l.endMs), Xt = I === "milestone" ? vt : Math.max(12, Me - Mt), St = Math.max(0, Math.min(100, Number(l.progress ?? 0))), Yt = !h && !l.readonly && (I !== "summary" || g), Ut = Yt && I === "task", Kt = I === "task" && l.segments?.length ? Ze(l, St) : "";
|
|
875
1797
|
return `
|
|
876
|
-
<div class="timeline-row${
|
|
877
|
-
${
|
|
1798
|
+
<div class="timeline-row${X}" part="timeline-row" data-task-id="${v(l.id)}">
|
|
1799
|
+
${l.baselineStart && l.baselineEnd ? `<span class="baseline" style="left:${nt($(l.baselineStart) || l.startMs).toFixed(2)}px;width:${Math.max(4, nt($(l.baselineEnd) || l.endMs) - nt($(l.baselineStart) || l.startMs)).toFixed(2)}px"></span>` : ""}
|
|
878
1800
|
<button
|
|
879
|
-
class="bar ${
|
|
880
|
-
part="bar ${
|
|
881
|
-
data-task-id="${
|
|
882
|
-
data-kind="${
|
|
883
|
-
|
|
884
|
-
${
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1801
|
+
class="bar ${I}${l.critical ? " critical" : ""}${Kt ? " split" : ""}${St >= 100 ? " complete" : ""}"
|
|
1802
|
+
part="bar ${I}"
|
|
1803
|
+
data-task-id="${v(l.id)}"
|
|
1804
|
+
data-kind="${I}"
|
|
1805
|
+
${l.progress != null ? `data-progress="${Math.round(St)}"` : ""}
|
|
1806
|
+
tabindex="${ct ? "0" : "-1"}"
|
|
1807
|
+
${this.selectedId === l.id ? 'aria-current="true"' : ""}
|
|
1808
|
+
style="left:${Mt.toFixed(2)}px;width:${Xt.toFixed(2)}px;--tone:${ft};--progress:${St.toFixed(2)}%"
|
|
1809
|
+
aria-label="${v(Q.name)}"
|
|
1810
|
+
${Yt ? 'data-draggable="true"' : ""}
|
|
888
1811
|
>
|
|
889
|
-
${
|
|
1812
|
+
${Ut ? '<span class="handle start" data-mode="resize-start"></span>' : ""}
|
|
890
1813
|
<span class="progress"></span>
|
|
891
|
-
${
|
|
892
|
-
${
|
|
893
|
-
<span class="
|
|
894
|
-
${nt ? '<span class="handle end" data-mode="resize-end"></span>' : ""}
|
|
1814
|
+
${Kt}
|
|
1815
|
+
${I === "milestone" ? '<span class="milestone-shape"></span>' : ""}
|
|
1816
|
+
${Ut ? '<span class="handle end" data-mode="resize-end"></span>' : ""}
|
|
895
1817
|
</button>
|
|
1818
|
+
${u ? `<span class="connector" data-task-id="${v(l.id)}" data-side="start" style="left:${(Mt - 18).toFixed(2)}px"></span><span class="connector" data-task-id="${v(l.id)}" data-side="end" style="left:${(Mt + Xt + 6).toFixed(2)}px"></span>` : ""}
|
|
896
1819
|
</div>
|
|
897
1820
|
`;
|
|
898
|
-
}).join("")
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
const
|
|
902
|
-
|
|
903
|
-
|
|
1821
|
+
}).join("");
|
|
1822
|
+
this.descriptions = pt, this.selectedLinkId && !r.some((l) => l.id === this.selectedLinkId) && (this.selectedLinkId = null);
|
|
1823
|
+
const ve = ie(this.getAttribute("schedule")) === "off" ? /* @__PURE__ */ new Set() : Ae(e.map(yt).filter((l) => l != null), r.map(Wt)), Ht = r.map((l) => {
|
|
1824
|
+
const B = F.get(l.source), I = F.get(l.target);
|
|
1825
|
+
if (!B || !I || V && !C.has(l.source) && !C.has(l.target)) return "";
|
|
1826
|
+
const Q = oe(B.task, B.index, I.task, I.index, l.type, nt), X = ve.has(l.id), ct = this.selectedLinkId === l.id, ft = `${B.task.label} dependency to ${I.task.label}${X ? `, ${t.linkViolated}` : ""}`;
|
|
1827
|
+
return `<path class="link-hit${X ? " violated" : ""}${ct ? " selected" : ""}" tabindex="-1" d="${Q}" data-link-id="${v(l.id)}" data-source-id="${v(l.source)}" data-target-id="${v(l.target)}" data-link-type="${v(l.type || "e2s")}"${X ? ' data-violated="true"' : ""} aria-label="${v(ft)}" /><path class="${[X ? "violated" : "", ct ? "selected" : ""].filter(Boolean).join(" ")}" d="${Q}" />`;
|
|
1828
|
+
}).join(""), Ot = (l) => `<marker id="${l}" markerWidth="9" markerHeight="9" refX="8.5" refY="4.5" orient="auto" markerUnits="userSpaceOnUse"><path d="M 0.5 0.75 L 8.5 4.5 L 0.5 8.25 z" /></marker>`, ye = Ht ? `<defs>${Ot("ui-gantt-arrow")}${Ot("ui-gantt-arrow-bad")}</defs>` : "", Pt = Ft(this.getAttribute("week-start")), _t = te(this.getAttribute("non-working-days"), this.getAttribute("holidays")), xe = Math.max(J, M - lt), gt = y + xe / q * D;
|
|
1829
|
+
this.layoutInfo = { min: y, dayWidth: q, gridWidth: lt, rangeEnd: gt }, this.renderedLeft = this.viewportLeft, this.visibleIds = c.map((l) => l.id);
|
|
1830
|
+
const zt = Y(Date.now()), Ct = nt(zt), Dt = zt >= y && zt <= gt, jt = m && Dt, we = jt ? `<div class="today" part="today" style="left:${Ct.toFixed(2)}px"></div>` : "", ke = jt && K !== "day" ? `<span class="today-pill" part="today-pill" style="left:clamp(22px, ${Ct.toFixed(2)}px, calc(100% - 22px))">${v(t.today)}</span>` : "", $e = this.focusRequest ?? this.captureFocus();
|
|
1831
|
+
this.focusRequest = null;
|
|
1832
|
+
const Vt = this.root.querySelector(".frame");
|
|
904
1833
|
this.setContent(`
|
|
905
|
-
<style>${
|
|
906
|
-
|
|
907
|
-
|
|
1834
|
+
<style>${Gt}</style>
|
|
1835
|
+
${P.width !== Tt || P.template !== ce ? `<style>@container ui-gantt (min-width: 641px) { .surface { --gantt-grid-w: ${P.width}px; --gantt-cols: ${P.template}; } }</style>` : ""}
|
|
1836
|
+
<section class="frame" part="frame" data-bar-variant="${s}"${T ? ` style="--ui-gantt-max-height:${T}"` : ""}>
|
|
1837
|
+
${b ? `
|
|
908
1838
|
<header class="toolbar" part="toolbar">
|
|
909
|
-
<div class="title"
|
|
1839
|
+
<div class="title">${v(t.title)} <span class="meta">${v(t.tasksCount.replace("{shown}", String(c.length)).replace("{total}", String(e.length)))}</span></div>
|
|
910
1840
|
<div class="controls">
|
|
911
|
-
<input class="control" value="${
|
|
912
|
-
<select class="select" aria-label="
|
|
1841
|
+
<input class="control" value="${v(o)}" placeholder="${v(t.filterPlaceholder)}" aria-label="${v(t.filterLabel)}" />
|
|
1842
|
+
<select class="select" aria-label="${v(t.sortLabel)}">
|
|
913
1843
|
${[
|
|
914
|
-
["none",
|
|
915
|
-
["start",
|
|
916
|
-
["end",
|
|
917
|
-
["label",
|
|
918
|
-
["progress",
|
|
919
|
-
].map(([
|
|
1844
|
+
["none", t.sortManual],
|
|
1845
|
+
["start", t.sortStart],
|
|
1846
|
+
["end", t.sortEnd],
|
|
1847
|
+
["label", t.sortName],
|
|
1848
|
+
["progress", t.sortProgress]
|
|
1849
|
+
].map(([l, B]) => `<option value="${l}" ${a === l ? "selected" : ""}>${v(B)}</option>`).join("")}
|
|
920
1850
|
</select>
|
|
921
|
-
${
|
|
1851
|
+
${d ? `<button class="add-button" data-action="add"><span aria-hidden="true">+</span> ${v(t.addTask)}</button>` : ""}
|
|
1852
|
+
${m ? `<button class="today-button" data-action="today" title="${v(t.scrollToToday)}" aria-label="${v(t.scrollToToday)}"${Dt ? "" : " disabled"}>${v(t.today)}</button>` : ""}
|
|
1853
|
+
${["day", "week", "month", "quarter", "year", "fit"].map((l) => `<button class="zoom-button" data-zoom="${l}" aria-pressed="${i === l}"${l === "fit" ? ` title="${v(t.zoomFitTitle)}"` : ""}>${v(sr(l, t))}</button>`).join("")}
|
|
922
1854
|
</div>
|
|
923
1855
|
</header>
|
|
924
1856
|
` : ""}
|
|
925
1857
|
<div class="viewport" part="viewport">
|
|
926
|
-
<div class="surface" part="surface" style="--day-width:${
|
|
927
|
-
<div class="grid-semantics" role="treegrid" aria-label="${
|
|
1858
|
+
<div class="surface" part="surface" style="--day-width:${q}px;grid-template-columns:var(--gantt-grid-w) minmax(${J.toFixed(2)}px, 1fr);inline-size:max(100%, calc(var(--gantt-grid-w) + ${J.toFixed(2)}px))">
|
|
1859
|
+
<div class="grid-semantics" role="treegrid" aria-label="${v(this.getAttribute("aria-label") || t.title)}" aria-rowcount="${c.length + 1}" aria-colcount="${z ? 1 : U.length}">
|
|
928
1860
|
<div class="grid-head" part="grid-head" role="row">
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1861
|
+
${U.map((l) => {
|
|
1862
|
+
const B = v(l.custom ?? t[l.title]);
|
|
1863
|
+
if (!l.sort) return `<span role="columnheader">${B}</span>`;
|
|
1864
|
+
const I = a === l.sort, Q = l.sort === "progress" ? "descending" : "ascending";
|
|
1865
|
+
return `<div role="columnheader" aria-sort="${I ? Q : "none"}" style="display:contents"><button class="head-button" data-sort="${l.sort}">${B}</button></div>`;
|
|
1866
|
+
}).join("")}
|
|
933
1867
|
</div>
|
|
934
1868
|
<div class="grid-body" part="grid-body" role="rowgroup">
|
|
935
|
-
${
|
|
936
|
-
${
|
|
937
|
-
${
|
|
1869
|
+
${V && rt ? `<div style="height:${rt}px"></div>` : ""}
|
|
1870
|
+
${me || `<div class="empty">${v(t.noMatches)}</div>`}
|
|
1871
|
+
${V && it ? `<div style="height:${it}px"></div>` : ""}
|
|
938
1872
|
</div>
|
|
939
1873
|
</div>
|
|
1874
|
+
<div class="splitter-rail head"><div class="splitter" role="separator" aria-orientation="vertical" aria-label="${v(t.splitter)}" aria-valuemin="${P.min}" aria-valuemax="${this.gridBounds.max}" aria-valuenow="${P.width}" tabindex="0"></div></div>
|
|
1875
|
+
<div class="splitter-rail body"><div class="splitter" aria-hidden="true"></div></div>
|
|
940
1876
|
<div class="timeline-head" part="timeline-head" aria-hidden="true">
|
|
941
|
-
<div class="scale" style="width:${
|
|
1877
|
+
<div class="scale" data-zoom="${K}" style="min-width:${J.toFixed(2)}px">${Ke(y, gt, q, K, Pt, this.viewportLeft, this.viewportWidth, _t)}${ke}</div>
|
|
942
1878
|
</div>
|
|
943
|
-
<div class="timeline-body" part="timeline-body" role="group" aria-label="
|
|
944
|
-
|
|
945
|
-
${
|
|
946
|
-
${
|
|
947
|
-
${
|
|
948
|
-
${
|
|
1879
|
+
<div class="timeline-body" part="timeline-body" role="group" aria-label="${v(t.timeline)}" data-zoom="${K}" style="min-width:${J.toFixed(2)}px;min-height:${H}px">
|
|
1880
|
+
${Ue(y, gt, q, _t, this.viewportLeft, this.viewportWidth)}
|
|
1881
|
+
${Ge(y, gt, q, K, Pt, this.viewportLeft, this.viewportWidth)}
|
|
1882
|
+
<svg class="links" width="${J.toFixed(0)}" height="${H}" viewBox="0 0 ${J.toFixed(0)} ${H}" aria-hidden="true">${ye}${Ht}</svg>
|
|
1883
|
+
${we}
|
|
1884
|
+
${V && rt ? `<div style="height:${rt}px"></div>` : ""}
|
|
1885
|
+
${be}
|
|
1886
|
+
${V && it ? `<div style="height:${it}px"></div>` : ""}
|
|
1887
|
+
<div class="bar-tip" part="tip" aria-hidden="true"></div>
|
|
949
1888
|
</div>
|
|
950
1889
|
</div>
|
|
951
1890
|
</div>
|
|
952
1891
|
</section>
|
|
953
1892
|
`);
|
|
954
|
-
const
|
|
955
|
-
|
|
1893
|
+
const j = this.root.querySelector(".viewport");
|
|
1894
|
+
if (j) {
|
|
1895
|
+
this.root.querySelector(".frame") !== Vt && (j.scrollTop = this.viewportTop, j.scrollLeft = this.viewportLeft, this.smoothTarget && typeof j.scrollTo == "function" && j.scrollTo({ ...this.smoothTarget, behavior: "smooth" })), this.viewportWidth = j.clientWidth || this.viewportWidth, this.viewportHeight = j.clientHeight || this.viewportHeight;
|
|
1896
|
+
const l = Math.max(0, j.offsetWidth - j.clientWidth);
|
|
1897
|
+
l !== this.scrollbarWidth && (this.scrollbarWidth = l, st && this.requestRender());
|
|
1898
|
+
}
|
|
1899
|
+
if (this.root.querySelector(".frame") !== Vt && this.bindEvents(), this.restoreFocus($e), j && j.clientWidth > 0 && this.initialScrollPending && (this.initialScrollPending = !1, this.getAttribute("initial-scroll") !== "start" && !st && m && Dt && Ct > j.clientWidth - lt - 48 && this.scrollToDate("today", { behavior: "auto" })), this.pendingReveal) {
|
|
1900
|
+
const l = this.pendingReveal;
|
|
1901
|
+
this.pendingReveal = null, this.reveal(l.id, l.behavior);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
/**
|
|
1905
|
+
* Every render replaces the whole shadow tree, which drops focus. Remember which control had it so
|
|
1906
|
+
* the replacement can take it back (and the caret, for the filter box); without this the filter
|
|
1907
|
+
* accepts one character per click.
|
|
1908
|
+
*/
|
|
1909
|
+
captureFocus() {
|
|
1910
|
+
const t = this.root.activeElement;
|
|
1911
|
+
if (!t) return null;
|
|
1912
|
+
const e = tr(t);
|
|
1913
|
+
if (!e) return null;
|
|
1914
|
+
const r = { selector: e };
|
|
1915
|
+
return t instanceof HTMLInputElement && (r.selection = [t.selectionStart, t.selectionEnd, t.selectionDirection]), r;
|
|
1916
|
+
}
|
|
1917
|
+
restoreFocus(t) {
|
|
1918
|
+
if (!t) return;
|
|
1919
|
+
const e = this.root.querySelector(t.selector);
|
|
1920
|
+
if (!e && t.reveal && (t.retries ?? 0) < 5) {
|
|
1921
|
+
this.focusRequest = { ...t, retries: (t.retries ?? 0) + 1 };
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1924
|
+
if (!(!e || this.root.activeElement === e) && (e.focus({ preventScroll: !t.reveal }), t.selectAll && e instanceof HTMLInputElement && e.select(), t.selection && e instanceof HTMLInputElement))
|
|
1925
|
+
try {
|
|
1926
|
+
e.setSelectionRange(t.selection[0], t.selection[1], t.selection[2] ?? void 0);
|
|
1927
|
+
} catch {
|
|
1928
|
+
}
|
|
956
1929
|
}
|
|
957
1930
|
bindEvents() {
|
|
958
1931
|
this.root.querySelectorAll("[data-task-id]").forEach((t) => {
|
|
959
|
-
t.addEventListener("click", (
|
|
960
|
-
const
|
|
961
|
-
if (!
|
|
1932
|
+
t.addEventListener("click", (e) => {
|
|
1933
|
+
const r = e.target, i = e.currentTarget;
|
|
1934
|
+
if (!i.classList.contains("bar") && r.closest(".bar")) return;
|
|
962
1935
|
if (this.drag?.moved) {
|
|
963
|
-
|
|
1936
|
+
e.stopPropagation();
|
|
964
1937
|
return;
|
|
965
1938
|
}
|
|
966
|
-
const
|
|
967
|
-
|
|
1939
|
+
const s = e.currentTarget.dataset.taskId;
|
|
1940
|
+
s && (r.closest('[data-action="toggle"]') || (i.classList.contains("bar") && e.stopPropagation(), this.selectTask(s)));
|
|
968
1941
|
});
|
|
969
1942
|
}), this.root.querySelectorAll('[data-action="toggle"]').forEach((t) => {
|
|
970
|
-
t.addEventListener("click", (
|
|
971
|
-
|
|
972
|
-
const
|
|
973
|
-
|
|
1943
|
+
t.addEventListener("click", (e) => {
|
|
1944
|
+
e.stopPropagation();
|
|
1945
|
+
const r = t.dataset.taskId;
|
|
1946
|
+
r && this.toggleTask(r);
|
|
974
1947
|
});
|
|
975
|
-
}), this.root.
|
|
1948
|
+
}), this.root.querySelector(".today-button")?.addEventListener("click", () => {
|
|
1949
|
+
this.scrollToDate("today", { behavior: "smooth", align: "center" });
|
|
1950
|
+
}), this.root.querySelectorAll(".zoom-button[data-zoom]").forEach((t) => {
|
|
976
1951
|
t.addEventListener("click", () => {
|
|
977
|
-
const
|
|
978
|
-
this.setAttribute("zoom",
|
|
1952
|
+
const e = t.dataset.zoom || "week";
|
|
1953
|
+
this.setAttribute("zoom", e), this.dispatchEvent(new CustomEvent("zoomchange", { detail: { zoom: e }, bubbles: !0, composed: !0 }));
|
|
979
1954
|
});
|
|
980
1955
|
}), this.root.querySelectorAll(".head-button[data-sort]").forEach((t) => {
|
|
981
1956
|
t.addEventListener("click", () => {
|
|
982
|
-
const
|
|
983
|
-
|
|
1957
|
+
const e = bt(t.dataset.sort || null);
|
|
1958
|
+
e === "none" ? this.removeAttribute("sort") : this.setAttribute("sort", e), this.dispatchEvent(new CustomEvent("sortchange", { detail: { sort: e }, bubbles: !0, composed: !0 }));
|
|
984
1959
|
});
|
|
985
|
-
})
|
|
986
|
-
|
|
987
|
-
this.
|
|
1960
|
+
});
|
|
1961
|
+
for (const t of ["wheel", "pointerdown", "touchstart", "keydown"])
|
|
1962
|
+
this.root.querySelector(".viewport")?.addEventListener(t, () => {
|
|
1963
|
+
this.smoothTarget = null;
|
|
1964
|
+
}, { passive: !0 });
|
|
1965
|
+
this.root.querySelector(".viewport")?.addEventListener("scroll", (t) => {
|
|
1966
|
+
const e = t.currentTarget, r = e.scrollTop, i = e.scrollLeft, a = Math.max(20, Math.ceil(Math.max(420, this.viewportHeight - N) / S)), s = Math.max(0, Math.floor(Math.max(0, r - N) / S)), o = this.virtualizedNow && (s < this.renderedFirstRow + 64 || s + a > this.renderedLastRow - 64), c = Math.abs(i - this.renderedLeft) >= 400;
|
|
1967
|
+
this.viewportTop = r, this.viewportLeft = i;
|
|
1968
|
+
const h = this.smoothTarget;
|
|
1969
|
+
h && (h.left == null || Math.abs(i - h.left) < 2) && (h.top == null || Math.abs(r - h.top) < 2) && (this.smoothTarget = null), !(this.drag || this.linking || !o && !c) && this.scrollFrame == null && (this.scrollFrame = window.setTimeout(() => {
|
|
988
1970
|
this.scrollFrame = null, this.requestRender();
|
|
989
1971
|
}, 24));
|
|
990
1972
|
}, { passive: !0 }), this.root.querySelector(".control")?.addEventListener("input", (t) => {
|
|
991
1973
|
this.internalFilter = t.currentTarget.value, this.dispatchEvent(new CustomEvent("filterchange", { detail: { filter: this.internalFilter }, bubbles: !0, composed: !0 })), this.requestRender();
|
|
992
1974
|
}), this.root.querySelector(".select")?.addEventListener("change", (t) => {
|
|
993
|
-
const
|
|
994
|
-
|
|
995
|
-
}), this.root.querySelectorAll(".
|
|
996
|
-
t.addEventListener("
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1975
|
+
const e = bt(t.currentTarget.value);
|
|
1976
|
+
e === "none" ? this.removeAttribute("sort") : this.setAttribute("sort", e), this.dispatchEvent(new CustomEvent("sortchange", { detail: { sort: e }, bubbles: !0, composed: !0 }));
|
|
1977
|
+
}), this.bindLinks(), this.root.querySelectorAll(".bar").forEach((t) => {
|
|
1978
|
+
t.addEventListener("keydown", (e) => this.handleBarKeydown(t, e));
|
|
1979
|
+
}), this.bindTooltips(), this.bindSplitters(), this.bindRows(), this.bindEditing(), !this.hasAttribute("readonly") && this.root.querySelectorAll('.bar[data-draggable="true"]').forEach((t) => {
|
|
1980
|
+
t.addEventListener("pointerdown", (e) => this.startDrag(t, e)), t.addEventListener("pointermove", (e) => {
|
|
1981
|
+
!this.drag || this.drag.bar !== t || (e.preventDefault(), this.drag.lastClientX = e.clientX, this.updateDrag());
|
|
1982
|
+
}), t.addEventListener("pointerup", () => this.endDrag(!0)), t.addEventListener("pointercancel", () => this.endDrag(!1));
|
|
1983
|
+
});
|
|
1984
|
+
}
|
|
1985
|
+
snapUnit() {
|
|
1986
|
+
const t = this.getAttribute("snap");
|
|
1987
|
+
return t === "week" || t === "month" ? t : "day";
|
|
1988
|
+
}
|
|
1989
|
+
/** The nearest boundary of the snap unit (a week start, a 1st of the month), or the day itself. */
|
|
1990
|
+
snapDate(t) {
|
|
1991
|
+
const e = this.snapUnit();
|
|
1992
|
+
if (e === "day") return t;
|
|
1993
|
+
const r = Ft(this.getAttribute("week-start")), i = ue(t, e, r), a = pe(i, e);
|
|
1994
|
+
return t - i < a - t ? i : a;
|
|
1995
|
+
}
|
|
1996
|
+
startDrag(t, e) {
|
|
1997
|
+
if (e.button !== 0) return;
|
|
1998
|
+
const r = t.dataset.taskId;
|
|
1999
|
+
if (!r) return;
|
|
2000
|
+
const i = this.getTasks().find((u) => u.id === r), a = $(i?.start), s = $(i?.end);
|
|
2001
|
+
if (!i || a == null || s == null) return;
|
|
2002
|
+
e.preventDefault();
|
|
2003
|
+
const o = t.dataset.kind || "task", c = e.target.dataset.mode || "move", h = o === "milestone" ? "move" : c, d = this.selectedId !== r;
|
|
2004
|
+
this.selectedId = r, this.dispatchEvent(new CustomEvent("taskselect", { detail: { id: r }, bubbles: !0, composed: !0 }));
|
|
2005
|
+
const g = (u) => {
|
|
2006
|
+
u.key === "Escape" && (u.preventDefault(), u.stopPropagation(), this.endDrag(!1));
|
|
2007
|
+
};
|
|
2008
|
+
this.drag = {
|
|
2009
|
+
taskId: r,
|
|
2010
|
+
mode: h,
|
|
2011
|
+
startX: e.clientX,
|
|
2012
|
+
startScroll: this.root.querySelector(".viewport")?.scrollLeft ?? 0,
|
|
2013
|
+
lastClientX: e.clientX,
|
|
2014
|
+
originalStart: a,
|
|
2015
|
+
originalEnd: s,
|
|
2016
|
+
kind: o,
|
|
2017
|
+
rangeMin: this.rangeMin(),
|
|
2018
|
+
dayWidth: this.dayWidthNow,
|
|
2019
|
+
latestStart: a,
|
|
2020
|
+
latestEnd: s,
|
|
2021
|
+
frame: null,
|
|
2022
|
+
scrollFrame: null,
|
|
2023
|
+
bar: t,
|
|
2024
|
+
originalLeft: t.style.left,
|
|
2025
|
+
originalWidth: t.style.width,
|
|
2026
|
+
moved: !1,
|
|
2027
|
+
selectedChanged: d,
|
|
2028
|
+
onKey: g
|
|
2029
|
+
}, window.addEventListener("keydown", g, !0), t.classList.add("dragging"), t.setPointerCapture(e.pointerId), this.autoScrollLoop();
|
|
2030
|
+
}
|
|
2031
|
+
/** The dates the bar would take for the pointer's position, snapped, and a repaint of the bar and the tooltip. */
|
|
2032
|
+
updateDrag() {
|
|
2033
|
+
const t = this.drag;
|
|
2034
|
+
if (!t) return;
|
|
2035
|
+
const e = this.root.querySelector(".viewport"), r = t.lastClientX - t.startX + (e ? e.scrollLeft - t.startScroll : 0);
|
|
2036
|
+
Math.abs(r) > 2 && (t.moved = !0);
|
|
2037
|
+
const i = Math.round(r / t.dayWidth);
|
|
2038
|
+
let a = t.originalStart, s = t.originalEnd;
|
|
2039
|
+
if (t.mode === "move") {
|
|
2040
|
+
const o = Math.round((this.snapDate(A(t.originalStart, i)) - t.originalStart) / D);
|
|
2041
|
+
a = A(t.originalStart, o), s = t.kind === "milestone" ? a : A(t.originalEnd, o);
|
|
2042
|
+
} else t.mode === "resize-start" ? a = Math.min(this.snapDate(A(t.originalStart, i)), A(s, -1)) : s = Math.max(this.snapDate(A(t.originalEnd, i)), A(a, 1));
|
|
2043
|
+
t.latestStart = a, t.latestEnd = s, t.frame == null && (t.frame = requestAnimationFrame(() => {
|
|
2044
|
+
const o = this.drag;
|
|
2045
|
+
if (!o) return;
|
|
2046
|
+
o.frame = null;
|
|
2047
|
+
const c = (o.latestStart - o.rangeMin) / D * o.dayWidth - (o.kind === "milestone" ? vt / 2 : 0), h = o.kind === "milestone" ? vt : Math.max(12, (o.latestEnd - o.latestStart) / D * o.dayWidth);
|
|
2048
|
+
o.bar.style.left = `${c.toFixed(2)}px`, o.bar.style.width = `${h.toFixed(2)}px`, Lt = this.getAttribute("locale") || void 0;
|
|
2049
|
+
const d = Z({ dateStyle: "medium" }), g = this.labelSet();
|
|
2050
|
+
this.showTip(
|
|
2051
|
+
o.bar,
|
|
2052
|
+
o.kind === "milestone" ? d.format(new Date(o.latestStart)) : `${d.format(new Date(o.latestStart))} ${g.to} ${d.format(new Date(o.latestEnd))}`
|
|
2053
|
+
);
|
|
2054
|
+
}));
|
|
2055
|
+
}
|
|
2056
|
+
/** While a drag is held near the left or right edge of the timeline the chart scrolls, faster the nearer the edge. */
|
|
2057
|
+
autoScrollLoop() {
|
|
2058
|
+
const t = this.drag;
|
|
2059
|
+
t && (t.scrollFrame = requestAnimationFrame(() => {
|
|
2060
|
+
const e = this.drag;
|
|
2061
|
+
if (!e) return;
|
|
2062
|
+
const r = this.root.querySelector(".viewport");
|
|
2063
|
+
if (r && r.clientWidth > 0 && e.moved) {
|
|
2064
|
+
const i = r.getBoundingClientRect(), a = 56, s = Math.max(i.left, this.root.querySelector(".grid-body")?.getBoundingClientRect().right ?? i.left);
|
|
2065
|
+
let o = 0;
|
|
2066
|
+
e.lastClientX > i.right - a ? o = (e.lastClientX - (i.right - a)) / a : e.lastClientX < s + a && r.scrollLeft > 0 && (o = -(s + a - e.lastClientX) / a), o && (r.scrollLeft += Math.max(-1.5, Math.min(1.5, o)) * 16, this.updateDrag());
|
|
2067
|
+
}
|
|
2068
|
+
this.autoScrollLoop();
|
|
2069
|
+
}));
|
|
2070
|
+
}
|
|
2071
|
+
/** Finish a drag: drop it (commit the new dates), or cancel it (the bar goes back where it was). */
|
|
2072
|
+
endDrag(t) {
|
|
2073
|
+
const e = this.drag;
|
|
2074
|
+
if (!e) return;
|
|
2075
|
+
e.frame != null && cancelAnimationFrame(e.frame), e.scrollFrame != null && cancelAnimationFrame(e.scrollFrame), window.removeEventListener("keydown", e.onKey, !0), e.bar.classList.remove("dragging"), this.drag = null, this.hideTip();
|
|
2076
|
+
const r = () => {
|
|
2077
|
+
e.bar.style.left = e.originalLeft, e.bar.style.width = e.originalWidth;
|
|
2078
|
+
};
|
|
2079
|
+
if (!e.moved) {
|
|
2080
|
+
e.selectedChanged && this.requestRender();
|
|
2081
|
+
return;
|
|
2082
|
+
}
|
|
2083
|
+
if (!t) {
|
|
2084
|
+
r(), this.requestRender();
|
|
2085
|
+
return;
|
|
2086
|
+
}
|
|
2087
|
+
if (e.latestStart === e.originalStart && e.latestEnd === e.originalEnd) {
|
|
2088
|
+
r();
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2091
|
+
this.applyChanges(
|
|
2092
|
+
this.plan({ id: e.taskId, start: e.latestStart, end: e.latestEnd, previousStart: e.originalStart, previousEnd: e.originalEnd }),
|
|
2093
|
+
e.mode === "move" ? "drag" : "resize"
|
|
2094
|
+
) || r();
|
|
2095
|
+
}
|
|
2096
|
+
/**
|
|
2097
|
+
* With `cascade`, moving a parent moves everything under it by the same number of days (a read-only task stays
|
|
2098
|
+
* put). Anything else is the one change.
|
|
2099
|
+
*/
|
|
2100
|
+
plan(t) {
|
|
2101
|
+
const e = this.withCascade(t);
|
|
2102
|
+
if (ie(this.getAttribute("schedule")) !== "auto") return e;
|
|
2103
|
+
const r = this.getLinks();
|
|
2104
|
+
if (!r.length) return e;
|
|
2105
|
+
const i = this.getTasks().map(yt).filter((c) => c != null), a = new Map(i.map((c) => [c.id, c])), s = new Map(e.map((c) => [c.id, { start: c.start, end: c.end }])), o = [...e];
|
|
2106
|
+
for (const c of Le(i, r.map(Wt), s, this.hasAttribute("cascade"))) {
|
|
2107
|
+
const h = o.findIndex((d) => d.id === c.id);
|
|
2108
|
+
if (h >= 0)
|
|
2109
|
+
o[h] = { ...o[h], start: c.start, end: c.end };
|
|
2110
|
+
else {
|
|
2111
|
+
const d = a.get(c.id);
|
|
2112
|
+
d && o.push({ id: c.id, start: c.start, end: c.end, previousStart: d.start, previousEnd: d.end });
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
return o;
|
|
2116
|
+
}
|
|
2117
|
+
withCascade(t) {
|
|
2118
|
+
if (!this.hasAttribute("cascade")) return [t];
|
|
2119
|
+
const e = wt(t.previousStart, t.start);
|
|
2120
|
+
if (e === 0 || t.end - t.start !== t.previousEnd - t.previousStart) return [t];
|
|
2121
|
+
const r = this.getTasks(), i = de(r.map(yt).filter((s) => s != null), t.id);
|
|
2122
|
+
if (!i.length) return [t];
|
|
2123
|
+
const a = i.filter((s) => !s.readonly).map((s) => ({ id: s.id, start: O(s.start, e), end: O(s.end, e), previousStart: s.start, previousEnd: s.end }));
|
|
2124
|
+
return [t, ...a];
|
|
2125
|
+
}
|
|
2126
|
+
/**
|
|
2127
|
+
* Apply a change to dates. First a cancelable `beforetaskchange` (a host that rejects the change calls
|
|
2128
|
+
* preventDefault and nothing moves), then the new dates are kept and `taskchange` fires for each task.
|
|
2129
|
+
*/
|
|
2130
|
+
applyChanges(t, e) {
|
|
2131
|
+
const [r, ...i] = t, a = (o) => ({
|
|
2132
|
+
id: o.id,
|
|
2133
|
+
start: W(o.start),
|
|
2134
|
+
end: W(o.end),
|
|
2135
|
+
previousStart: W(o.previousStart),
|
|
2136
|
+
previousEnd: W(o.previousEnd)
|
|
2137
|
+
});
|
|
2138
|
+
if (!this.dispatchEvent(
|
|
2139
|
+
new CustomEvent("beforetaskchange", {
|
|
2140
|
+
detail: { ...a(r), source: e, affected: i.map(a) },
|
|
2141
|
+
bubbles: !0,
|
|
2142
|
+
composed: !0,
|
|
2143
|
+
cancelable: !0
|
|
2144
|
+
})
|
|
2145
|
+
))
|
|
2146
|
+
return this.requestRender(), !1;
|
|
2147
|
+
for (const o of t) this.overrides.set(o.id, { ...this.overrides.get(o.id), start: W(o.start), end: W(o.end) });
|
|
2148
|
+
this.bumpOverrides();
|
|
2149
|
+
for (const o of t)
|
|
2150
|
+
this.dispatchEvent(new CustomEvent("taskchange", { detail: { id: o.id, start: W(o.start), end: W(o.end) }, bubbles: !0, composed: !0 }));
|
|
2151
|
+
return this.requestRender(), !0;
|
|
2152
|
+
}
|
|
2153
|
+
/**
|
|
2154
|
+
* Keyboard on a bar. Moving between rows (and selecting) works on every bar, summaries and read-only
|
|
2155
|
+
* charts included; only the keys that change dates or delete need an editable bar.
|
|
2156
|
+
*/
|
|
2157
|
+
handleBarKeydown(t, e) {
|
|
2158
|
+
const r = t.dataset.taskId;
|
|
2159
|
+
if (!r) return;
|
|
2160
|
+
const i = t.dataset.draggable === "true" && !this.hasAttribute("readonly");
|
|
2161
|
+
if (e.key === "Escape") {
|
|
2162
|
+
this.hideTip();
|
|
2163
|
+
return;
|
|
2164
|
+
}
|
|
2165
|
+
if (e.key === "F2") {
|
|
2166
|
+
e.preventDefault(), this.startRename(r);
|
|
2167
|
+
return;
|
|
2168
|
+
}
|
|
2169
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
2170
|
+
e.preventDefault(), this.selectTask(r);
|
|
2171
|
+
return;
|
|
2172
|
+
}
|
|
2173
|
+
if (["ArrowUp", "ArrowDown", "Home", "End", "PageUp", "PageDown"].includes(e.key)) {
|
|
2174
|
+
e.preventDefault(), this.navigate(r, e.key, "bar");
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2177
|
+
if (!i) return;
|
|
2178
|
+
if (e.key === "Delete" || e.key === "Backspace") {
|
|
2179
|
+
e.preventDefault(), this.dispatchEvent(new CustomEvent("taskdelete", { detail: { id: r }, bubbles: !0, composed: !0 }));
|
|
2180
|
+
return;
|
|
2181
|
+
}
|
|
2182
|
+
if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
|
|
2183
|
+
const a = this.getTasks().find((b) => b.id === r), s = $(a?.start), o = $(a?.end);
|
|
2184
|
+
if (!a || s == null || o == null) return;
|
|
2185
|
+
e.preventDefault();
|
|
2186
|
+
const c = e.key === "ArrowLeft" ? -1 : 1, h = t.dataset.kind === "milestone", d = e.shiftKey && !h && t.dataset.kind !== "summary", g = d ? s : A(s, c), u = h ? g : d ? Math.max(A(o, c), A(s, 1)) : A(o, c);
|
|
2187
|
+
this.applyChanges(this.plan({ id: r, start: g, end: u, previousStart: s, previousEnd: o }), "keyboard");
|
|
2188
|
+
}
|
|
2189
|
+
/**
|
|
2190
|
+
* Show the tooltip for a bar: above its visible part, centred, kept inside the scroll area; below the bar when
|
|
2191
|
+
* there is no room under the sticky header.
|
|
2192
|
+
*/
|
|
2193
|
+
showTip(t, e) {
|
|
2194
|
+
const r = this.root.querySelector(".bar-tip"), i = this.root.querySelector(".timeline-body"), a = this.root.querySelector(".viewport"), s = this.root.querySelector(".grid-body"), o = this.descriptions.get(t.dataset.taskId || "");
|
|
2195
|
+
if (!r || !i || !a || !o) return;
|
|
2196
|
+
r.innerHTML = `<strong>${v(o.title)}</strong><span>${v(e ? [o.kindWord, e].filter(Boolean).join(" · ") : o.when)}</span>` + (o.more ? `<span>${v(o.more)}</span>` : ""), r.setAttribute("data-open", "");
|
|
2197
|
+
const c = i.getBoundingClientRect(), h = t.getBoundingClientRect(), d = a.getBoundingClientRect(), g = Math.max(d.left, s ? s.getBoundingClientRect().right : d.left) + 4, u = d.right - 4, b = r.offsetWidth, m = r.offsetHeight, p = (Math.max(h.left, g) + Math.min(h.right, u)) / 2, k = Math.max(g, Math.min(p - b / 2, u - b)), w = h.top - (d.top + N) >= m + 8 ? h.top - m - 6 : h.bottom + 6;
|
|
2198
|
+
r.style.left = `${(k - c.left).toFixed(1)}px`, r.style.top = `${(w - c.top).toFixed(1)}px`;
|
|
2199
|
+
}
|
|
2200
|
+
hideTip() {
|
|
2201
|
+
this.tipTimer != null && (window.clearTimeout(this.tipTimer), this.tipTimer = null), this.root.querySelector(".bar-tip")?.removeAttribute("data-open");
|
|
2202
|
+
}
|
|
2203
|
+
/** Links: select one by clicking it (then Delete asks to remove it), and with `linkable` draw new ones from the dots beside a bar. */
|
|
2204
|
+
bindLinks() {
|
|
2205
|
+
const t = (e) => ({
|
|
2206
|
+
id: e.dataset.linkId || "",
|
|
2207
|
+
source: e.dataset.sourceId || "",
|
|
2208
|
+
target: e.dataset.targetId || "",
|
|
2209
|
+
type: e.dataset.linkType || "e2s"
|
|
2210
|
+
});
|
|
2211
|
+
this.root.querySelectorAll(".link-hit").forEach((e) => {
|
|
2212
|
+
e.addEventListener("click", (r) => {
|
|
2213
|
+
r.stopPropagation(), this.selectedLinkId = e.dataset.linkId || null, this.focusRequest = { selector: `.link-hit[data-link-id="${ot(e.dataset.linkId || "")}"]` }, this.dispatchEvent(new CustomEvent("linkselect", { detail: t(e), bubbles: !0, composed: !0 })), this.requestRender();
|
|
2214
|
+
}), e.addEventListener("keydown", (r) => {
|
|
2215
|
+
if (r.key === "Delete" || r.key === "Backspace") {
|
|
2216
|
+
if (this.hasAttribute("readonly")) return;
|
|
2217
|
+
r.preventDefault(), this.dispatchEvent(new CustomEvent("linkdelete", { detail: t(e), bubbles: !0, composed: !0 }));
|
|
2218
|
+
} else r.key === "Enter" || r.key === " " ? (r.preventDefault(), this.dispatchEvent(new CustomEvent("linkselect", { detail: t(e), bubbles: !0, composed: !0 }))) : r.key === "Escape" && (this.selectedLinkId = null, this.requestRender());
|
|
1007
2219
|
});
|
|
1008
|
-
}),
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
2220
|
+
}), this.root.querySelectorAll(".connector").forEach((e) => {
|
|
2221
|
+
e.addEventListener("pointerdown", (r) => this.startLink(e, r)), e.addEventListener("pointermove", (r) => this.updateLink(r)), e.addEventListener("pointerup", () => this.endLink(!0)), e.addEventListener("pointercancel", () => this.endLink(!1));
|
|
2222
|
+
});
|
|
2223
|
+
}
|
|
2224
|
+
startLink(t, e) {
|
|
2225
|
+
if (e.button !== 0) return;
|
|
2226
|
+
const r = t.dataset.taskId, i = t.dataset.side === "start" ? "start" : "end", a = this.root.querySelector(".timeline-body"), s = this.root.querySelector("svg.links"), o = r ? this.root.querySelector(At(r)) : null;
|
|
2227
|
+
if (!r || !a || !s || !o) return;
|
|
2228
|
+
e.preventDefault();
|
|
2229
|
+
const c = a.getBoundingClientRect(), h = o.getBoundingClientRect(), d = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
2230
|
+
d.setAttribute("class", "link-draft"), s.appendChild(d);
|
|
2231
|
+
const g = (u) => {
|
|
2232
|
+
u.key === "Escape" && (u.preventDefault(), u.stopPropagation(), this.endLink(!1));
|
|
2233
|
+
};
|
|
2234
|
+
this.linking = {
|
|
2235
|
+
sourceId: r,
|
|
2236
|
+
side: i,
|
|
2237
|
+
fromX: (i === "start" ? h.left : h.right) - c.left,
|
|
2238
|
+
fromY: h.top + h.height / 2 - c.top,
|
|
2239
|
+
draft: d,
|
|
2240
|
+
targetId: null,
|
|
2241
|
+
targetSide: null,
|
|
2242
|
+
onKey: g
|
|
2243
|
+
}, window.addEventListener("keydown", g, !0), this.root.querySelector(".frame")?.setAttribute("data-linking", ""), t.setPointerCapture(e.pointerId), this.updateLink(e);
|
|
2244
|
+
}
|
|
2245
|
+
/** The rubber-band line follows the pointer; the bar (or dot) under it, and which end of it, is the target. */
|
|
2246
|
+
updateLink(t) {
|
|
2247
|
+
const e = this.linking, r = this.root.querySelector(".timeline-body");
|
|
2248
|
+
if (!e || !r) return;
|
|
2249
|
+
const i = r.getBoundingClientRect();
|
|
2250
|
+
e.draft.setAttribute("d", `M ${e.fromX.toFixed(1)} ${e.fromY.toFixed(1)} L ${(t.clientX - i.left).toFixed(1)} ${(t.clientY - i.top).toFixed(1)}`), this.root.querySelectorAll(".bar.link-target").forEach((d) => {
|
|
2251
|
+
d.classList.remove("link-target"), d.removeAttribute("data-link-side");
|
|
2252
|
+
}), e.targetId = null, e.targetSide = null;
|
|
2253
|
+
const a = this.root.elementFromPoint(t.clientX, t.clientY), s = a?.closest?.(".connector"), o = s ? this.root.querySelector(At(s.dataset.taskId || "")) : a?.closest?.(".bar"), c = o?.dataset.taskId;
|
|
2254
|
+
if (!o || !c || c === e.sourceId) return;
|
|
2255
|
+
const h = o.getBoundingClientRect();
|
|
2256
|
+
e.targetId = c, e.targetSide = s ? s.dataset.side === "start" ? "start" : "end" : t.clientX < h.left + h.width / 2 ? "start" : "end", o.classList.add("link-target"), o.setAttribute("data-link-side", e.targetSide);
|
|
2257
|
+
}
|
|
2258
|
+
/** Let go: a drop on another task asks the host for a link (`linkcreate`), anywhere else nothing happens. */
|
|
2259
|
+
endLink(t) {
|
|
2260
|
+
const e = this.linking;
|
|
2261
|
+
if (!e || (window.removeEventListener("keydown", e.onKey, !0), this.linking = null, e.draft.remove(), this.root.querySelector(".frame")?.removeAttribute("data-linking"), this.root.querySelectorAll(".bar.link-target").forEach((i) => {
|
|
2262
|
+
i.classList.remove("link-target"), i.removeAttribute("data-link-side");
|
|
2263
|
+
}), !t || !e.targetId || !e.targetSide)) return;
|
|
2264
|
+
const r = `${e.side === "start" ? "s" : "e"}2${e.targetSide === "start" ? "s" : "e"}`;
|
|
2265
|
+
this.getLinks().some((i) => i.source === e.sourceId && i.target === e.targetId && (i.type || "e2s") === r) || this.dispatchEvent(new CustomEvent("linkcreate", { detail: { source: e.sourceId, target: e.targetId, type: r }, bubbles: !0, composed: !0 }));
|
|
2266
|
+
}
|
|
2267
|
+
/** Rename a task in place: the name becomes a text field. Does nothing in a read-only chart or for a read-only task. */
|
|
2268
|
+
startRename(t) {
|
|
2269
|
+
if (this.hasAttribute("readonly")) return;
|
|
2270
|
+
const e = this.getTasks().find((r) => r.id === t);
|
|
2271
|
+
!e || e.readonly || (this.editingId = t, this.editingValue = e.label, this.focusRequest = { selector: ".rename", reveal: !0, retries: 0, selectAll: !0 }, this.reveal(t, "auto"), this.requestRender());
|
|
2272
|
+
}
|
|
2273
|
+
/** Leave the rename field: keep the new name (when `save` and it is not empty or unchanged and nobody refuses it) or drop it. */
|
|
2274
|
+
finishRename(t) {
|
|
2275
|
+
const e = this.editingId;
|
|
2276
|
+
if (!e) return;
|
|
2277
|
+
const r = this.editingValue.trim(), i = this.getTasks().find((a) => a.id === e)?.label;
|
|
2278
|
+
this.editingId = null, t && r && i != null && r !== i && this.dispatchEvent(
|
|
2279
|
+
new CustomEvent("taskrename", { detail: { id: e, label: r, previousLabel: i }, bubbles: !0, composed: !0, cancelable: !0 })
|
|
2280
|
+
) && (this.overrides.set(e, { ...this.overrides.get(e), label: r }), this.bumpOverrides()), this.focusRequest = { selector: Nt(e) }, this.requestRender();
|
|
2281
|
+
}
|
|
2282
|
+
/** Create a task: the host is told where (`taskcreate`) and adds it; nothing is added here. */
|
|
2283
|
+
requestCreate(t, e) {
|
|
2284
|
+
const r = e ? this.getTasks().find((i) => i.id === e) : void 0;
|
|
2285
|
+
this.dispatchEvent(
|
|
2286
|
+
new CustomEvent("taskcreate", {
|
|
2287
|
+
detail: { start: W(t), end: W(A(t, He)), parent: r?.parent, after: r?.id },
|
|
2288
|
+
bubbles: !0,
|
|
2289
|
+
composed: !0
|
|
2290
|
+
})
|
|
2291
|
+
);
|
|
2292
|
+
}
|
|
2293
|
+
bindEditing() {
|
|
2294
|
+
this.root.querySelectorAll(".grid-row .task-name").forEach((e) => {
|
|
2295
|
+
e.addEventListener("dblclick", (r) => {
|
|
2296
|
+
if (r.target.closest(".expander, .rename")) return;
|
|
2297
|
+
const i = e.closest(".grid-row")?.dataset.taskId;
|
|
2298
|
+
i && this.startRename(i);
|
|
1057
2299
|
});
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
2300
|
+
});
|
|
2301
|
+
const t = this.root.querySelector(".rename");
|
|
2302
|
+
t && (t.addEventListener("input", () => {
|
|
2303
|
+
this.editingValue = t.value;
|
|
2304
|
+
}), t.addEventListener("keydown", (e) => {
|
|
2305
|
+
e.stopPropagation(), e.key === "Enter" ? (e.preventDefault(), this.finishRename(!0)) : e.key === "Escape" && (e.preventDefault(), this.finishRename(!1));
|
|
2306
|
+
}), t.addEventListener("blur", () => {
|
|
2307
|
+
this.suppressBlur || this.finishRename(!0);
|
|
2308
|
+
})), this.root.querySelector(".add-button")?.addEventListener("click", () => {
|
|
2309
|
+
const { min: e, rangeEnd: r } = this.layoutInfo, i = Y(Date.now());
|
|
2310
|
+
this.requestCreate(i >= e && i <= r ? i : e, this.selectedId ?? void 0);
|
|
2311
|
+
}), this.hasAttribute("creatable") && !this.hasAttribute("readonly") && this.root.querySelectorAll(".timeline-row").forEach((e) => {
|
|
2312
|
+
e.addEventListener("dblclick", (r) => {
|
|
2313
|
+
if (r.target !== e) return;
|
|
2314
|
+
const i = this.root.querySelector(".timeline-body");
|
|
2315
|
+
if (!i) return;
|
|
2316
|
+
const { min: a, dayWidth: s } = this.layoutInfo, o = Math.floor((r.clientX - i.getBoundingClientRect().left) / s);
|
|
2317
|
+
this.requestCreate(A(a, o), e.dataset.taskId);
|
|
2318
|
+
});
|
|
2319
|
+
});
|
|
2320
|
+
}
|
|
2321
|
+
/** The task list is a treegrid: a row takes focus and the arrow keys move around it. */
|
|
2322
|
+
bindRows() {
|
|
2323
|
+
this.root.querySelectorAll(".grid-row").forEach((t) => {
|
|
2324
|
+
t.addEventListener("keydown", (e) => {
|
|
2325
|
+
if (e.target !== t) return;
|
|
2326
|
+
const r = t.dataset.taskId;
|
|
2327
|
+
if (r) {
|
|
2328
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
2329
|
+
e.preventDefault(), this.selectTask(r);
|
|
2330
|
+
return;
|
|
2331
|
+
}
|
|
2332
|
+
if (e.key === "F2") {
|
|
2333
|
+
e.preventDefault(), this.startRename(r);
|
|
2334
|
+
return;
|
|
2335
|
+
}
|
|
2336
|
+
this.navigate(r, e.key, "row") && e.preventDefault();
|
|
1065
2337
|
}
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
2338
|
+
});
|
|
2339
|
+
});
|
|
2340
|
+
}
|
|
2341
|
+
/** Drag the splitter (or use the arrow keys on it) to resize the task list; it is kept in `grid-width`. */
|
|
2342
|
+
bindSplitters() {
|
|
2343
|
+
const t = this.root.querySelector(".surface"), e = this.root.querySelectorAll(".splitter"), r = (i) => {
|
|
2344
|
+
const a = Math.round(Math.max(this.gridBounds.min, Math.min(this.gridBounds.max, i)));
|
|
2345
|
+
a !== this.gridBounds.width && (this.setAttribute("grid-width", String(a)), this.dispatchEvent(new CustomEvent("gridresize", { detail: { width: a }, bubbles: !0, composed: !0 })));
|
|
2346
|
+
};
|
|
2347
|
+
e.forEach((i) => {
|
|
2348
|
+
i.addEventListener("pointerdown", (a) => {
|
|
2349
|
+
if (a.button !== 0 || !t) return;
|
|
2350
|
+
a.preventDefault();
|
|
2351
|
+
const s = a.clientX, { min: o, max: c, width: h } = this.gridBounds;
|
|
2352
|
+
let d = h;
|
|
2353
|
+
i.setPointerCapture(a.pointerId), e.forEach((b) => b.classList.add("dragging"));
|
|
2354
|
+
const g = (b) => {
|
|
2355
|
+
d = Math.max(o, Math.min(c, h + b.clientX - s)), t.style.setProperty("--gantt-grid-w", `${d}px`);
|
|
2356
|
+
}, u = (b) => {
|
|
2357
|
+
if (i.removeEventListener("pointermove", g), i.removeEventListener("pointerup", u), i.removeEventListener("pointercancel", u), e.forEach((m) => m.classList.remove("dragging")), b.type === "pointercancel") {
|
|
2358
|
+
t.style.removeProperty("--gantt-grid-w");
|
|
2359
|
+
return;
|
|
2360
|
+
}
|
|
2361
|
+
r(d);
|
|
1070
2362
|
};
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
2363
|
+
i.addEventListener("pointermove", g), i.addEventListener("pointerup", u), i.addEventListener("pointercancel", u);
|
|
2364
|
+
}), i.addEventListener("keydown", (a) => {
|
|
2365
|
+
if (i.getAttribute("role") !== "separator") return;
|
|
2366
|
+
const s = a.shiftKey ? 64 : 16, { min: o, max: c, width: h } = this.gridBounds, d = a.key === "ArrowLeft" ? h - s : a.key === "ArrowRight" ? h + s : a.key === "Home" ? o : a.key === "End" ? c : null;
|
|
2367
|
+
d != null && (a.preventDefault(), r(d));
|
|
2368
|
+
});
|
|
2369
|
+
});
|
|
2370
|
+
}
|
|
2371
|
+
bindTooltips() {
|
|
2372
|
+
this.root.querySelectorAll(".bar").forEach((t) => {
|
|
2373
|
+
t.addEventListener("pointerenter", (e) => {
|
|
2374
|
+
e.pointerType !== "touch" && (this.hideTip(), this.tipTimer = window.setTimeout(() => {
|
|
2375
|
+
this.tipTimer = null, this.showTip(t);
|
|
2376
|
+
}, 150));
|
|
2377
|
+
}), t.addEventListener("pointerleave", () => this.hideTip()), t.addEventListener("pointerdown", () => {
|
|
2378
|
+
this.pointerFocus = !0, window.setTimeout(() => {
|
|
2379
|
+
this.pointerFocus = !1;
|
|
2380
|
+
}, 0), this.hideTip();
|
|
2381
|
+
}), t.addEventListener("blur", () => this.hideTip()), t.addEventListener("focus", () => {
|
|
2382
|
+
this.pointerFocus || this.showTip(t);
|
|
2383
|
+
});
|
|
2384
|
+
});
|
|
2385
|
+
}
|
|
2386
|
+
/** The tasks as an array. Setting it writes the `tasks` attribute (dates may be `Date`s: they become `YYYY-MM-DD`). */
|
|
2387
|
+
get tasks() {
|
|
2388
|
+
return qt(this.getAttribute("tasks"));
|
|
2389
|
+
}
|
|
2390
|
+
set tasks(t) {
|
|
2391
|
+
ne(this, "tasks", t, !0);
|
|
2392
|
+
}
|
|
2393
|
+
/** The holidays as an array of `YYYY-MM-DD` strings or `{ date, label }` objects. Setting it writes the attribute (a `Date` becomes its local day). */
|
|
2394
|
+
get holidays() {
|
|
2395
|
+
return qt(this.getAttribute("holidays"));
|
|
2396
|
+
}
|
|
2397
|
+
set holidays(t) {
|
|
2398
|
+
if (!Array.isArray(t)) {
|
|
2399
|
+
this.removeAttribute("holidays");
|
|
2400
|
+
return;
|
|
2401
|
+
}
|
|
2402
|
+
try {
|
|
2403
|
+
this.setAttribute(
|
|
2404
|
+
"holidays",
|
|
2405
|
+
JSON.stringify(t.map((e) => e && typeof e == "object" && !(e instanceof Date) ? { ...e, date: xt(e.date) } : xt(e)))
|
|
2406
|
+
);
|
|
2407
|
+
} catch {
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
/** The links as an array. Setting it writes the `links` attribute. */
|
|
2411
|
+
get links() {
|
|
2412
|
+
return qt(this.getAttribute("links"));
|
|
2413
|
+
}
|
|
2414
|
+
set links(t) {
|
|
2415
|
+
ne(this, "links", t, !1);
|
|
2416
|
+
}
|
|
2417
|
+
resolveBehavior(t) {
|
|
2418
|
+
return t === "smooth" && typeof matchMedia == "function" && matchMedia("(prefers-reduced-motion: reduce)").matches ? "auto" : t ?? "auto";
|
|
2419
|
+
}
|
|
2420
|
+
scrollViewport(t, e) {
|
|
2421
|
+
const r = this.root.querySelector(".viewport");
|
|
2422
|
+
if (!r) return;
|
|
2423
|
+
const i = this.resolveBehavior(e);
|
|
2424
|
+
if (this.smoothTarget = i === "smooth" ? t : null, this.smoothTarget) {
|
|
2425
|
+
const a = this.smoothTarget;
|
|
2426
|
+
window.setTimeout(() => {
|
|
2427
|
+
this.smoothTarget === a && (this.smoothTarget = null);
|
|
2428
|
+
}, 2e3);
|
|
2429
|
+
}
|
|
2430
|
+
if (i !== "smooth" && (t.left != null && (this.viewportLeft = t.left), t.top != null && (this.viewportTop = t.top)), typeof r.scrollTo == "function") {
|
|
2431
|
+
r.scrollTo({ ...t, behavior: i });
|
|
2432
|
+
return;
|
|
2433
|
+
}
|
|
2434
|
+
t.left != null && (r.scrollLeft = t.left), t.top != null && (r.scrollTop = t.top);
|
|
2435
|
+
}
|
|
2436
|
+
/**
|
|
2437
|
+
* Scroll the timeline to a date: a `YYYY-MM-DD` string, a `Date`, a timestamp, or `"today"`. `align` is `center`
|
|
2438
|
+
* (the default) or `start`, which leaves a little room before the date. Does nothing for a date that is not valid.
|
|
2439
|
+
*/
|
|
2440
|
+
scrollToDate(t, e = {}) {
|
|
2441
|
+
const r = t === "today" ? Y(Date.now()) : typeof t == "number" ? Number.isFinite(t) ? Y(t) : null : $(t), i = this.root.querySelector(".viewport");
|
|
2442
|
+
r == null || !i || this.scrollViewport({ left: this.leftFor(i, r, e.align ?? "center") }, e.behavior);
|
|
2443
|
+
}
|
|
2444
|
+
leftFor(t, e, r) {
|
|
2445
|
+
const { min: i, dayWidth: a, gridWidth: s } = this.layoutInfo, o = (e - i) / D * a, c = r === "start" ? o - 24 : o - Math.max(0, t.clientWidth - s) / 2;
|
|
2446
|
+
return Math.max(0, Math.min(c, Math.max(0, t.scrollWidth - t.clientWidth)));
|
|
2447
|
+
}
|
|
2448
|
+
/**
|
|
2449
|
+
* Bring a task into view: open the branches above it, then scroll its row (and its bar) on screen when they are
|
|
2450
|
+
* not. Returns false for an id that is not there or is filtered out.
|
|
2451
|
+
*/
|
|
2452
|
+
scrollToTask(t, e = {}) {
|
|
2453
|
+
const r = this.getTasks(), i = new Map(r.map((d) => [d.id, d])), a = i.get(t);
|
|
2454
|
+
if (!a) return !1;
|
|
2455
|
+
let s = !1, o = a.parent;
|
|
2456
|
+
for (let d = 0; o && d < 1e3; d += 1)
|
|
2457
|
+
this.collapsed.has(o) && (this.collapsed.delete(o), this.dispatchEvent(new CustomEvent("tasktoggle", { detail: { id: o, open: !0 }, bubbles: !0, composed: !0 })), s = !0), o = i.get(o)?.parent;
|
|
2458
|
+
const c = this.getAttribute("filter") ?? this.internalFilter;
|
|
2459
|
+
return this.getVisibleTasks(r, c, bt(this.getAttribute("sort"))).some((d) => d.id === t) ? s || !this.visibleIds.includes(t) ? (this.pendingReveal = { id: t, behavior: e.behavior ?? "auto" }, this.requestRender(), !0) : this.reveal(t, e.behavior ?? "auto") : !1;
|
|
2460
|
+
}
|
|
2461
|
+
reveal(t, e) {
|
|
2462
|
+
const r = this.visibleIds.indexOf(t), i = this.root.querySelector(".viewport"), a = this.getTasks().find((g) => g.id === t);
|
|
2463
|
+
if (r < 0 || !i || !a) return !1;
|
|
2464
|
+
const s = {}, o = N + r * S;
|
|
2465
|
+
if (!(o >= i.scrollTop + N && o + S <= i.scrollTop + i.clientHeight)) {
|
|
2466
|
+
const g = r * S + S / 2 - (i.clientHeight - N) / 2;
|
|
2467
|
+
s.top = Math.max(0, Math.min(g, Math.max(0, i.scrollHeight - i.clientHeight)));
|
|
2468
|
+
}
|
|
2469
|
+
const h = $(a.start), d = $(a.end);
|
|
2470
|
+
if (h != null) {
|
|
2471
|
+
const { min: g, dayWidth: u, gridWidth: b } = this.layoutInfo, m = (h - g) / D * u, p = ((d ?? h) - g) / D * u, k = i.scrollLeft;
|
|
2472
|
+
(m < k || p > k + i.clientWidth - b) && (s.left = this.leftFor(i, h, "start"));
|
|
2473
|
+
}
|
|
2474
|
+
return (s.left != null || s.top != null) && this.scrollViewport(s, e), !0;
|
|
2475
|
+
}
|
|
2476
|
+
/** Open or close a branch (`open` omitted toggles it) and fire `tasktoggle`. */
|
|
2477
|
+
toggleTask(t, e) {
|
|
2478
|
+
const r = !this.collapsed.has(t), i = e ?? !r;
|
|
2479
|
+
i !== r && (i ? this.collapsed.delete(t) : this.collapsed.add(t), this.dispatchEvent(new CustomEvent("tasktoggle", { detail: { id: t, open: i }, bubbles: !0, composed: !0 })), this.requestRender());
|
|
2480
|
+
}
|
|
2481
|
+
expandAll() {
|
|
2482
|
+
for (const t of [...this.collapsed]) this.toggleTask(t, !0);
|
|
2483
|
+
}
|
|
2484
|
+
collapseAll() {
|
|
2485
|
+
const t = new Set(this.getTasks().map((e) => e.parent).filter((e) => !!e));
|
|
2486
|
+
for (const e of t) this.toggleTask(e, !1);
|
|
2487
|
+
}
|
|
2488
|
+
/**
|
|
2489
|
+
* The whole chart as a standalone SVG string (not just the part on screen): the time header, the task names, the bars with
|
|
2490
|
+
* their progress, baselines, parts, milestones and links, the today line and any non-working days. Rows follow the current
|
|
2491
|
+
* filter, sort and collapsed branches; the scale is the one on screen. Colours are the theme's resolved at this moment.
|
|
2492
|
+
* Returns an empty string when there is nothing to draw.
|
|
2493
|
+
*/
|
|
2494
|
+
exportSvg() {
|
|
2495
|
+
Lt = this.getAttribute("locale") || void 0;
|
|
2496
|
+
const t = this.labelSet(), e = this.getTasks(), { min: r, dayWidth: i } = this.layoutInfo;
|
|
2497
|
+
if (!e.length || r === 0) return "";
|
|
2498
|
+
const a = this.getVisibleTasks(e, this.getAttribute("filter") ?? this.internalFilter, bt(this.getAttribute("sort")));
|
|
2499
|
+
if (!a.length) return "";
|
|
2500
|
+
const s = getComputedStyle(this), o = (f) => {
|
|
2501
|
+
const E = /^var\((--[\w-]+),\s*([^)]+)\)$/.exec(f);
|
|
2502
|
+
return E ? s.getPropertyValue(E[1]).trim() || E[2].trim() : f;
|
|
2503
|
+
}, c = o("var(--ui-color-text, #0f172a)"), h = o("var(--ui-color-muted, #526175)"), d = o("var(--ui-color-border, #cbd5e1)"), g = o("var(--ui-color-surface, #ffffff)");
|
|
2504
|
+
o("var(--ui-color-primary, #2563eb)");
|
|
2505
|
+
const u = o("var(--ui-color-danger, #dc2626)"), b = this.root.querySelector(".scale")?.getAttribute("data-zoom") || "week", m = Ft(this.getAttribute("week-start")), p = te(this.getAttribute("non-working-days"), this.getAttribute("holidays")), k = A(Math.max(...e.map((f) => $(f.end) ?? 0)), 3), y = Math.max(320, (k - r) / D * i), w = r + y / i * D, x = 260, M = x + y, T = N + a.length * S, z = (f) => (f - r) / D * i, U = Z({ dateStyle: "medium" }), P = Z({ month: "long", year: "numeric" }), lt = Z({ month: "short" }), st = (f, E) => {
|
|
2506
|
+
const L = new Date(f);
|
|
2507
|
+
return E === "day" || E === "week" ? String(L.getDate()) : E === "month" ? lt.format(L) : E === "quarter" ? `Q${Math.floor(L.getMonth() / 3) + 1}` : String(L.getFullYear());
|
|
2508
|
+
}, R = [], q = (f, E, L, _, G) => `<rect x="${f.toFixed(1)}" y="${E.toFixed(1)}" width="${Math.max(0, L).toFixed(1)}" height="${_}" ${G}/>`;
|
|
2509
|
+
R.push(q(0, 0, M, T, `fill="${g}"`));
|
|
2510
|
+
const K = b === "year" ? null : b === "day" || b === "week" ? "month" : "year";
|
|
2511
|
+
if (K)
|
|
2512
|
+
for (const f of kt(K, m, r, w, i, 0, Number.POSITIVE_INFINITY)) {
|
|
2513
|
+
const E = K === "month" ? P.format(new Date(f.start)) : String(new Date(f.start).getFullYear());
|
|
2514
|
+
R.push(`<line x1="${(x + f.left).toFixed(1)}" y1="0" x2="${(x + f.left).toFixed(1)}" y2="26" stroke="${d}"/>`), f.width >= It && R.push(`<text x="${(x + f.left + 8).toFixed(1)}" y="17" font-size="11" font-weight="700" fill="${c}">${v(E)}</text>`);
|
|
2515
|
+
}
|
|
2516
|
+
const J = K ? 26 : 0;
|
|
2517
|
+
for (const f of kt(b, m, r, w, i, 0, Number.POSITIVE_INFINITY))
|
|
2518
|
+
R.push(`<line x1="${(x + f.left).toFixed(1)}" y1="${J}" x2="${(x + f.left).toFixed(1)}" y2="${T}" stroke="${d}" stroke-opacity=".45"/>`), (f.width >= It || b === "day") && R.push(`<text x="${(x + f.left + 8).toFixed(1)}" y="${J + 18}" font-size="11" font-weight="650" fill="${h}">${v(st(f.start, b))}</text>`);
|
|
2519
|
+
if (R.push(`<line x1="0" y1="${N}" x2="${M}" y2="${N}" stroke="${d}"/>`), R.push(`<text x="10" y="${N / 2 + 4}" font-size="11" font-weight="700" fill="${h}">${v(t.columnTask.toUpperCase())}</text>`), i >= fe && (p.weekdays.size || p.holidays.size))
|
|
2520
|
+
for (let f = r; f < w; f = A(f, 1))
|
|
2521
|
+
ge(f, p) && R.push(q(x + z(f), N, i, a.length * S, `fill="${p.holidays.has(f) ? "#d97706" : h}" fill-opacity="${p.holidays.has(f) ? ".15" : ".1"}"`));
|
|
2522
|
+
a.forEach((f, E) => {
|
|
2523
|
+
const L = N + E * S;
|
|
2524
|
+
R.push(`<line x1="0" y1="${L + S}" x2="${M}" y2="${L + S}" stroke="${d}" stroke-opacity=".5"/>`), R.push(`<text x="${10 + f.depth * 18}" y="${L + S / 2 + 4}" font-size="12" font-weight="650" fill="${c}">${v(f.label.length > 34 - f.depth * 2 ? `${f.label.slice(0, 33 - f.depth * 2)}…` : f.label)}</text>`);
|
|
2525
|
+
}), R.push(`<line x1="${x}" y1="0" x2="${x}" y2="${T}" stroke="${d}"/>`);
|
|
2526
|
+
const H = [], V = new Map(a.map((f, E) => [f.id, { task: f, index: E }]));
|
|
2527
|
+
for (const f of this.getLinks()) {
|
|
2528
|
+
const E = V.get(f.source), L = V.get(f.target);
|
|
2529
|
+
!E || !L || H.push(`<path d="${oe(E.task, E.index, L.task, L.index, f.type, z)}" fill="none" stroke="${h}" stroke-width="1.35" stroke-linecap="round" stroke-linejoin="round" marker-end="url(#arrow)"/>`);
|
|
2530
|
+
}
|
|
2531
|
+
a.forEach((f, E) => {
|
|
2532
|
+
const L = E * S, _ = ut(f), G = f.critical ? u : o(mt[f.tone || (_ === "summary" ? "info" : "default")] || mt.default), at = z(f.startMs), dt = Math.max(12, z(f.endMs) - at);
|
|
2533
|
+
if (f.baselineStart && f.baselineEnd) {
|
|
2534
|
+
const F = $(f.baselineStart) ?? f.startMs, C = $(f.baselineEnd) ?? f.endMs;
|
|
2535
|
+
H.push(q(z(F), L + 33, Math.max(4, z(C) - z(F)), 4, `rx="2" fill="${h}" fill-opacity=".5"`));
|
|
2536
|
+
}
|
|
2537
|
+
if (_ === "milestone") {
|
|
2538
|
+
const F = at, C = L + 21;
|
|
2539
|
+
H.push(`<polygon points="${F},${C - 10} ${F + 10},${C} ${F},${C + 10} ${F - 10},${C}" fill="${G}"/>`);
|
|
2540
|
+
return;
|
|
2541
|
+
}
|
|
2542
|
+
const rt = _ === "summary" ? L + 17 : L + 12, it = _ === "summary" ? 9 : 18, $t = Math.max(0, Math.min(100, Number(f.progress ?? 0))), pt = (F, C, tt) => {
|
|
2543
|
+
H.push(q(F, rt, C, it, `rx="4" fill="${G}"`)), _ === "summary" && H.push(q(F, rt, C, it, 'rx="3" fill="#000" fill-opacity=".2"')), tt > 0 && H.push(q(F, rt, C * tt / 100, it, 'rx="4" fill="#000" fill-opacity=".28"'));
|
|
1076
2544
|
};
|
|
1077
|
-
|
|
1078
|
-
const
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
e.preventDefault(), this.dispatchEvent(new CustomEvent("taskdelete", { detail: { id: n }, bubbles: !0, composed: !0 }));
|
|
1082
|
-
return;
|
|
1083
|
-
}
|
|
1084
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
1085
|
-
e.preventDefault(), this.selectTask(n);
|
|
1086
|
-
return;
|
|
1087
|
-
}
|
|
1088
|
-
if (e.key === "ArrowUp" || e.key === "ArrowDown") {
|
|
1089
|
-
e.preventDefault(), this.selectAdjacentTask(n, e.key === "ArrowDown" ? 1 : -1);
|
|
1090
|
-
return;
|
|
2545
|
+
if (_ === "task" && f.segments?.length)
|
|
2546
|
+
for (const F of f.segments) {
|
|
2547
|
+
const C = $(F.start), tt = $(F.end);
|
|
2548
|
+
C == null || tt == null || tt <= C || pt(z(Math.max(f.startMs, C)), z(Math.min(f.endMs, tt)) - z(Math.max(f.startMs, C)), F.progress ?? $t);
|
|
1091
2549
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
if (!l || o == null || p == null) return;
|
|
1095
|
-
e.preventDefault();
|
|
1096
|
-
const u = e.key === "ArrowLeft" ? -1 : 1, d = t.dataset.kind === "milestone", g = e.shiftKey && !d ? o : x(o, u), c = d ? g : e.shiftKey ? Math.max(x(p, u), x(o, 1)) : x(p, u), m = { id: n, start: E(g), end: E(c) };
|
|
1097
|
-
this.overrides.set(n, { start: m.start, end: m.end }), this.bumpOverrides(), this.dispatchEvent(new CustomEvent("taskchange", { detail: m, bubbles: !0, composed: !0 })), this.requestRender();
|
|
1098
|
-
});
|
|
2550
|
+
else
|
|
2551
|
+
pt(at, dt, $t);
|
|
1099
2552
|
});
|
|
2553
|
+
const et = Y(Date.now());
|
|
2554
|
+
return this.getAttribute("show-today") !== "false" && et >= r && et <= w && (H.push(`<line x1="${z(et).toFixed(1)}" y1="0" x2="${z(et).toFixed(1)}" y2="${a.length * S}" stroke="${u}" stroke-width="2"/>`), R.push(`<text x="${(x + z(et)).toFixed(1)}" y="${N - 6}" font-size="10" font-weight="700" text-anchor="middle" fill="${u}">${v(t.today)}</text>`)), R.push(`<g transform="translate(${x} ${N})">${H.join("")}</g>`), R.push(`<title>${v(this.getAttribute("aria-label") || t.title)} (${v(U.format(new Date(r)))} – ${v(U.format(new Date(k)))})</title>`), `<svg xmlns="http://www.w3.org/2000/svg" width="${Math.round(M)}" height="${T}" viewBox="0 0 ${Math.round(M)} ${T}" font-family="system-ui, -apple-system, 'Segoe UI', sans-serif"><defs><marker id="arrow" markerWidth="9" markerHeight="9" refX="8.5" refY="4.5" orient="auto" markerUnits="userSpaceOnUse"><path d="M 0.5 0.75 L 8.5 4.5 L 0.5 8.25 z" fill="${h}"/></marker></defs>` + R.join("") + "</svg>";
|
|
1100
2555
|
}
|
|
1101
2556
|
rangeMin() {
|
|
1102
|
-
const
|
|
1103
|
-
return
|
|
2557
|
+
const e = this.getTasks().map((r) => $(r.start)).filter((r) => r != null);
|
|
2558
|
+
return e.length ? A(Math.min(...e), -2) : Y(Date.now());
|
|
1104
2559
|
}
|
|
1105
2560
|
selectTask(t) {
|
|
1106
|
-
if (this.captureViewport(), this.selectedId === t) {
|
|
2561
|
+
if (this.captureViewport(), this.selectedLinkId = null, this.selectedId === t) {
|
|
1107
2562
|
this.dispatchEvent(new CustomEvent("taskselect", { detail: { id: t }, bubbles: !0, composed: !0 }));
|
|
1108
2563
|
return;
|
|
1109
2564
|
}
|
|
1110
2565
|
this.selectedId = t, this.dispatchEvent(new CustomEvent("taskselect", { detail: { id: t }, bubbles: !0, composed: !0 })), this.requestRender();
|
|
1111
2566
|
}
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
2567
|
+
/** Select a task and give focus to its row or its bar, scrolling it into view first. */
|
|
2568
|
+
focusTask(t, e) {
|
|
2569
|
+
this.selectedId !== t && (this.selectedId = t, this.dispatchEvent(new CustomEvent("taskselect", { detail: { id: t }, bubbles: !0, composed: !0 }))), this.focusRequest = { selector: e === "row" ? Nt(t) : At(t), reveal: !0, retries: 0 }, this.reveal(t, "auto"), this.requestRender();
|
|
2570
|
+
}
|
|
2571
|
+
/**
|
|
2572
|
+
* Keyboard movement between rows, the same from a row and from a bar: Up and Down, Home and End, PageUp and PageDown. From a row,
|
|
2573
|
+
* Right and Left also open, close and climb the tree. Returns whether the key was one of these.
|
|
2574
|
+
*/
|
|
2575
|
+
navigate(t, e, r) {
|
|
2576
|
+
const i = this.visibleIds, a = i.indexOf(t);
|
|
2577
|
+
if (a < 0) return !1;
|
|
2578
|
+
let s;
|
|
2579
|
+
switch (e) {
|
|
2580
|
+
case "ArrowUp":
|
|
2581
|
+
s = i[a - 1];
|
|
2582
|
+
break;
|
|
2583
|
+
case "ArrowDown":
|
|
2584
|
+
s = i[a + 1];
|
|
2585
|
+
break;
|
|
2586
|
+
case "Home":
|
|
2587
|
+
s = i[0];
|
|
2588
|
+
break;
|
|
2589
|
+
case "End":
|
|
2590
|
+
s = i[i.length - 1];
|
|
2591
|
+
break;
|
|
2592
|
+
case "PageUp":
|
|
2593
|
+
s = i[Math.max(0, a - 10)];
|
|
2594
|
+
break;
|
|
2595
|
+
case "PageDown":
|
|
2596
|
+
s = i[Math.min(i.length - 1, a + 10)];
|
|
2597
|
+
break;
|
|
2598
|
+
case "ArrowRight":
|
|
2599
|
+
case "ArrowLeft": {
|
|
2600
|
+
if (r !== "row") return !1;
|
|
2601
|
+
const o = this.getTasks(), h = new Set(o.map((d) => d.parent).filter((d) => !!d)).has(t);
|
|
2602
|
+
if (e === "ArrowRight") {
|
|
2603
|
+
if (!h) return !0;
|
|
2604
|
+
this.collapsed.has(t) ? this.toggleTask(t, !0) : s = i[a + 1];
|
|
2605
|
+
} else if (h && !this.collapsed.has(t))
|
|
2606
|
+
this.toggleTask(t, !1);
|
|
2607
|
+
else {
|
|
2608
|
+
const d = o.find((g) => g.id === t)?.parent;
|
|
2609
|
+
d && i.includes(d) && (s = d);
|
|
2610
|
+
}
|
|
2611
|
+
break;
|
|
2612
|
+
}
|
|
2613
|
+
default:
|
|
2614
|
+
return !1;
|
|
2615
|
+
}
|
|
2616
|
+
return s && s !== t && this.focusTask(s, r), !0;
|
|
1121
2617
|
}
|
|
1122
2618
|
captureViewport() {
|
|
1123
2619
|
const t = this.root.querySelector(".viewport");
|
|
1124
2620
|
t && (this.viewportTop = t.scrollTop, this.viewportLeft = t.scrollLeft, this.viewportWidth = t.clientWidth || this.viewportWidth, this.viewportHeight = t.clientHeight || this.viewportHeight);
|
|
1125
2621
|
}
|
|
1126
2622
|
getTasks() {
|
|
1127
|
-
const t = this.getAttribute("tasks");
|
|
1128
|
-
return t === this.taskCacheRaw && this.taskCacheVersion === this.overridesVersion ? this.taskCache : (this.taskCacheRaw = t, this.taskCacheVersion = this.overridesVersion, this.taskCache =
|
|
2623
|
+
const t = this.getAttribute("tasks"), e = this.hasAttribute("auto-summary"), r = this.hasAttribute("critical-path") ? this.getAttribute("links") ?? "" : null;
|
|
2624
|
+
return t === this.taskCacheRaw && this.taskCacheVersion === this.overridesVersion && e === this.taskCacheAuto && r === this.taskCacheCritical ? this.taskCache : (this.taskCacheRaw = t, this.taskCacheVersion = this.overridesVersion, this.taskCacheAuto = e, this.taskCacheCritical = r, this.taskCache = _e(t, this.overrides, e), e && (this.taskCache = or(this.taskCache)), r !== null && (this.taskCache = lr(this.taskCache, Zt(r))), this.seedCollapsed(this.taskCache), this.visibleCacheKey = "", this.taskCache);
|
|
2625
|
+
}
|
|
2626
|
+
seedCollapsed(t) {
|
|
2627
|
+
for (const e of t)
|
|
2628
|
+
this.seededIds.has(e.id) || (this.seededIds.add(e.id), e.open === !1 && this.collapsed.add(e.id));
|
|
1129
2629
|
}
|
|
1130
2630
|
getLinks() {
|
|
1131
2631
|
const t = this.getAttribute("links");
|
|
1132
|
-
return t === this.linkCacheRaw ? this.linkCache : (this.linkCacheRaw = t, this.linkCache =
|
|
2632
|
+
return t === this.linkCacheRaw ? this.linkCache : (this.linkCacheRaw = t, this.linkCache = Zt(t), this.linkCache);
|
|
1133
2633
|
}
|
|
1134
|
-
getVisibleTasks(t,
|
|
1135
|
-
const
|
|
1136
|
-
return
|
|
2634
|
+
getVisibleTasks(t, e, r) {
|
|
2635
|
+
const i = Array.from(this.collapsed).sort().join(","), a = `${this.taskCacheRaw || ""}|${this.overridesVersion}|${e}|${r}|${i}`;
|
|
2636
|
+
return a === this.visibleCacheKey ? this.visibleCache : (this.visibleCacheKey = a, this.visibleCache = Ve(t, this.collapsed, e, r), this.visibleCache);
|
|
1137
2637
|
}
|
|
1138
2638
|
bumpOverrides() {
|
|
1139
2639
|
this.overridesVersion += 1, this.taskCacheVersion = -1, this.visibleCacheKey = "";
|
|
1140
2640
|
}
|
|
1141
|
-
shouldRenderOnAttributeChange(t,
|
|
1142
|
-
|
|
2641
|
+
shouldRenderOnAttributeChange(t, e, r) {
|
|
2642
|
+
if (t === "tasks") {
|
|
2643
|
+
this.overrides.clear(), this.bumpOverrides();
|
|
2644
|
+
const i = ee(r);
|
|
2645
|
+
Je(ee(e), i) || (this.viewportTop = 0, this.viewportLeft = 0, this.initialScrollPending = !0, this.collapsed.clear(), this.seededIds.clear()), this.selectedId != null && !i.has(this.selectedId) && (this.selectedId = null);
|
|
2646
|
+
}
|
|
2647
|
+
return t === "links" && (this.linkCacheRaw = null), (t === "auto-summary" || t === "cascade") && this.bumpOverrides(), !0;
|
|
1143
2648
|
}
|
|
1144
2649
|
}
|
|
1145
|
-
typeof customElements < "u" && !customElements.get("ui-gantt") && customElements.define("ui-gantt",
|
|
2650
|
+
typeof customElements < "u" && !customElements.get("ui-gantt") && customElements.define("ui-gantt", pr);
|
|
1146
2651
|
export {
|
|
1147
|
-
|
|
2652
|
+
pr as UIGantt
|
|
1148
2653
|
};
|