@konce-pt/angular 0.6.1 → 0.7.5

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.
Files changed (121) hide show
  1. package/CHANGELOG.md +384 -0
  2. package/README.md +38 -19
  3. package/fesm2022/konce-pt-angular-charts.mjs +1409 -0
  4. package/fesm2022/konce-pt-angular-charts.mjs.map +1 -0
  5. package/fesm2022/konce-pt-angular-grid.mjs +232 -0
  6. package/{dist/fesm2022 → fesm2022}/konce-pt-angular-grid.mjs.map +1 -1
  7. package/fesm2022/konce-pt-angular-map.mjs +4568 -0
  8. package/fesm2022/konce-pt-angular-map.mjs.map +1 -0
  9. package/fesm2022/konce-pt-angular-roadmap.mjs +1919 -0
  10. package/fesm2022/konce-pt-angular-roadmap.mjs.map +1 -0
  11. package/fesm2022/konce-pt-angular.mjs +12307 -0
  12. package/fesm2022/konce-pt-angular.mjs.map +1 -0
  13. package/map/src/lib/llms.txt +741 -0
  14. package/package.json +44 -54
  15. package/roadmap/src/lib/llms.txt +112 -0
  16. package/src/lib/app-shell/llms.txt +89 -0
  17. package/src/lib/breakpoint/llms.txt +45 -0
  18. package/{dist/src → src}/lib/datepicker/llms.txt +3 -0
  19. package/src/lib/dialog/llms.txt +68 -0
  20. package/{dist/src → src}/lib/form-field/llms.txt +24 -1
  21. package/{dist/src → src}/lib/icon/llms.txt +2 -1
  22. package/{dist/src → src}/lib/input/llms.txt +14 -4
  23. package/src/lib/input-number/llms.txt +61 -0
  24. package/{dist/src → src}/lib/select/llms.txt +9 -0
  25. package/src/lib/sidenav/llms.txt +29 -0
  26. package/src/lib/toolbar/llms.txt +31 -0
  27. package/src/lib/tooltip/llms.txt +33 -0
  28. package/{dist/types → types}/konce-pt-angular-charts.d.ts +1 -0
  29. package/types/konce-pt-angular-charts.d.ts.map +1 -0
  30. package/{dist/types → types}/konce-pt-angular-grid.d.ts +1 -0
  31. package/types/konce-pt-angular-grid.d.ts.map +1 -0
  32. package/{dist/types → types}/konce-pt-angular-icons.d.ts +1 -0
  33. package/types/konce-pt-angular-icons.d.ts.map +1 -0
  34. package/types/konce-pt-angular-map.d.ts +1585 -0
  35. package/types/konce-pt-angular-map.d.ts.map +1 -0
  36. package/types/konce-pt-angular-roadmap.d.ts +649 -0
  37. package/types/konce-pt-angular-roadmap.d.ts.map +1 -0
  38. package/{dist/types → types}/konce-pt-angular.d.ts +373 -23
  39. package/types/konce-pt-angular.d.ts.map +1 -0
  40. package/dist/LICENSE +0 -21
  41. package/dist/README.md +0 -316
  42. package/dist/fesm2022/konce-pt-angular-charts.mjs +0 -957
  43. package/dist/fesm2022/konce-pt-angular-charts.mjs.map +0 -1
  44. package/dist/fesm2022/konce-pt-angular-grid.mjs +0 -218
  45. package/dist/fesm2022/konce-pt-angular.mjs +0 -8462
  46. package/dist/fesm2022/konce-pt-angular.mjs.map +0 -1
  47. package/dist/package.json +0 -79
  48. package/dist/src/lib/app-shell/llms.txt +0 -29
  49. package/dist/src/lib/dialog/llms.txt +0 -27
  50. package/dist/src/lib/input-number/llms.txt +0 -17
  51. package/dist/src/lib/sidenav/llms.txt +0 -20
  52. package/dist/src/lib/toolbar/llms.txt +0 -23
  53. package/dist/src/lib/tooltip/llms.txt +0 -19
  54. /package/{dist/charts → charts}/src/lib/llms.txt +0 -0
  55. /package/{dist/fesm2022 → fesm2022}/konce-pt-angular-icons.mjs +0 -0
  56. /package/{dist/fesm2022 → fesm2022}/konce-pt-angular-icons.mjs.map +0 -0
  57. /package/{dist/grid → grid}/src/lib/llms.txt +0 -0
  58. /package/{dist/src → src}/lib/accordion/llms.txt +0 -0
  59. /package/{dist/src → src}/lib/alert/llms.txt +0 -0
  60. /package/{dist/src → src}/lib/autocomplete/llms.txt +0 -0
  61. /package/{dist/src → src}/lib/avatar/llms.txt +0 -0
  62. /package/{dist/src → src}/lib/avatar-group/llms.txt +0 -0
  63. /package/{dist/src → src}/lib/badge/llms.txt +0 -0
  64. /package/{dist/src → src}/lib/bottom-sheet/llms.txt +0 -0
  65. /package/{dist/src → src}/lib/breadcrumb/llms.txt +0 -0
  66. /package/{dist/src → src}/lib/button/llms.txt +0 -0
  67. /package/{dist/src → src}/lib/button-group/llms.txt +0 -0
  68. /package/{dist/src → src}/lib/card/llms.txt +0 -0
  69. /package/{dist/src → src}/lib/carousel/llms.txt +0 -0
  70. /package/{dist/src → src}/lib/checkbox/llms.txt +0 -0
  71. /package/{dist/src → src}/lib/chip/llms.txt +0 -0
  72. /package/{dist/src → src}/lib/chips-input/llms.txt +0 -0
  73. /package/{dist/src → src}/lib/color-picker/llms.txt +0 -0
  74. /package/{dist/src → src}/lib/confirm/llms.txt +0 -0
  75. /package/{dist/src → src}/lib/context-menu/llms.txt +0 -0
  76. /package/{dist/src → src}/lib/data-table/llms.txt +0 -0
  77. /package/{dist/src → src}/lib/data-view/llms.txt +0 -0
  78. /package/{dist/src → src}/lib/date-range/llms.txt +0 -0
  79. /package/{dist/src → src}/lib/divider/llms.txt +0 -0
  80. /package/{dist/src → src}/lib/drawer/llms.txt +0 -0
  81. /package/{dist/src → src}/lib/empty/llms.txt +0 -0
  82. /package/{dist/src → src}/lib/fab/llms.txt +0 -0
  83. /package/{dist/src → src}/lib/fieldset/llms.txt +0 -0
  84. /package/{dist/src → src}/lib/file-upload/llms.txt +0 -0
  85. /package/{dist/src → src}/lib/galleria/llms.txt +0 -0
  86. /package/{dist/src → src}/lib/i18n/llms.txt +0 -0
  87. /package/{dist/src → src}/lib/icon-button/llms.txt +0 -0
  88. /package/{dist/src → src}/lib/image/llms.txt +0 -0
  89. /package/{dist/src → src}/lib/input-mask/llms.txt +0 -0
  90. /package/{dist/src → src}/lib/input-otp/llms.txt +0 -0
  91. /package/{dist/src → src}/lib/knob/llms.txt +0 -0
  92. /package/{dist/src → src}/lib/listbox/llms.txt +0 -0
  93. /package/{dist/src → src}/lib/megamenu/llms.txt +0 -0
  94. /package/{dist/src → src}/lib/menu/llms.txt +0 -0
  95. /package/{dist/src → src}/lib/menubar/llms.txt +0 -0
  96. /package/{dist/src → src}/lib/meter-group/llms.txt +0 -0
  97. /package/{dist/src → src}/lib/order-list/llms.txt +0 -0
  98. /package/{dist/src → src}/lib/paginator/llms.txt +0 -0
  99. /package/{dist/src → src}/lib/panel/llms.txt +0 -0
  100. /package/{dist/src → src}/lib/password/llms.txt +0 -0
  101. /package/{dist/src → src}/lib/pick-list/llms.txt +0 -0
  102. /package/{dist/src → src}/lib/popover/llms.txt +0 -0
  103. /package/{dist/src → src}/lib/progress/llms.txt +0 -0
  104. /package/{dist/src → src}/lib/radio-group/llms.txt +0 -0
  105. /package/{dist/src → src}/lib/rating/llms.txt +0 -0
  106. /package/{dist/src → src}/lib/rich-text/llms.txt +0 -0
  107. /package/{dist/src → src}/lib/scroll-top/llms.txt +0 -0
  108. /package/{dist/src → src}/lib/skeleton/llms.txt +0 -0
  109. /package/{dist/src → src}/lib/slider/llms.txt +0 -0
  110. /package/{dist/src → src}/lib/speed-dial/llms.txt +0 -0
  111. /package/{dist/src → src}/lib/spinner/llms.txt +0 -0
  112. /package/{dist/src → src}/lib/split-button/llms.txt +0 -0
  113. /package/{dist/src → src}/lib/splitter/llms.txt +0 -0
  114. /package/{dist/src → src}/lib/stepper/llms.txt +0 -0
  115. /package/{dist/src → src}/lib/switch/llms.txt +0 -0
  116. /package/{dist/src → src}/lib/switch-group/llms.txt +0 -0
  117. /package/{dist/src → src}/lib/tabs/llms.txt +0 -0
  118. /package/{dist/src → src}/lib/textarea/llms.txt +0 -0
  119. /package/{dist/src → src}/lib/timeline/llms.txt +0 -0
  120. /package/{dist/src → src}/lib/toast/llms.txt +0 -0
  121. /package/{dist/src → src}/lib/tree/llms.txt +0 -0
