@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/dist/gantt.esm.js CHANGED
@@ -1,11 +1,138 @@
1
- import { E as It } from "./ElementBase-BeZT54Hj.mjs";
2
- const I = 1440 * 60 * 1e3, y = 42, j = 62, J = 376, N = 28, At = 15, vt = At * Math.SQRT2 / 2, gt = {
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
- }, ht = `
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: ${J}px minmax(640px, 1fr);
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
- background: color-mix(in srgb, var(--ui-gantt-panel) 96%, transparent);
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: minmax(170px, 1fr) 82px 80px 44px;
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: 9px 8px;
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: ${y}px;
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: minmax(170px, 1fr) 82px 80px 44px;
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
- .bar.split .progress {
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
- .drag-tip {
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
- z-index: 6;
465
- inset-block-start: -28px;
466
- inset-inline-start: 50%;
467
- transform: translateX(-50%);
468
- padding: 4px 7px;
469
- border-radius: 6px;
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
- font-weight: 700;
474
- white-space: nowrap;
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.dragging .drag-tip {
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)) 28%, transparent);
523
- opacity: 1;
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
- .today::before {
561
- content: "Today";
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-start: 4px;
564
- inset-inline-start: 6px;
565
- color: var(--ui-color-danger, #dc2626);
566
- font-size: 11px;
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 (max-width: 760px) {
631
- .surface {
632
- grid-template-columns: 270px minmax(560px, 1fr);
1139
+ @media (prefers-reduced-motion: reduce) {
1140
+ .bar,
1141
+ .progress {
1142
+ transition: none !important;
633
1143
  }
1144
+ }
634
1145
 
635
- .grid-head,
636
- .grid-row {
637
- grid-template-columns: minmax(150px, 1fr) 64px 56px;
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
- .grid-head span:nth-child(3),
641
- .grid-cell:nth-child(3) {
642
- display: none;
1155
+ .controls,
1156
+ .splitter-rail,
1157
+ .connector,
1158
+ .bar-tip,
1159
+ .handle {
1160
+ display: none !important;
643
1161
  }
644
- }
645
1162
 
646
- @media (prefers-reduced-motion: reduce) {
647
- .bar,
648
- .progress {
649
- transition: none !important;
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 b(a) {
685
- return String(a ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
1223
+ function v(n) {
1224
+ return String(n ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
686
1225
  }
687
- function f(a) {
688
- if (a instanceof Date) {
689
- const n = a.getTime();
690
- return Number.isFinite(n) ? F(n) : null;
691
- }
692
- const t = String(a || "").trim(), i = /^(\d{4})-(\d{2})-(\d{2})$/.exec(t);
693
- if (i) {
694
- const n = Number(i[1]), s = Number(i[2]), l = Number(i[3]), p = new Date(n, s - 1, l).getTime();
695
- return Number.isFinite(p) ? F(p) : null;
696
- }
697
- const e = Date.parse(t);
698
- return Number.isFinite(e) ? F(e) : null;
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 F(a) {
701
- const t = new Date(a);
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 x(a, t) {
705
- return F(a + t * I);
1243
+ function A(n, t) {
1244
+ return Y(n + t * D);
706
1245
  }
707
- function E(a) {
708
- const t = new Date(a), i = t.getFullYear(), e = String(t.getMonth() + 1).padStart(2, "0"), n = String(t.getDate()).padStart(2, "0");
709
- return `${i}-${e}-${n}`;
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 O(a) {
712
- return new Intl.DateTimeFormat(void 0, { month: "short", day: "numeric" }).format(new Date(a));
1250
+ function ht(n) {
1251
+ return Z({ month: "short", day: "numeric" }).format(new Date(n));
713
1252
  }
714
- function bt(a) {
715
- return a === "day" || a === "month" || a === "quarter" || a === "year" ? a : "week";
1253
+ function Re(n) {
1254
+ return n === "day" || n === "month" || n === "quarter" || n === "year" || n === "fit" ? n : "week";
716
1255
  }
717
- function _(a) {
718
- return a === "start" || a === "end" || a === "label" || a === "progress" ? a : "none";
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 Lt(a) {
721
- return a === "soft" || a === "striped" || a === "outline" || a === "glass" ? a : "solid";
1260
+ function bt(n) {
1261
+ return n === "start" || n === "end" || n === "label" || n === "progress" ? n : "none";
722
1262
  }
723
- function Dt(a, t) {
724
- if (!a) return [];
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 i = JSON.parse(a);
727
- return Array.isArray(i) ? i.map((e, n) => {
728
- if (!e || typeof e != "object") return null;
729
- const s = e, l = String(s.label || s.text || "").trim(), o = f(s.start), p = Number(s.duration);
730
- let u = f(s.end);
731
- if (u == null && o != null && Number.isFinite(p) && (u = x(o, Math.max(0, p))), !l || o == null) return null;
732
- const d = s.type === "summary" || s.type === "milestone" ? s.type : "task", g = d === "milestone" ? o : Math.max(u ?? x(o, 1), x(o, 1)), c = String(s.id || `task-${n + 1}`), m = t.get(c) || {};
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: c,
735
- label: l,
736
- start: m.start || E(o),
737
- end: m.end || E(g),
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: d,
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 Ft(a) {
756
- if (!a) return [];
1304
+ function Zt(n) {
1305
+ if (!n) return [];
757
1306
  try {
758
- const t = JSON.parse(a);
759
- return Array.isArray(t) ? t.map((i, e) => {
760
- if (!i || typeof i != "object") return null;
761
- const n = i, s = String(n.source || "").trim(), l = String(n.target || "").trim();
762
- if (!s || !l) return null;
763
- const o = n.type === "s2s" || n.type === "e2e" || n.type === "s2e" ? n.type : "e2s";
764
- return { id: String(n.id || `link-${e + 1}`), source: s, target: l, type: o };
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 Rt(a, t) {
771
- return t === "none" ? a : [...a].sort((i, e) => {
772
- if (t === "label") return i.label.localeCompare(e.label);
773
- if (t === "progress") return Number(e.progress ?? 0) - Number(i.progress ?? 0);
774
- const n = f(t === "start" ? i.start : i.end) ?? 0, s = f(t === "start" ? e.start : e.end) ?? 0;
775
- return n - s;
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 qt(a, t, i, e) {
779
- const n = /* @__PURE__ */ new Map(), s = new Map(a.map((d) => [d.id, d]));
780
- a.forEach((d) => {
781
- const g = d.parent && s.has(d.parent) ? d.parent : "__root__", c = n.get(g) || [];
782
- c.push(d), n.set(g, c);
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 l = i.trim().toLowerCase(), o = (d) => !l || d.label.toLowerCase().includes(l) || (d.assignee || "").toLowerCase().includes(l) ? !0 : (n.get(d.id) || []).some(o), p = [], u = (d, g) => {
785
- Rt(n.get(d) || [], e).forEach((c) => {
786
- if (!o(c)) return;
787
- const m = f(c.start), h = f(c.end);
788
- if (m == null || h == null) return;
789
- const w = (n.get(c.id) || []).length > 0;
790
- p.push({ ...c, depth: g, hasChildren: w, startMs: m, endMs: h }), w && !t.has(c.id) && u(c.id, g + 1);
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 u("__root__", 0), p;
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 Nt(a, t, i, e, n = 0, s = Number.POSITIVE_INFINITY) {
796
- const l = Math.max(1, Math.ceil((t - a) / I)), o = e === "day" ? 1 : e === "week" ? 7 : e === "month" ? 30 : e === "quarter" ? 91 : 365, p = Math.max(0, Math.floor(Math.max(0, n - 480) / i / o) * o), u = Math.min(l, Math.ceil((n + s + 960) / i / o) * o), d = [];
797
- for (let g = p; g <= u; g += o) {
798
- const c = g * i, m = x(a, g), h = e === "day" ? O(m) : new Intl.DateTimeFormat(void 0, { month: "short", year: e === "week" || e === "month" ? void 0 : "numeric" }).format(new Date(m));
799
- d.push(`<span class="tick" style="left:${c.toFixed(2)}px">${b(h)}</span>`);
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
- return d.join("");
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 H(a) {
804
- return a.type === "summary" || a.hasChildren ? "summary" : a.type === "milestone" || a.startMs === a.endMs ? "milestone" : "task";
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 ft(a) {
807
- const t = H(a);
808
- return t === "summary" ? 17 + 9 / 2 : t === "milestone" ? 7 + N / 2 : 12 + 18 / 2;
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 Ht(a, t) {
811
- return H(a) === "milestone" ? t(a.startMs) - vt : t(a.startMs);
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 Vt(a, t) {
814
- return H(a) === "milestone" ? t(a.startMs) + vt : t(a.endMs);
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 xt(a, t, i) {
817
- return t === "start" ? Ht(a, i) : Vt(a, i);
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 Wt(a = "e2s") {
820
- return a === "s2s" ? { source: "start", target: "start" } : a === "e2e" ? { source: "end", target: "end" } : a === "s2e" ? { source: "start", target: "end" } : { source: "end", target: "start" };
1621
+ function ie(n) {
1622
+ return n === "auto" || n === "off" ? n : "warn";
821
1623
  }
822
- function jt(a, t, i, e, n, s) {
823
- const l = Wt(n), o = l.source === "start" ? -1 : 1, p = l.target === "start" ? 1 : -1, u = xt(a, l.source, s), d = xt(i, l.target, s), g = t * y + ft(a), c = e * y + ft(i), m = 22, h = u + o * m, w = d - p * m, M = 26;
824
- let k = (h + w) / 2;
825
- if (o === p) {
826
- const T = o === 1 ? Math.max(h, w, u, d) + M : Math.min(h, w, u, d) - M;
827
- k = (o === 1 ? w > h + M : w < h - M) ? k : T;
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 ? w < h : w > h) && (k = o === 1 ? Math.max(h, u, d) + M : Math.min(h, u, d) - M);
830
- const A = Math.abs(c - g) < 1 ? l.target === "start" ? c - 16 : c + 16 : c > g ? c - y / 2 + 3 : c + y / 2 - 3;
831
- return [[u, g], [h, g], [k, g], [k, A], [w, A], [w, c], [d, c]].map(([T, R], P) => `${P === 0 ? "M" : "L"} ${T.toFixed(1)} ${R.toFixed(1)}`).join(" ");
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 _t extends It {
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
- const t = this.getTasks(), i = this.getLinks(), e = bt(this.getAttribute("zoom")), n = _(this.getAttribute("sort")), s = Lt(this.getAttribute("bar-variant")), l = this.getAttribute("filter") ?? this.internalFilter, o = this.getVisibleTasks(t, l, n), p = this.hasAttribute("readonly"), u = this.getAttribute("show-toolbar") !== "false", d = this.getAttribute("show-today") !== "false";
842
- if (!t.length) {
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>${ht}</style>
1745
+ <style>${Gt}</style>
845
1746
  <section class="frame" part="frame">
846
- <div class="empty" part="empty">No tasks in this range</div>
1747
+ <div class="empty" part="empty">${v(t.empty)}</div>
847
1748
  </section>
848
1749
  `);