@@ -0,0 +1,1919 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, input, booleanAttribute, model, linkedSignal, computed, output, viewChild, signal, afterRenderEffect, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { KptI18n, KptTooltip } from '@konce-pt/angular';
4
+
5
+ /*
6
+ * Arytmetyka kalendarzowa w czasie LOKALNYM, odporna na zmianę czasu (DST).
7
+ *
8
+ * Roadmapa jest narzędziem planistycznym — użytkownik myśli zegarem ściennym,
9
+ * więc wszystkie obliczenia idą po lokalnych polach daty.
10
+ *
11
+ * ZASADA: nigdy nie dodajemy milisekund. `t + 86_400_000` na 2026-03-29 00:00 CET
12
+ * daje 2026-03-30 01:00 (dryf kumulatywny), a na 2026-10-25 daje tę samą datę
13
+ * dwa razy. Zamiast tego iterujemy po polach `Date` (`setDate`, `setHours`),
14
+ * bo one normalizują wynik do lokalnego zegara ściennego.
15
+ *
16
+ * Wszystkie różnice dni/tygodni liczymy przez `Math.round`, nigdy `floor`/`ceil`:
17
+ * doba DST to 0.958 albo 1.042 doby, a `round` te odchyłki pochłania.
18
+ */
19
+ const MS_DAY = 86_400_000;
20
+ const MS_WEEK = 604_800_000;
21
+ const MS_HOUR = 3_600_000;
22
+ /** Normalizuje dowolne wejście daty do epoch ms. Zwraca NaN dla wartości nieparsowalnych. */
23
+ function toTimestamp(value) {
24
+ if (typeof value === 'number')
25
+ return value;
26
+ if (value instanceof Date)
27
+ return value.getTime();
28
+ return new Date(value).getTime();
29
+ }
30
+ /** Kopia obiektu Date — nigdy nie mutujemy argumentów. */
31
+ function clone(date) {
32
+ return new Date(date);
33
+ }
34
+ /**
35
+ * Początek lokalnej doby.
36
+ *
37
+ * Guard: w strefach, gdzie przejście DST wypada o północy (np. America/Santiago),
38
+ * `setHours(0, …)` potrafi wylądować w dobie poprzedniej — wtedy korygujemy
39
+ * o jedną dobę w przód.
40
+ */
41
+ function startOfDay(date) {
42
+ const result = clone(date);
43
+ result.setHours(0, 0, 0, 0);
44
+ if (result.getDate() !== date.getDate() || result.getMonth() !== date.getMonth()) {
45
+ result.setTime(date.getTime());
46
+ result.setHours(0, 0, 0, 0);
47
+ if (result.getTime() < date.getTime() - MS_DAY)
48
+ result.setDate(result.getDate() + 1);
49
+ }
50
+ return result;
51
+ }
52
+ /** Dodaje `n` dób kalendarzowych, zachowując godzinę ścienną (DST-safe). */
53
+ function addDays(date, n) {
54
+ const result = clone(date);
55
+ result.setDate(result.getDate() + n);
56
+ return result;
57
+ }
58
+ /** Dodaje `n` godzin ściennych. Godzina nieistniejąca (wiosna) normalizuje się w przód. */
59
+ function addHours(date, n) {
60
+ const result = clone(date);
61
+ result.setHours(result.getHours() + n);
62
+ return result;
63
+ }
64
+ /** Liczba dni w miesiącu (`month` 0-indeksowany). */
65
+ function daysInMonth(year, month) {
66
+ return new Date(year, month + 1, 0).getDate();
67
+ }
68
+ /** Dodaje `n` miesięcy z przycięciem dnia (31 stycznia + 1 miesiąc = 28/29 lutego). */
69
+ function addMonths(date, n) {
70
+ const result = clone(date);
71
+ const day = result.getDate();
72
+ result.setDate(1);
73
+ result.setMonth(result.getMonth() + n);
74
+ result.setDate(Math.min(day, daysInMonth(result.getFullYear(), result.getMonth())));
75
+ return result;
76
+ }
77
+ /**
78
+ * Różnica w pełnych dobach kalendarzowych.
79
+ * `Math.round` jest konieczny: doba DST ma 23 h (0.958) albo 25 h (1.042),
80
+ * więc `Math.floor` gubiłby jeden dzień dwa razy w roku.
81
+ */
82
+ function diffDays(a, b) {
83
+ return Math.round((startOfDay(b).getTime() - startOfDay(a).getTime()) / MS_DAY);
84
+ }
85
+ /** Czy obie daty wypadają w tej samej lokalnej dobie? */
86
+ function isSameDay(a, b) {
87
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
88
+ }
89
+ // ──────────────────────────────────────────────────────────────────── ISO 8601
90
+ /**
91
+ * Dzień tygodnia wg ISO 8601: 1 = poniedziałek … 7 = niedziela.
92
+ * `Date#getDay()` zwraca 0 dla niedzieli, stąd przesunięcie.
93
+ */
94
+ function isoWeekday(date) {
95
+ return ((date.getDay() + 6) % 7) + 1;
96
+ }
97
+ /** Poniedziałek tygodnia ISO zawierającego `date` (początek doby). */
98
+ function startOfIsoWeek(date) {
99
+ const day = startOfDay(date);
100
+ return startOfDay(addDays(day, -(isoWeekday(day) - 1)));
101
+ }
102
+ /**
103
+ * Czwartek tygodnia ISO zawierającego `date`.
104
+ *
105
+ * Czwartek jest kotwicą ISO: rok czwartka to rok tygodnia, a odległość między
106
+ * czwartkami dwóch tygodni to zawsze dokładnie N tygodni.
107
+ */
108
+ function isoThursday(date) {
109
+ const day = startOfDay(date);
110
+ return startOfDay(addDays(day, 4 - isoWeekday(day)));
111
+ }
112
+ /**
113
+ * Rok tygodnia ISO 8601 — bywa różny od `getFullYear()` na przełomie roku.
114
+ * Przykłady: 2025-12-29 → 2026 (tydzień 1), 2027-01-03 → 2026 (tydzień 53).
115
+ */
116
+ function isoWeekYear(date) {
117
+ return isoThursday(date).getFullYear();
118
+ }
119
+ /**
120
+ * Numer tygodnia ISO 8601 (1..53).
121
+ * Tydzień zaczyna się w poniedziałek, a tydzień 1 to ten, który zawiera pierwszy
122
+ * czwartek roku (równoważnie: zawiera 4 stycznia).
123
+ *
124
+ * `Math.round` jest tu krytyczny: przy DST różnica dwóch północy to 7 dni ±1 h
125
+ * (6.994 / 7.006), więc `Math.floor` zaniżałby numer tygodnia przez pół roku.
126
+ */
127
+ function isoWeekNumber(date) {
128
+ const thursday = isoThursday(date);
129
+ const jan4 = new Date(thursday.getFullYear(), 0, 4);
130
+ const firstThursday = isoThursday(jan4);
131
+ return 1 + Math.round((thursday.getTime() - firstThursday.getTime()) / MS_WEEK);
132
+ }
133
+ /** Liczba tygodni ISO w danym roku tygodniowym (52 albo 53). */
134
+ function isoWeeksInYear(weekYear) {
135
+ // 28 grudnia zawsze leży w ostatnim tygodniu ISO danego roku.
136
+ return isoWeekNumber(new Date(weekYear, 11, 28));
137
+ }
138
+ // ─────────────────────────────────────────────────── zaokrąglanie do jednostki
139
+ /** Początek jednostki zoomu zawierającej `date` (w dół). */
140
+ function floorToUnit(date, zoom) {
141
+ const result = clone(date);
142
+ switch (zoom) {
143
+ case 'hour':
144
+ result.setMinutes(0, 0, 0);
145
+ return result;
146
+ case 'day':
147
+ return startOfDay(result);
148
+ case 'week':
149
+ return startOfIsoWeek(result);
150
+ case 'month':
151
+ result.setDate(1);
152
+ return startOfDay(result);
153
+ case 'quarter':
154
+ result.setMonth(Math.floor(result.getMonth() / 3) * 3, 1);
155
+ return startOfDay(result);
156
+ case 'year':
157
+ result.setMonth(0, 1);
158
+ return startOfDay(result);
159
+ }
160
+ }
161
+ /** Dodaje `n` jednostek zoomu (DST-safe — po polach daty, nie po ms). */
162
+ function addUnits(date, zoom, n) {
163
+ switch (zoom) {
164
+ case 'hour':
165
+ return addHours(date, n);
166
+ case 'day':
167
+ return addDays(date, n);
168
+ case 'week':
169
+ return addDays(date, n * 7);
170
+ case 'month':
171
+ return addMonths(date, n);
172
+ case 'quarter':
173
+ return addMonths(date, n * 3);
174
+ case 'year':
175
+ return addMonths(date, n * 12);
176
+ }
177
+ }
178
+ /** Początek następnej jednostki zoomu; bez zmian, gdy `date` już leży na granicy. */
179
+ function ceilToUnit(date, zoom) {
180
+ const floored = floorToUnit(date, zoom);
181
+ if (floored.getTime() === date.getTime())
182
+ return floored;
183
+ return addUnits(floored, zoom, 1);
184
+ }
185
+ /**
186
+ * Liczba pełnych jednostek zoomu między dwiema granicami (DST-safe).
187
+ * Dla 'hour' liczymy wprost po ms — doba DST po prostu ma 23 albo 25 godzin
188
+ * i tyle właśnie powinno powstać kolumn.
189
+ */
190
+ function countUnits(start, end, zoom) {
191
+ switch (zoom) {
192
+ case 'hour':
193
+ return Math.round((end.getTime() - start.getTime()) / MS_HOUR);
194
+ case 'day':
195
+ return diffDays(start, end);
196
+ case 'week':
197
+ return Math.round(diffDays(start, end) / 7);
198
+ case 'month':
199
+ return (end.getFullYear() - start.getFullYear()) * 12 + (end.getMonth() - start.getMonth());
200
+ case 'quarter':
201
+ return Math.round(((end.getFullYear() - start.getFullYear()) * 12 + (end.getMonth() - start.getMonth())) / 3);
202
+ case 'year':
203
+ return end.getFullYear() - start.getFullYear();
204
+ }
205
+ }
206
+ /** Numer kwartału (1..4) dla danej daty. */
207
+ function quarterOf(date) {
208
+ return Math.floor(date.getMonth() / 3) + 1;
209
+ }
210
+
211
+ /*
212
+ * Skalowanie liniowe i pomocniki liczbowe.
213
+ *
214
+ * `linearScale` i `extent` są celowo zduplikowane z `charts/src/core/scale.ts`
215
+ * (ten sam kontrakt): import między subpathami tworzyłby twardą zależność
216
+ * `roadmap → charts` dla kilkunastu linii kodu.
217
+ */
218
+ /** Zakres [min, max] tablicy liczb (0..0 dla pustej). */
219
+ function extent(values) {
220
+ if (values.length === 0)
221
+ return [0, 0];
222
+ let min = values[0];
223
+ let max = values[0];
224
+ for (const v of values) {
225
+ if (v < min)
226
+ min = v;
227
+ if (v > max)
228
+ max = v;
229
+ }
230
+ return [min, max];
231
+ }
232
+ /** Interpolacja liniowa domeny → zakresu (np. czas → udział 0..1). */
233
+ function linearScale(domainMin, domainMax, rangeMin, rangeMax) {
234
+ const dSpan = domainMax - domainMin || 1;
235
+ const rSpan = rangeMax - rangeMin;
236
+ return (value) => rangeMin + ((value - domainMin) / dSpan) * rSpan;
237
+ }
238
+ /** Ogranicza wartość do przedziału [min, max]. */
239
+ function clamp(value, min, max) {
240
+ if (value < min)
241
+ return min;
242
+ if (value > max)
243
+ return max;
244
+ return value;
245
+ }
246
+ /**
247
+ * Skraca liczbę na potrzeby atrybutu `d` ścieżki SVG.
248
+ * Odpowiednik prywatnego `n()` z `charts/src/core/bar.ts` — trzyma ścieżki krótkie.
249
+ */
250
+ function svgNum(value) {
251
+ return Number.isInteger(value) ? String(value) : value.toFixed(3);
252
+ }
253
+
254
+ /*
255
+ * Wyznaczanie zakresu osi czasu z danych.
256
+ *
257
+ * Krańce zawsze wyrównujemy na zewnątrz do granicy jednostki zoomu, żeby
258
+ * pierwsza i ostatnia kolumna nie były ucięte w połowie.
259
+ */
260
+ /** Margines po obu stronach danych, w jednostkach zoomu. */
261
+ const DEFAULT_PAD = {
262
+ hour: 2,
263
+ day: 2,
264
+ week: 1,
265
+ month: 1,
266
+ quarter: 1,
267
+ year: 0,
268
+ };
269
+ /** Minimalna szerokość osi, w jednostkach zoomu — żeby jeden item nie dał jednej kolumny. */
270
+ const DEFAULT_MIN = {
271
+ hour: 12,
272
+ day: 14,
273
+ week: 8,
274
+ month: 6,
275
+ quarter: 4,
276
+ year: 3,
277
+ };
278
+ /** Zbiera skrajne wartości czasu z itemów, pomijając wpisy z niepoprawną datą. */
279
+ function collectBounds(items) {
280
+ const bounds = [];
281
+ for (const item of items) {
282
+ const start = toTimestamp(item.start);
283
+ if (!Number.isFinite(start))
284
+ continue;
285
+ const end = item.end !== undefined ? toTimestamp(item.end) : start;
286
+ bounds.push(start, Number.isFinite(end) ? end : start);
287
+ }
288
+ return bounds;
289
+ }
290
+ /**
291
+ * Zakres osi wyliczony z danych: min(start)…max(end) powiększony o margines
292
+ * i wyrównany do granic jednostki zoomu.
293
+ * Dla pustej listy zwraca okno wokół „dziś" o szerokości `minUnits`.
294
+ */
295
+ function autoRange(items, opts) {
296
+ const { zoom } = opts;
297
+ const pad = opts.padUnits ?? DEFAULT_PAD[zoom];
298
+ const minUnits = opts.minUnits ?? DEFAULT_MIN[zoom];
299
+ const todayTs = opts.today ?? Date.now();
300
+ const bounds = collectBounds(items);
301
+ if ((opts.includeToday ?? true) && bounds.length > 0)
302
+ bounds.push(todayTs);
303
+ const [min, max] = bounds.length > 0 ? extent(bounds) : [todayTs, todayTs];
304
+ // Margines dodajemy po polach daty (DST-safe), nie przez arytmetykę na ms.
305
+ let start = floorToUnit(addUnits(new Date(min), zoom, -pad), zoom);
306
+ let end = ceilToUnit(addUnits(new Date(max), zoom, pad), zoom);
307
+ // Dociągnięcie do minimalnej szerokości — symetrycznie, zaczynając od prawej.
308
+ let units = countUnits(start, end, zoom);
309
+ while (units < minUnits) {
310
+ end = addUnits(end, zoom, 1);
311
+ units++;
312
+ if (units < minUnits) {
313
+ start = addUnits(start, zoom, -1);
314
+ units++;
315
+ }
316
+ }
317
+ return { start: start.getTime(), end: end.getTime() };
318
+ }
319
+ /**
320
+ * Ostateczny zakres osi. `config.range` ma priorytet, a brakujący kraniec
321
+ * uzupełniamy z `autoRange`. Wynik zawsze leży na granicach jednostki zoomu.
322
+ */
323
+ function resolveRange(config, items) {
324
+ const zoom = config.zoom ?? 'week';
325
+ const todaySource = config.today ?? config.timeline?.today;
326
+ const today = todaySource !== undefined ? toTimestamp(todaySource) : Date.now();
327
+ // `KptRoadmapNormalizedItem` niesie oryginał w `source` — sprowadzamy oba kształty
328
+ // do postaci akceptowanej przez `autoRange`.
329
+ const raw = items.map((item) => ('source' in item ? item.source : item));
330
+ const auto = autoRange(raw, { zoom, today });
331
+ const startTs = config.range?.start ?? auto.start;
332
+ const endTs = config.range?.end ?? auto.end;
333
+ const start = floorToUnit(new Date(startTs), zoom);
334
+ let end = ceilToUnit(new Date(endTs), zoom);
335
+ // Zakres musi obejmować co najmniej jedną kolumnę.
336
+ if (end.getTime() <= start.getTime())
337
+ end = addUnits(start, zoom, 1);
338
+ return { start: start.getTime(), end: end.getTime() };
339
+ }
340
+
341
+ /*
342
+ * Układ wierszy: normalizacja itemów, spłaszczenie grup do listy wierszy
343
+ * renderowalnych, przydział pasów przy nakładaniu się zadań i wirtualizacja.
344
+ *
345
+ * Offset pionowy (`top`) liczymy tutaj, dzięki czemu warstwa widoku pozycjonuje
346
+ * wiersze absolutnie, a wirtualizacja sprowadza się do wycięcia podlisty —
347
+ * bez elementów dystansowych i z obsługą wierszy o różnej wysokości.
348
+ */
349
+ const DEFAULT_ITEM_HEIGHT = 44;
350
+ const DEFAULT_GROUP_HEIGHT = 38;
351
+ const DEFAULT_LANE_HEIGHT = 28;
352
+ /**
353
+ * Normalizuje itemy: daty na ms, brak `end` oznacza kamień milowy.
354
+ * Odrzuca wpisy z nieparsowalną datą oraz duplikaty `id`.
355
+ */
356
+ function normalizeItems(items) {
357
+ const valid = [];
358
+ const invalid = [];
359
+ const seen = new Set();
360
+ for (const item of items) {
361
+ if (!item.id) {
362
+ invalid.push({ id: String(item.label ?? '?'), reason: 'brak id' });
363
+ continue;
364
+ }
365
+ if (seen.has(item.id)) {
366
+ invalid.push({ id: item.id, reason: 'zduplikowane id' });
367
+ continue;
368
+ }
369
+ const start = toTimestamp(item.start);
370
+ if (!Number.isFinite(start)) {
371
+ invalid.push({ id: item.id, reason: 'niepoprawna data początkowa' });
372
+ continue;
373
+ }
374
+ const rawEnd = item.end !== undefined ? toTimestamp(item.end) : start;
375
+ if (!Number.isFinite(rawEnd)) {
376
+ invalid.push({ id: item.id, reason: 'niepoprawna data końcowa' });
377
+ continue;
378
+ }
379
+ // Odwrócony zakres prostujemy zamiast odrzucać — dane bywają brudne.
380
+ const from = Math.min(start, rawEnd);
381
+ const to = Math.max(start, rawEnd);
382
+ const isMilestone = item.milestone === true || item.end === undefined || from === to;
383
+ seen.add(item.id);
384
+ valid.push({
385
+ source: item,
386
+ id: item.id,
387
+ start: from,
388
+ end: isMilestone ? from : to,
389
+ isMilestone,
390
+ durationMs: isMilestone ? 0 : to - from,
391
+ });
392
+ }
393
+ return { valid, invalid };
394
+ }
395
+ /** Zakres zbiorczy listy itemów: min(start)…max(end). */
396
+ function spanOf(items) {
397
+ if (items.length === 0)
398
+ return null;
399
+ let start = items[0].start;
400
+ let end = items[0].end;
401
+ for (const item of items) {
402
+ if (item.start < start)
403
+ start = item.start;
404
+ if (item.end > end)
405
+ end = item.end;
406
+ }
407
+ return { start, end };
408
+ }
409
+ /**
410
+ * Postęp grupy ważony czasem trwania.
411
+ * Kamienie milowe mają zerową długość, więc liczymy je z wagą 1.
412
+ */
413
+ function rollupProgress(items) {
414
+ let weightSum = 0;
415
+ let progressSum = 0;
416
+ let any = false;
417
+ for (const item of items) {
418
+ const progress = item.source.progress;
419
+ if (progress === undefined)
420
+ continue;
421
+ any = true;
422
+ const weight = item.isMilestone ? 1 : Math.max(1, item.durationMs);
423
+ weightSum += weight;
424
+ progressSum += weight * Math.min(1, Math.max(0, progress));
425
+ }
426
+ return any && weightSum > 0 ? progressSum / weightSum : null;
427
+ }
428
+ /**
429
+ * Zachłanne przydzielanie pasów (interval partitioning).
430
+ * Itemy sortujemy po czasie startu i wkładamy do pierwszego pasa, w którym
431
+ * poprzednie zadanie zdążyło się skończyć. Zwraca liczbę użytych pasów.
432
+ */
433
+ function packLanes(items, minGapMs = 0) {
434
+ const sorted = [...items].sort((a, b) => a.start - b.start || a.end - b.end);
435
+ /** Ostatni zajęty koniec dla każdego pasa. */
436
+ const laneEnd = [];
437
+ for (const item of sorted) {
438
+ const to = Math.max(item.end, item.start);
439
+ let lane = laneEnd.findIndex((end) => end + minGapMs <= item.start);
440
+ if (lane === -1) {
441
+ lane = laneEnd.length;
442
+ laneEnd.push(to);
443
+ }
444
+ else {
445
+ laneEnd[lane] = to;
446
+ }
447
+ item.lane = lane;
448
+ }
449
+ return Math.max(1, laneEnd.length);
450
+ }
451
+ /** Głębokość grupy w drzewie `parentId` (z zabezpieczeniem przed cyklem). */
452
+ function groupDepth(group, byId) {
453
+ let depth = 0;
454
+ let current = group.parentId;
455
+ const guard = new Set([group.id]);
456
+ while (current && !guard.has(current)) {
457
+ guard.add(current);
458
+ depth++;
459
+ current = byId.get(current)?.parentId;
460
+ }
461
+ return depth;
462
+ }
463
+ /** Czy któryś z przodków grupy jest zwinięty? */
464
+ function hasCollapsedAncestor(group, byId, collapsed) {
465
+ let current = group.parentId;
466
+ const guard = new Set([group.id]);
467
+ while (current && !guard.has(current)) {
468
+ if (collapsed.has(current))
469
+ return true;
470
+ guard.add(current);
471
+ current = byId.get(current)?.parentId;
472
+ }
473
+ return false;
474
+ }
475
+ /**
476
+ * Spłaszcza grupy i itemy do listy wierszy renderowalnych.
477
+ *
478
+ * Kolejność: grupy w kolejności podanej przez użytkownika, wewnątrz itemy
479
+ * posortowane po czasie startu, na końcu itemy bez `groupId`. Zwinięta grupa
480
+ * nie emituje wierszy potomnych, ale jej zakres zbiorczy nadal obejmuje dzieci.
481
+ *
482
+ * @param collapsed zbiór `id` grup zwiniętych (stan trzymany w sygnale komponentu)
483
+ */
484
+ function buildRows(items, groups, collapsed, opts) {
485
+ const mode = opts?.mode ?? 'row-per-item';
486
+ const itemHeight = opts?.itemHeight ?? DEFAULT_ITEM_HEIGHT;
487
+ const groupHeight = opts?.groupHeight ?? DEFAULT_GROUP_HEIGHT;
488
+ const laneHeight = opts?.laneHeight ?? DEFAULT_LANE_HEIGHT;
489
+ const minGap = opts?.packMinGapMs ?? 0;
490
+ const { valid, invalid } = normalizeItems(items);
491
+ const byId = new Map(valid.map((item) => [item.id, item]));
492
+ const rowOfItem = new Map();
493
+ const rows = [];
494
+ const groupList = groups ?? [];
495
+ const groupById = new Map(groupList.map((group) => [group.id, group]));
496
+ // Itemy w kubełkach wg grupy; nieznane `groupId` traktujemy jak brak grupy.
497
+ const buckets = new Map();
498
+ const ungrouped = [];
499
+ for (const item of valid) {
500
+ const groupId = item.source.groupId;
501
+ if (groupId && groupById.has(groupId)) {
502
+ const bucket = buckets.get(groupId);
503
+ if (bucket)
504
+ bucket.push(item);
505
+ else
506
+ buckets.set(groupId, [item]);
507
+ }
508
+ else {
509
+ ungrouped.push(item);
510
+ }
511
+ }
512
+ for (const bucket of buckets.values())
513
+ bucket.sort((a, b) => a.start - b.start || a.end - b.end);
514
+ ungrouped.sort((a, b) => a.start - b.start || a.end - b.end);
515
+ let top = 0;
516
+ /** Dokłada wiersze itemów — jeden na item albo jeden spakowany. */
517
+ const pushItemRows = (bucket, depth, keyPrefix) => {
518
+ if (bucket.length === 0)
519
+ return;
520
+ if (mode === 'pack') {
521
+ const rowKey = `${keyPrefix}:pack`;
522
+ const placed = bucket.map((item) => ({ ...item, lane: 0, rowKey }));
523
+ const laneCount = packLanes(placed, minGap);
524
+ const height = Math.max(itemHeight, laneCount * laneHeight + 8);
525
+ for (const item of placed)
526
+ rowOfItem.set(item.id, rowKey);
527
+ rows.push({
528
+ key: rowKey,
529
+ kind: 'item',
530
+ index: rows.length,
531
+ depth,
532
+ top,
533
+ height,
534
+ laneCount,
535
+ items: placed,
536
+ });
537
+ top += height;
538
+ return;
539
+ }
540
+ for (const item of bucket) {
541
+ const rowKey = `item:${item.id}`;
542
+ rowOfItem.set(item.id, rowKey);
543
+ rows.push({
544
+ key: rowKey,
545
+ kind: 'item',
546
+ index: rows.length,
547
+ depth,
548
+ top,
549
+ height: itemHeight,
550
+ laneCount: 1,
551
+ items: [{ ...item, lane: 0, rowKey }],
552
+ });
553
+ top += itemHeight;
554
+ }
555
+ };
556
+ for (const group of groupList) {
557
+ // Grupa pod zwiniętym przodkiem w ogóle się nie renderuje.
558
+ if (hasCollapsedAncestor(group, groupById, collapsed))
559
+ continue;
560
+ const bucket = buckets.get(group.id) ?? [];
561
+ const depth = groupDepth(group, groupById);
562
+ const isCollapsed = collapsed.has(group.id);
563
+ const rowKey = `group:${group.id}`;
564
+ rows.push({
565
+ key: rowKey,
566
+ kind: 'group',
567
+ index: rows.length,
568
+ depth,
569
+ top,
570
+ height: groupHeight,
571
+ laneCount: 1,
572
+ group,
573
+ groupState: {
574
+ collapsed: isCollapsed,
575
+ itemCount: bucket.length,
576
+ // Pasek zbiorczy obejmuje dzieci również wtedy, gdy grupa jest zwinięta.
577
+ span: group.summary === 'none' ? null : spanOf(bucket),
578
+ progress: rollupProgress(bucket),
579
+ },
580
+ items: [],
581
+ });
582
+ top += groupHeight;
583
+ if (!isCollapsed)
584
+ pushItemRows(bucket, depth + 1, `group:${group.id}`);
585
+ }
586
+ if (ungrouped.length > 0) {
587
+ if (opts?.showUngroupedHeader) {
588
+ const rowKey = 'group:__ungrouped';
589
+ const isCollapsed = collapsed.has('__ungrouped');
590
+ rows.push({
591
+ key: rowKey,
592
+ kind: 'group',
593
+ index: rows.length,
594
+ depth: 0,
595
+ top,
596
+ height: groupHeight,
597
+ laneCount: 1,
598
+ group: { id: '__ungrouped', label: opts.ungroupedLabel ?? '' },
599
+ groupState: {
600
+ collapsed: isCollapsed,
601
+ itemCount: ungrouped.length,
602
+ span: spanOf(ungrouped),
603
+ progress: rollupProgress(ungrouped),
604
+ },
605
+ items: [],
606
+ });
607
+ top += groupHeight;
608
+ if (!isCollapsed)
609
+ pushItemRows(ungrouped, 1, 'ungrouped');
610
+ }
611
+ else {
612
+ pushItemRows(ungrouped, 0, 'ungrouped');
613
+ }
614
+ }
615
+ return { rows, totalHeight: top, byId, rowOfItem, invalid };
616
+ }
617
+ /** Indeks pierwszego wiersza sięgającego pozycji `y` (binary search po `top`). */
618
+ function firstRowAt(rows, y) {
619
+ let lo = 0;
620
+ let hi = rows.length - 1;
621
+ while (lo < hi) {
622
+ const mid = (lo + hi) >> 1;
623
+ if (rows[mid].top + rows[mid].height <= y)
624
+ lo = mid + 1;
625
+ else
626
+ hi = mid;
627
+ }
628
+ return lo;
629
+ }
630
+ /**
631
+ * Wycinek wierszy widocznych w oknie przewijania.
632
+ * Szuka binarnie po skumulowanym `top`, więc działa także dla wierszy
633
+ * o różnej wysokości (tryb 'pack').
634
+ */
635
+ function sliceRows(layout, scrollTop, viewportHeight, buffer = 4) {
636
+ const all = layout.rows;
637
+ if (all.length === 0 || viewportHeight <= 0) {
638
+ return { rows: all, startIndex: 0, endIndex: all.length };
639
+ }
640
+ const first = firstRowAt(all, Math.max(0, scrollTop));
641
+ const last = firstRowAt(all, Math.max(0, scrollTop + viewportHeight));
642
+ const startIndex = Math.max(0, first - buffer);
643
+ const endIndex = Math.min(all.length, last + buffer + 1);
644
+ return { rows: all.slice(startIndex, endIndex), startIndex, endIndex };
645
+ }
646
+
647
+ /*
648
+ * Geometria zależności między paskami — warstwa SVG nad torami.
649
+ *
650
+ * Overlay pracuje w pikselach, nie w procentach: zaokrąglenie narożnika wymaga
651
+ * tych samych jednostek na obu osiach, inaczej łuki byłyby zniekształcone.
652
+ */
653
+ const DEFAULT_STUB = 12;
654
+ const DEFAULT_CORNER = 6;
655
+ const DEFAULT_ARROW = 6;
656
+ /** Stabilny klucz krawędzi — pozwala pominąć duplikaty i służy jako trackBy. */
657
+ function linkKey(link) {
658
+ return link.id ?? `${link.from}→${link.to}:${link.type ?? 'FS'}`;
659
+ }
660
+ /**
661
+ * Scala zależności z `item.dependsOn` (skrót oznacza typ 'FS' bez lagu)
662
+ * z listą `config.links`, odrzuca krawędzie wiszące i wykrywa cykle.
663
+ */
664
+ function resolveLinks(items, links) {
665
+ const known = new Set(items.map((item) => item.id));
666
+ const merged = new Map();
667
+ const add = (link) => {
668
+ if (!known.has(link.from) || !known.has(link.to) || link.from === link.to)
669
+ return;
670
+ const key = linkKey(link);
671
+ if (!merged.has(key))
672
+ merged.set(key, link);
673
+ };
674
+ for (const item of items) {
675
+ for (const dependency of item.source.dependsOn ?? []) {
676
+ if (typeof dependency === 'string')
677
+ add({ from: dependency, to: item.id, type: 'FS' });
678
+ else
679
+ add(dependency);
680
+ }
681
+ }
682
+ for (const link of links ?? [])
683
+ add(link);
684
+ return { links: [...merged.values()], cycles: findCycles([...merged.values()]) };
685
+ }
686
+ /** Wykrywa cykle w grafie zależności (DFS z kolorowaniem wierzchołków). */
687
+ function findCycles(links) {
688
+ const adjacency = new Map();
689
+ for (const link of links) {
690
+ const list = adjacency.get(link.from);
691
+ if (list)
692
+ list.push(link.to);
693
+ else
694
+ adjacency.set(link.from, [link.to]);
695
+ }
696
+ const cycles = [];
697
+ const state = new Map();
698
+ const stack = [];
699
+ const visit = (node) => {
700
+ const current = state.get(node);
701
+ if (current === 'done')
702
+ return;
703
+ if (current === 'visiting') {
704
+ const from = stack.lastIndexOf(node);
705
+ if (from !== -1)
706
+ cycles.push(stack.slice(from));
707
+ return;
708
+ }
709
+ state.set(node, 'visiting');
710
+ stack.push(node);
711
+ for (const next of adjacency.get(node) ?? [])
712
+ visit(next);
713
+ stack.pop();
714
+ state.set(node, 'done');
715
+ };
716
+ for (const node of adjacency.keys())
717
+ visit(node);
718
+ return cycles;
719
+ }
720
+ /** Czy zależność jest naruszona? Liczone w ms, nie w pikselach. */
721
+ function violates(link, source, target) {
722
+ const lag = link.lag ?? 0;
723
+ switch (link.type ?? 'FS') {
724
+ case 'FS':
725
+ return target.startTs < source.endTs + lag;
726
+ case 'SS':
727
+ return target.startTs < source.startTs + lag;
728
+ case 'FF':
729
+ return target.endTs < source.endTs + lag;
730
+ case 'SF':
731
+ return target.endTs < source.startTs + lag;
732
+ }
733
+ }
734
+ /** Usuwa punkty pokrywające się i leżące na jednej prostej z sąsiadami. */
735
+ function dedupeCollinear(points) {
736
+ const result = [];
737
+ for (const point of points) {
738
+ const last = result.at(-1);
739
+ if (last && Math.abs(last.x - point.x) < 1e-6 && Math.abs(last.y - point.y) < 1e-6)
740
+ continue;
741
+ result.push(point);
742
+ }
743
+ for (let i = result.length - 2; i >= 1; i--) {
744
+ const previous = result[i - 1];
745
+ const current = result[i];
746
+ const next = result[i + 1];
747
+ const collinearX = Math.abs(previous.x - current.x) < 1e-6 && Math.abs(current.x - next.x) < 1e-6;
748
+ const collinearY = Math.abs(previous.y - current.y) < 1e-6 && Math.abs(current.y - next.y) < 1e-6;
749
+ if (collinearX || collinearY)
750
+ result.splice(i, 1);
751
+ }
752
+ return result;
753
+ }
754
+ /**
755
+ * Łamana ortogonalna z zaokrąglonymi narożnikami.
756
+ * Narożnik rysujemy krzywą kwadratową — dla kąta prostego wygląda jak łuk,
757
+ * a ścieżka jest o połowę krótsza. Promień przycinamy do połowy krótszego
758
+ * z sąsiednich odcinków, żeby ciasne zakręty nie tworzyły pętli.
759
+ */
760
+ function roundedPolyline(points, radius) {
761
+ const path = dedupeCollinear(points);
762
+ if (path.length < 2)
763
+ return '';
764
+ const parts = [`M ${svgNum(path[0].x)} ${svgNum(path[0].y)}`];
765
+ for (let i = 1; i < path.length - 1; i++) {
766
+ const previous = path[i - 1];
767
+ const current = path[i];
768
+ const next = path[i + 1];
769
+ const lengthIn = Math.hypot(current.x - previous.x, current.y - previous.y);
770
+ const lengthOut = Math.hypot(next.x - current.x, next.y - current.y);
771
+ const r = Math.max(0, Math.min(radius, lengthIn / 2, lengthOut / 2));
772
+ if (r === 0 || lengthIn === 0 || lengthOut === 0) {
773
+ parts.push(`L ${svgNum(current.x)} ${svgNum(current.y)}`);
774
+ continue;
775
+ }
776
+ const enter = {
777
+ x: current.x + ((previous.x - current.x) / lengthIn) * r,
778
+ y: current.y + ((previous.y - current.y) / lengthIn) * r,
779
+ };
780
+ const leave = {
781
+ x: current.x + ((next.x - current.x) / lengthOut) * r,
782
+ y: current.y + ((next.y - current.y) / lengthOut) * r,
783
+ };
784
+ parts.push(`L ${svgNum(enter.x)} ${svgNum(enter.y)}`, `Q ${svgNum(current.x)} ${svgNum(current.y)} ${svgNum(leave.x)} ${svgNum(leave.y)}`);
785
+ }
786
+ const last = path.at(-1);
787
+ parts.push(`L ${svgNum(last.x)} ${svgNum(last.y)}`);
788
+ return parts.join(' ');
789
+ }
790
+ /** Grot strzałki w punkcie `point`, skierowany zgodnie z `direction`. */
791
+ function arrowHead(point, direction, size) {
792
+ const sign = direction === 'right' ? 1 : -1;
793
+ const baseX = point.x - sign * size;
794
+ return [
795
+ `M ${svgNum(point.x)} ${svgNum(point.y)}`,
796
+ `L ${svgNum(baseX)} ${svgNum(point.y - size / 2)}`,
797
+ `L ${svgNum(baseX)} ${svgNum(point.y + size / 2)}`,
798
+ 'Z',
799
+ ].join(' ');
800
+ }
801
+ /** Kotwica wyjścia/wejścia zależnie od typu: -1 = lewa krawędź, 1 = prawa. */
802
+ function anchors(type) {
803
+ switch (type) {
804
+ case 'FS':
805
+ return { out: 1, in: -1 };
806
+ case 'SS':
807
+ return { out: -1, in: -1 };
808
+ case 'FF':
809
+ return { out: 1, in: 1 };
810
+ case 'SF':
811
+ return { out: -1, in: 1 };
812
+ }
813
+ }
814
+ function edgePoint(rect, side) {
815
+ return { x: side === 1 ? rect.x + rect.width : rect.x, y: rect.y + rect.height / 2 };
816
+ }
817
+ /**
818
+ * Trasa łamanej między dwoma kotwicami.
819
+ *
820
+ * Konwencja kierunków: `outDir` i `inDir` wskazują stronę paska (-1 = lewa
821
+ * krawędź, +1 = prawa), więc odsunięcie „na zewnątrz" to zawsze `x + dir * stub`.
822
+ *
823
+ * Gdy cel leży za końcem źródła, wystarczy prosty schodek: pion wypada między
824
+ * paskami. W przeciwnym razie trzeba je obejść poziomym objazdem prowadzonym
825
+ * w połowie odstępu między wierszami.
826
+ */
827
+ function route(out, outDir, anchor, endPoint, inDir, stub, detourGap) {
828
+ const afterOut = out.x + outDir * stub;
829
+ const beforeIn = endPoint.x + inDir * stub;
830
+ if (outDir === inDir) {
831
+ // SS / FF — obie kotwice po tej samej stronie pasków. Wspólny pion musi ominąć
832
+ // oba paski, więc bierzemy punkt najdalej wysunięty w kierunku wyjścia;
833
+ // inaczej trasa schowałaby się pod paskiem, z którego wychodzi.
834
+ const middle = outDir === -1 ? Math.min(afterOut, beforeIn) : Math.max(afterOut, beforeIn);
835
+ return [out, { x: middle, y: out.y }, { x: middle, y: endPoint.y }, endPoint];
836
+ }
837
+ // FS / SF — kotwice po przeciwnych stronach. Prosty schodek ma sens tylko wtedy,
838
+ // gdy między paskami zostaje miejsce na czytelny pion. Zapas liczymy względem
839
+ // rzeczywistej krawędzi celu (`anchor`), a nie skróconego o grot końca trzonu.
840
+ // Warunek `room >= 0` jest równoważny „odstęp >= 2 * stub", więc próg zawijasa
841
+ // wynika wprost z geometrii i nie wymaga osobnej stałej.
842
+ const room = (anchor.x + inDir * stub - afterOut) * (inDir === -1 ? 1 : -1);
843
+ if (room >= 0) {
844
+ const lo = Math.min(afterOut, beforeIn);
845
+ const hi = Math.max(afterOut, beforeIn);
846
+ const middle = Math.min(hi, Math.max(lo, (out.x + endPoint.x) / 2));
847
+ return [out, { x: middle, y: out.y }, { x: middle, y: endPoint.y }, endPoint];
848
+ }
849
+ // Paski stykają się albo cel cofa się względem źródła — prowadzimy trasę wokół,
850
+ // przez odstęp między wierszami.
851
+ const detourY = Math.abs(endPoint.y - out.y) < 1e-6 ? out.y + detourGap : (out.y + endPoint.y) / 2;
852
+ return [
853
+ out,
854
+ { x: afterOut, y: out.y },
855
+ { x: afterOut, y: detourY },
856
+ { x: beforeIn, y: detourY },
857
+ { x: beforeIn, y: endPoint.y },
858
+ endPoint,
859
+ ];
860
+ }
861
+ /**
862
+ * Geometria pojedynczej zależności.
863
+ * Zwraca `null`, gdy któryś koniec nie jest widoczny (zwinięta grupa albo
864
+ * wiersz poza oknem wirtualizacji).
865
+ */
866
+ function linkGeometry(link, source, target, opts) {
867
+ if (!source || !target)
868
+ return null;
869
+ const type = link.type ?? 'FS';
870
+ const stub = opts?.stub ?? DEFAULT_STUB;
871
+ const corner = opts?.cornerRadius ?? DEFAULT_CORNER;
872
+ const arrowSize = opts?.arrowSize ?? DEFAULT_ARROW;
873
+ const side = anchors(type);
874
+ const from = edgePoint(source.rect, side.out);
875
+ const to = edgePoint(target.rect, side.in);
876
+ // Grot dosuwamy do krawędzi paska, więc trzon kończy się tuż przed nim —
877
+ // czyli o długość grotu na zewnątrz celu.
878
+ const tip = to;
879
+ const shaftEnd = { x: to.x + side.in * arrowSize, y: to.y };
880
+ const detourGap = Math.max(source.rect.height, target.rect.height);
881
+ const points = route(from, side.out, to, shaftEnd, side.in, stub, detourGap);
882
+ const highlight = opts?.highlightViolations ?? true;
883
+ return {
884
+ key: linkKey(link),
885
+ from: link.from,
886
+ to: link.to,
887
+ type,
888
+ path: roundedPolyline(points, corner),
889
+ arrow: arrowHead(tip, side.in === -1 ? 'right' : 'left', arrowSize),
890
+ variant: link.variant,
891
+ color: link.color,
892
+ isViolated: highlight ? violates(link, source, target) : false,
893
+ };
894
+ }
895
+
896
+ /*
897
+ * Model osi czasu: kolumny dolnego poziomu nagłówka, pasma górnego poziomu
898
+ * oraz skala czas ↔ pozycja.
899
+ *
900
+ * Skala domyślna ('uniform') odwzorowuje czas odcinkami: każda kolumna ma równą
901
+ * szerokość, a wewnątrz niej interpolujemy liniowo po ms. Dzięki temu doba po
902
+ * zmianie czasu (23/25 h) i luty nie są węższe od sąsiadów, a skala pozostaje
903
+ * ciągła i monotoniczna — zadanie może zaczynać się w środku dnia.
904
+ */
905
+ /** Domyślna jednostka górnego poziomu nagłówka dla danego zoomu. */
906
+ const DEFAULT_BAND = {
907
+ hour: 'day',
908
+ day: 'week',
909
+ week: 'month',
910
+ month: 'quarter',
911
+ quarter: 'year',
912
+ year: 'none',
913
+ };
914
+ const DEFAULT_MAX_CELLS = 4000;
915
+ const DEFAULT_WEEKEND = [0, 6];
916
+ function resolveOptions(zoom, opts) {
917
+ const workHours = opts?.workHours;
918
+ return {
919
+ locale: opts?.locale,
920
+ todayTs: opts?.today !== undefined ? toTimestamp(opts.today) : Date.now(),
921
+ weekendDays: opts?.weekendDays ?? DEFAULT_WEEKEND,
922
+ // Okno godzin roboczych ma sens tylko przy kolumnach godzinowych.
923
+ workHours: zoom === 'hour' && workHours
924
+ ? {
925
+ from: workHours.from,
926
+ to: workHours.to,
927
+ mode: workHours.mode ?? 'skip',
928
+ skipWeekends: workHours.skipWeekends ?? true,
929
+ }
930
+ : null,
931
+ bandUnit: opts?.bandUnit ?? DEFAULT_BAND[zoom],
932
+ positionMode: opts?.positionMode ?? 'uniform',
933
+ weekPrefix: opts?.weekPrefix ?? 'W',
934
+ maxCells: opts?.maxCells ?? DEFAULT_MAX_CELLS,
935
+ };
936
+ }
937
+ function makeFormatters(locale) {
938
+ return {
939
+ dayNum: new Intl.DateTimeFormat(locale, { day: 'numeric' }),
940
+ weekdayShort: new Intl.DateTimeFormat(locale, { weekday: 'short' }),
941
+ dayMonth: new Intl.DateTimeFormat(locale, { day: '2-digit', month: '2-digit' }),
942
+ monthLong: new Intl.DateTimeFormat(locale, { month: 'long' }),
943
+ monthShort: new Intl.DateTimeFormat(locale, { month: 'short' }),
944
+ dayLong: new Intl.DateTimeFormat(locale, { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }),
945
+ hour: new Intl.DateTimeFormat(locale, { hour: '2-digit', minute: '2-digit', hour12: false }),
946
+ };
947
+ }
948
+ function isWeekendDay(date, o) {
949
+ return o.weekendDays.includes(date.getDay());
950
+ }
951
+ /** Czy dana godzina mieści się w oknie roboczym (i poza weekendem, gdy tak ustawiono)? */
952
+ function isWorkingSlot(date, o) {
953
+ const wh = o.workHours;
954
+ if (!wh)
955
+ return true;
956
+ if (wh.skipWeekends && isWeekendDay(date, o))
957
+ return false;
958
+ const hour = date.getHours();
959
+ return hour >= wh.from && hour < wh.to;
960
+ }
961
+ /**
962
+ * Kotwica przypisania kolumny do pasma.
963
+ * Dla tygodnia bierzemy czwartek — tak samo jak ISO wyznacza rok tygodnia,
964
+ * więc tydzień na przełomie miesiąca trafia do miesiąca swojego czwartku.
965
+ */
966
+ function bandAnchor(cellStart, zoom) {
967
+ if (zoom !== 'week')
968
+ return cellStart;
969
+ const anchor = new Date(cellStart);
970
+ anchor.setDate(anchor.getDate() + 3);
971
+ return anchor;
972
+ }
973
+ /**
974
+ * Wielka pierwsza litera. `Intl` zwraca nazwy zgodne z ortografią języka — po polsku,
975
+ * francusku czy czesku miesiące i dni tygodnia pisze się małą literą, po angielsku
976
+ * i niemiecku wielką. Na osi czasu i w podpowiedziach chcemy jednolitego wyglądu
977
+ * niezależnie od języka, więc podnosimy wyłącznie pierwszy znak; reszta zostaje bez
978
+ * zmian, żeby „wtorek, 15 września 2026" nie stało się „Wtorek, 15 Września 2026".
979
+ */
980
+ function capitalizeFirst(text) {
981
+ if (!text)
982
+ return text;
983
+ const first = text.charAt(0);
984
+ const upper = first.toLocaleUpperCase();
985
+ // `toLocaleUpperCase` bywa dłuższe niż źródło (np. niemieckie ß → SS) — wtedy
986
+ // zostawiamy oryginał, bo zamiana zmieniłaby długość i wygląd etykiety.
987
+ return upper.length === first.length ? upper + text.slice(1) : text;
988
+ }
989
+ function cellLabels(start, zoom, o, f) {
990
+ switch (zoom) {
991
+ case 'hour':
992
+ return {
993
+ label: f.hour.format(start),
994
+ title: capitalizeFirst(`${f.dayLong.format(start)}, ${f.hour.format(start)}`),
995
+ };
996
+ case 'day':
997
+ return {
998
+ label: f.dayNum.format(start),
999
+ sublabel: capitalizeFirst(f.weekdayShort.format(start)),
1000
+ title: capitalizeFirst(f.dayLong.format(start)),
1001
+ };
1002
+ case 'week': {
1003
+ // Podpis to prawdziwy zakres dat tygodnia: poniedziałek – niedziela.
1004
+ const sunday = new Date(start);
1005
+ sunday.setDate(sunday.getDate() + 6);
1006
+ const range = `${f.dayMonth.format(start)}–${f.dayMonth.format(sunday)}`;
1007
+ return {
1008
+ label: `${o.weekPrefix}${isoWeekNumber(start)}`,
1009
+ sublabel: range,
1010
+ title: `${o.weekPrefix}${isoWeekNumber(start)} ${isoWeekYear(start)} · ${range}`,
1011
+ };
1012
+ }
1013
+ case 'month':
1014
+ return {
1015
+ label: capitalizeFirst(f.monthShort.format(start)),
1016
+ title: capitalizeFirst(`${f.monthLong.format(start)} ${start.getFullYear()}`),
1017
+ };
1018
+ case 'quarter':
1019
+ return { label: `Q${quarterOf(start)}`, title: `Q${quarterOf(start)} ${start.getFullYear()}` };
1020
+ case 'year':
1021
+ return { label: String(start.getFullYear()), title: String(start.getFullYear()) };
1022
+ }
1023
+ }
1024
+ function bandLabels(start, unit, f) {
1025
+ switch (unit) {
1026
+ case 'hour':
1027
+ return { label: f.hour.format(start) };
1028
+ case 'day':
1029
+ return { label: capitalizeFirst(f.dayLong.format(start)) };
1030
+ case 'week':
1031
+ return { label: `W${isoWeekNumber(start)}`, sublabel: capitalizeFirst(f.monthLong.format(start)) };
1032
+ case 'month':
1033
+ return { label: capitalizeFirst(f.monthLong.format(start)), sublabel: String(start.getFullYear()) };
1034
+ case 'quarter':
1035
+ return { label: `Q${quarterOf(start)}`, sublabel: String(start.getFullYear()) };
1036
+ case 'year':
1037
+ return { label: String(start.getFullYear()) };
1038
+ }
1039
+ }
1040
+ /**
1041
+ * Rok kolumny użyty do wykrycia granicy roku.
1042
+ * Przy zoomie tygodniowym musi to być rok tygodnia ISO — inaczej W53/2026
1043
+ * (28.12.2026) dostałaby błędnie znacznik „2027".
1044
+ */
1045
+ function yearOfCell(date, zoom) {
1046
+ return zoom === 'week' ? isoWeekYear(date) : date.getFullYear();
1047
+ }
1048
+ /**
1049
+ * Początek następnej kolumny albo `null`, gdy kursor nie potrafi się już posunąć.
1050
+ * DST wiosną przy zoomie godzinowym: 02:00 nie istnieje, więc `setHours`
1051
+ * normalizuje na 03:00 i zwykły krok mógłby nie wyprzedzić `cursor` — wtedy
1052
+ * przeskakujemy o dwie godziny.
1053
+ */
1054
+ function nextBoundary(cursor, zoom) {
1055
+ const next = addUnits(cursor, zoom, 1);
1056
+ if (next.getTime() > cursor.getTime())
1057
+ return next;
1058
+ const jumped = addHours(cursor, 2);
1059
+ return jumped.getTime() > cursor.getTime() ? jumped : null;
1060
+ }
1061
+ /**
1062
+ * Czy kolumna obejmuje „dziś"? Dla jednostek grubszych niż tydzień akceptujemy
1063
+ * dodatkowo zgodność samej daty — `todayTs` bywa znormalizowane do północy.
1064
+ */
1065
+ function isTodayCell(cursor, next, zoom, o, today) {
1066
+ const withinSpan = o.todayTs >= cursor.getTime() && o.todayTs < next.getTime();
1067
+ if (zoom === 'hour' || zoom === 'day' || zoom === 'week')
1068
+ return withinSpan;
1069
+ return withinSpan || isSameDay(cursor, today);
1070
+ }
1071
+ /** Flagi wyglądu kolumny: tło weekendu/godzin niepracujących i granice okresów. */
1072
+ function cellDecorations(cursor, zoom, o) {
1073
+ const isDayGrid = zoom === 'day' || zoom === 'hour';
1074
+ return {
1075
+ isWeekend: isDayGrid && isWeekendDay(cursor, o),
1076
+ isNonWorking: o.workHours?.mode === 'dim' && !isWorkingSlot(cursor, o),
1077
+ isMonthBoundary: isDayGrid && cursor.getDate() === 1,
1078
+ isQuarterBoundary: (zoom === 'month' || zoom === 'quarter') && cursor.getMonth() % 3 === 0,
1079
+ };
1080
+ }
1081
+ function buildCells(range, zoom, o, f) {
1082
+ const cells = [];
1083
+ const today = new Date(o.todayTs);
1084
+ // Startujemy od granicy jednostki — inaczej pierwsza kolumna byłaby ucięta.
1085
+ let cursor = floorToUnit(new Date(range.start), zoom);
1086
+ let pendingGap = false;
1087
+ let previousYear = null;
1088
+ let guard = 0;
1089
+ while (cursor.getTime() < range.end && guard++ < o.maxCells) {
1090
+ const next = nextBoundary(cursor, zoom);
1091
+ if (next === null)
1092
+ break;
1093
+ if (o.workHours?.mode === 'skip' && !isWorkingSlot(cursor, o)) {
1094
+ pendingGap = true;
1095
+ cursor = next;
1096
+ continue;
1097
+ }
1098
+ const year = yearOfCell(cursor, zoom);
1099
+ const labels = cellLabels(cursor, zoom, o, f);
1100
+ cells.push({
1101
+ index: cells.length,
1102
+ key: String(cursor.getTime()),
1103
+ start: cursor.getTime(),
1104
+ end: next.getTime(),
1105
+ label: labels.label,
1106
+ sublabel: labels.sublabel,
1107
+ title: labels.title,
1108
+ ratioStart: 0,
1109
+ ratioEnd: 0,
1110
+ isToday: isTodayCell(cursor, next, zoom, o, today),
1111
+ ...cellDecorations(cursor, zoom, o),
1112
+ isYearBoundary: previousYear !== null && year !== previousYear,
1113
+ hasGapBefore: pendingGap,
1114
+ });
1115
+ pendingGap = false;
1116
+ previousYear = year;
1117
+ cursor = next;
1118
+ }
1119
+ return cells;
1120
+ }
1121
+ function applyRatios(cells, mode, range) {
1122
+ const count = cells.length;
1123
+ if (count === 0)
1124
+ return;
1125
+ if (mode === 'uniform') {
1126
+ const step = 1 / count;
1127
+ for (const cell of cells) {
1128
+ cell.ratioStart = cell.index * step;
1129
+ cell.ratioEnd = (cell.index + 1) * step;
1130
+ }
1131
+ return;
1132
+ }
1133
+ const scale = linearScale(range.start, range.end, 0, 1);
1134
+ for (const cell of cells) {
1135
+ cell.ratioStart = clamp(scale(cell.start), 0, 1);
1136
+ cell.ratioEnd = clamp(scale(cell.end), 0, 1);
1137
+ }
1138
+ }
1139
+ /** Indeks kolumny zawierającej `ts`, albo pierwszej kolumny po `ts`. */
1140
+ function findCell(cells, ts) {
1141
+ let lo = 0;
1142
+ let hi = cells.length - 1;
1143
+ while (lo < hi) {
1144
+ const mid = (lo + hi) >> 1;
1145
+ if (ts >= cells[mid].end)
1146
+ lo = mid + 1;
1147
+ else
1148
+ hi = mid;
1149
+ }
1150
+ return lo;
1151
+ }
1152
+ function buildBands(cells, zoom, o, f) {
1153
+ const unit = o.bandUnit;
1154
+ if (unit === 'none' || cells.length === 0)
1155
+ return [];
1156
+ const bands = [];
1157
+ let seenYear = null;
1158
+ for (const cell of cells) {
1159
+ const anchor = bandAnchor(new Date(cell.start), zoom);
1160
+ const bandStart = floorToUnit(anchor, unit);
1161
+ const key = String(bandStart.getTime());
1162
+ const last = bands.at(-1);
1163
+ if (last?.key === key) {
1164
+ last.toIndex = cell.index;
1165
+ last.span += 1;
1166
+ last.ratioEnd = cell.ratioEnd;
1167
+ last.end = cell.end;
1168
+ continue;
1169
+ }
1170
+ const labels = bandLabels(bandStart, unit, f);
1171
+ const year = unit === 'week' ? isoWeekYear(bandStart) : bandStart.getFullYear();
1172
+ const isYearStart = seenYear === null ? bands.length === 0 : year !== seenYear;
1173
+ seenYear = year;
1174
+ bands.push({
1175
+ key,
1176
+ start: cell.start,
1177
+ end: cell.end,
1178
+ fromIndex: cell.index,
1179
+ toIndex: cell.index,
1180
+ span: 1,
1181
+ ratioStart: cell.ratioStart,
1182
+ ratioEnd: cell.ratioEnd,
1183
+ label: labels.label,
1184
+ sublabel: labels.sublabel,
1185
+ isYearStart,
1186
+ // Znacznik roku niesie tylko pierwsze pasmo danego roku.
1187
+ yearBadge: isYearStart ? String(year) : undefined,
1188
+ });
1189
+ }
1190
+ return bands;
1191
+ }
1192
+ /**
1193
+ * Buduje model osi czasu: kolumny, pasma nagłówka i skalę.
1194
+ * Wszystkie obliczenia w czasie LOKALNYM przeglądarki.
1195
+ *
1196
+ * @param range zakres osi w ms — zwykle wynik `resolveRange`
1197
+ * @param zoom poziom przybliżenia
1198
+ * @param opts locale, „dziś", weekendy, godziny robocze, tryb pozycjonowania
1199
+ */
1200
+ function buildTimeline(range, zoom, opts) {
1201
+ const o = resolveOptions(zoom, opts);
1202
+ const f = makeFormatters(o.locale);
1203
+ const cells = buildCells(range, zoom, o, f);
1204
+ applyRatios(cells, o.positionMode, range);
1205
+ const bands = buildBands(cells, zoom, o, f);
1206
+ const count = cells.length;
1207
+ const first = cells[0];
1208
+ const last = cells[count - 1];
1209
+ /** Czas → 0..1. Skala odcinkowa: indeks kolumny + interpolacja w jej wnętrzu. */
1210
+ const toRatio = (ts) => {
1211
+ if (count === 0)
1212
+ return 0;
1213
+ if (o.positionMode === 'linear') {
1214
+ return clamp(linearScale(range.start, range.end, 0, 1)(ts), 0, 1);
1215
+ }
1216
+ if (ts <= first.start)
1217
+ return 0;
1218
+ if (ts >= last.end)
1219
+ return 1;
1220
+ const index = findCell(cells, ts);
1221
+ const cell = cells[index];
1222
+ // `ts` może wpaść w lukę wyciętą przez `workHours: 'skip'` — wtedy
1223
+ // interpolacja wychodzi poza [0, 1] i clamp dociąga ją do krawędzi kolumny.
1224
+ const inner = clamp(linearScale(cell.start, cell.end, 0, 1)(ts), 0, 1);
1225
+ return (index + inner) / count;
1226
+ };
1227
+ const fromRatio = (ratio) => {
1228
+ if (count === 0)
1229
+ return range.start;
1230
+ if (o.positionMode === 'linear') {
1231
+ return range.start + clamp(ratio, 0, 1) * (range.end - range.start);
1232
+ }
1233
+ const scaled = clamp(ratio, 0, 1) * count;
1234
+ const index = Math.min(count - 1, Math.floor(scaled));
1235
+ const cell = cells[index];
1236
+ // Poprawnie odwzorowuje także dobę 25-godzinną — długość bierzemy z kolumny.
1237
+ return cell.start + (scaled - index) * (cell.end - cell.start);
1238
+ };
1239
+ const span = (startTs, endTs) => {
1240
+ const lo = Math.min(startTs, endTs);
1241
+ const hi = Math.max(startTs, endTs);
1242
+ const axisStart = count > 0 ? first.start : range.start;
1243
+ const axisEnd = count > 0 ? last.end : range.end;
1244
+ const left = toRatio(lo);
1245
+ const right = toRatio(hi);
1246
+ return {
1247
+ left,
1248
+ width: Math.max(0, right - left),
1249
+ clippedStart: lo < axisStart,
1250
+ clippedEnd: hi > axisEnd,
1251
+ // Kamień milowy ma zerową szerokość, więc sam dotyk krawędzi wystarczy.
1252
+ visible: hi >= axisStart && lo <= axisEnd,
1253
+ };
1254
+ };
1255
+ const todayInRange = count > 0 && o.todayTs >= first.start && o.todayTs <= last.end;
1256
+ return {
1257
+ zoom,
1258
+ range,
1259
+ positionMode: o.positionMode,
1260
+ cells,
1261
+ bands,
1262
+ hasBands: bands.length > 0,
1263
+ todayRatio: todayInRange ? toRatio(o.todayTs) : null,
1264
+ toRatio,
1265
+ fromRatio,
1266
+ span,
1267
+ };
1268
+ }
1269
+
1270
+ const _c0 = ["scrollEl"];
1271
+ const _c1 = ["laneMeasure"];
1272
+ const _forTrack0 = ($index, $item) => $item.key;
1273
+ const _forTrack1 = ($index, $item) => $item.item.id;
1274
+ function KptRoadmap_Conditional_5_For_2_Conditional_1_Template(rf, ctx) { if (rf & 1) {
1275
+ i0.ɵɵelementStart(0, "span", 14);
1276
+ i0.ɵɵtext(1);
1277
+ i0.ɵɵelementEnd();
1278
+ } if (rf & 2) {
1279
+ const band_r1 = i0.ɵɵnextContext().$implicit;
1280
+ i0.ɵɵadvance();
1281
+ i0.ɵɵtextInterpolate(band_r1.yearBadge);
1282
+ } }
1283
+ function KptRoadmap_Conditional_5_For_2_Conditional_4_Template(rf, ctx) { if (rf & 1) {
1284
+ i0.ɵɵelementStart(0, "span", 16);
1285
+ i0.ɵɵtext(1);
1286
+ i0.ɵɵelementEnd();
1287
+ } if (rf & 2) {
1288
+ const band_r1 = i0.ɵɵnextContext().$implicit;
1289
+ i0.ɵɵadvance();
1290
+ i0.ɵɵtextInterpolate(band_r1.sublabel);
1291
+ } }
1292
+ function KptRoadmap_Conditional_5_For_2_Template(rf, ctx) { if (rf & 1) {
1293
+ i0.ɵɵelementStart(0, "div", 13);
1294
+ i0.ɵɵconditionalCreate(1, KptRoadmap_Conditional_5_For_2_Conditional_1_Template, 2, 1, "span", 14);
1295
+ i0.ɵɵelementStart(2, "span", 15);
1296
+ i0.ɵɵtext(3);
1297
+ i0.ɵɵelementEnd();
1298
+ i0.ɵɵconditionalCreate(4, KptRoadmap_Conditional_5_For_2_Conditional_4_Template, 2, 1, "span", 16);
1299
+ i0.ɵɵelementEnd();
1300
+ } if (rf & 2) {
1301
+ const band_r1 = ctx.$implicit;
1302
+ i0.ɵɵstyleProp("inline-size", (band_r1.ratioEnd - band_r1.ratioStart) * 100, "%");
1303
+ i0.ɵɵclassProp("kpt-roadmap__band--year-start", band_r1.isYearStart);
1304
+ i0.ɵɵproperty("kptTooltip", band_r1.sublabel ? band_r1.label + " " + band_r1.sublabel : band_r1.label);
1305
+ i0.ɵɵadvance();
1306
+ i0.ɵɵconditional(band_r1.yearBadge ? 1 : -1);
1307
+ i0.ɵɵadvance(2);
1308
+ i0.ɵɵtextInterpolate(band_r1.label);
1309
+ i0.ɵɵadvance();
1310
+ i0.ɵɵconditional(band_r1.sublabel && !band_r1.yearBadge ? 4 : -1);
1311
+ } }
1312
+ function KptRoadmap_Conditional_5_Template(rf, ctx) { if (rf & 1) {
1313
+ i0.ɵɵelementStart(0, "div", 6);
1314
+ i0.ɵɵrepeaterCreate(1, KptRoadmap_Conditional_5_For_2_Template, 5, 8, "div", 12, _forTrack0);
1315
+ i0.ɵɵelementEnd();
1316
+ } if (rf & 2) {
1317
+ const ctx_r1 = i0.ɵɵnextContext();
1318
+ i0.ɵɵadvance();
1319
+ i0.ɵɵrepeater(ctx_r1.timeline().bands);
1320
+ } }
1321
+ function KptRoadmap_For_9_Conditional_3_Template(rf, ctx) { if (rf & 1) {
1322
+ i0.ɵɵelementStart(0, "span", 19);
1323
+ i0.ɵɵtext(1);
1324
+ i0.ɵɵelementEnd();
1325
+ } if (rf & 2) {
1326
+ const cell_r3 = i0.ɵɵnextContext().$implicit;
1327
+ i0.ɵɵadvance();
1328
+ i0.ɵɵtextInterpolate(cell_r3.sublabel);
1329
+ } }
1330
+ function KptRoadmap_For_9_Template(rf, ctx) { if (rf & 1) {
1331
+ i0.ɵɵelementStart(0, "div", 17)(1, "span", 18);
1332
+ i0.ɵɵtext(2);
1333
+ i0.ɵɵelementEnd();
1334
+ i0.ɵɵconditionalCreate(3, KptRoadmap_For_9_Conditional_3_Template, 2, 1, "span", 19);
1335
+ i0.ɵɵelementEnd();
1336
+ } if (rf & 2) {
1337
+ const cell_r3 = ctx.$implicit;
1338
+ const ctx_r1 = i0.ɵɵnextContext();
1339
+ i0.ɵɵstyleProp("inline-size", (cell_r3.ratioEnd - cell_r3.ratioStart) * 100, "%");
1340
+ i0.ɵɵclassProp("kpt-roadmap__cell--weekend", cell_r3.isWeekend)("kpt-roadmap__cell--today", cell_r3.isToday && ctx_r1.showToday())("kpt-roadmap__cell--year-boundary", cell_r3.isYearBoundary)("kpt-roadmap__cell--gap-before", cell_r3.hasGapBefore);
1341
+ i0.ɵɵproperty("kptTooltip", cell_r3.title);
1342
+ i0.ɵɵadvance(2);
1343
+ i0.ɵɵtextInterpolate(cell_r3.label);
1344
+ i0.ɵɵadvance();
1345
+ i0.ɵɵconditional(cell_r3.sublabel ? 3 : -1);
1346
+ } }
1347
+ function KptRoadmap_Conditional_10_Template(rf, ctx) { if (rf & 1) {
1348
+ i0.ɵɵelement(0, "span", 20);
1349
+ } if (rf & 2) {
1350
+ const ctx_r1 = i0.ɵɵnextContext();
1351
+ i0.ɵɵstyleProp("inset-inline-start", ctx_r1.timeline().todayRatio * 100, "%");
1352
+ i0.ɵɵattribute("title", ctx_r1.i18n.t("roadmap.today"));
1353
+ } }
1354
+ function KptRoadmap_Conditional_11_Template(rf, ctx) { if (rf & 1) {
1355
+ i0.ɵɵelementStart(0, "div", 10);
1356
+ i0.ɵɵtext(1);
1357
+ i0.ɵɵelementEnd();
1358
+ } if (rf & 2) {
1359
+ const ctx_r1 = i0.ɵɵnextContext();
1360
+ i0.ɵɵadvance();
1361
+ i0.ɵɵtextInterpolate(ctx_r1.i18n.t("roadmap.empty"));
1362
+ } }
1363
+ function KptRoadmap_Conditional_12_For_4_Template(rf, ctx) { if (rf & 1) {
1364
+ i0.ɵɵelement(0, "div", 28);
1365
+ } if (rf & 2) {
1366
+ const cell_r4 = ctx.$implicit;
1367
+ i0.ɵɵstyleProp("inline-size", (cell_r4.ratioEnd - cell_r4.ratioStart) * 100, "%");
1368
+ i0.ɵɵclassProp("kpt-roadmap__grid-cell--weekend", cell_r4.isWeekend)("kpt-roadmap__grid-cell--non-working", cell_r4.isNonWorking)("kpt-roadmap__grid-cell--year-boundary", cell_r4.isYearBoundary)("kpt-roadmap__grid-cell--gap-before", cell_r4.hasGapBefore);
1369
+ } }
1370
+ function KptRoadmap_Conditional_12_Conditional_5_Template(rf, ctx) { if (rf & 1) {
1371
+ i0.ɵɵelement(0, "div", 29);
1372
+ } if (rf & 2) {
1373
+ const ctx_r1 = i0.ɵɵnextContext(2);
1374
+ i0.ɵɵstyleProp("inset-inline-start", ctx_r1.timeline().todayRatio * 100, "%");
1375
+ } }
1376
+ function KptRoadmap_Conditional_12_Conditional_6_For_2_Template(rf, ctx) { if (rf & 1) {
1377
+ i0.ɵɵnamespaceSVG();
1378
+ i0.ɵɵelementStart(0, "g", 31);
1379
+ i0.ɵɵelement(1, "path", 32)(2, "path", 33);
1380
+ i0.ɵɵelementEnd();
1381
+ } if (rf & 2) {
1382
+ const link_r5 = ctx.$implicit;
1383
+ i0.ɵɵclassProp("kpt-roadmap__link--violated", link_r5.isViolated);
1384
+ i0.ɵɵattribute("data-variant", link_r5.variant);
1385
+ i0.ɵɵadvance();
1386
+ i0.ɵɵstyleProp("stroke", link_r5.color);
1387
+ i0.ɵɵattribute("d", link_r5.path);
1388
+ i0.ɵɵadvance();
1389
+ i0.ɵɵstyleProp("fill", link_r5.color);
1390
+ i0.ɵɵattribute("d", link_r5.arrow);
1391
+ } }
1392
+ function KptRoadmap_Conditional_12_Conditional_6_Template(rf, ctx) { if (rf & 1) {
1393
+ i0.ɵɵnamespaceSVG();
1394
+ i0.ɵɵelementStart(0, "svg", 26);
1395
+ i0.ɵɵrepeaterCreate(1, KptRoadmap_Conditional_12_Conditional_6_For_2_Template, 3, 9, ":svg:g", 30, _forTrack0);
1396
+ i0.ɵɵelementEnd();
1397
+ } if (rf & 2) {
1398
+ const ctx_r1 = i0.ɵɵnextContext(2);
1399
+ i0.ɵɵattribute("viewBox", "0 0 " + ctx_r1.laneWidth() + " " + ctx_r1.layout().totalHeight)("width", ctx_r1.laneWidth())("height", ctx_r1.layout().totalHeight);
1400
+ i0.ɵɵadvance();
1401
+ i0.ɵɵrepeater(ctx_r1.links());
1402
+ } }
1403
+ function KptRoadmap_Conditional_12_For_8_Conditional_2_Conditional_5_Template(rf, ctx) { if (rf & 1) {
1404
+ i0.ɵɵelementStart(0, "span", 41);
1405
+ i0.ɵɵtext(1);
1406
+ i0.ɵɵelementEnd();
1407
+ } if (rf & 2) {
1408
+ const group_r7 = i0.ɵɵnextContext();
1409
+ i0.ɵɵadvance();
1410
+ i0.ɵɵtextInterpolate(group_r7.sublabel);
1411
+ } }
1412
+ function KptRoadmap_Conditional_12_For_8_Conditional_2_Conditional_6_Conditional_0_Template(rf, ctx) { if (rf & 1) {
1413
+ i0.ɵɵelementStart(0, "span", 42);
1414
+ i0.ɵɵtext(1);
1415
+ i0.ɵɵelementEnd();
1416
+ } if (rf & 2) {
1417
+ const state_r8 = i0.ɵɵnextContext();
1418
+ i0.ɵɵadvance();
1419
+ i0.ɵɵtextInterpolate(state_r8.itemCount);
1420
+ } }
1421
+ function KptRoadmap_Conditional_12_For_8_Conditional_2_Conditional_6_Template(rf, ctx) { if (rf & 1) {
1422
+ i0.ɵɵconditionalCreate(0, KptRoadmap_Conditional_12_For_8_Conditional_2_Conditional_6_Conditional_0_Template, 2, 1, "span", 42);
1423
+ } if (rf & 2) {
1424
+ i0.ɵɵconditional(ctx.itemCount ? 0 : -1);
1425
+ } }
1426
+ function KptRoadmap_Conditional_12_For_8_Conditional_2_Template(rf, ctx) { if (rf & 1) {
1427
+ const _r6 = i0.ɵɵgetCurrentView();
1428
+ i0.ɵɵelementStart(0, "button", 37);
1429
+ i0.ɵɵlistener("click", function KptRoadmap_Conditional_12_For_8_Conditional_2_Template_button_click_0_listener() { const group_r7 = i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.toggleGroup(group_r7)); });
1430
+ i0.ɵɵelement(1, "span", 38);
1431
+ i0.ɵɵelementEnd();
1432
+ i0.ɵɵelementStart(2, "span", 39)(3, "span", 40);
1433
+ i0.ɵɵtext(4);
1434
+ i0.ɵɵelementEnd();
1435
+ i0.ɵɵconditionalCreate(5, KptRoadmap_Conditional_12_For_8_Conditional_2_Conditional_5_Template, 2, 1, "span", 41);
1436
+ i0.ɵɵelementEnd();
1437
+ i0.ɵɵconditionalCreate(6, KptRoadmap_Conditional_12_For_8_Conditional_2_Conditional_6_Template, 1, 1);
1438
+ } if (rf & 2) {
1439
+ let tmp_21_0;
1440
+ const group_r7 = ctx;
1441
+ const row_r9 = i0.ɵɵnextContext().$implicit;
1442
+ const ctx_r1 = i0.ɵɵnextContext(2);
1443
+ i0.ɵɵattribute("aria-expanded", !ctx_r1.isCollapsed(group_r7))("aria-label", ctx_r1.isCollapsed(group_r7) ? ctx_r1.i18n.t("roadmap.expandGroup") : ctx_r1.i18n.t("roadmap.collapseGroup"));
1444
+ i0.ɵɵadvance();
1445
+ i0.ɵɵclassProp("kpt-roadmap__toggle-icon--open", !ctx_r1.isCollapsed(group_r7));
1446
+ i0.ɵɵadvance();
1447
+ i0.ɵɵproperty("kptTooltip", group_r7.sublabel ? group_r7.label + " \u2014 " + group_r7.sublabel : group_r7.label);
1448
+ i0.ɵɵadvance(2);
1449
+ i0.ɵɵtextInterpolate(group_r7.label);
1450
+ i0.ɵɵadvance();
1451
+ i0.ɵɵconditional(group_r7.sublabel ? 5 : -1);
1452
+ i0.ɵɵadvance();
1453
+ i0.ɵɵconditional((tmp_21_0 = row_r9.groupState) ? 6 : -1, tmp_21_0);
1454
+ } }
1455
+ function KptRoadmap_Conditional_12_For_8_Conditional_3_Conditional_2_Template(rf, ctx) { if (rf & 1) {
1456
+ i0.ɵɵelementStart(0, "span", 41);
1457
+ i0.ɵɵtext(1);
1458
+ i0.ɵɵelementEnd();
1459
+ } if (rf & 2) {
1460
+ const first_r10 = i0.ɵɵnextContext();
1461
+ i0.ɵɵadvance();
1462
+ i0.ɵɵtextInterpolate(first_r10.source.sublabel);
1463
+ } }
1464
+ function KptRoadmap_Conditional_12_For_8_Conditional_3_Template(rf, ctx) { if (rf & 1) {
1465
+ i0.ɵɵelementStart(0, "span", 43);
1466
+ i0.ɵɵtext(1);
1467
+ i0.ɵɵelementEnd();
1468
+ i0.ɵɵconditionalCreate(2, KptRoadmap_Conditional_12_For_8_Conditional_3_Conditional_2_Template, 2, 1, "span", 41);
1469
+ } if (rf & 2) {
1470
+ const first_r10 = ctx;
1471
+ const row_r9 = i0.ɵɵnextContext().$implicit;
1472
+ i0.ɵɵadvance();
1473
+ i0.ɵɵtextInterpolate(first_r10.source.label);
1474
+ i0.ɵɵadvance();
1475
+ i0.ɵɵconditional(row_r9.items.length === 1 && first_r10.source.sublabel ? 2 : -1);
1476
+ } }
1477
+ function KptRoadmap_Conditional_12_For_8_Conditional_5_Conditional_0_Template(rf, ctx) { if (rf & 1) {
1478
+ i0.ɵɵelement(0, "div", 45);
1479
+ } if (rf & 2) {
1480
+ const sum_r11 = ctx;
1481
+ i0.ɵɵstyleProp("inset-inline-start", sum_r11.left, "%")("inline-size", sum_r11.width, "%")("background", sum_r11.color);
1482
+ i0.ɵɵattribute("data-variant", sum_r11.variant);
1483
+ } }
1484
+ function KptRoadmap_Conditional_12_For_8_Conditional_5_Template(rf, ctx) { if (rf & 1) {
1485
+ i0.ɵɵconditionalCreate(0, KptRoadmap_Conditional_12_For_8_Conditional_5_Conditional_0_Template, 1, 7, "div", 44);
1486
+ } if (rf & 2) {
1487
+ let tmp_14_0;
1488
+ const row_r9 = i0.ɵɵnextContext().$implicit;
1489
+ const ctx_r1 = i0.ɵɵnextContext(2);
1490
+ i0.ɵɵconditional((tmp_14_0 = ctx_r1.summary(row_r9)) ? 0 : -1, tmp_14_0);
1491
+ } }
1492
+ function KptRoadmap_Conditional_12_For_8_Conditional_6_For_1_Conditional_0_Template(rf, ctx) { if (rf & 1) {
1493
+ const _r12 = i0.ɵɵgetCurrentView();
1494
+ i0.ɵɵelementStart(0, "div", 48);
1495
+ i0.ɵɵlistener("click", function KptRoadmap_Conditional_12_For_8_Conditional_6_For_1_Conditional_0_Template_div_click_0_listener() { i0.ɵɵrestoreView(_r12); const bar_r13 = i0.ɵɵnextContext().$implicit; const ctx_r1 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r1.onItemClick(bar_r13.item)); })("keydown", function KptRoadmap_Conditional_12_For_8_Conditional_6_For_1_Conditional_0_Template_div_keydown_0_listener($event) { i0.ɵɵrestoreView(_r12); const bar_r13 = i0.ɵɵnextContext().$implicit; const ctx_r1 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r1.onItemKeydown($event, bar_r13.item)); });
1496
+ i0.ɵɵelementEnd();
1497
+ } if (rf & 2) {
1498
+ const bar_r13 = i0.ɵɵnextContext().$implicit;
1499
+ const ctx_r1 = i0.ɵɵnextContext(4);
1500
+ i0.ɵɵstyleProp("inset-inline-start", bar_r13.left, "%")("inset-block-start", bar_r13.top, "px")("inline-size", bar_r13.height, "px")("block-size", bar_r13.height, "px")("background", bar_r13.color);
1501
+ i0.ɵɵproperty("kptTooltip", bar_r13.title);
1502
+ i0.ɵɵattribute("data-variant", bar_r13.variant)("aria-label", ctx_r1.i18n.t("roadmap.milestone") + ": " + bar_r13.label);
1503
+ } }
1504
+ function KptRoadmap_Conditional_12_For_8_Conditional_6_For_1_Conditional_1_Conditional_1_Template(rf, ctx) { if (rf & 1) {
1505
+ i0.ɵɵelement(0, "span", 52);
1506
+ } if (rf & 2) {
1507
+ const bar_r13 = i0.ɵɵnextContext(2).$implicit;
1508
+ i0.ɵɵstyleProp("inline-size", bar_r13.progress * 100, "%");
1509
+ } }
1510
+ function KptRoadmap_Conditional_12_For_8_Conditional_6_For_1_Conditional_1_Template(rf, ctx) { if (rf & 1) {
1511
+ const _r14 = i0.ɵɵgetCurrentView();
1512
+ i0.ɵɵelementStart(0, "div", 49);
1513
+ i0.ɵɵlistener("click", function KptRoadmap_Conditional_12_For_8_Conditional_6_For_1_Conditional_1_Template_div_click_0_listener() { i0.ɵɵrestoreView(_r14); const bar_r13 = i0.ɵɵnextContext().$implicit; const ctx_r1 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r1.onItemClick(bar_r13.item)); })("keydown", function KptRoadmap_Conditional_12_For_8_Conditional_6_For_1_Conditional_1_Template_div_keydown_0_listener($event) { i0.ɵɵrestoreView(_r14); const bar_r13 = i0.ɵɵnextContext().$implicit; const ctx_r1 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r1.onItemKeydown($event, bar_r13.item)); });
1514
+ i0.ɵɵconditionalCreate(1, KptRoadmap_Conditional_12_For_8_Conditional_6_For_1_Conditional_1_Conditional_1_Template, 1, 2, "span", 50);
1515
+ i0.ɵɵelementStart(2, "span", 51);
1516
+ i0.ɵɵtext(3);
1517
+ i0.ɵɵelementEnd()();
1518
+ } if (rf & 2) {
1519
+ const bar_r13 = i0.ɵɵnextContext().$implicit;
1520
+ i0.ɵɵstyleProp("inset-inline-start", bar_r13.left, "%")("inline-size", bar_r13.width, "%")("inset-block-start", bar_r13.top, "px")("block-size", bar_r13.height, "px")("background", bar_r13.color);
1521
+ i0.ɵɵclassProp("kpt-roadmap__bar--clipped-start", bar_r13.clippedStart)("kpt-roadmap__bar--clipped-end", bar_r13.clippedEnd)("kpt-roadmap__bar--disabled", bar_r13.item.source.disabled);
1522
+ i0.ɵɵproperty("kptTooltip", bar_r13.title);
1523
+ i0.ɵɵattribute("data-variant", bar_r13.variant);
1524
+ i0.ɵɵadvance();
1525
+ i0.ɵɵconditional(bar_r13.progress !== null ? 1 : -1);
1526
+ i0.ɵɵadvance(2);
1527
+ i0.ɵɵtextInterpolate(bar_r13.label);
1528
+ } }
1529
+ function KptRoadmap_Conditional_12_For_8_Conditional_6_For_1_Template(rf, ctx) { if (rf & 1) {
1530
+ i0.ɵɵconditionalCreate(0, KptRoadmap_Conditional_12_For_8_Conditional_6_For_1_Conditional_0_Template, 1, 13, "div", 46)(1, KptRoadmap_Conditional_12_For_8_Conditional_6_For_1_Conditional_1_Template, 4, 20, "div", 47);
1531
+ } if (rf & 2) {
1532
+ const bar_r13 = ctx.$implicit;
1533
+ i0.ɵɵconditional(bar_r13.isMilestone ? 0 : 1);
1534
+ } }
1535
+ function KptRoadmap_Conditional_12_For_8_Conditional_6_Template(rf, ctx) { if (rf & 1) {
1536
+ i0.ɵɵrepeaterCreate(0, KptRoadmap_Conditional_12_For_8_Conditional_6_For_1_Template, 2, 1, null, null, _forTrack1);
1537
+ } if (rf & 2) {
1538
+ const row_r9 = i0.ɵɵnextContext().$implicit;
1539
+ const ctx_r1 = i0.ɵɵnextContext(2);
1540
+ i0.ɵɵrepeater(ctx_r1.bars(row_r9));
1541
+ } }
1542
+ function KptRoadmap_Conditional_12_For_8_Template(rf, ctx) { if (rf & 1) {
1543
+ i0.ɵɵelementStart(0, "div", 34)(1, "div", 35);
1544
+ i0.ɵɵconditionalCreate(2, KptRoadmap_Conditional_12_For_8_Conditional_2_Template, 7, 8)(3, KptRoadmap_Conditional_12_For_8_Conditional_3_Template, 3, 2);
1545
+ i0.ɵɵelementEnd();
1546
+ i0.ɵɵelementStart(4, "div", 36);
1547
+ i0.ɵɵconditionalCreate(5, KptRoadmap_Conditional_12_For_8_Conditional_5_Template, 1, 1)(6, KptRoadmap_Conditional_12_For_8_Conditional_6_Template, 2, 0);
1548
+ i0.ɵɵelementEnd()();
1549
+ } if (rf & 2) {
1550
+ let tmp_19_0;
1551
+ const row_r9 = ctx.$implicit;
1552
+ const ctx_r1 = i0.ɵɵnextContext(2);
1553
+ i0.ɵɵstyleProp("inset-block-start", row_r9.top, "px")("block-size", row_r9.height, "px");
1554
+ i0.ɵɵclassProp("kpt-roadmap__row--group", row_r9.kind === "group");
1555
+ i0.ɵɵadvance();
1556
+ i0.ɵɵstyleProp("padding-inline-start", 0.75 + row_r9.depth * 1, "rem");
1557
+ i0.ɵɵclassProp("kpt-roadmap__label--group", row_r9.kind === "group");
1558
+ i0.ɵɵproperty("kptTooltip", ctx_r1.labelTooltip(row_r9));
1559
+ i0.ɵɵadvance();
1560
+ i0.ɵɵconditional((tmp_19_0 = row_r9.group) ? 2 : (tmp_19_0 = row_r9.items[0]) ? 3 : -1, tmp_19_0);
1561
+ i0.ɵɵadvance(3);
1562
+ i0.ɵɵconditional(row_r9.kind === "group" ? 5 : 6);
1563
+ } }
1564
+ function KptRoadmap_Conditional_12_Template(rf, ctx) { if (rf & 1) {
1565
+ i0.ɵɵelementStart(0, "div", 21)(1, "div", 22)(2, "div", 23);
1566
+ i0.ɵɵrepeaterCreate(3, KptRoadmap_Conditional_12_For_4_Template, 1, 10, "div", 24, _forTrack0);
1567
+ i0.ɵɵelementEnd();
1568
+ i0.ɵɵconditionalCreate(5, KptRoadmap_Conditional_12_Conditional_5_Template, 1, 2, "div", 25);
1569
+ i0.ɵɵelementEnd();
1570
+ i0.ɵɵconditionalCreate(6, KptRoadmap_Conditional_12_Conditional_6_Template, 3, 3, ":svg:svg", 26);
1571
+ i0.ɵɵrepeaterCreate(7, KptRoadmap_Conditional_12_For_8_Template, 7, 13, "div", 27, _forTrack0);
1572
+ i0.ɵɵelementEnd();
1573
+ } if (rf & 2) {
1574
+ const ctx_r1 = i0.ɵɵnextContext();
1575
+ i0.ɵɵstyleProp("block-size", ctx_r1.layout().totalHeight, "px");
1576
+ i0.ɵɵadvance(3);
1577
+ i0.ɵɵrepeater(ctx_r1.timeline().cells);
1578
+ i0.ɵɵadvance(2);
1579
+ i0.ɵɵconditional(ctx_r1.showToday() && ctx_r1.timeline().todayRatio !== null ? 5 : -1);
1580
+ i0.ɵɵadvance();
1581
+ i0.ɵɵconditional(ctx_r1.links().length > 0 ? 6 : -1);
1582
+ i0.ɵɵadvance();
1583
+ i0.ɵɵrepeater(ctx_r1.visibleRows());
1584
+ } }
1585
+ /** Domyślna szerokość kolumny dla danego zoomu (px). */
1586
+ const CELL_WIDTH = {
1587
+ year: 96,
1588
+ quarter: 88,
1589
+ month: 80,
1590
+ week: 76,
1591
+ day: 44,
1592
+ hour: 46,
1593
+ };
1594
+ /** Wysokość paska w trybie „jeden item na wiersz" (px). */
1595
+ const BAR_HEIGHT = 22;
1596
+ /** Odstęp między pasami w trybie „pack" (px). */
1597
+ const LANE_GAP = 4;
1598
+ /**
1599
+ * Roadmapa — wiersze zadań na poziomej osi czasu (widok typu Gantt).
1600
+ *
1601
+ * Układ opiera się na DOM i `position: sticky` (kolumna etykiet oraz nagłówek),
1602
+ * paski są pozycjonowane procentowo w torze, a zależności rysuje warstwa SVG
1603
+ * rozpięta nad torami. Cała geometria pochodzi z rdzenia w `../core`.
1604
+ */
1605
+ class KptRoadmap {
1606
+ constructor() {
1607
+ this.i18n = inject(KptI18n);
1608
+ /** Pełna konfiguracja: dane, zoom, zakres i opcje. */
1609
+ this.config = input.required(/* @ts-ignore */
1610
+ ...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
1611
+ /** Stała wysokość obszaru przewijania (np. '420px'). Włącza przyklejony nagłówek. */
1612
+ this.scrollHeight = input(null, /* @ts-ignore */
1613
+ ...(ngDevMode ? [{ debugName: "scrollHeight" }] : /* istanbul ignore next */ []));
1614
+ /** Szerokość kolumny etykiet. */
1615
+ this.labelWidth = input('14rem', /* @ts-ignore */
1616
+ ...(ngDevMode ? [{ debugName: "labelWidth" }] : /* istanbul ignore next */ []));
1617
+ /** Szerokość kolumny osi czasu; brak ⇒ wartość zależna od zoomu. */
1618
+ this.cellWidthPx = input(null, /* @ts-ignore */
1619
+ ...(ngDevMode ? [{ debugName: "cellWidthPx" }] : /* istanbul ignore next */ []));
1620
+ /** Znacznik „dziś". */
1621
+ this.showToday = input(true, { ...(ngDevMode ? { debugName: "showToday" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1622
+ /** Renderowanie tylko widocznych wierszy. Warto włączyć od ~100 wierszy. */
1623
+ this.virtual = input(false, { ...(ngDevMode ? { debugName: "virtual" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1624
+ /** Zwinięte grupy (dwukierunkowe). */
1625
+ this.collapsedGroups = model([], /* @ts-ignore */
1626
+ ...(ngDevMode ? [{ debugName: "collapsedGroups" }] : /* istanbul ignore next */ []));
1627
+ /**
1628
+ * Grupy zwinięte na starcie przez `group.collapsed`. Trzymamy je osobno od modelu,
1629
+ * żeby dwukierunkowe wiązanie pozostało w rękach aplikacji, a zwinięcie zadeklarowane
1630
+ * w danych nadal działało. Resetuje się, gdy zmieni się lista grup.
1631
+ */
1632
+ this.collapsedFromConfig = linkedSignal({ ...(ngDevMode ? { debugName: "collapsedFromConfig" } : /* istanbul ignore next */ {}), source: () => this.config().groups,
1633
+ computation: (groups) => (groups ?? []).filter((group) => group.collapsed).map((group) => group.id) });
1634
+ /** Suma obu źródeł — stan faktycznie użyty do budowy wierszy. */
1635
+ this.collapsedIds = computed(() => new Set([...this.collapsedFromConfig(), ...this.collapsedGroups()]), /* @ts-ignore */
1636
+ ...(ngDevMode ? [{ debugName: "collapsedIds" }] : /* istanbul ignore next */ []));
1637
+ this.itemClick = output();
1638
+ /** Emitowane przy każdej zmianie wyliczonego zakresu osi. */
1639
+ this.rangeChange = output();
1640
+ this.scrollEl = viewChild('scrollEl', /* @ts-ignore */
1641
+ ...(ngDevMode ? [{ debugName: "scrollEl" }] : /* istanbul ignore next */ []));
1642
+ this.laneEl = viewChild('laneMeasure', /* @ts-ignore */
1643
+ ...(ngDevMode ? [{ debugName: "laneEl" }] : /* istanbul ignore next */ []));
1644
+ this.scrollTop = signal(0, /* @ts-ignore */
1645
+ ...(ngDevMode ? [{ debugName: "scrollTop" }] : /* istanbul ignore next */ []));
1646
+ this.viewportHeight = signal(0, /* @ts-ignore */
1647
+ ...(ngDevMode ? [{ debugName: "viewportHeight" }] : /* istanbul ignore next */ []));
1648
+ /** Szerokość toru w px — potrzebna warstwie SVG, która pracuje w pikselach. */
1649
+ this.laneWidth = signal(0, /* @ts-ignore */
1650
+ ...(ngDevMode ? [{ debugName: "laneWidth" }] : /* istanbul ignore next */ []));
1651
+ this.zoom = computed(() => this.config().zoom ?? 'week', /* @ts-ignore */
1652
+ ...(ngDevMode ? [{ debugName: "zoom" }] : /* istanbul ignore next */ []));
1653
+ this.cellWidth = computed(() => this.cellWidthPx() ?? CELL_WIDTH[this.zoom()], /* @ts-ignore */
1654
+ ...(ngDevMode ? [{ debugName: "cellWidth" }] : /* istanbul ignore next */ []));
1655
+ this.range = computed(() => resolveRange(this.config(), this.config().items), /* @ts-ignore */
1656
+ ...(ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []));
1657
+ this.timeline = computed(() => {
1658
+ const config = this.config();
1659
+ return buildTimeline(this.range(), this.zoom(), {
1660
+ ...config.timeline,
1661
+ locale: config.locale ?? config.timeline?.locale ?? this.i18n.locale(),
1662
+ today: config.today ?? config.timeline?.today,
1663
+ weekPrefix: config.timeline?.weekPrefix ?? this.i18n.t('roadmap.week'),
1664
+ });
1665
+ }, /* @ts-ignore */
1666
+ ...(ngDevMode ? [{ debugName: "timeline" }] : /* istanbul ignore next */ []));
1667
+ this.layout = computed(() => {
1668
+ const config = this.config();
1669
+ return buildRows(config.items, config.groups, this.collapsedIds(), config.rows);
1670
+ }, /* @ts-ignore */
1671
+ ...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
1672
+ /** Wiersze faktycznie renderowane — całość albo okno wirtualizacji. */
1673
+ this.visibleRows = computed(() => {
1674
+ const layout = this.layout();
1675
+ if (!this.virtual())
1676
+ return layout.rows;
1677
+ const height = this.viewportHeight();
1678
+ if (height <= 0)
1679
+ return layout.rows;
1680
+ return sliceRows(layout, this.scrollTop(), height).rows;
1681
+ }, /* @ts-ignore */
1682
+ ...(ngDevMode ? [{ debugName: "visibleRows" }] : /* istanbul ignore next */ []));
1683
+ this.isEmpty = computed(() => this.layout().rows.length === 0, /* @ts-ignore */
1684
+ ...(ngDevMode ? [{ debugName: "isEmpty" }] : /* istanbul ignore next */ []));
1685
+ this.barHeight = computed(() => this.config().rows?.laneHeight ?? BAR_HEIGHT, /* @ts-ignore */
1686
+ ...(ngDevMode ? [{ debugName: "barHeight" }] : /* istanbul ignore next */ []));
1687
+ /**
1688
+ * Geometria zależności w pikselach.
1689
+ * Liczona dopiero po zmierzeniu toru; wiersze poza oknem wirtualizacji
1690
+ * nie mają końców, więc ich krawędzie są pomijane.
1691
+ */
1692
+ this.links = computed(() => {
1693
+ const config = this.config();
1694
+ if (config.dependencies?.enabled === false)
1695
+ return [];
1696
+ const width = this.laneWidth();
1697
+ if (width <= 0)
1698
+ return [];
1699
+ const layout = this.layout();
1700
+ const timeline = this.timeline();
1701
+ const height = this.barHeight();
1702
+ const endpoints = new Map();
1703
+ for (const row of this.visibleRows()) {
1704
+ for (const item of row.items) {
1705
+ const geom = timeline.span(item.start, item.end);
1706
+ if (!geom.visible)
1707
+ continue;
1708
+ // Romb kamienia milowego jest renderowany wyśrodkowany na dacie
1709
+ // (`translateX(-50%)`), więc jego prostokąt zaczyna się o pół rozmiaru wcześniej.
1710
+ const left = geom.left * width - (item.isMilestone ? height / 2 : 0);
1711
+ endpoints.set(item.id, {
1712
+ id: item.id,
1713
+ rect: {
1714
+ x: left,
1715
+ y: row.top + this.barTop(row, item.lane),
1716
+ width: Math.max(geom.width * width, item.isMilestone ? height : 1),
1717
+ height,
1718
+ },
1719
+ startTs: item.start,
1720
+ endTs: item.end,
1721
+ });
1722
+ }
1723
+ }
1724
+ const resolved = resolveLinks([...layout.byId.values()], config.links);
1725
+ const result = [];
1726
+ for (const link of resolved.links) {
1727
+ const geom = linkGeometry(link, endpoints.get(link.from), endpoints.get(link.to), config.dependencies);
1728
+ if (geom)
1729
+ result.push(geom);
1730
+ }
1731
+ return result;
1732
+ }, /* @ts-ignore */
1733
+ ...(ngDevMode ? [{ debugName: "links" }] : /* istanbul ignore next */ []));
1734
+ this.trackRow = (_, row) => row.key;
1735
+ this.trackBar = (_, bar) => bar.item.id;
1736
+ // Zakres jest wielkością wyliczaną — informujemy o nim po każdym przeliczeniu.
1737
+ afterRenderEffect(() => {
1738
+ this.rangeChange.emit(this.range());
1739
+ });
1740
+ // Pomiar toru i okna przewijania; oba wpływają wyłącznie na warstwę widoku.
1741
+ afterRenderEffect((onCleanup) => {
1742
+ const lane = this.laneEl()?.nativeElement;
1743
+ const scroll = this.scrollEl()?.nativeElement;
1744
+ if (!lane || typeof ResizeObserver === 'undefined')
1745
+ return;
1746
+ const observer = new ResizeObserver(() => {
1747
+ this.laneWidth.set(lane.clientWidth);
1748
+ if (scroll)
1749
+ this.viewportHeight.set(scroll.clientHeight);
1750
+ });
1751
+ observer.observe(lane);
1752
+ if (scroll)
1753
+ observer.observe(scroll);
1754
+ this.laneWidth.set(lane.clientWidth);
1755
+ if (scroll)
1756
+ this.viewportHeight.set(scroll.clientHeight);
1757
+ onCleanup(() => observer.disconnect());
1758
+ });
1759
+ // Ostrzeżenie dla danych, których nie da się narysować.
1760
+ afterRenderEffect(() => {
1761
+ const invalid = this.layout().invalid;
1762
+ if (invalid.length > 0 && typeof ngDevMode !== 'undefined' && ngDevMode) {
1763
+ console.warn('[kpt-roadmap] Pominięto elementy:', invalid.map((entry) => `${entry.id} (${entry.reason})`).join(', '));
1764
+ }
1765
+ });
1766
+ }
1767
+ onScroll(event) {
1768
+ const target = event.target;
1769
+ this.scrollTop.set(target.scrollTop);
1770
+ }
1771
+ /** Wysokość paska wraz z odstępem — wspólna dla widoku i geometrii zależności. */
1772
+ laneStep() {
1773
+ return this.barHeight() + LANE_GAP;
1774
+ }
1775
+ /** Offset pionowy paska wewnątrz wiersza (px). */
1776
+ barTop(row, lane) {
1777
+ const total = row.laneCount * this.barHeight() + (row.laneCount - 1) * LANE_GAP;
1778
+ return Math.max(0, (row.height - total) / 2) + lane * this.laneStep();
1779
+ }
1780
+ /** Paski danego wiersza gotowe do renderu. */
1781
+ bars(row) {
1782
+ const timeline = this.timeline();
1783
+ const height = this.barHeight();
1784
+ const result = [];
1785
+ for (const item of row.items) {
1786
+ const geom = timeline.span(item.start, item.end);
1787
+ if (!geom.visible)
1788
+ continue;
1789
+ const source = item.source;
1790
+ result.push({
1791
+ item,
1792
+ left: geom.left * 100,
1793
+ width: geom.width * 100,
1794
+ top: this.barTop(row, item.lane),
1795
+ height,
1796
+ label: source.label,
1797
+ title: source.sublabel ? `${source.label} — ${source.sublabel}` : source.label,
1798
+ variant: source.variant ?? 'primary',
1799
+ color: source.color,
1800
+ progress: source.progress ?? null,
1801
+ isMilestone: item.isMilestone,
1802
+ clippedStart: geom.clippedStart,
1803
+ clippedEnd: geom.clippedEnd,
1804
+ });
1805
+ }
1806
+ return result;
1807
+ }
1808
+ /** Pasek zbiorczy wiersza grupy. */
1809
+ summary(row) {
1810
+ const span = row.groupState?.span;
1811
+ if (!span)
1812
+ return null;
1813
+ const geom = this.timeline().span(span.start, span.end);
1814
+ if (!geom.visible)
1815
+ return null;
1816
+ return {
1817
+ left: geom.left * 100,
1818
+ width: geom.width * 100,
1819
+ variant: row.group?.variant ?? 'neutral',
1820
+ color: row.group?.color,
1821
+ };
1822
+ }
1823
+ toggleGroup(group) {
1824
+ const current = this.collapsedGroups();
1825
+ if (this.collapsedIds().has(group.id)) {
1826
+ // Rozwijanie musi zdjąć grupę z obu źródeł, inaczej `group.collapsed`
1827
+ // natychmiast zwinęłoby ją z powrotem.
1828
+ this.collapsedFromConfig.update((ids) => ids.filter((id) => id !== group.id));
1829
+ this.collapsedGroups.set(current.filter((id) => id !== group.id));
1830
+ return;
1831
+ }
1832
+ this.collapsedGroups.set([...current, group.id]);
1833
+ }
1834
+ onItemClick(item) {
1835
+ if (item.source.disabled)
1836
+ return;
1837
+ this.itemClick.emit(item.source);
1838
+ }
1839
+ /** Obsługa klawiatury na pasku — Enter/Spacja działają jak kliknięcie. */
1840
+ onItemKeydown(event, item) {
1841
+ if (event.key !== 'Enter' && event.key !== ' ')
1842
+ return;
1843
+ event.preventDefault();
1844
+ this.onItemClick(item);
1845
+ }
1846
+ /**
1847
+ * Treść dymka dla kolumny etykiet. Pusta tam, gdzie dymek nie miałby czego
1848
+ * uzupełnić — dyrektywa pomija wtedy podpowiedź.
1849
+ */
1850
+ labelTooltip(row) {
1851
+ // Wiersz grupy ma własny dymek, osadzony głębiej (na stosie etykiet).
1852
+ if (row.kind === 'group')
1853
+ return '';
1854
+ // Tryb 'pack' mieści wiele zadań w jednym wierszu — nie ma jednej etykiety.
1855
+ if (row.items.length !== 1)
1856
+ return '';
1857
+ const source = row.items[0].source;
1858
+ return source.sublabel ? `${source.label} — ${source.sublabel}` : source.label;
1859
+ }
1860
+ isCollapsed(group) {
1861
+ return group ? this.collapsedIds().has(group.id) : false;
1862
+ }
1863
+ static { this.ɵfac = function KptRoadmap_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || KptRoadmap)(); }; }
1864
+ static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: KptRoadmap, selectors: [["kpt-roadmap"]], viewQuery: function KptRoadmap_Query(rf, ctx) { if (rf & 1) {
1865
+ i0.ɵɵviewQuerySignal(ctx.scrollEl, _c0, 5)(ctx.laneEl, _c1, 5);
1866
+ } if (rf & 2) {
1867
+ i0.ɵɵqueryAdvance(2);
1868
+ } }, hostAttrs: [1, "kpt-roadmap"], hostVars: 8, hostBindings: function KptRoadmap_HostBindings(rf, ctx) { if (rf & 2) {
1869
+ i0.ɵɵstyleProp("--%NS%kpt-roadmap-cells", ctx.timeline().cells.length)("--%NS%kpt-roadmap-cell-w", ctx.cellWidth(), "px")("--%NS%kpt-roadmap-label-w", ctx.labelWidth());
1870
+ i0.ɵɵclassProp("kpt-roadmap--fixed-height", !!ctx.scrollHeight());
1871
+ } }, inputs: { config: [1, "config"], scrollHeight: [1, "scrollHeight"], labelWidth: [1, "labelWidth"], cellWidthPx: [1, "cellWidthPx"], showToday: [1, "showToday"], virtual: [1, "virtual"], collapsedGroups: [1, "collapsedGroups"] }, outputs: { collapsedGroups: "collapsedGroupsChange", itemClick: "itemClick", rangeChange: "rangeChange" }, decls: 13, vars: 5, consts: [["scrollEl", ""], ["laneMeasure", ""], [1, "kpt-roadmap__scroll", 3, "scroll"], [1, "kpt-roadmap__head"], [1, "kpt-roadmap__head-label"], [1, "kpt-roadmap__head-track"], [1, "kpt-roadmap__bands"], [1, "kpt-roadmap__cells"], [1, "kpt-roadmap__cell", 3, "kpt-roadmap__cell--weekend", "kpt-roadmap__cell--today", "kpt-roadmap__cell--year-boundary", "kpt-roadmap__cell--gap-before", "inline-size", "kptTooltip"], [1, "kpt-roadmap__today-dot", 3, "inset-inline-start"], [1, "kpt-roadmap__empty"], [1, "kpt-roadmap__body", 3, "block-size"], ["kptTooltipOnlyWhenTruncated", "", 1, "kpt-roadmap__band", 3, "kpt-roadmap__band--year-start", "inline-size", "kptTooltip"], ["kptTooltipOnlyWhenTruncated", "", 1, "kpt-roadmap__band", 3, "kptTooltip"], [1, "kpt-roadmap__year-badge"], [1, "kpt-roadmap__band-label"], [1, "kpt-roadmap__band-sublabel"], [1, "kpt-roadmap__cell", 3, "kptTooltip"], [1, "kpt-roadmap__cell-label"], [1, "kpt-roadmap__cell-sublabel"], [1, "kpt-roadmap__today-dot"], [1, "kpt-roadmap__body"], ["aria-hidden", "true", 1, "kpt-roadmap__grid"], [1, "kpt-roadmap__grid-cells"], [1, "kpt-roadmap__grid-cell", 3, "kpt-roadmap__grid-cell--weekend", "kpt-roadmap__grid-cell--non-working", "kpt-roadmap__grid-cell--year-boundary", "kpt-roadmap__grid-cell--gap-before", "inline-size"], [1, "kpt-roadmap__today", 3, "inset-inline-start"], ["aria-hidden", "true", 1, "kpt-roadmap__links"], [1, "kpt-roadmap__row", 3, "kpt-roadmap__row--group", "inset-block-start", "block-size"], [1, "kpt-roadmap__grid-cell"], [1, "kpt-roadmap__today"], [1, "kpt-roadmap__link", 3, "kpt-roadmap__link--violated"], [1, "kpt-roadmap__link"], [1, "kpt-roadmap__link-path"], [1, "kpt-roadmap__link-arrow"], [1, "kpt-roadmap__row"], ["kptTooltipOnlyWhenTruncated", "", 1, "kpt-roadmap__label", 3, "kptTooltip"], [1, "kpt-roadmap__lane"], ["type", "button", 1, "kpt-roadmap__toggle", 3, "click"], [1, "kpt-roadmap__toggle-icon"], ["kptTooltipOnlyWhenTruncated", "", 1, "kpt-roadmap__label-stack", 3, "kptTooltip"], [1, "kpt-roadmap__label-text", "kpt-roadmap__label-text--group"], [1, "kpt-roadmap__label-sub"], [1, "kpt-roadmap__label-count"], [1, "kpt-roadmap__label-text"], [1, "kpt-roadmap__summary", 3, "inset-inline-start", "inline-size", "background"], [1, "kpt-roadmap__summary"], ["tabindex", "0", 1, "kpt-roadmap__milestone", 3, "kptTooltip", "inset-inline-start", "inset-block-start", "inline-size", "block-size", "background"], ["kptTooltipOnlyWhenTruncated", "", "tabindex", "0", 1, "kpt-roadmap__bar", 3, "kptTooltip", "kpt-roadmap__bar--clipped-start", "kpt-roadmap__bar--clipped-end", "kpt-roadmap__bar--disabled", "inset-inline-start", "inline-size", "inset-block-start", "block-size", "background"], ["tabindex", "0", 1, "kpt-roadmap__milestone", 3, "click", "keydown", "kptTooltip"], ["kptTooltipOnlyWhenTruncated", "", "tabindex", "0", 1, "kpt-roadmap__bar", 3, "click", "keydown", "kptTooltip"], [1, "kpt-roadmap__bar-progress", 3, "inline-size"], [1, "kpt-roadmap__bar-label"], [1, "kpt-roadmap__bar-progress"]], template: function KptRoadmap_Template(rf, ctx) { if (rf & 1) {
1872
+ i0.ɵɵelementStart(0, "div", 2, 0);
1873
+ i0.ɵɵlistener("scroll", function KptRoadmap_Template_div_scroll_0_listener($event) { return ctx.onScroll($event); });
1874
+ i0.ɵɵelementStart(2, "div", 3);
1875
+ i0.ɵɵelement(3, "div", 4);
1876
+ i0.ɵɵelementStart(4, "div", 5);
1877
+ i0.ɵɵconditionalCreate(5, KptRoadmap_Conditional_5_Template, 3, 0, "div", 6);
1878
+ i0.ɵɵelementStart(6, "div", 7, 1);
1879
+ i0.ɵɵrepeaterCreate(8, KptRoadmap_For_9_Template, 4, 13, "div", 8, _forTrack0);
1880
+ i0.ɵɵelementEnd();
1881
+ i0.ɵɵconditionalCreate(10, KptRoadmap_Conditional_10_Template, 1, 3, "span", 9);
1882
+ i0.ɵɵelementEnd()();
1883
+ i0.ɵɵconditionalCreate(11, KptRoadmap_Conditional_11_Template, 2, 1, "div", 10)(12, KptRoadmap_Conditional_12_Template, 9, 4, "div", 11);
1884
+ i0.ɵɵelementEnd();
1885
+ } if (rf & 2) {
1886
+ i0.ɵɵstyleProp("max-block-size", ctx.scrollHeight());
1887
+ i0.ɵɵadvance(5);
1888
+ i0.ɵɵconditional(ctx.timeline().hasBands ? 5 : -1);
1889
+ i0.ɵɵadvance(3);
1890
+ i0.ɵɵrepeater(ctx.timeline().cells);
1891
+ i0.ɵɵadvance(2);
1892
+ i0.ɵɵconditional(ctx.showToday() && ctx.timeline().todayRatio !== null ? 10 : -1);
1893
+ i0.ɵɵadvance();
1894
+ i0.ɵɵconditional(ctx.isEmpty() ? 11 : 12);
1895
+ } }, dependencies: [KptTooltip], styles: ["@layer kpt.components{.kpt-roadmap{display:block;--%NS%kpt-roadmap-cells: 1;--%NS%kpt-roadmap-cell-w: 76px;--%NS%kpt-roadmap-label-w: 14rem;--%NS%kpt-roadmap-track-w: calc(var(--%NS%kpt-roadmap-cell-w) * var(--%NS%kpt-roadmap-cells));color:var(--%NS%kpt-color-on-surface);font-family:var(--%NS%kpt-font-family-sans);background:var(--%NS%kpt-color-surface);border:var(--%NS%kpt-border-width-thin, 1px) solid var(--%NS%kpt-color-border);border-radius:var(--%NS%kpt-radius-lg, .75rem);overflow:hidden}.kpt-roadmap__scroll{overflow:auto;position:relative}.kpt-roadmap__head,.kpt-roadmap__row{display:grid;grid-template-columns:var(--%NS%kpt-roadmap-label-w) minmax(var(--%NS%kpt-roadmap-track-w),1fr);inline-size:max-content;min-inline-size:100%}.kpt-roadmap__head{position:sticky;inset-block-start:0;z-index:4;background:var(--%NS%kpt-color-surface-variant, var(--%NS%kpt-color-surface));border-block-end:var(--%NS%kpt-border-width-thin, 1px) solid var(--%NS%kpt-color-border)}.kpt-roadmap__head-label{position:sticky;inset-inline-start:0;z-index:5;background:var(--%NS%kpt-color-surface-variant, var(--%NS%kpt-color-surface));border-inline-end:var(--%NS%kpt-border-width-thin, 1px) solid var(--%NS%kpt-color-border)}.kpt-roadmap__head-track{position:relative;display:flex;flex-direction:column;min-inline-size:0}.kpt-roadmap__bands,.kpt-roadmap__cells,.kpt-roadmap__grid-cells{display:flex;inline-size:100%}.kpt-roadmap__band{position:relative;display:flex;align-items:center;justify-content:center;gap:var(--%NS%kpt-space-2, .5rem);min-inline-size:0;padding:var(--%NS%kpt-space-2, .5rem) var(--%NS%kpt-space-3, .75rem);font-size:var(--%NS%kpt-font-size-sm, .875rem);font-weight:var(--%NS%kpt-font-weight-medium, 500);color:var(--%NS%kpt-color-on-surface-muted);white-space:nowrap;overflow:hidden;border-inline-start:var(--%NS%kpt-border-width-thin, 1px) solid var(--%NS%kpt-color-border)}.kpt-roadmap__band:first-child{border-inline-start:0}.kpt-roadmap__band--year-start{border-inline-start:var(--%NS%kpt-border-width-medium, 2px) solid var(--%NS%kpt-color-border-strong)}.kpt-roadmap__band-label{overflow:hidden;text-overflow:ellipsis}.kpt-roadmap__band-sublabel{color:var(--%NS%kpt-color-text-subtle, var(--%NS%kpt-color-on-surface-muted))}.kpt-roadmap__year-badge{display:inline-flex;align-items:center;padding:.125rem var(--%NS%kpt-space-2, .5rem);font-size:var(--%NS%kpt-font-size-xs, .75rem);font-weight:var(--%NS%kpt-font-weight-semibold, 600);color:var(--%NS%kpt-color-surface);background:var(--%NS%kpt-color-on-surface);border-radius:var(--%NS%kpt-radius-full, 9999px)}.kpt-roadmap__cell{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.125rem;min-inline-size:0;padding:var(--%NS%kpt-space-2, .5rem) .25rem;text-align:center;white-space:nowrap;border-inline-start:var(--%NS%kpt-border-width-thin, 1px) solid var(--%NS%kpt-color-border)}.kpt-roadmap__cell:first-child{border-inline-start:0}.kpt-roadmap__cell-label{font-size:var(--%NS%kpt-font-size-xs, .75rem);font-weight:var(--%NS%kpt-font-weight-semibold, 600);letter-spacing:.02em;text-transform:uppercase;color:var(--%NS%kpt-color-on-surface-muted)}.kpt-roadmap__cell-sublabel{font-size:var(--%NS%kpt-font-size-xs, .75rem);color:var(--%NS%kpt-color-text-subtle, var(--%NS%kpt-color-on-surface-muted));overflow:hidden;text-overflow:ellipsis;inline-size:100%}.kpt-roadmap__cell--weekend{background:var(--%NS%kpt-color-surface-sunken, var(--%NS%kpt-color-muted))}.kpt-roadmap__cell--today .kpt-roadmap__cell-label{color:var(--%NS%kpt-color-primary)}.kpt-roadmap__cell--year-boundary,.kpt-roadmap__grid-cell--year-boundary{border-inline-start:var(--%NS%kpt-border-width-medium, 2px) solid var(--%NS%kpt-color-border-strong)}.kpt-roadmap__cell--gap-before,.kpt-roadmap__grid-cell--gap-before{border-inline-start:var(--%NS%kpt-border-width-thin, 1px) dashed var(--%NS%kpt-color-border-strong)}.kpt-roadmap__body{position:relative;inline-size:calc(var(--%NS%kpt-roadmap-label-w) + var(--%NS%kpt-roadmap-track-w));min-inline-size:100%}.kpt-roadmap__grid{position:absolute;inset-block:0;inset-inline-start:var(--%NS%kpt-roadmap-label-w);inset-inline-end:0;z-index:0;pointer-events:none}.kpt-roadmap__grid-cells{block-size:100%}.kpt-roadmap__grid-cell{min-inline-size:0;border-inline-start:var(--%NS%kpt-border-width-thin, 1px) solid var(--%NS%kpt-color-border)}.kpt-roadmap__grid-cell:first-child{border-inline-start:0}.kpt-roadmap__grid-cell--weekend,.kpt-roadmap__grid-cell--non-working{background:var(--%NS%kpt-color-surface-sunken, var(--%NS%kpt-color-muted))}.kpt-roadmap__today{position:absolute;inset-block:0;inline-size:0;border-inline-start:var(--%NS%kpt-border-width-medium, 2px) dashed color-mix(in oklch,var(--%NS%kpt-color-primary) 40%,transparent)}.kpt-roadmap__today-dot{position:absolute;inset-block-end:0rem;inline-size:.5rem;block-size:.5rem;transform:translate(-50%);background:color-mix(in oklch,var(--%NS%kpt-color-primary) 40%,transparent);border-radius:var(--%NS%kpt-radius-full, 9999px);pointer-events:none}.kpt-roadmap__links{position:absolute;inset-block-start:0;inset-inline-start:var(--%NS%kpt-roadmap-label-w);z-index:2;pointer-events:none;overflow:visible}.kpt-roadmap__link-path{fill:none;stroke:var(--%NS%kpt-color-on-surface-muted);stroke-width:1.75}.kpt-roadmap__link-arrow{fill:var(--%NS%kpt-color-on-surface-muted)}.kpt-roadmap__link--violated .kpt-roadmap__link-path{stroke:var(--%NS%kpt-color-danger);stroke-dasharray:4 3}.kpt-roadmap__link--violated .kpt-roadmap__link-arrow{fill:var(--%NS%kpt-color-danger)}.kpt-roadmap__row{position:absolute;inset-inline:0;border-block-end:var(--%NS%kpt-border-width-thin, 1px) solid var(--%NS%kpt-color-border)}.kpt-roadmap__row:hover{background:color-mix(in oklch,var(--%NS%kpt-color-primary) 10%,transparent)}.kpt-roadmap__row:hover .kpt-roadmap__label{background:color-mix(in oklch,var(--%NS%kpt-color-primary) 10%,var(--%NS%kpt-color-surface))}.kpt-roadmap__row--group,.kpt-roadmap__row--group .kpt-roadmap__label{background:var(--%NS%kpt-color-surface-variant, var(--%NS%kpt-color-surface))}.kpt-roadmap__row--%NS%group:hover,.kpt-roadmap__row--%NS%group:hover .kpt-roadmap__label{background:color-mix(in oklch,var(--%NS%kpt-color-primary) 10%,var(--%NS%kpt-color-surface-variant, var(--%NS%kpt-color-surface)))}.kpt-roadmap__label{position:sticky;inset-inline-start:0;z-index:3;display:flex;flex-direction:column;justify-content:center;gap:.125rem;min-inline-size:0;padding-inline-end:var(--%NS%kpt-space-3, .75rem);background:var(--%NS%kpt-color-surface);border-inline-end:var(--%NS%kpt-border-width-thin, 1px) solid var(--%NS%kpt-color-border)}.kpt-roadmap__label--group{justify-content:start;flex-direction:row;align-items:center;gap:var(--%NS%kpt-space-2, .5rem)}.kpt-roadmap__label-text{overflow:hidden;font-size:var(--%NS%kpt-font-size-sm, .875rem);font-weight:var(--%NS%kpt-font-weight-medium, 500);text-overflow:ellipsis;white-space:nowrap}.kpt-roadmap__label-text--group{font-weight:var(--%NS%kpt-font-weight-semibold, 600)}.kpt-roadmap__label-sub{overflow:hidden;font-size:var(--%NS%kpt-font-size-xs, .75rem);color:var(--%NS%kpt-color-on-surface-muted);text-overflow:ellipsis;white-space:nowrap}.kpt-roadmap__label-stack{display:flex;flex-direction:column;justify-content:center;gap:.125rem;min-inline-size:0}.kpt-roadmap__label-count{align-self:center;flex:0 0 auto;padding:0 var(--%NS%kpt-space-2, .5rem);font-size:var(--%NS%kpt-font-size-xs, .75rem);color:var(--%NS%kpt-color-on-surface-muted);background:var(--%NS%kpt-color-muted);border-radius:var(--%NS%kpt-radius-full, 9999px)}.kpt-roadmap__toggle{display:inline-flex;flex:0 0 auto;align-items:center;justify-content:center;inline-size:1.25rem;block-size:1.25rem;padding:0;color:inherit;cursor:pointer;background:transparent;border:0;border-radius:var(--%NS%kpt-radius-sm, .25rem)}.kpt-roadmap__toggle:focus-visible{outline:var(--%NS%kpt-border-width-medium, 2px) solid var(--%NS%kpt-color-focus-ring);outline-offset:1px}.kpt-roadmap__toggle-icon{inline-size:0;block-size:0;border-block-start:.3rem solid transparent;border-block-end:.3rem solid transparent;border-inline-start:.4rem solid currentcolor;transition:transform var(--%NS%kpt-motion-duration-fast, .12s) var(--%NS%kpt-motion-easing-standard)}.kpt-roadmap__toggle-icon--open{transform:rotate(90deg)}.kpt-roadmap__lane{position:relative;min-inline-size:0}.kpt-roadmap__bar,.kpt-roadmap__milestone,.kpt-roadmap__summary{--%NS%_color: var(--%NS%kpt-color-primary)}.kpt-roadmap__bar[data-variant=neutral],.kpt-roadmap__milestone[data-variant=neutral],.kpt-roadmap__summary[data-variant=neutral]{--%NS%_color: var(--%NS%kpt-color-muted)}.kpt-roadmap__bar[data-variant=success],.kpt-roadmap__milestone[data-variant=success],.kpt-roadmap__summary[data-variant=success]{--%NS%_color: var(--%NS%kpt-color-success)}.kpt-roadmap__bar[data-variant=danger],.kpt-roadmap__milestone[data-variant=danger],.kpt-roadmap__summary[data-variant=danger]{--%NS%_color: var(--%NS%kpt-color-danger)}.kpt-roadmap__bar[data-variant=warning],.kpt-roadmap__milestone[data-variant=warning],.kpt-roadmap__summary[data-variant=warning]{--%NS%_color: var(--%NS%kpt-color-warning)}.kpt-roadmap__bar[data-variant=info],.kpt-roadmap__milestone[data-variant=info],.kpt-roadmap__summary[data-variant=info]{--%NS%_color: var(--%NS%kpt-color-info)}.kpt-roadmap__bar{position:absolute;z-index:1;display:flex;align-items:center;min-inline-size:.25rem;padding-inline:var(--%NS%kpt-space-2, .5rem);overflow:hidden;cursor:pointer;background:var(--%NS%_color);border-radius:var(--%NS%kpt-radius-full, 9999px);transition:filter var(--%NS%kpt-motion-duration-fast, .12s) var(--%NS%kpt-motion-easing-standard)}.kpt-roadmap__bar:hover{filter:brightness(1.08)}.kpt-roadmap__bar:focus-visible{outline:var(--%NS%kpt-border-width-medium, 2px) solid var(--%NS%kpt-color-focus-ring);outline-offset:2px}.kpt-roadmap__bar--disabled{cursor:default;opacity:.55}.kpt-roadmap__bar--clipped-start{border-start-start-radius:0;border-end-start-radius:0;clip-path:polygon(.5rem 0,100% 0,100% 100%,.5rem 100%,0 50%)}.kpt-roadmap__bar--clipped-end{border-start-end-radius:0;border-end-end-radius:0;clip-path:polygon(0 0,calc(100% - .5rem) 0,100% 50%,calc(100% - .5rem) 100%,0 100%)}.kpt-roadmap__bar--clipped-start.kpt-roadmap__bar--clipped-end{clip-path:polygon(.5rem 0,calc(100% - .5rem) 0,100% 50%,calc(100% - .5rem) 100%,.5rem 100%,0 50%)}.kpt-roadmap__bar-progress{position:absolute;inset-block:0;inset-inline-start:0;background:#00000038;pointer-events:none}.kpt-roadmap__bar-label{position:relative;overflow:hidden;font-size:var(--%NS%kpt-font-size-xs, .75rem);font-weight:var(--%NS%kpt-font-weight-medium, 500);color:var(--%NS%kpt-color-text-inverse, #fff);text-overflow:ellipsis;white-space:nowrap}.kpt-roadmap__bar[data-variant=neutral] .kpt-roadmap__bar-label{color:var(--%NS%kpt-color-on-muted)}.kpt-roadmap__milestone{position:absolute;z-index:1;cursor:pointer;background:var(--%NS%_color);transform:translate(-50%) rotate(45deg);border-radius:var(--%NS%kpt-radius-sm, .25rem)}.kpt-roadmap__milestone:focus-visible{outline:var(--%NS%kpt-border-width-medium, 2px) solid var(--%NS%kpt-color-focus-ring);outline-offset:2px}.kpt-roadmap__summary{position:absolute;inset-block-start:50%;z-index:1;block-size:.5rem;background:var(--%NS%_color);border-radius:var(--%NS%kpt-radius-full, 9999px);transform:translateY(-50%)}.kpt-roadmap__summary[data-variant=neutral]{--%NS%_color: var(--%NS%kpt-color-on-surface-muted)}.kpt-roadmap__empty{padding:var(--%NS%kpt-space-8, 2rem);color:var(--%NS%kpt-color-on-surface-muted);text-align:center}}\n"], encapsulation: 2 }); }
1896
+ }
1897
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(KptRoadmap, [{
1898
+ type: Component,
1899
+ args: [{ selector: 'kpt-roadmap', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [KptTooltip], host: {
1900
+ class: 'kpt-roadmap',
1901
+ '[style.--kpt-roadmap-cells]': 'timeline().cells.length',
1902
+ '[style.--kpt-roadmap-cell-w.px]': 'cellWidth()',
1903
+ '[style.--kpt-roadmap-label-w]': 'labelWidth()',
1904
+ '[class.kpt-roadmap--fixed-height]': '!!scrollHeight()',
1905
+ }, template: "<div\n #scrollEl\n class=\"kpt-roadmap__scroll\"\n [style.max-block-size]=\"scrollHeight()\"\n (scroll)=\"onScroll($event)\"\n>\n <!-- Nag\u0142\u00F3wek: przykleja si\u0119 do g\u00F3ry, gdy podano sta\u0142\u0105 wysoko\u015B\u0107. -->\n <div class=\"kpt-roadmap__head\">\n <div class=\"kpt-roadmap__head-label\"></div>\n\n <div class=\"kpt-roadmap__head-track\">\n @if (timeline().hasBands) {\n <div class=\"kpt-roadmap__bands\">\n @for (band of timeline().bands; track band.key) {\n <div\n class=\"kpt-roadmap__band\"\n [class.kpt-roadmap__band--year-start]=\"band.isYearStart\"\n [style.inline-size.%]=\"(band.ratioEnd - band.ratioStart) * 100\"\n [kptTooltip]=\"band.sublabel ? band.label + ' ' + band.sublabel : band.label\"\n kptTooltipOnlyWhenTruncated\n >\n @if (band.yearBadge) {\n <span class=\"kpt-roadmap__year-badge\">{{ band.yearBadge }}</span>\n }\n <span class=\"kpt-roadmap__band-label\">{{ band.label }}</span>\n @if (band.sublabel && !band.yearBadge) {\n <span class=\"kpt-roadmap__band-sublabel\">{{ band.sublabel }}</span>\n }\n </div>\n }\n </div>\n }\n\n <!-- Nag\u0142\u00F3wek definiuje siatk\u0119, wi\u0119c to on jest \u017Ar\u00F3d\u0142em pomiaru szeroko\u015Bci toru. -->\n <div #laneMeasure class=\"kpt-roadmap__cells\">\n @for (cell of timeline().cells; track cell.key) {\n <div\n class=\"kpt-roadmap__cell\"\n [class.kpt-roadmap__cell--weekend]=\"cell.isWeekend\"\n [class.kpt-roadmap__cell--today]=\"cell.isToday && showToday()\"\n [class.kpt-roadmap__cell--year-boundary]=\"cell.isYearBoundary\"\n [class.kpt-roadmap__cell--gap-before]=\"cell.hasGapBefore\"\n [style.inline-size.%]=\"(cell.ratioEnd - cell.ratioStart) * 100\"\n [kptTooltip]=\"cell.title\"\n >\n <span class=\"kpt-roadmap__cell-label\">{{ cell.label }}</span>\n @if (cell.sublabel) {\n <span class=\"kpt-roadmap__cell-sublabel\">{{ cell.sublabel }}</span>\n }\n </div>\n }\n </div>\n\n <!-- Znacznik \u201Edzi\u015B\" mieszka w nag\u0142\u00F3wku: warstwa siatki ma w\u0142asny kontekst\n uk\u0142adania, wi\u0119c nic w niej nie mo\u017Ce znale\u017A\u0107 si\u0119 nad nag\u0142\u00F3wkiem. -->\n @if (showToday() && timeline().todayRatio !== null) {\n <span\n class=\"kpt-roadmap__today-dot\"\n [style.inset-inline-start.%]=\"timeline().todayRatio! * 100\"\n [attr.title]=\"i18n.t('roadmap.today')\"\n ></span>\n }\n </div>\n </div>\n\n @if (isEmpty()) {\n <div class=\"kpt-roadmap__empty\">{{ i18n.t('roadmap.empty') }}</div>\n } @else {\n <div class=\"kpt-roadmap__body\" [style.block-size.px]=\"layout().totalHeight\">\n <!-- Siatka t\u0142a: kolumny weekendowe, granice roku i znacznik dzi\u015B. -->\n <div class=\"kpt-roadmap__grid\" aria-hidden=\"true\">\n <div class=\"kpt-roadmap__grid-cells\">\n @for (cell of timeline().cells; track cell.key) {\n <div\n class=\"kpt-roadmap__grid-cell\"\n [class.kpt-roadmap__grid-cell--weekend]=\"cell.isWeekend\"\n [class.kpt-roadmap__grid-cell--non-working]=\"cell.isNonWorking\"\n [class.kpt-roadmap__grid-cell--year-boundary]=\"cell.isYearBoundary\"\n [class.kpt-roadmap__grid-cell--gap-before]=\"cell.hasGapBefore\"\n [style.inline-size.%]=\"(cell.ratioEnd - cell.ratioStart) * 100\"\n ></div>\n }\n </div>\n\n @if (showToday() && timeline().todayRatio !== null) {\n <!-- Sama prowadnica; znacznik siedzi w nag\u0142\u00F3wku, \u017Ceby by\u0142 nad nim widoczny. -->\n <div class=\"kpt-roadmap__today\" [style.inset-inline-start.%]=\"timeline().todayRatio! * 100\"></div>\n }\n </div>\n\n <!-- Zale\u017Cno\u015Bci rysowane nad paskami, pod kolumn\u0105 etykiet. -->\n @if (links().length > 0) {\n <svg\n class=\"kpt-roadmap__links\"\n [attr.viewBox]=\"'0 0 ' + laneWidth() + ' ' + layout().totalHeight\"\n [attr.width]=\"laneWidth()\"\n [attr.height]=\"layout().totalHeight\"\n aria-hidden=\"true\"\n >\n @for (link of links(); track link.key) {\n <g\n class=\"kpt-roadmap__link\"\n [class.kpt-roadmap__link--violated]=\"link.isViolated\"\n [attr.data-variant]=\"link.variant\"\n >\n <path class=\"kpt-roadmap__link-path\" [attr.d]=\"link.path\" [style.stroke]=\"link.color\" />\n <path class=\"kpt-roadmap__link-arrow\" [attr.d]=\"link.arrow\" [style.fill]=\"link.color\" />\n </g>\n }\n </svg>\n }\n\n @for (row of visibleRows(); track row.key) {\n <div\n class=\"kpt-roadmap__row\"\n [class.kpt-roadmap__row--group]=\"row.kind === 'group'\"\n [style.inset-block-start.px]=\"row.top\"\n [style.block-size.px]=\"row.height\"\n >\n <div\n class=\"kpt-roadmap__label\"\n [style.padding-inline-start.rem]=\"0.75 + row.depth * 1\"\n [class.kpt-roadmap__label--group]=\"row.kind === 'group'\"\n [kptTooltip]=\"labelTooltip(row)\"\n kptTooltipOnlyWhenTruncated\n >\n @if (row.group; as group) {\n <button\n type=\"button\"\n class=\"kpt-roadmap__toggle\"\n [attr.aria-expanded]=\"!isCollapsed(group)\"\n [attr.aria-label]=\"isCollapsed(group) ? i18n.t('roadmap.expandGroup') : i18n.t('roadmap.collapseGroup')\"\n (click)=\"toggleGroup(group)\"\n >\n <span class=\"kpt-roadmap__toggle-icon\" [class.kpt-roadmap__toggle-icon--open]=\"!isCollapsed(group)\">\n </span>\n </button>\n <!-- Dymek siedzi na stacku, nie na ca\u0142ej etykiecie: w grupie tekst jest\n o poziom g\u0142\u0119biej, a pomiar obejmuje tylko bezpo\u015Brednie dzieci hosta. -->\n <span\n class=\"kpt-roadmap__label-stack\"\n [kptTooltip]=\"group.sublabel ? group.label + ' \u2014 ' + group.sublabel : group.label\"\n kptTooltipOnlyWhenTruncated\n >\n <span class=\"kpt-roadmap__label-text kpt-roadmap__label-text--group\">{{ group.label }}</span>\n @if (group.sublabel) {\n <span class=\"kpt-roadmap__label-sub\">{{ group.sublabel }}</span>\n }\n </span>\n @if (row.groupState; as state) {\n @if (state.itemCount) {\n <span class=\"kpt-roadmap__label-count\">{{ state.itemCount }}</span>\n }\n }\n } @else if (row.items[0]; as first) {\n <span class=\"kpt-roadmap__label-text\">{{ first.source.label }}</span>\n @if (row.items.length === 1 && first.source.sublabel) {\n <span class=\"kpt-roadmap__label-sub\">{{ first.source.sublabel }}</span>\n }\n }\n </div>\n\n <div class=\"kpt-roadmap__lane\">\n @if (row.kind === 'group') {\n @if (summary(row); as sum) {\n <div\n class=\"kpt-roadmap__summary\"\n [attr.data-variant]=\"sum.variant\"\n [style.inset-inline-start.%]=\"sum.left\"\n [style.inline-size.%]=\"sum.width\"\n [style.background]=\"sum.color\"\n ></div>\n }\n } @else {\n @for (bar of bars(row); track bar.item.id) {\n @if (bar.isMilestone) {\n <div\n class=\"kpt-roadmap__milestone\"\n [attr.data-variant]=\"bar.variant\"\n [kptTooltip]=\"bar.title\"\n [attr.aria-label]=\"i18n.t('roadmap.milestone') + ': ' + bar.label\"\n [style.inset-inline-start.%]=\"bar.left\"\n [style.inset-block-start.px]=\"bar.top\"\n [style.inline-size.px]=\"bar.height\"\n [style.block-size.px]=\"bar.height\"\n [style.background]=\"bar.color\"\n tabindex=\"0\"\n (click)=\"onItemClick(bar.item)\"\n (keydown)=\"onItemKeydown($event, bar.item)\"\n ></div>\n } @else {\n <div\n class=\"kpt-roadmap__bar\"\n [attr.data-variant]=\"bar.variant\"\n [kptTooltip]=\"bar.title\"\n kptTooltipOnlyWhenTruncated\n [class.kpt-roadmap__bar--clipped-start]=\"bar.clippedStart\"\n [class.kpt-roadmap__bar--clipped-end]=\"bar.clippedEnd\"\n [class.kpt-roadmap__bar--disabled]=\"bar.item.source.disabled\"\n [style.inset-inline-start.%]=\"bar.left\"\n [style.inline-size.%]=\"bar.width\"\n [style.inset-block-start.px]=\"bar.top\"\n [style.block-size.px]=\"bar.height\"\n [style.background]=\"bar.color\"\n tabindex=\"0\"\n (click)=\"onItemClick(bar.item)\"\n (keydown)=\"onItemKeydown($event, bar.item)\"\n >\n @if (bar.progress !== null) {\n <span class=\"kpt-roadmap__bar-progress\" [style.inline-size.%]=\"bar.progress * 100\"></span>\n }\n <span class=\"kpt-roadmap__bar-label\">{{ bar.label }}</span>\n </div>\n }\n }\n }\n </div>\n </div>\n }\n </div>\n }\n</div>\n", styles: ["@layer kpt.components{.kpt-roadmap{display:block;--kpt-roadmap-cells: 1;--kpt-roadmap-cell-w: 76px;--kpt-roadmap-label-w: 14rem;--kpt-roadmap-track-w: calc(var(--kpt-roadmap-cell-w) * var(--kpt-roadmap-cells));color:var(--kpt-color-on-surface);font-family:var(--kpt-font-family-sans);background:var(--kpt-color-surface);border:var(--kpt-border-width-thin, 1px) solid var(--kpt-color-border);border-radius:var(--kpt-radius-lg, .75rem);overflow:hidden}.kpt-roadmap__scroll{overflow:auto;position:relative}.kpt-roadmap__head,.kpt-roadmap__row{display:grid;grid-template-columns:var(--kpt-roadmap-label-w) minmax(var(--kpt-roadmap-track-w),1fr);inline-size:max-content;min-inline-size:100%}.kpt-roadmap__head{position:sticky;inset-block-start:0;z-index:4;background:var(--kpt-color-surface-variant, var(--kpt-color-surface));border-block-end:var(--kpt-border-width-thin, 1px) solid var(--kpt-color-border)}.kpt-roadmap__head-label{position:sticky;inset-inline-start:0;z-index:5;background:var(--kpt-color-surface-variant, var(--kpt-color-surface));border-inline-end:var(--kpt-border-width-thin, 1px) solid var(--kpt-color-border)}.kpt-roadmap__head-track{position:relative;display:flex;flex-direction:column;min-inline-size:0}.kpt-roadmap__bands,.kpt-roadmap__cells,.kpt-roadmap__grid-cells{display:flex;inline-size:100%}.kpt-roadmap__band{position:relative;display:flex;align-items:center;justify-content:center;gap:var(--kpt-space-2, .5rem);min-inline-size:0;padding:var(--kpt-space-2, .5rem) var(--kpt-space-3, .75rem);font-size:var(--kpt-font-size-sm, .875rem);font-weight:var(--kpt-font-weight-medium, 500);color:var(--kpt-color-on-surface-muted);white-space:nowrap;overflow:hidden;border-inline-start:var(--kpt-border-width-thin, 1px) solid var(--kpt-color-border)}.kpt-roadmap__band:first-child{border-inline-start:0}.kpt-roadmap__band--year-start{border-inline-start:var(--kpt-border-width-medium, 2px) solid var(--kpt-color-border-strong)}.kpt-roadmap__band-label{overflow:hidden;text-overflow:ellipsis}.kpt-roadmap__band-sublabel{color:var(--kpt-color-text-subtle, var(--kpt-color-on-surface-muted))}.kpt-roadmap__year-badge{display:inline-flex;align-items:center;padding:.125rem var(--kpt-space-2, .5rem);font-size:var(--kpt-font-size-xs, .75rem);font-weight:var(--kpt-font-weight-semibold, 600);color:var(--kpt-color-surface);background:var(--kpt-color-on-surface);border-radius:var(--kpt-radius-full, 9999px)}.kpt-roadmap__cell{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.125rem;min-inline-size:0;padding:var(--kpt-space-2, .5rem) .25rem;text-align:center;white-space:nowrap;border-inline-start:var(--kpt-border-width-thin, 1px) solid var(--kpt-color-border)}.kpt-roadmap__cell:first-child{border-inline-start:0}.kpt-roadmap__cell-label{font-size:var(--kpt-font-size-xs, .75rem);font-weight:var(--kpt-font-weight-semibold, 600);letter-spacing:.02em;text-transform:uppercase;color:var(--kpt-color-on-surface-muted)}.kpt-roadmap__cell-sublabel{font-size:var(--kpt-font-size-xs, .75rem);color:var(--kpt-color-text-subtle, var(--kpt-color-on-surface-muted));overflow:hidden;text-overflow:ellipsis;inline-size:100%}.kpt-roadmap__cell--weekend{background:var(--kpt-color-surface-sunken, var(--kpt-color-muted))}.kpt-roadmap__cell--today .kpt-roadmap__cell-label{color:var(--kpt-color-primary)}.kpt-roadmap__cell--year-boundary,.kpt-roadmap__grid-cell--year-boundary{border-inline-start:var(--kpt-border-width-medium, 2px) solid var(--kpt-color-border-strong)}.kpt-roadmap__cell--gap-before,.kpt-roadmap__grid-cell--gap-before{border-inline-start:var(--kpt-border-width-thin, 1px) dashed var(--kpt-color-border-strong)}.kpt-roadmap__body{position:relative;inline-size:calc(var(--kpt-roadmap-label-w) + var(--kpt-roadmap-track-w));min-inline-size:100%}.kpt-roadmap__grid{position:absolute;inset-block:0;inset-inline-start:var(--kpt-roadmap-label-w);inset-inline-end:0;z-index:0;pointer-events:none}.kpt-roadmap__grid-cells{block-size:100%}.kpt-roadmap__grid-cell{min-inline-size:0;border-inline-start:var(--kpt-border-width-thin, 1px) solid var(--kpt-color-border)}.kpt-roadmap__grid-cell:first-child{border-inline-start:0}.kpt-roadmap__grid-cell--weekend,.kpt-roadmap__grid-cell--non-working{background:var(--kpt-color-surface-sunken, var(--kpt-color-muted))}.kpt-roadmap__today{position:absolute;inset-block:0;inline-size:0;border-inline-start:var(--kpt-border-width-medium, 2px) dashed color-mix(in oklch,var(--kpt-color-primary) 40%,transparent)}.kpt-roadmap__today-dot{position:absolute;inset-block-end:0rem;inline-size:.5rem;block-size:.5rem;transform:translate(-50%);background:color-mix(in oklch,var(--kpt-color-primary) 40%,transparent);border-radius:var(--kpt-radius-full, 9999px);pointer-events:none}.kpt-roadmap__links{position:absolute;inset-block-start:0;inset-inline-start:var(--kpt-roadmap-label-w);z-index:2;pointer-events:none;overflow:visible}.kpt-roadmap__link-path{fill:none;stroke:var(--kpt-color-on-surface-muted);stroke-width:1.75}.kpt-roadmap__link-arrow{fill:var(--kpt-color-on-surface-muted)}.kpt-roadmap__link--violated .kpt-roadmap__link-path{stroke:var(--kpt-color-danger);stroke-dasharray:4 3}.kpt-roadmap__link--violated .kpt-roadmap__link-arrow{fill:var(--kpt-color-danger)}.kpt-roadmap__row{position:absolute;inset-inline:0;border-block-end:var(--kpt-border-width-thin, 1px) solid var(--kpt-color-border)}.kpt-roadmap__row:hover{background:color-mix(in oklch,var(--kpt-color-primary) 10%,transparent)}.kpt-roadmap__row:hover .kpt-roadmap__label{background:color-mix(in oklch,var(--kpt-color-primary) 10%,var(--kpt-color-surface))}.kpt-roadmap__row--group,.kpt-roadmap__row--group .kpt-roadmap__label{background:var(--kpt-color-surface-variant, var(--kpt-color-surface))}.kpt-roadmap__row--group:hover,.kpt-roadmap__row--group:hover .kpt-roadmap__label{background:color-mix(in oklch,var(--kpt-color-primary) 10%,var(--kpt-color-surface-variant, var(--kpt-color-surface)))}.kpt-roadmap__label{position:sticky;inset-inline-start:0;z-index:3;display:flex;flex-direction:column;justify-content:center;gap:.125rem;min-inline-size:0;padding-inline-end:var(--kpt-space-3, .75rem);background:var(--kpt-color-surface);border-inline-end:var(--kpt-border-width-thin, 1px) solid var(--kpt-color-border)}.kpt-roadmap__label--group{justify-content:start;flex-direction:row;align-items:center;gap:var(--kpt-space-2, .5rem)}.kpt-roadmap__label-text{overflow:hidden;font-size:var(--kpt-font-size-sm, .875rem);font-weight:var(--kpt-font-weight-medium, 500);text-overflow:ellipsis;white-space:nowrap}.kpt-roadmap__label-text--group{font-weight:var(--kpt-font-weight-semibold, 600)}.kpt-roadmap__label-sub{overflow:hidden;font-size:var(--kpt-font-size-xs, .75rem);color:var(--kpt-color-on-surface-muted);text-overflow:ellipsis;white-space:nowrap}.kpt-roadmap__label-stack{display:flex;flex-direction:column;justify-content:center;gap:.125rem;min-inline-size:0}.kpt-roadmap__label-count{align-self:center;flex:0 0 auto;padding:0 var(--kpt-space-2, .5rem);font-size:var(--kpt-font-size-xs, .75rem);color:var(--kpt-color-on-surface-muted);background:var(--kpt-color-muted);border-radius:var(--kpt-radius-full, 9999px)}.kpt-roadmap__toggle{display:inline-flex;flex:0 0 auto;align-items:center;justify-content:center;inline-size:1.25rem;block-size:1.25rem;padding:0;color:inherit;cursor:pointer;background:transparent;border:0;border-radius:var(--kpt-radius-sm, .25rem)}.kpt-roadmap__toggle:focus-visible{outline:var(--kpt-border-width-medium, 2px) solid var(--kpt-color-focus-ring);outline-offset:1px}.kpt-roadmap__toggle-icon{inline-size:0;block-size:0;border-block-start:.3rem solid transparent;border-block-end:.3rem solid transparent;border-inline-start:.4rem solid currentcolor;transition:transform var(--kpt-motion-duration-fast, .12s) var(--kpt-motion-easing-standard)}.kpt-roadmap__toggle-icon--open{transform:rotate(90deg)}.kpt-roadmap__lane{position:relative;min-inline-size:0}.kpt-roadmap__bar,.kpt-roadmap__milestone,.kpt-roadmap__summary{--_color: var(--kpt-color-primary)}.kpt-roadmap__bar[data-variant=neutral],.kpt-roadmap__milestone[data-variant=neutral],.kpt-roadmap__summary[data-variant=neutral]{--_color: var(--kpt-color-muted)}.kpt-roadmap__bar[data-variant=success],.kpt-roadmap__milestone[data-variant=success],.kpt-roadmap__summary[data-variant=success]{--_color: var(--kpt-color-success)}.kpt-roadmap__bar[data-variant=danger],.kpt-roadmap__milestone[data-variant=danger],.kpt-roadmap__summary[data-variant=danger]{--_color: var(--kpt-color-danger)}.kpt-roadmap__bar[data-variant=warning],.kpt-roadmap__milestone[data-variant=warning],.kpt-roadmap__summary[data-variant=warning]{--_color: var(--kpt-color-warning)}.kpt-roadmap__bar[data-variant=info],.kpt-roadmap__milestone[data-variant=info],.kpt-roadmap__summary[data-variant=info]{--_color: var(--kpt-color-info)}.kpt-roadmap__bar{position:absolute;z-index:1;display:flex;align-items:center;min-inline-size:.25rem;padding-inline:var(--kpt-space-2, .5rem);overflow:hidden;cursor:pointer;background:var(--_color);border-radius:var(--kpt-radius-full, 9999px);transition:filter var(--kpt-motion-duration-fast, .12s) var(--kpt-motion-easing-standard)}.kpt-roadmap__bar:hover{filter:brightness(1.08)}.kpt-roadmap__bar:focus-visible{outline:var(--kpt-border-width-medium, 2px) solid var(--kpt-color-focus-ring);outline-offset:2px}.kpt-roadmap__bar--disabled{cursor:default;opacity:.55}.kpt-roadmap__bar--clipped-start{border-start-start-radius:0;border-end-start-radius:0;clip-path:polygon(.5rem 0,100% 0,100% 100%,.5rem 100%,0 50%)}.kpt-roadmap__bar--clipped-end{border-start-end-radius:0;border-end-end-radius:0;clip-path:polygon(0 0,calc(100% - .5rem) 0,100% 50%,calc(100% - .5rem) 100%,0 100%)}.kpt-roadmap__bar--clipped-start.kpt-roadmap__bar--clipped-end{clip-path:polygon(.5rem 0,calc(100% - .5rem) 0,100% 50%,calc(100% - .5rem) 100%,.5rem 100%,0 50%)}.kpt-roadmap__bar-progress{position:absolute;inset-block:0;inset-inline-start:0;background:#00000038;pointer-events:none}.kpt-roadmap__bar-label{position:relative;overflow:hidden;font-size:var(--kpt-font-size-xs, .75rem);font-weight:var(--kpt-font-weight-medium, 500);color:var(--kpt-color-text-inverse, #fff);text-overflow:ellipsis;white-space:nowrap}.kpt-roadmap__bar[data-variant=neutral] .kpt-roadmap__bar-label{color:var(--kpt-color-on-muted)}.kpt-roadmap__milestone{position:absolute;z-index:1;cursor:pointer;background:var(--_color);transform:translate(-50%) rotate(45deg);border-radius:var(--kpt-radius-sm, .25rem)}.kpt-roadmap__milestone:focus-visible{outline:var(--kpt-border-width-medium, 2px) solid var(--kpt-color-focus-ring);outline-offset:2px}.kpt-roadmap__summary{position:absolute;inset-block-start:50%;z-index:1;block-size:.5rem;background:var(--_color);border-radius:var(--kpt-radius-full, 9999px);transform:translateY(-50%)}.kpt-roadmap__summary[data-variant=neutral]{--_color: var(--kpt-color-on-surface-muted)}.kpt-roadmap__empty{padding:var(--kpt-space-8, 2rem);color:var(--kpt-color-on-surface-muted);text-align:center}}\n"] }]
1906
+ }], () => [], { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: true }] }], scrollHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollHeight", required: false }] }], labelWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelWidth", required: false }] }], cellWidthPx: [{ type: i0.Input, args: [{ isSignal: true, alias: "cellWidthPx", required: false }] }], showToday: [{ type: i0.Input, args: [{ isSignal: true, alias: "showToday", required: false }] }], virtual: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtual", required: false }] }], collapsedGroups: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsedGroups", required: false }] }, { type: i0.Output, args: ["collapsedGroupsChange"] }], itemClick: [{ type: i0.Output, args: ["itemClick"] }], rangeChange: [{ type: i0.Output, args: ["rangeChange"] }], scrollEl: [{ type: i0.ViewChild, args: ['scrollEl', { isSignal: true }] }], laneEl: [{ type: i0.ViewChild, args: ['laneMeasure', { isSignal: true }] }] }); })();
1907
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(KptRoadmap, { className: "KptRoadmap", filePath: "lib/roadmap.component.ts", lineNumber: 94 }); })();
1908
+
1909
+ /*
1910
+ * Public API — @konce-pt/angular/roadmap
1911
+ * Roadmapa / oś czasu typu Gantt (sygnały, zoneless). Rdzeń obliczeń: pure TS.
1912
+ */
1913
+
1914
+ /**
1915
+ * Generated bundle index. Do not edit.
1916
+ */
1917
+
1918
+ export { KptRoadmap, addDays, addHours, addMonths, addUnits, arrowHead, autoRange, buildRows, buildTimeline, ceilToUnit, countUnits, daysInMonth, diffDays, floorToUnit, isSameDay, isoWeekNumber, isoWeekYear, isoWeekday, isoWeeksInYear, linkGeometry, normalizeItems, packLanes, resolveLinks, resolveRange, rollupProgress, roundedPolyline, sliceRows, spanOf, startOfDay, startOfIsoWeek, toTimestamp };
1919
+ //# sourceMappingURL=konce-pt-angular-roadmap.mjs.map