849
1750
  return;
850
1751
  }
851
- const g = t.map((r) => f(r.start)).filter((r) => r != null), c = t.map((r) => f(r.end)).filter((r) => r != null), m = x(Math.min(...g), -2), h = x(Math.max(...c), 3), w = Math.max(7, Math.ceil((h - m) / I)), M = gt[e], k = Math.max(680, w * M), A = Math.max(o.length * y, y), S = this.getAttribute("virtualized") !== "false" && o.length > 240, T = Math.max(20, Math.ceil(Math.max(420, this.viewportHeight - j) / y)), R = 160, P = Math.max(0, this.viewportTop - j), wt = Math.max(0, Math.floor(P / y)), X = Math.max(180, T * 4), L = S ? Math.max(0, Math.floor(wt / X) * X - R) : 0, B = S ? Math.min(o.length, L + T + R * 2 + X) : o.length, K = o.slice(L, B), V = L * y, W = Math.max(0, (o.length - B) * y);
852
- this.renderedFirstRow = L, this.renderedLastRow = B;
853
- const Q = new Map(o.map((r, $) => [r.id, { task: r, index: $ }])), tt = new Set(K.map((r) => r.id)), z = (r) => (r - m) / I * M, yt = K.map((r, $) => {
854
- const C = L + $, v = this.selectedId === r.id ? " selected" : "", D = H(r), G = r.progress == null ? "-" : `${Math.round(r.progress)}%`, U = D === "milestone" ? O(r.startMs) : `${O(r.startMs)} - ${O(r.endMs)}`;
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${v}" part="grid-row" data-task-id="${b(r.id)}" role="row" aria-rowindex="${C + 2}" aria-level="${r.depth + 1}" ${r.hasChildren ? `aria-expanded="${!this.collapsed.has(r.id)}"` : ""}>
857
- <div class="grid-cell task-name" role="gridcell" style="padding-left:${10 + r.depth * 18}px">
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(""), kt = K.map((r, $) => {
867
- const C = L + $, v = H(r), D = this.selectedId === r.id ? " selected" : "", G = this.selectedId === r.id || !this.selectedId && C === 0, U = mt[r.tone || (v === "summary" ? "info" : "default")] || mt.default, rt = v === "milestone" ? z(r.startMs) - N / 2 : z(r.startMs), St = z(v === "milestone" ? r.startMs : r.endMs), zt = v === "milestone" ? N : Math.max(12, St - rt), Ct = Math.max(0, Math.min(100, Number(r.progress ?? 0))), at = !p && !r.readonly && v !== "summary", nt = at && v === "task", it = v === "task" && r.segments?.length ? r.segments.map((st) => {
868
- const ot = f(st.start), lt = f(st.end);
869
- if (ot == null || lt == null) return "";
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${D}" part="timeline-row" data-task-id="${b(r.id)}">
877
- ${r.baselineStart && r.baselineEnd ? `<span class="baseline" style="left:${z(f(r.baselineStart) || r.startMs).toFixed(2)}px;width:${Math.max(4, z(f(r.baselineEnd) || r.endMs) - z(f(r.baselineStart) || r.startMs)).toFixed(2)}px"></span>` : ""}
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 ${v}${r.critical ? " critical" : ""}${it ? " split" : ""}"
880
- part="bar ${v}"
881
- data-task-id="${b(r.id)}"
882
- data-kind="${v}"
883
- tabindex="${G ? "0" : "-1"}"
884
- ${this.selectedId === r.id ? 'aria-current="true"' : ""}
885
- style="left:${rt.toFixed(2)}px;width:${zt.toFixed(2)}px;--tone:${U};--progress:${Ct.toFixed(2)}%"
886
- aria-label="${b(r.label)}"
887
- ${at ? 'data-draggable="true"' : ""}
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
- ${nt ? '<span class="handle start" data-mode="resize-start"></span>' : ""}
1812
+ ${Ut ? '<span class="handle start" data-mode="resize-start"></span>' : ""}
890
1813
  <span class="progress"></span>
891
- ${it}
892
- ${v === "milestone" ? '<span class="milestone-shape"></span>' : ""}
893
- <span class="drag-tip"></span>
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(""), et = i.map((r) => {
899
- const $ = Q.get(r.source), C = Q.get(r.target);
900
- if (!$ || !C || S && !tt.has(r.source) && !tt.has(r.target)) return "";
901
- const v = jt($.task, $.index, C.task, C.index, r.type, z), D = `${$.task.label} dependency to ${C.task.label}`;
902
- return `<path class="link-hit" d="${v}" data-link-id="${b(r.id)}" data-source-id="${b(r.source)}" data-target-id="${b(r.target)}" data-link-type="${b(r.type || "e2s")}" aria-label="${b(D)}" /><path d="${v}" />`;
903
- }).join(""), $t = et ? '<defs><marker id="ui-gantt-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" /></marker></defs>' : "", Y = F(Date.now()), Mt = z(Y), Et = d && Y >= m && Y <= h ? `<div class="today" part="today" style="left:${Mt.toFixed(2)}px"></div>` : "";
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>${ht}</style>
906
- <section class="frame" part="frame" data-bar-variant="${s}">
907
- ${u ? `
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">Project schedule <span class="meta">${o.length}/${t.length} tasks</span></div>
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="${b(l)}" placeholder="Filter tasks" aria-label="Filter tasks" />
912
- <select class="select" aria-label="Sort tasks">
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", "Manual"],
915
- ["start", "Start date"],
916
- ["end", "End date"],
917
- ["label", "Name"],
918
- ["progress", "Progress"]
919
- ].map(([r, $]) => `<option value="${r}" ${n === r ? "selected" : ""}>${$}</option>`).join("")}
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
- ${["day", "week", "month", "quarter", "year"].map((r) => `<button class="zoom-button" data-zoom="${r}" aria-pressed="${e === r}">${r}</button>`).join("")}
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:${M}px;grid-template-columns:${J}px ${k.toFixed(2)}px;inline-size:max(100%, ${(J + k).toFixed(2)}px)">
927
- <div class="grid-semantics" role="treegrid" aria-label="${b(this.getAttribute("aria-label") || "Project schedule")}" aria-rowcount="${o.length + 1}" aria-colcount="4">
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
- <div role="columnheader" aria-sort="${n === "label" ? "ascending" : "none"}" style="display:contents"><button class="head-button" data-sort="label">Task</button></div>
930
- <span role="columnheader">Owner</span>
931
- <div role="columnheader" aria-sort="${n === "start" ? "ascending" : "none"}" style="display:contents"><button class="head-button" data-sort="start">Dates</button></div>
932
- <div role="columnheader" aria-sort="${n === "progress" ? "descending" : "none"}" style="display:contents"><button class="head-button" data-sort="progress">%</button></div>
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
- ${S && V ? `<div style="height:${V}px"></div>` : ""}
936
- ${yt || '<div class="empty">No matching tasks</div>'}
937
- ${S && W ? `<div style="height:${W}px"></div>` : ""}
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:${k.toFixed(2)}px">${Nt(m, h, M, e, this.viewportLeft, this.viewportWidth)}</div>
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="Timeline" style="width:${k.toFixed(2)}px;min-height:${A}px">
944
- <svg class="links" width="${k.toFixed(0)}" height="${A}" viewBox="0 0 ${k.toFixed(0)} ${A}" aria-hidden="true">${$t}${et}</svg>
945
- ${Et}
946
- ${S && V ? `<div style="height:${V}px"></div>` : ""}
947
- ${kt}
948
- ${S && W ? `<div style="height:${W}px"></div>` : ""}
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 q = this.root.querySelector(".viewport");
955
- q && (q.scrollTop = this.viewportTop, q.scrollLeft = this.viewportLeft, this.viewportWidth = q.clientWidth || this.viewportWidth, this.viewportHeight = q.clientHeight || this.viewportHeight), this.bindEvents();
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", (i) => {
960
- const e = i.target, n = i.currentTarget;
961
- if (!n.classList.contains("bar") && e.closest(".bar")) return;
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
- i.stopPropagation();
1936
+ e.stopPropagation();
964
1937
  return;
965
1938
  }
966
- const l = i.currentTarget.dataset.taskId;
967
- l && (e.closest('[data-action="toggle"]') || (n.classList.contains("bar") && i.stopPropagation(), this.selectTask(l)));
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", (i) => {
971
- i.stopPropagation();
972
- const e = t.dataset.taskId;
973
- e && (this.collapsed.has(e) ? this.collapsed.delete(e) : this.collapsed.add(e), this.requestRender());
1943
+ t.addEventListener("click", (e) => {
1944
+ e.stopPropagation();
1945
+ const r = t.dataset.taskId;
1946
+ r && this.toggleTask(r);
974
1947
  });
975
- }), this.root.querySelectorAll("[data-zoom]").forEach((t) => {
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 i = t.dataset.zoom || "week";
978
- this.setAttribute("zoom", i), this.dispatchEvent(new CustomEvent("zoomchange", { detail: { zoom: i }, bubbles: !0, composed: !0 }));
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 i = _(t.dataset.sort || null);
983
- i === "none" ? this.removeAttribute("sort") : this.setAttribute("sort", i), this.dispatchEvent(new CustomEvent("sortchange", { detail: { sort: i }, bubbles: !0, composed: !0 }));
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
- }), this.root.querySelector(".viewport")?.addEventListener("scroll", (t) => {
986
- const i = t.currentTarget, e = i.scrollTop, n = i.scrollLeft, s = Math.max(20, Math.ceil(Math.max(420, this.viewportHeight - j) / y)), l = Math.max(0, Math.floor(Math.max(0, e - j) / y)), o = l < this.renderedFirstRow + 64 || l + s > this.renderedLastRow - 64, p = Math.abs(n - this.viewportLeft) >= Math.max(480, this.viewportWidth * 0.6);
987
- this.viewportTop = e, this.viewportLeft = n, !(!o && !p) && this.scrollFrame == null && (this.scrollFrame = window.setTimeout(() => {
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 i = _(t.currentTarget.value);
994
- i === "none" ? this.removeAttribute("sort") : this.setAttribute("sort", i), this.dispatchEvent(new CustomEvent("sortchange", { detail: { sort: i }, bubbles: !0, composed: !0 }));
995
- }), this.root.querySelectorAll(".link-hit").forEach((t) => {
996
- t.addEventListener("click", (i) => {
997
- i.stopPropagation(), this.dispatchEvent(new CustomEvent("linkselect", {
998
- detail: {
999
- id: t.dataset.linkId || "",
1000
- source: t.dataset.sourceId || "",
1001
- target: t.dataset.targetId || "",
1002
- type: t.dataset.linkType || "e2s"
1003
- },
1004
- bubbles: !0,
1005
- composed: !0
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
- }), !this.hasAttribute("readonly") && this.root.querySelectorAll('.bar[data-draggable="true"]').forEach((t) => {
1009
- t.addEventListener("pointerdown", (e) => {
1010
- if (e.button !== 0) return;
1011
- const n = t.dataset.taskId;
1012
- if (!n) return;
1013
- const l = this.getTasks().find((h) => h.id === n), o = f(l?.start), p = f(l?.end);
1014
- if (!l || o == null || p == null) return;
1015
- e.preventDefault();
1016
- const u = t.dataset.kind || "task", d = e.target.dataset.mode || "move", g = u === "milestone" ? "move" : d, c = bt(this.getAttribute("zoom")), m = this.selectedId !== n;
1017
- this.selectedId = n, this.dispatchEvent(new CustomEvent("taskselect", { detail: { id: n }, bubbles: !0, composed: !0 })), this.drag = {
1018
- taskId: n,
1019
- mode: g,
1020
- startX: e.clientX,
1021
- originalStart: o,
1022
- originalEnd: p,
1023
- kind: u,
1024
- rangeMin: this.rangeMin(),
1025
- dayWidth: gt[c],
1026
- latestStart: o,
1027
- latestEnd: p,
1028
- frame: null,
1029
- bar: t,
1030
- moved: !1,
1031
- selectedChanged: m
1032
- }, t.classList.add("dragging"), t.setPointerCapture(e.pointerId);
1033
- }), t.addEventListener("pointermove", (e) => {
1034
- if (!this.drag || this.drag.taskId !== t.dataset.taskId) return;
1035
- e.preventDefault();
1036
- const n = e.clientX - this.drag.startX;
1037
- Math.abs(n) > 2 && (this.drag.moved = !0);
1038
- let s = this.drag.originalStart, l = this.drag.originalEnd;
1039
- if (this.drag.mode === "move") {
1040
- const o = Math.round(n / this.drag.dayWidth);
1041
- s = x(this.drag.originalStart, o), l = this.drag.kind === "milestone" ? s : x(this.drag.originalEnd, o);
1042
- } else if (this.drag.mode === "resize-start") {
1043
- const o = Math.round(n / this.drag.dayWidth);
1044
- s = Math.min(x(this.drag.originalStart, o), x(l, -1));
1045
- } else {
1046
- const o = Math.round(n / this.drag.dayWidth);
1047
- l = Math.max(x(this.drag.originalEnd, o), x(s, 1));
1048
- }
1049
- this.drag.latestStart = s, this.drag.latestEnd = l, this.drag.frame == null && (this.drag.frame = requestAnimationFrame(() => {
1050
- if (!this.drag) return;
1051
- this.drag.frame = null;
1052
- const o = (this.drag.latestStart - this.drag.rangeMin) / I * this.drag.dayWidth - (this.drag.kind === "milestone" ? N / 2 : 0), p = this.drag.kind === "milestone" ? N : Math.max(12, (this.drag.latestEnd - this.drag.latestStart) / I * this.drag.dayWidth);
1053
- this.drag.bar.style.left = `${o.toFixed(2)}px`, this.drag.bar.style.width = `${p.toFixed(2)}px`;
1054
- const u = this.drag.bar.querySelector(".drag-tip");
1055
- u && (u.textContent = this.drag.kind === "milestone" ? E(this.drag.latestStart) : `${E(this.drag.latestStart)} - ${E(this.drag.latestEnd)}`);
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
- const i = () => {
1059
- if (!this.drag) return;
1060
- this.drag.frame != null && cancelAnimationFrame(this.drag.frame);
1061
- const e = this.drag;
1062
- if (e.bar.classList.remove("dragging"), this.drag = null, !e.moved) {
1063
- e.selectedChanged && this.requestRender();
1064
- return;
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
- const n = {
1067
- id: e.taskId,
1068
- start: E(e.latestStart),
1069
- end: E(e.latestEnd)
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
- this.overrides.set(e.taskId, { start: n.start, end: n.end }), this.bumpOverrides(), this.dispatchEvent(new CustomEvent("taskchange", {
1072
- detail: n,
1073
- bubbles: !0,
1074
- composed: !0
1075
- })), this.requestRender();
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
- t.addEventListener("pointerup", i), t.addEventListener("pointercancel", i), t.addEventListener("keydown", (e) => {
1078
- const n = t.dataset.taskId;
1079
- if (!n) return;
1080
- if (e.key === "Delete" || e.key === "Backspace") {
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
- if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
1093
- const l = this.getTasks().find((h) => h.id === n), o = f(l?.start), p = f(l?.end);
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 i = this.getTasks().map((e) => f(e.start)).filter((e) => e != null);
1103
- return i.length ? x(Math.min(...i), -2) : F(Date.now());
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
- selectAdjacentTask(t, i) {
1113
- const e = this.getVisibleTasks(
1114
- this.getTasks(),
1115
- this.getAttribute("filter") ?? this.internalFilter,
1116
- _(this.getAttribute("sort"))
1117
- ), n = e.findIndex((l) => l.id === t), s = e[Math.max(0, Math.min(e.length - 1, n + i))];
1118
- !s || s.id === t || (this.selectedId = s.id, this.dispatchEvent(new CustomEvent("taskselect", { detail: { id: s.id }, bubbles: !0, composed: !0 })), this.requestRender(), window.requestAnimationFrame(() => {
1119
- this.root.querySelector(`.bar[data-task-id="${CSS.escape(s.id)}"]`)?.focus();
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 = Dt(t, this.overrides), this.visibleCacheKey = "", 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 = Ft(t), this.linkCache);
2632
+ return t === this.linkCacheRaw ? this.linkCache : (this.linkCacheRaw = t, this.linkCache = Zt(t), this.linkCache);
1133
2633
  }
1134
- getVisibleTasks(t, i, e) {
1135
- const n = Array.from(this.collapsed).sort().join(","), s = `${this.taskCacheRaw || ""}|${this.overridesVersion}|${i}|${e}|${n}`;
1136
- return s === this.visibleCacheKey ? this.visibleCache : (this.visibleCacheKey = s, this.visibleCache = qt(t, this.collapsed, i, e), this.visibleCache);
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, i, e) {
1142
- return t === "tasks" && (this.overrides.clear(), this.bumpOverrides(), this.viewportTop = 0, this.viewportLeft = 0), t === "links" && (this.linkCacheRaw = null), !0;
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", _t);
2650
+ typeof customElements < "u" && !customElements.get("ui-gantt") && customElements.define("ui-gantt", pr);
1146
2651
  export {
1147
- _t as UIGantt
2652
+ pr as UIGantt
1148
2653
  };