@caribeng/ui 22.4.0 → 22.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/README.md +311 -313
  2. package/fesm2022/caribeng-ui-account-picker.mjs +533 -0
  3. package/fesm2022/caribeng-ui-aging.mjs +488 -0
  4. package/fesm2022/{caribeng-ui-caribe-alert.mjs → caribeng-ui-alert.mjs} +45 -45
  5. package/fesm2022/caribeng-ui-amount.mjs +249 -0
  6. package/fesm2022/caribeng-ui-anchor.mjs +2 -2
  7. package/fesm2022/{caribeng-ui-caribe-animation-lottie.mjs → caribeng-ui-animation-lottie.mjs} +41 -41
  8. package/fesm2022/caribeng-ui-audit-log.mjs +323 -0
  9. package/fesm2022/caribeng-ui-avatar.mjs +2 -2
  10. package/fesm2022/caribeng-ui-breadcrumb.mjs +13 -3
  11. package/fesm2022/{caribeng-ui-caribe-button-group.mjs → caribeng-ui-button-group.mjs} +15 -15
  12. package/fesm2022/caribeng-ui-button.mjs +2 -2
  13. package/fesm2022/{caribeng-ui-caribe-card-content.mjs → caribeng-ui-card-content.mjs} +39 -39
  14. package/fesm2022/{caribeng-ui-caribe-card-move.mjs → caribeng-ui-card-move.mjs} +65 -65
  15. package/fesm2022/{caribeng-ui-caribe-card-primary.mjs → caribeng-ui-card-primary.mjs} +31 -31
  16. package/fesm2022/{caribeng-ui-caribe-card-select.mjs → caribeng-ui-card-select.mjs} +67 -67
  17. package/fesm2022/caribeng-ui-carousel.mjs +2 -2
  18. package/fesm2022/caribeng-ui-cascadeselect.mjs +45 -9
  19. package/fesm2022/caribeng-ui-chat.mjs +2 -2
  20. package/fesm2022/{caribeng-ui-caribe-coachmark.mjs → caribeng-ui-coachmark.mjs} +85 -85
  21. package/fesm2022/caribeng-ui-command-palette.mjs +2 -2
  22. package/fesm2022/caribeng-ui-comments.mjs +864 -0
  23. package/fesm2022/caribeng-ui-datagrid.mjs +1635 -0
  24. package/fesm2022/caribeng-ui-dataview.mjs +2 -2
  25. package/fesm2022/caribeng-ui-datepicker.mjs +2 -2
  26. package/fesm2022/caribeng-ui-document.mjs +495 -0
  27. package/fesm2022/{caribeng-ui-caribe-dots-indicator.mjs → caribeng-ui-dots-indicator.mjs} +41 -41
  28. package/fesm2022/caribeng-ui-dynamic-form.mjs +1581 -0
  29. package/fesm2022/caribeng-ui-editor.mjs +2303 -1311
  30. package/fesm2022/{caribeng-ui-caribe-expansion-panel.mjs → caribeng-ui-expansion-panel.mjs} +39 -39
  31. package/fesm2022/{caribeng-ui-caribe-fab-menu.mjs → caribeng-ui-fab-menu.mjs} +75 -75
  32. package/fesm2022/caribeng-ui-file-card.mjs +2 -2
  33. package/fesm2022/{caribeng-ui-caribe-file-download.mjs → caribeng-ui-file-download.mjs} +63 -63
  34. package/fesm2022/caribeng-ui-financial-report.mjs +380 -0
  35. package/fesm2022/{caribeng-ui-caribe-float-menu.mjs → caribeng-ui-float-menu.mjs} +31 -31
  36. package/fesm2022/caribeng-ui-floatlabel.mjs +2 -2
  37. package/fesm2022/caribeng-ui-flow.mjs +1508 -0
  38. package/fesm2022/{caribeng-ui-caribe-footer.mjs → caribeng-ui-footer.mjs} +19 -19
  39. package/fesm2022/caribeng-ui-gantt.mjs +1094 -0
  40. package/fesm2022/{caribeng-ui-caribe-header.mjs → caribeng-ui-header.mjs} +49 -49
  41. package/fesm2022/{caribeng-ui-caribe-icon-button.mjs → caribeng-ui-icon-button.mjs} +23 -23
  42. package/fesm2022/caribeng-ui-image-cropper.mjs +671 -0
  43. package/fesm2022/caribeng-ui-import-wizard.mjs +746 -0
  44. package/fesm2022/caribeng-ui-infinite-scroll.mjs +226 -0
  45. package/fesm2022/caribeng-ui-input-number.mjs +22 -6
  46. package/fesm2022/{caribeng-ui-caribe-input-search.mjs → caribeng-ui-input-search.mjs} +65 -65
  47. package/fesm2022/{caribeng-ui-caribe-input-token.mjs → caribeng-ui-input-token.mjs} +59 -59
  48. package/fesm2022/caribeng-ui-inputotp.mjs +2 -2
  49. package/fesm2022/caribeng-ui-journal-entry.mjs +340 -0
  50. package/fesm2022/caribeng-ui-kanban.mjs +6 -6
  51. package/fesm2022/caribeng-ui-kpi.mjs +189 -0
  52. package/fesm2022/{caribeng-ui-caribe-link.mjs → caribeng-ui-link.mjs} +37 -37
  53. package/fesm2022/caribeng-ui-masonry.mjs +93 -0
  54. package/fesm2022/caribeng-ui-media-player.mjs +821 -0
  55. package/fesm2022/caribeng-ui-mentions.mjs +2 -2
  56. package/fesm2022/caribeng-ui-notification-center.mjs +636 -0
  57. package/fesm2022/{caribeng-ui-caribe-off-canvas.mjs → caribeng-ui-off-canvas.mjs} +55 -55
  58. package/fesm2022/{caribeng-ui-caribe-onboarding.mjs → caribeng-ui-onboarding.mjs} +105 -105
  59. package/fesm2022/caribeng-ui-orderlist.mjs +2 -2
  60. package/fesm2022/caribeng-ui-pdfviewer.mjs +1755 -25
  61. package/fesm2022/caribeng-ui-period-picker.mjs +637 -0
  62. package/fesm2022/caribeng-ui-phone-input.mjs +2 -2
  63. package/fesm2022/caribeng-ui-picklist.mjs +2 -2
  64. package/fesm2022/caribeng-ui-pivot.mjs +419 -0
  65. package/fesm2022/caribeng-ui-pricing.mjs +2 -2
  66. package/fesm2022/caribeng-ui-rating.mjs +14 -17
  67. package/fesm2022/caribeng-ui-reconciliation.mjs +386 -0
  68. package/fesm2022/caribeng-ui-scheduler.mjs +1027 -0
  69. package/fesm2022/caribeng-ui-select.mjs +2 -2
  70. package/fesm2022/{caribeng-ui-caribe-shortcut.mjs → caribeng-ui-shortcut.mjs} +29 -29
  71. package/fesm2022/caribeng-ui-sidebar.mjs +2 -2
  72. package/fesm2022/caribeng-ui-signature-pad.mjs +654 -0
  73. package/fesm2022/{caribeng-ui-caribe-status.mjs → caribeng-ui-status.mjs} +11 -11
  74. package/fesm2022/{caribeng-ui-caribe-step-indicator.mjs → caribeng-ui-step-indicator.mjs} +53 -53
  75. package/fesm2022/caribeng-ui-stepper.mjs +2 -2
  76. package/fesm2022/caribeng-ui-steps.mjs +2 -2
  77. package/fesm2022/caribeng-ui-table.mjs +323 -15
  78. package/fesm2022/caribeng-ui-tabmenu.mjs +2 -2
  79. package/fesm2022/caribeng-ui-tabs.mjs +2 -2
  80. package/fesm2022/{caribeng-ui-caribe-tag-button.mjs → caribeng-ui-tag-button.mjs} +25 -25
  81. package/fesm2022/caribeng-ui-tag.mjs +2 -2
  82. package/fesm2022/caribeng-ui-tieredmenu.mjs +6 -6
  83. package/fesm2022/{caribeng-ui-caribe-tracker.mjs → caribeng-ui-tracker.mjs} +29 -29
  84. package/fesm2022/{caribeng-ui-caribe-transaction-status.mjs → caribeng-ui-transaction-status.mjs} +11 -11
  85. package/fesm2022/caribeng-ui-treeselect.mjs +44 -9
  86. package/fesm2022/caribeng-ui-treetable.mjs +236 -17
  87. package/fesm2022/{caribeng-ui-caribe-voucher.mjs → caribeng-ui-voucher.mjs} +45 -45
  88. package/fesm2022/caribeng-ui-xlsx.mjs +486 -0
  89. package/fesm2022/caribeng-ui.mjs +1181 -1
  90. package/package.json +217 -177
  91. package/types/caribeng-ui-account-picker.d.ts +142 -0
  92. package/types/caribeng-ui-aging.d.ts +123 -0
  93. package/types/{caribeng-ui-caribe-alert.d.ts → caribeng-ui-alert.d.ts} +2 -2
  94. package/types/caribeng-ui-amount.d.ts +82 -0
  95. package/types/{caribeng-ui-caribe-animation-lottie.d.ts → caribeng-ui-animation-lottie.d.ts} +2 -2
  96. package/types/caribeng-ui-audit-log.d.ts +86 -0
  97. package/types/caribeng-ui-breadcrumb.d.ts +2 -2
  98. package/types/{caribeng-ui-caribe-button-group.d.ts → caribeng-ui-button-group.d.ts} +2 -2
  99. package/types/{caribeng-ui-caribe-card-content.d.ts → caribeng-ui-card-content.d.ts} +2 -2
  100. package/types/{caribeng-ui-caribe-card-move.d.ts → caribeng-ui-card-move.d.ts} +1 -1
  101. package/types/{caribeng-ui-caribe-card-primary.d.ts → caribeng-ui-card-primary.d.ts} +2 -2
  102. package/types/{caribeng-ui-caribe-card-select.d.ts → caribeng-ui-card-select.d.ts} +2 -2
  103. package/types/caribeng-ui-cascadeselect.d.ts +6 -0
  104. package/types/{caribeng-ui-caribe-coachmark.d.ts → caribeng-ui-coachmark.d.ts} +2 -2
  105. package/types/caribeng-ui-comments.d.ts +214 -0
  106. package/types/caribeng-ui-datagrid.d.ts +269 -0
  107. package/types/caribeng-ui-document.d.ts +138 -0
  108. package/types/{caribeng-ui-caribe-dots-indicator.d.ts → caribeng-ui-dots-indicator.d.ts} +2 -2
  109. package/types/caribeng-ui-dynamic-form.d.ts +409 -0
  110. package/types/caribeng-ui-editor.d.ts +110 -12
  111. package/types/{caribeng-ui-caribe-expansion-panel.d.ts → caribeng-ui-expansion-panel.d.ts} +1 -1
  112. package/types/{caribeng-ui-caribe-fab-menu.d.ts → caribeng-ui-fab-menu.d.ts} +2 -2
  113. package/types/{caribeng-ui-caribe-file-download.d.ts → caribeng-ui-file-download.d.ts} +1 -1
  114. package/types/caribeng-ui-financial-report.d.ts +102 -0
  115. package/types/{caribeng-ui-caribe-float-menu.d.ts → caribeng-ui-float-menu.d.ts} +2 -2
  116. package/types/caribeng-ui-flow.d.ts +228 -0
  117. package/types/{caribeng-ui-caribe-footer.d.ts → caribeng-ui-footer.d.ts} +2 -2
  118. package/types/caribeng-ui-gantt.d.ts +244 -0
  119. package/types/{caribeng-ui-caribe-header.d.ts → caribeng-ui-header.d.ts} +2 -2
  120. package/types/{caribeng-ui-caribe-icon-button.d.ts → caribeng-ui-icon-button.d.ts} +2 -2
  121. package/types/{caribeng-ui-caribe-illustration.d.ts → caribeng-ui-illustration.d.ts} +1 -1
  122. package/types/caribeng-ui-image-cropper.d.ts +169 -0
  123. package/types/caribeng-ui-import-wizard.d.ts +149 -0
  124. package/types/caribeng-ui-infinite-scroll.d.ts +75 -0
  125. package/types/caribeng-ui-input-number.d.ts +1 -1
  126. package/types/{caribeng-ui-caribe-input-search.d.ts → caribeng-ui-input-search.d.ts} +2 -2
  127. package/types/{caribeng-ui-caribe-input-token.d.ts → caribeng-ui-input-token.d.ts} +2 -2
  128. package/types/caribeng-ui-journal-entry.d.ts +110 -0
  129. package/types/caribeng-ui-kpi.d.ts +64 -0
  130. package/types/{caribeng-ui-caribe-link.d.ts → caribeng-ui-link.d.ts} +2 -2
  131. package/types/{caribeng-ui-caribe-logo.d.ts → caribeng-ui-logo.d.ts} +1 -1
  132. package/types/caribeng-ui-masonry.d.ts +32 -0
  133. package/types/caribeng-ui-media-player.d.ts +140 -0
  134. package/types/caribeng-ui-notification-center.d.ts +153 -0
  135. package/types/{caribeng-ui-caribe-off-canvas.d.ts → caribeng-ui-off-canvas.d.ts} +2 -2
  136. package/types/{caribeng-ui-caribe-onboarding.d.ts → caribeng-ui-onboarding.d.ts} +2 -2
  137. package/types/caribeng-ui-pdfviewer.d.ts +245 -5
  138. package/types/caribeng-ui-period-picker.d.ts +148 -0
  139. package/types/caribeng-ui-pivot.d.ts +116 -0
  140. package/types/caribeng-ui-reconciliation.d.ts +109 -0
  141. package/types/caribeng-ui-scheduler.d.ts +188 -0
  142. package/types/{caribeng-ui-caribe-shortcut.d.ts → caribeng-ui-shortcut.d.ts} +2 -2
  143. package/types/caribeng-ui-signature-pad.d.ts +151 -0
  144. package/types/{caribeng-ui-caribe-status.d.ts → caribeng-ui-status.d.ts} +2 -2
  145. package/types/{caribeng-ui-caribe-step-indicator.d.ts → caribeng-ui-step-indicator.d.ts} +2 -2
  146. package/types/caribeng-ui-table.d.ts +66 -2
  147. package/types/{caribeng-ui-caribe-tag-button.d.ts → caribeng-ui-tag-button.d.ts} +2 -2
  148. package/types/{caribeng-ui-caribe-tracker.d.ts → caribeng-ui-tracker.d.ts} +2 -2
  149. package/types/{caribeng-ui-caribe-transaction-status.d.ts → caribeng-ui-transaction-status.d.ts} +2 -2
  150. package/types/caribeng-ui-treeselect.d.ts +6 -0
  151. package/types/caribeng-ui-treetable.d.ts +67 -2
  152. package/types/{caribeng-ui-caribe-voucher.d.ts → caribeng-ui-voucher.d.ts} +2 -2
  153. package/types/caribeng-ui-xlsx.d.ts +79 -0
  154. package/types/caribeng-ui.d.ts +711 -2
  155. package/fesm2022/caribeng-ui-caribe-calendar.mjs +0 -92
  156. package/fesm2022/caribeng-ui-caribe-circle-loading.mjs +0 -130
  157. package/fesm2022/caribeng-ui-caribe-data-table.mjs +0 -94
  158. package/fesm2022/caribeng-ui-caribe-file-select.mjs +0 -88
  159. package/fesm2022/caribeng-ui-caribe-inline-alert.mjs +0 -100
  160. package/fesm2022/caribeng-ui-caribe-input-date.mjs +0 -138
  161. package/fesm2022/caribeng-ui-caribe-input-select.mjs +0 -154
  162. package/fesm2022/caribeng-ui-caribe-loader.mjs +0 -48
  163. package/fesm2022/caribeng-ui-caribe-modal.mjs +0 -119
  164. package/fesm2022/caribeng-ui-caribe-pictogram.mjs +0 -52
  165. package/fesm2022/caribeng-ui-caribe-pop-over.mjs +0 -104
  166. package/fesm2022/caribeng-ui-caribe-preloader.mjs +0 -57
  167. package/fesm2022/caribeng-ui-caribe-progress-bar.mjs +0 -84
  168. package/fesm2022/caribeng-ui-caribe-stroke.mjs +0 -69
  169. package/fesm2022/caribeng-ui-caribe-switch.mjs +0 -182
  170. package/types/caribeng-ui-caribe-calendar.d.ts +0 -27
  171. package/types/caribeng-ui-caribe-circle-loading.d.ts +0 -28
  172. package/types/caribeng-ui-caribe-data-table.d.ts +0 -26
  173. package/types/caribeng-ui-caribe-file-select.d.ts +0 -31
  174. package/types/caribeng-ui-caribe-inline-alert.d.ts +0 -28
  175. package/types/caribeng-ui-caribe-input-date.d.ts +0 -40
  176. package/types/caribeng-ui-caribe-input-select.d.ts +0 -47
  177. package/types/caribeng-ui-caribe-loader.d.ts +0 -22
  178. package/types/caribeng-ui-caribe-modal.d.ts +0 -21
  179. package/types/caribeng-ui-caribe-pictogram.d.ts +0 -15
  180. package/types/caribeng-ui-caribe-pop-over.d.ts +0 -17
  181. package/types/caribeng-ui-caribe-preloader.d.ts +0 -24
  182. package/types/caribeng-ui-caribe-progress-bar.d.ts +0 -28
  183. package/types/caribeng-ui-caribe-stroke.d.ts +0 -17
  184. package/types/caribeng-ui-caribe-switch.d.ts +0 -55
  185. /package/fesm2022/{caribeng-ui-caribe-illustration.mjs → caribeng-ui-illustration.mjs} +0 -0
  186. /package/fesm2022/{caribeng-ui-caribe-logo.mjs → caribeng-ui-logo.mjs} +0 -0
@@ -0,0 +1,1094 @@
1
+ import * as i0 from '@angular/core';
2
+ import { afterNextRender, inject, Injector, ElementRef, model, input, booleanAttribute, output, signal, viewChild, computed, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { cbFormat, injectCbTranslation } from '@caribeng/ui';
4
+ import { CbIcon } from '@caribeng/ui/icon';
5
+
6
+ /**
7
+ * Modelo del diagrama de Gantt: fechas, árbol de tareas, grupos calculados,
8
+ * dependencias (fin → inicio) y planificación automática. Funciones puras: el
9
+ * componente las usa y los tests las prueban sin DOM.
10
+ */
11
+ // --- Fechas -------------------------------------------------------------------------------
12
+ function toDay(value) {
13
+ if (value instanceof Date)
14
+ return isNaN(value.getTime()) ? null : new Date(value.getFullYear(), value.getMonth(), value.getDate());
15
+ if (typeof value !== 'string' || !value)
16
+ return null;
17
+ const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(value);
18
+ const date = m ? new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3])) : new Date(value);
19
+ return isNaN(date.getTime()) ? null : new Date(date.getFullYear(), date.getMonth(), date.getDate());
20
+ }
21
+ function isoDay(date) {
22
+ const pad = (n) => String(n).padStart(2, '0');
23
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
24
+ }
25
+ function addDays(date, days) {
26
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate() + days);
27
+ }
28
+ /** Días de calendario entre `a` y `b` (`b` − `a`). */
29
+ function dayDiff(a, b) {
30
+ return Math.round((Date.UTC(b.getFullYear(), b.getMonth(), b.getDate()) - Date.UTC(a.getFullYear(), a.getMonth(), a.getDate())) / 86_400_000);
31
+ }
32
+ /** Lunes de la semana de `date` (semana ISO). */
33
+ function startOfWeek(date) {
34
+ return addDays(date, -((date.getDay() + 6) % 7));
35
+ }
36
+ /** Número de semana ISO. */
37
+ function isoWeek(date) {
38
+ const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
39
+ const day = d.getUTCDay() || 7;
40
+ d.setUTCDate(d.getUTCDate() + 4 - day);
41
+ const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
42
+ return Math.ceil(((d.getTime() - yearStart.getTime()) / 86_400_000 + 1) / 7);
43
+ }
44
+ /** Resuelve fechas y avance (los grupos abarcan a sus tareas y promedian su avance por duración). */
45
+ function resolveTasks(tasks) {
46
+ const byId = new Map(tasks.map((t) => [t.id, t]));
47
+ const children = new Map();
48
+ for (const t of tasks) {
49
+ if (t.parent && byId.has(t.parent) && t.parent !== t.id) {
50
+ const list = children.get(t.parent) ?? [];
51
+ list.push(t.id);
52
+ children.set(t.parent, list);
53
+ }
54
+ }
55
+ const out = new Map();
56
+ const visiting = new Set();
57
+ const level = (t) => {
58
+ let n = 0;
59
+ const seen = new Set();
60
+ for (let p = t.parent; p && byId.has(p) && !seen.has(p); p = byId.get(p).parent) {
61
+ seen.add(p);
62
+ n++;
63
+ }
64
+ return n;
65
+ };
66
+ const resolve = (id) => {
67
+ const done = out.get(id);
68
+ if (done)
69
+ return done;
70
+ const task = byId.get(id);
71
+ if (!task || visiting.has(id))
72
+ return null;
73
+ visiting.add(id);
74
+ const kids = children.get(id) ?? [];
75
+ const isGroup = task.type === 'group' || kids.length > 0;
76
+ let start = toDay(task.start);
77
+ let end = toDay(task.end) ?? start;
78
+ let progress = clampProgress(task.progress ?? 0);
79
+ if (isGroup && kids.length) {
80
+ const resolved = kids.map(resolve).filter((r) => !!r);
81
+ if (resolved.length) {
82
+ start = new Date(Math.min(...resolved.map((r) => r.start.getTime())));
83
+ end = new Date(Math.max(...resolved.map((r) => r.end.getTime())));
84
+ const weights = resolved.map((r) => Math.max(1, dayDiff(r.start, r.end) + 1));
85
+ const total = weights.reduce((a, b) => a + b, 0);
86
+ progress = Math.round(resolved.reduce((sum, r, i) => sum + r.progress * weights[i], 0) / total);
87
+ }
88
+ }
89
+ visiting.delete(id);
90
+ if (!start)
91
+ return null;
92
+ if (!end || end < start)
93
+ end = start;
94
+ const isMilestone = task.type === 'milestone' && !isGroup;
95
+ if (isMilestone)
96
+ end = start;
97
+ const result = { task, start, end, progress, level: level(task), isGroup, isMilestone, children: kids };
98
+ out.set(id, result);
99
+ return result;
100
+ };
101
+ for (const t of tasks)
102
+ resolve(t.id);
103
+ return out;
104
+ }
105
+ function clampProgress(value) {
106
+ return Math.max(0, Math.min(100, Math.round(Number(value) || 0)));
107
+ }
108
+ /** Filas visibles: el árbol en orden, sin los hijos de los grupos plegados. */
109
+ function visibleRows(tasks, resolved, collapsed) {
110
+ const rows = [];
111
+ const ids = new Set(tasks.map((t) => t.id));
112
+ const walk = (parent) => {
113
+ for (const t of tasks) {
114
+ const p = t.parent && ids.has(t.parent) && t.parent !== t.id ? t.parent : null;
115
+ if (p !== parent)
116
+ continue;
117
+ const r = resolved.get(t.id);
118
+ if (!r)
119
+ continue;
120
+ rows.push(r);
121
+ if (r.children.length && !collapsed.has(t.id))
122
+ walk(t.id);
123
+ }
124
+ };
125
+ walk(null);
126
+ return rows;
127
+ }
128
+ /** Ids de todas las tareas dentro de un grupo (a cualquier nivel). */
129
+ function descendants(tasks, id) {
130
+ const out = [];
131
+ const queue = [id];
132
+ while (queue.length) {
133
+ const current = queue.shift();
134
+ for (const t of tasks) {
135
+ if (t.parent === current && !out.includes(t.id)) {
136
+ out.push(t.id);
137
+ queue.push(t.id);
138
+ }
139
+ }
140
+ }
141
+ return out;
142
+ }
143
+ // --- Dependencias ---------------------------------------------------------------------------
144
+ /** ¿`to` depende (directa o indirectamente) de `from`? Evita crear ciclos. */
145
+ function dependsOn(tasks, to, from) {
146
+ const byId = new Map(tasks.map((t) => [t.id, t]));
147
+ const seen = new Set();
148
+ const stack = [to];
149
+ while (stack.length) {
150
+ const id = stack.pop();
151
+ if (id === from)
152
+ return true;
153
+ if (seen.has(id))
154
+ continue;
155
+ seen.add(id);
156
+ stack.push(...(byId.get(id)?.dependencies ?? []));
157
+ }
158
+ return false;
159
+ }
160
+ /** ¿Se puede crear la dependencia `from` → `to`? (sin repetir, sin ciclos ni grupos que se contienen). */
161
+ function canLink(tasks, from, to) {
162
+ if (from === to)
163
+ return false;
164
+ const target = tasks.find((t) => t.id === to);
165
+ if (!target || !tasks.some((t) => t.id === from))
166
+ return false;
167
+ if (target.dependencies?.includes(from))
168
+ return false;
169
+ if (descendants(tasks, from).includes(to) || descendants(tasks, to).includes(from))
170
+ return false;
171
+ return !dependsOn(tasks, from, to);
172
+ }
173
+ /**
174
+ * Planificación automática: cada tarea empieza como pronto el día siguiente al
175
+ * fin de sus predecesoras; si no, se desplaza conservando su duración. Devuelve
176
+ * las tareas cambiadas (las de grupos se resuelven con sus hijas).
177
+ */
178
+ function autoSchedule(tasks) {
179
+ let current = [...tasks];
180
+ for (let pass = 0; pass < tasks.length + 1; pass++) {
181
+ const resolved = resolveTasks(current);
182
+ let changed = false;
183
+ current = current.map((t) => {
184
+ if (!t.dependencies?.length)
185
+ return t;
186
+ const self = resolved.get(t.id);
187
+ if (!self || self.isGroup)
188
+ return t;
189
+ const ends = t.dependencies.map((d) => resolved.get(d)?.end).filter((d) => !!d);
190
+ if (!ends.length)
191
+ return t;
192
+ const earliest = addDays(new Date(Math.max(...ends.map((d) => d.getTime()))), 1);
193
+ if (self.start >= earliest)
194
+ return t;
195
+ const shift = dayDiff(self.start, earliest);
196
+ changed = true;
197
+ return { ...t, start: isoDay(addDays(self.start, shift)), end: isoDay(addDays(self.end, shift)) };
198
+ });
199
+ if (!changed)
200
+ break;
201
+ }
202
+ return current;
203
+ }
204
+
205
+ /** Alto de cada fila (px), igual que en el CSS. */
206
+ const ROW = 40;
207
+ /** Píxeles por día según la escala. */
208
+ const DAY_WIDTH = { day: 36, week: 14, month: 4 };
209
+ let nextUid = 0;
210
+ /**
211
+ * Diagrama de Gantt de CaribeNG: tabla de tareas en árbol (grupos plegables) y
212
+ * barras en el tiempo, con escalas de día, semana y mes.
213
+ *
214
+ * - Arrastra una barra para moverla, sus bordes para cambiar inicio o fin, el
215
+ * tirador inferior para el avance y el punto de la derecha hasta otra tarea
216
+ * para crear una dependencia (fin → inicio).
217
+ * - Con `autoSchedule`, al retrasar una tarea se desplazan las que dependen de ella.
218
+ * - Los grupos abarcan a sus tareas y promedian su avance; los hitos son rombos.
219
+ * - Con el teclado, cada barra es un botón: flechas mueven, Mayús + flechas cambian
220
+ * el fin, Alt + flechas el inicio, + / − el avance, Supr quita sus dependencias
221
+ * y Enter abre la tarea. Cada cambio se anuncia.
222
+ *
223
+ * @example
224
+ * ```html
225
+ * <cb-gantt [(tasks)]="tareas" [(scale)]="escala" (taskChange)="guardar($event)" />
226
+ * ```
227
+ */
228
+ class CbGantt {
229
+ x(date) {
230
+ return dayDiff(this.range().start, date) * this.dayWidth();
231
+ }
232
+ // --- Textos ---------------------------------------------------------------------------
233
+ scaleLabel(s) {
234
+ const t = this.t();
235
+ return s === 'day' ? t.ganttDay : s === 'week' ? t.ganttWeek : t.ganttMonth;
236
+ }
237
+ columnLabel(col) {
238
+ const t = this.t();
239
+ return { name: t.ganttTask, start: t.ganttStart, end: t.ganttEnd, duration: t.ganttDuration, progress: t.ganttProgress, assignee: t.ganttAssignee }[col];
240
+ }
241
+ dateText(d) {
242
+ return new Intl.DateTimeFormat(this.t().intlLocale, { day: 'numeric', month: 'short' }).format(d);
243
+ }
244
+ cellText(r, col) {
245
+ switch (col) {
246
+ case 'start':
247
+ return this.dateText(r.start);
248
+ case 'end':
249
+ return this.dateText(r.end);
250
+ case 'duration':
251
+ return r.isMilestone ? '—' : cbFormat(this.t().ganttDays, dayDiff(r.start, r.end) + 1);
252
+ case 'progress':
253
+ return r.isMilestone ? '—' : `${r.progress} %`;
254
+ case 'assignee':
255
+ return r.task.assignee ?? '';
256
+ default:
257
+ return r.task.name;
258
+ }
259
+ }
260
+ barLabel(b) {
261
+ const t = this.t();
262
+ const deps = (b.row.task.dependencies ?? []).map((id) => this.resolved().get(id)?.task.name).filter(Boolean);
263
+ const after = deps.length ? `. ${cbFormat(t.ganttDependsOn, deps.join(', '))}` : '';
264
+ if (b.row.isMilestone)
265
+ return cbFormat(t.ganttMilestoneLabel, b.row.task.name, this.dateText(b.start)) + after;
266
+ const range = `${this.dateText(b.start)} – ${this.dateText(b.end)}`;
267
+ return cbFormat(b.row.isGroup ? t.ganttGroupLabel : t.ganttBarLabel, b.row.task.name, range, b.progress) + after;
268
+ }
269
+ // --- Árbol ---------------------------------------------------------------------------------
270
+ toggle(id) {
271
+ this.collapsed.update((set) => {
272
+ const next = new Set(set);
273
+ if (next.has(id))
274
+ next.delete(id);
275
+ else
276
+ next.add(id);
277
+ return next;
278
+ });
279
+ }
280
+ /** Selecciona la tarea, lleva su barra a la vista y le da el foco. */
281
+ focusBar(id) {
282
+ this.selected.set(id);
283
+ afterNextRender(() => {
284
+ const bar = this.barEl(id);
285
+ const scroller = this.scroller()?.nativeElement;
286
+ if (!bar || !scroller)
287
+ return;
288
+ const grid = scroller.querySelector('.cb-gantt__grid')?.offsetWidth ?? 0;
289
+ const left = bar.offsetLeft + grid;
290
+ if (left < scroller.scrollLeft + grid || left + bar.offsetWidth > scroller.scrollLeft + scroller.clientWidth) {
291
+ scroller.scrollLeft = Math.max(0, left - grid - 48);
292
+ }
293
+ bar.focus({ preventScroll: true });
294
+ }, { injector: this.injector });
295
+ }
296
+ /** Desplaza el diagrama hasta hoy (centrado en la parte visible). */
297
+ scrollToToday(smooth = true) {
298
+ const x = this.todayX();
299
+ const scroller = this.scroller()?.nativeElement;
300
+ if (x === null || !scroller)
301
+ return;
302
+ const grid = scroller.querySelector('.cb-gantt__grid')?.offsetWidth ?? 0;
303
+ scroller.scrollTo?.({ left: Math.max(0, x - (scroller.clientWidth - grid) / 3), behavior: smooth ? 'smooth' : 'auto' });
304
+ }
305
+ constructor() {
306
+ /** Textos de interfaz en el idioma activo (`provideCaribeNG({ locale })`). */
307
+ this.t = injectCbTranslation();
308
+ this.fmt = cbFormat;
309
+ this.injector = inject(Injector);
310
+ this.host = inject(ElementRef);
311
+ this.uid = `cb-gantt-${++nextUid}`;
312
+ this.row = ROW;
313
+ this.scales = ['day', 'week', 'month'];
314
+ /** Tareas (bidireccional: se actualiza al arrastrar, con el teclado y al planificar). */
315
+ this.tasks = model([], /* @ts-ignore */
316
+ ...(ngDevMode ? [{ debugName: "tasks" }] : /* istanbul ignore next */ []));
317
+ /** Escala: un día, una semana o un mes por columna. */
318
+ this.scale = model('day', /* @ts-ignore */
319
+ ...(ngDevMode ? [{ debugName: "scale" }] : /* istanbul ignore next */ []));
320
+ /** Columnas de la tabla de la izquierda y su orden. */
321
+ this.columns = input(['name', 'start', 'end', 'duration', 'progress'], /* @ts-ignore */
322
+ ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
323
+ /** Ancho de la tabla de la izquierda; por defecto, el de sus columnas (como mucho, el 60 % del componente). */
324
+ this.gridWidth = input('', /* @ts-ignore */
325
+ ...(ngDevMode ? [{ debugName: "gridWidth" }] : /* istanbul ignore next */ []));
326
+ /** Alto del componente (con scroll). */
327
+ this.height = input('28rem', /* @ts-ignore */
328
+ ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
329
+ /** Al retrasar una tarea, desplaza las que dependen de ella (fin → inicio). */
330
+ this.autoSchedule = input(true, { ...(ngDevMode ? { debugName: "autoSchedule" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
331
+ /** Sin edición: no se arrastra ni se crean dependencias. */
332
+ this.readonly = input(false, { ...(ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
333
+ /** Barra con la escala y el botón Hoy. */
334
+ this.showToolbar = input(true, { ...(ngDevMode ? { debugName: "showToolbar" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
335
+ /** Texto sin tareas. Por defecto, "Sin datos". */
336
+ this.emptyMessage = input(undefined, /* @ts-ignore */
337
+ ...(ngDevMode ? [{ debugName: "emptyMessage" }] : /* istanbul ignore next */ []));
338
+ this.ariaLabel = input('', /* @ts-ignore */
339
+ ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
340
+ /** Una tarea cambió de fechas o de avance (también las desplazadas por la planificación). */
341
+ this.taskChange = output();
342
+ /** Doble clic o Enter sobre una tarea. */
343
+ this.taskOpen = output();
344
+ /** Dependencia creada (`to` depende de `from`). */
345
+ this.linkAdd = output();
346
+ /** Dependencia quitada. */
347
+ this.linkRemove = output();
348
+ this.collapsed = signal(new Set(), /* @ts-ignore */
349
+ ...(ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []));
350
+ this.selected = signal(null, /* @ts-ignore */
351
+ ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
352
+ this.selectedLink = signal(null, /* @ts-ignore */
353
+ ...(ngDevMode ? [{ debugName: "selectedLink" }] : /* istanbul ignore next */ []));
354
+ this.preview = signal(null, /* @ts-ignore */
355
+ ...(ngDevMode ? [{ debugName: "preview" }] : /* istanbul ignore next */ []));
356
+ this.announcement = signal('', /* @ts-ignore */
357
+ ...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
358
+ this.drag = null;
359
+ this.scroller = viewChild('scroller', /* @ts-ignore */
360
+ ...(ngDevMode ? [{ debugName: "scroller" }] : /* istanbul ignore next */ []));
361
+ this.chart = viewChild('chart', /* @ts-ignore */
362
+ ...(ngDevMode ? [{ debugName: "chart" }] : /* istanbul ignore next */ []));
363
+ this.resolved = computed(() => resolveTasks(this.tasks()), /* @ts-ignore */
364
+ ...(ngDevMode ? [{ debugName: "resolved" }] : /* istanbul ignore next */ []));
365
+ /** Ancho de la tabla: el indicado o el nombre (14rem) más el de cada columna. */
366
+ this.gridSize = computed(() => {
367
+ if (this.gridWidth())
368
+ return this.gridWidth();
369
+ const widths = { name: 14, start: 4.5, end: 4.5, duration: 4.5, progress: 4, assignee: 6.5 };
370
+ return `${this.columns().reduce((sum, c) => sum + widths[c], 0)}rem`;
371
+ }, /* @ts-ignore */
372
+ ...(ngDevMode ? [{ debugName: "gridSize" }] : /* istanbul ignore next */ []));
373
+ this.rows = computed(() => visibleRows(this.tasks(), this.resolved(), this.collapsed()), /* @ts-ignore */
374
+ ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
375
+ this.dayWidth = computed(() => DAY_WIDTH[this.scale()], /* @ts-ignore */
376
+ ...(ngDevMode ? [{ debugName: "dayWidth" }] : /* istanbul ignore next */ []));
377
+ /** Primer y último día del diagrama (con margen, alineados a la unidad de la escala). */
378
+ this.range = computed(() => {
379
+ const all = [...this.resolved().values()];
380
+ const today = toDay(new Date());
381
+ let min = all.length ? new Date(Math.min(...all.map((r) => r.start.getTime()))) : today;
382
+ let max = all.length ? new Date(Math.max(...all.map((r) => r.end.getTime()))) : addDays(today, 30);
383
+ const scale = this.scale();
384
+ if (scale === 'day') {
385
+ min = addDays(min, -3);
386
+ max = addDays(max, 10);
387
+ }
388
+ else if (scale === 'week') {
389
+ min = startOfWeek(addDays(min, -7));
390
+ max = addDays(startOfWeek(addDays(max, 14)), 6);
391
+ }
392
+ else {
393
+ min = new Date(min.getFullYear(), min.getMonth() - 1, 1);
394
+ max = new Date(max.getFullYear(), max.getMonth() + 2, 0);
395
+ }
396
+ return { start: min, end: max, days: dayDiff(min, max) + 1 };
397
+ }, /* @ts-ignore */
398
+ ...(ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []));
399
+ this.chartWidth = computed(() => this.range().days * this.dayWidth(), /* @ts-ignore */
400
+ ...(ngDevMode ? [{ debugName: "chartWidth" }] : /* istanbul ignore next */ []));
401
+ /** Unidades de la escala (fila inferior de la cabecera y columnas del fondo). */
402
+ this.units = computed(() => {
403
+ const { start, days } = this.range();
404
+ const locale = this.t().intlLocale;
405
+ const scale = this.scale();
406
+ const w = this.dayWidth();
407
+ const units = [];
408
+ if (scale === 'day') {
409
+ const weekday = new Intl.DateTimeFormat(locale, { weekday: 'narrow' });
410
+ for (let i = 0; i < days; i++) {
411
+ const d = addDays(start, i);
412
+ units.push({ key: isoDay(d), label: `${weekday.format(d)} ${d.getDate()}`, x: i * w, width: w, weekend: d.getDay() === 0 || d.getDay() === 6 });
413
+ }
414
+ }
415
+ else if (scale === 'week') {
416
+ const day = new Intl.DateTimeFormat(locale, { day: 'numeric', month: 'short' });
417
+ for (let i = 0; i < days; i += 7) {
418
+ const d = addDays(start, i);
419
+ units.push({ key: isoDay(d), label: `${cbFormat(this.t().ganttWeekShort, isoWeek(d))} · ${day.format(d)}`, x: i * w, width: 7 * w });
420
+ }
421
+ }
422
+ else {
423
+ const month = new Intl.DateTimeFormat(locale, { month: 'short' });
424
+ for (let d = new Date(start); d <= this.range().end; d = new Date(d.getFullYear(), d.getMonth() + 1, 1)) {
425
+ const next = new Date(d.getFullYear(), d.getMonth() + 1, 1);
426
+ units.push({ key: isoDay(d), label: month.format(d), x: this.x(d), width: dayDiff(d, next) * w });
427
+ }
428
+ }
429
+ return units;
430
+ }, /* @ts-ignore */
431
+ ...(ngDevMode ? [{ debugName: "units" }] : /* istanbul ignore next */ []));
432
+ /** Fila superior de la cabecera: meses (día y semana) o años (mes). */
433
+ this.topUnits = computed(() => {
434
+ const { start, end } = this.range();
435
+ const locale = this.t().intlLocale;
436
+ const units = [];
437
+ if (this.scale() === 'month') {
438
+ for (let y = start.getFullYear(); y <= end.getFullYear(); y++) {
439
+ const from = new Date(Math.max(start.getTime(), new Date(y, 0, 1).getTime()));
440
+ const to = new Date(Math.min(addDays(end, 1).getTime(), new Date(y + 1, 0, 1).getTime()));
441
+ units.push({ key: String(y), label: String(y), x: this.x(from), width: dayDiff(from, to) * this.dayWidth() });
442
+ }
443
+ return units;
444
+ }
445
+ const format = new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric' });
446
+ for (let d = new Date(start.getFullYear(), start.getMonth(), 1); d <= end; d = new Date(d.getFullYear(), d.getMonth() + 1, 1)) {
447
+ const from = d < start ? start : d;
448
+ const next = new Date(d.getFullYear(), d.getMonth() + 1, 1);
449
+ const to = next > addDays(end, 1) ? addDays(end, 1) : next;
450
+ const label = format.format(d);
451
+ // Solo la primera letra en mayúscula ("Noviembre de 2026", no "Noviembre De 2026").
452
+ units.push({ key: isoDay(d), label: label.charAt(0).toUpperCase() + label.slice(1), x: this.x(from), width: dayDiff(from, to) * this.dayWidth() });
453
+ }
454
+ return units;
455
+ }, /* @ts-ignore */
456
+ ...(ngDevMode ? [{ debugName: "topUnits" }] : /* istanbul ignore next */ []));
457
+ this.todayX = computed(() => {
458
+ const today = toDay(new Date());
459
+ const { start, end } = this.range();
460
+ return today < start || today > end ? null : this.x(today) + this.dayWidth() / 2;
461
+ }, /* @ts-ignore */
462
+ ...(ngDevMode ? [{ debugName: "todayX" }] : /* istanbul ignore next */ []));
463
+ /** Geometría de las barras (con la vista previa del arrastre). */
464
+ this.bars = computed(() => {
465
+ const preview = this.preview();
466
+ const moving = preview && preview.mode === 'move' ? new Set([preview.id, ...descendants(this.tasks(), preview.id)]) : null;
467
+ const w = this.dayWidth();
468
+ return this.rows().map((r, index) => {
469
+ let start = r.start;
470
+ let end = r.end;
471
+ let progress = r.progress;
472
+ if (preview && moving?.has(r.task.id)) {
473
+ start = addDays(start, preview.delta);
474
+ end = addDays(end, preview.delta);
475
+ }
476
+ else if (preview?.id === r.task.id) {
477
+ if (preview.mode === 'start')
478
+ start = addDays(start, Math.min(preview.delta, dayDiff(start, end)));
479
+ if (preview.mode === 'end')
480
+ end = addDays(end, Math.max(preview.delta, -dayDiff(start, end)));
481
+ if (preview.mode === 'progress')
482
+ progress = preview.progress;
483
+ }
484
+ if (moving && r.isGroup && !moving.has(r.task.id)) {
485
+ // Un grupo que contiene la tarea movida se recalcula en la vista previa.
486
+ const kids = descendants(this.tasks(), r.task.id).filter((id) => moving.has(id));
487
+ if (kids.length) {
488
+ const resolved = this.resolved();
489
+ const spans = descendants(this.tasks(), r.task.id)
490
+ .map((id) => resolved.get(id))
491
+ .filter((k) => !!k && !k.isGroup)
492
+ .map((k) => (moving.has(k.task.id) ? [addDays(k.start, preview.delta), addDays(k.end, preview.delta)] : [k.start, k.end]));
493
+ if (spans.length) {
494
+ start = new Date(Math.min(...spans.map((s) => s[0].getTime())));
495
+ end = new Date(Math.max(...spans.map((s) => s[1].getTime())));
496
+ }
497
+ }
498
+ }
499
+ const x = this.x(start);
500
+ const width = r.isMilestone ? Math.max(18, w) : (dayDiff(start, end) + 1) * w;
501
+ return { row: r, index, x: r.isMilestone ? x + w / 2 - 9 : x, width: r.isMilestone ? 18 : width, y: index * ROW, start, end, progress };
502
+ });
503
+ }, /* @ts-ignore */
504
+ ...(ngDevMode ? [{ debugName: "bars" }] : /* istanbul ignore next */ []));
505
+ /** Flechas de dependencias entre barras visibles. */
506
+ this.links = computed(() => {
507
+ const bars = new Map(this.bars().map((b) => [b.row.task.id, b]));
508
+ const out = [];
509
+ for (const b of this.bars()) {
510
+ for (const dep of b.row.task.dependencies ?? []) {
511
+ const from = bars.get(dep);
512
+ if (!from)
513
+ continue;
514
+ const x1 = from.x + from.width;
515
+ const y1 = from.y + ROW / 2;
516
+ const x2 = b.x;
517
+ const y2 = b.y + ROW / 2;
518
+ let path;
519
+ if (x2 - x1 >= 16) {
520
+ const mx = x1 + 8;
521
+ path = `M${x1} ${y1} H${mx} V${y2} H${x2 - 1}`;
522
+ }
523
+ else {
524
+ const midY = y2 > y1 ? b.y : b.y + ROW;
525
+ path = `M${x1} ${y1} H${x1 + 8} V${midY} H${x2 - 10} V${y2} H${x2 - 1}`;
526
+ }
527
+ out.push({ key: `${dep}>${b.row.task.id}`, from: dep, to: b.row.task.id, fromName: from.row.task.name, toName: b.row.task.name, path, mid: { x: x1 + 8, y: (y1 + y2) / 2 } });
528
+ }
529
+ }
530
+ return out;
531
+ }, /* @ts-ignore */
532
+ ...(ngDevMode ? [{ debugName: "links" }] : /* istanbul ignore next */ []));
533
+ /** Línea provisional al crear una dependencia. */
534
+ this.linkLine = computed(() => {
535
+ const p = this.preview();
536
+ if (p?.mode !== 'link' || !p.point)
537
+ return null;
538
+ const from = this.bars().find((b) => b.row.task.id === p.id);
539
+ if (!from)
540
+ return null;
541
+ return `M${from.x + from.width} ${from.y + ROW / 2} L${p.point.x} ${p.point.y}`;
542
+ }, /* @ts-ignore */
543
+ ...(ngDevMode ? [{ debugName: "linkLine" }] : /* istanbul ignore next */ []));
544
+ // Al abrir, el diagrama empieza cerca de hoy.
545
+ afterNextRender(() => this.scrollToToday(false));
546
+ }
547
+ barEl(id) {
548
+ return Array.from(this.host.nativeElement.querySelectorAll('.cb-gantt__bar')).find((b) => b.dataset['ganttId'] === id) ?? null;
549
+ }
550
+ // --- Cambios ---------------------------------------------------------------------------------
551
+ /** Aplica un cambio a una tarea (y a las desplazadas por la planificación) y lo emite. */
552
+ apply(id, mode, delta, progress) {
553
+ const tasks = this.tasks();
554
+ const resolved = this.resolved();
555
+ const target = resolved.get(id);
556
+ if (!target)
557
+ return;
558
+ const shift = (t, d) => {
559
+ const r = resolved.get(t.id);
560
+ return { ...t, start: isoDay(addDays(r.start, d)), end: isoDay(addDays(r.end, d)) };
561
+ };
562
+ let next;
563
+ if (mode === 'move') {
564
+ if (!delta)
565
+ return;
566
+ const moved = new Set([id, ...descendants(tasks, id)]);
567
+ next = tasks.map((t) => (moved.has(t.id) && !resolved.get(t.id)?.isGroup ? shift(t, delta) : t));
568
+ }
569
+ else if (mode === 'progress') {
570
+ const value = clampProgress(progress ?? 0);
571
+ if (value === target.progress || target.isGroup)
572
+ return;
573
+ next = tasks.map((t) => (t.id === id ? { ...t, progress: value } : t));
574
+ }
575
+ else {
576
+ if (!delta || target.isGroup || target.isMilestone)
577
+ return;
578
+ const length = dayDiff(target.start, target.end);
579
+ next = tasks.map((t) => {
580
+ if (t.id !== id)
581
+ return t;
582
+ return mode === 'start'
583
+ ? { ...t, start: isoDay(addDays(target.start, Math.min(delta, length))) }
584
+ : { ...t, end: isoDay(addDays(target.end, Math.max(delta, -length))) };
585
+ });
586
+ }
587
+ if (this.autoSchedule())
588
+ next = autoSchedule(next);
589
+ this.commit(tasks, next, id, mode === 'progress' ? 'progress' : mode === 'move' ? 'move' : 'resize');
590
+ }
591
+ commit(before, after, id, reason) {
592
+ const prev = new Map(before.map((t) => [t.id, t]));
593
+ this.tasks.set(after);
594
+ const own = new Set([id, ...descendants(before, id)]);
595
+ const changes = [];
596
+ for (const t of after) {
597
+ const p = prev.get(t.id);
598
+ if (!p || p === t)
599
+ continue;
600
+ if (p.start === t.start && p.end === t.end && p.progress === t.progress && p.dependencies === t.dependencies)
601
+ continue;
602
+ changes.push({ task: t, previous: p, reason: own.has(t.id) ? reason : 'schedule' });
603
+ }
604
+ // Primero las replanificadas; al final, la tarea que cambió el usuario.
605
+ changes.sort((a, b) => Number(a.reason !== 'schedule') - Number(b.reason !== 'schedule'));
606
+ for (const change of changes)
607
+ this.taskChange.emit(change);
608
+ const r = this.resolveFresh(id);
609
+ if (r) {
610
+ const text = reason === 'progress'
611
+ ? cbFormat(this.t().ganttProgressChanged, r.task.name, r.progress)
612
+ : cbFormat(this.t().ganttMoved, r.task.name, `${this.dateText(r.start)} – ${this.dateText(r.end)}`);
613
+ this.announcement.set(text);
614
+ }
615
+ }
616
+ resolveFresh(id) {
617
+ return this.resolved().get(id);
618
+ }
619
+ /** Crea la dependencia `from` → `to` (si no forma un ciclo). */
620
+ addLink(from, to) {
621
+ const tasks = this.tasks();
622
+ if (!canLink(tasks, from, to))
623
+ return false;
624
+ let next = tasks.map((t) => (t.id === to ? { ...t, dependencies: [...(t.dependencies ?? []), from] } : t));
625
+ if (this.autoSchedule())
626
+ next = autoSchedule(next);
627
+ this.commit(tasks, next, to, 'schedule');
628
+ this.linkAdd.emit({ from, to });
629
+ const names = [from, to].map((id) => this.resolved().get(id)?.task.name ?? id);
630
+ this.announcement.set(cbFormat(this.t().ganttLinkAdded, names[0], names[1]));
631
+ return true;
632
+ }
633
+ /** Quita la dependencia `from` → `to`. */
634
+ removeLink(from, to) {
635
+ const tasks = this.tasks();
636
+ const target = tasks.find((t) => t.id === to);
637
+ if (!target?.dependencies?.includes(from))
638
+ return;
639
+ this.tasks.set(tasks.map((t) => (t.id === to ? { ...t, dependencies: t.dependencies.filter((d) => d !== from) } : t)));
640
+ this.selectedLink.set(null);
641
+ this.linkRemove.emit({ from, to });
642
+ this.announcement.set(this.t().ganttLinkRemoved);
643
+ }
644
+ selectLink(link) {
645
+ if (this.readonly())
646
+ return;
647
+ this.selectedLink.set(this.selectedLink()?.key === link.key ? null : link);
648
+ }
649
+ // --- Arrastre --------------------------------------------------------------------------------
650
+ onPointerDown(event, bar) {
651
+ this.selected.set(bar.row.task.id);
652
+ this.selectedLink.set(null);
653
+ if (this.readonly() || event.button !== 0)
654
+ return;
655
+ const target = event.target;
656
+ let mode = target.dataset['mode'] ?? 'move';
657
+ if (bar.row.isMilestone && mode !== 'link')
658
+ mode = 'move';
659
+ if (bar.row.isGroup && mode !== 'move' && mode !== 'link')
660
+ return;
661
+ event.preventDefault();
662
+ this.drag = { id: bar.row.task.id, mode, x: event.clientX, y: event.clientY, startProgress: bar.progress, width: bar.width, moved: false };
663
+ event.currentTarget.setPointerCapture?.(event.pointerId);
664
+ }
665
+ onPointerMove(event) {
666
+ const drag = this.drag;
667
+ if (!drag)
668
+ return;
669
+ const dx = event.clientX - drag.x;
670
+ if (!drag.moved && Math.abs(dx) < 3 && Math.abs(event.clientY - drag.y) < 3)
671
+ return;
672
+ drag.moved = true;
673
+ if (drag.mode === 'link') {
674
+ const rect = this.chart()?.nativeElement.getBoundingClientRect();
675
+ this.preview.set({ id: drag.id, mode: 'link', delta: 0, progress: 0, point: rect ? { x: event.clientX - rect.left, y: event.clientY - rect.top } : null });
676
+ return;
677
+ }
678
+ if (drag.mode === 'progress') {
679
+ const value = clampProgress(drag.startProgress + (dx / Math.max(1, drag.width)) * 100);
680
+ this.preview.set({ id: drag.id, mode: 'progress', delta: 0, progress: value, point: null });
681
+ return;
682
+ }
683
+ this.preview.set({ id: drag.id, mode: drag.mode, delta: Math.round(dx / this.dayWidth()), progress: 0, point: null });
684
+ }
685
+ onPointerUp(event) {
686
+ const drag = this.drag;
687
+ const preview = this.preview();
688
+ this.drag = null;
689
+ this.preview.set(null);
690
+ if (!drag?.moved || !preview)
691
+ return;
692
+ if (drag.mode === 'link') {
693
+ const doc = this.host.nativeElement.ownerDocument;
694
+ const under = doc.elementFromPoint?.(event.clientX, event.clientY);
695
+ const to = under?.closest('.cb-gantt__bar')?.dataset['ganttId'];
696
+ if (to)
697
+ this.addLink(drag.id, to);
698
+ return;
699
+ }
700
+ this.apply(drag.id, drag.mode, preview.delta, preview.progress);
701
+ }
702
+ cancelDrag() {
703
+ this.drag = null;
704
+ this.preview.set(null);
705
+ }
706
+ // --- Teclado ---------------------------------------------------------------------------------
707
+ onBarKeydown(event, r) {
708
+ if (event.key === 'Enter') {
709
+ event.preventDefault();
710
+ this.taskOpen.emit(r.task);
711
+ return;
712
+ }
713
+ if (event.key === 'Escape' && this.drag) {
714
+ this.cancelDrag();
715
+ return;
716
+ }
717
+ if (this.readonly())
718
+ return;
719
+ const id = r.task.id;
720
+ const step = event.key === 'ArrowRight' ? 1 : event.key === 'ArrowLeft' ? -1 : 0;
721
+ if (step) {
722
+ event.preventDefault();
723
+ const mode = event.shiftKey ? 'end' : event.altKey ? 'start' : 'move';
724
+ if (mode !== 'move' && (r.isGroup || r.isMilestone))
725
+ return;
726
+ this.apply(id, mode, step);
727
+ this.focusBar(id);
728
+ }
729
+ else if (['+', '=', '-', 'PageUp', 'PageDown'].includes(event.key) && !r.isGroup && !r.isMilestone) {
730
+ event.preventDefault();
731
+ const up = event.key === '+' || event.key === '=' || event.key === 'PageUp';
732
+ this.apply(id, 'progress', 0, r.progress + (up ? 10 : -10));
733
+ this.focusBar(id);
734
+ }
735
+ else if ((event.key === 'Delete' || event.key === 'Backspace') && r.task.dependencies?.length) {
736
+ event.preventDefault();
737
+ for (const from of [...r.task.dependencies])
738
+ this.removeLink(from, id);
739
+ this.focusBar(id);
740
+ }
741
+ }
742
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: CbGantt, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
743
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: CbGantt, isStandalone: true, selector: "cb-gantt", inputs: { tasks: { classPropertyName: "tasks", publicName: "tasks", isSignal: true, isRequired: false, transformFunction: null }, scale: { classPropertyName: "scale", publicName: "scale", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, gridWidth: { classPropertyName: "gridWidth", publicName: "gridWidth", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, autoSchedule: { classPropertyName: "autoSchedule", publicName: "autoSchedule", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, showToolbar: { classPropertyName: "showToolbar", publicName: "showToolbar", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { tasks: "tasksChange", scale: "scaleChange", taskChange: "taskChange", taskOpen: "taskOpen", linkAdd: "linkAdd", linkRemove: "linkRemove" }, host: { properties: { "class.cb-gantt--readonly": "readonly()" }, classAttribute: "cb-gantt" }, viewQueries: [{ propertyName: "scroller", first: true, predicate: ["scroller"], descendants: true, isSignal: true }, { propertyName: "chart", first: true, predicate: ["chart"], descendants: true, isSignal: true }], ngImport: i0, template: `
744
+ @if (showToolbar()) {
745
+ <div class="cb-gantt__toolbar">
746
+ <div class="cb-gantt__scales" role="group" [attr.aria-label]="t().ganttScale">
747
+ @for (s of scales; track s) {
748
+ <button type="button" class="cb-gantt__scale" [attr.aria-pressed]="scale() === s" (click)="scale.set(s)">{{ scaleLabel(s) }}</button>
749
+ }
750
+ </div>
751
+ <button type="button" class="cb-gantt__btn" (click)="scrollToToday()">{{ t().today }}</button>
752
+ </div>
753
+ }
754
+
755
+ <div #scroller class="cb-gantt__scroller" [style.height]="height()" (pointermove)="onPointerMove($event)" (pointerup)="onPointerUp($event)" (pointercancel)="cancelDrag()">
756
+ <div class="cb-gantt__inner" [style.--cb-gantt-grid]="gridSize()">
757
+ <!-- Cabecera: columnas de la tabla y escala de tiempo -->
758
+ <div class="cb-gantt__head">
759
+ <div class="cb-gantt__head-grid" aria-hidden="true">
760
+ @for (col of columns(); track col) {
761
+ <span class="cb-gantt__head-cell" [class]="'cb-gantt__head-cell cb-gantt__col-' + col">{{ columnLabel(col) }}</span>
762
+ }
763
+ </div>
764
+ <div class="cb-gantt__head-scale" [style.width.px]="chartWidth()" aria-hidden="true">
765
+ <div class="cb-gantt__head-row">
766
+ @for (u of topUnits(); track u.key) {
767
+ <span class="cb-gantt__unit cb-gantt__unit--top" [style.left.px]="u.x" [style.width.px]="u.width">{{ u.label }}</span>
768
+ }
769
+ </div>
770
+ <div class="cb-gantt__head-row">
771
+ @for (u of units(); track u.key) {
772
+ <span class="cb-gantt__unit" [class.cb-gantt__unit--weekend]="u.weekend" [style.left.px]="u.x" [style.width.px]="u.width">{{ u.label }}</span>
773
+ }
774
+ </div>
775
+ </div>
776
+ </div>
777
+
778
+ <div class="cb-gantt__body" [style.height.px]="rows().length * row">
779
+ <!-- Tabla de tareas (árbol) -->
780
+ <div class="cb-gantt__grid" role="treegrid" [attr.aria-label]="ariaLabel() || t().ganttChart" [attr.aria-rowcount]="rows().length">
781
+ @for (r of rows(); track r.task.id; let i = $index) {
782
+ <div
783
+ class="cb-gantt__row"
784
+ role="row"
785
+ [class.cb-is-selected]="selected() === r.task.id"
786
+ [attr.aria-level]="r.level + 1"
787
+ [attr.aria-expanded]="r.children.length ? !collapsed().has(r.task.id) : null"
788
+ [attr.aria-rowindex]="i + 1"
789
+ [attr.aria-selected]="selected() === r.task.id"
790
+ >
791
+ @for (col of columns(); track col) {
792
+ @if (col === 'name') {
793
+ <span class="cb-gantt__cell cb-gantt__col-name" role="gridcell" [style.padding-left]="0.5 + r.level * 1.1 + 'rem'">
794
+ @if (r.children.length) {
795
+ <button
796
+ type="button"
797
+ class="cb-gantt__toggle"
798
+ [attr.aria-label]="fmt(collapsed().has(r.task.id) ? t().ganttExpand : t().ganttCollapse, r.task.name)"
799
+ [attr.aria-expanded]="!collapsed().has(r.task.id)"
800
+ (click)="toggle(r.task.id)"
801
+ >
802
+ <cb-icon [name]="collapsed().has(r.task.id) ? 'chevron-right' : 'chevron-down'" [size]="14" aria-hidden="true" />
803
+ </button>
804
+ } @else {
805
+ <span class="cb-gantt__toggle-space" aria-hidden="true"></span>
806
+ }
807
+ <button type="button" class="cb-gantt__name" [class.cb-gantt__name--group]="r.isGroup" (click)="focusBar(r.task.id)" (dblclick)="taskOpen.emit(r.task)">
808
+ @if (r.isMilestone) {
809
+ <span class="cb-gantt__diamond-mini" aria-hidden="true"></span>
810
+ }
811
+ {{ r.task.name }}
812
+ </button>
813
+ </span>
814
+ } @else {
815
+ <span [class]="'cb-gantt__cell cb-gantt__col-' + col" role="gridcell">{{ cellText(r, col) }}</span>
816
+ }
817
+ }
818
+ </div>
819
+ }
820
+ </div>
821
+
822
+ <!-- Diagrama -->
823
+ <div #chart class="cb-gantt__chart" [style.width.px]="chartWidth()">
824
+ @for (u of units(); track u.key) {
825
+ <span class="cb-gantt__col" [class.cb-gantt__col--weekend]="u.weekend" [style.left.px]="u.x" [style.width.px]="u.width" aria-hidden="true"></span>
826
+ }
827
+ @for (r of rows(); track r.task.id; let i = $index) {
828
+ <span class="cb-gantt__stripe" [class.cb-is-selected]="selected() === r.task.id" [style.top.px]="i * row" aria-hidden="true"></span>
829
+ }
830
+ @if (todayX() !== null) {
831
+ <span class="cb-gantt__today" [style.left.px]="todayX()" aria-hidden="true"></span>
832
+ }
833
+
834
+ <svg class="cb-gantt__links" [attr.width]="chartWidth()" [attr.height]="rows().length * row" aria-hidden="true">
835
+ <defs>
836
+ <marker [attr.id]="uid + '-arrow'" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
837
+ <path d="M0 0 L8 4 L0 8 z" />
838
+ </marker>
839
+ </defs>
840
+ @for (l of links(); track l.key) {
841
+ <path class="cb-gantt__link-hit" [attr.d]="l.path" (click)="selectLink(l)" />
842
+ <path class="cb-gantt__link" [class.cb-is-selected]="selectedLink()?.key === l.key" [attr.d]="l.path" [attr.marker-end]="'url(#' + uid + '-arrow)'" />
843
+ }
844
+ @if (linkLine(); as line) {
845
+ <path class="cb-gantt__link cb-gantt__link--draft" [attr.d]="line" />
846
+ }
847
+ </svg>
848
+
849
+ @if (selectedLink(); as sl) {
850
+ <button
851
+ type="button"
852
+ class="cb-gantt__unlink"
853
+ [style.left.px]="sl.mid.x"
854
+ [style.top.px]="sl.mid.y"
855
+ [attr.aria-label]="fmt(t().ganttRemoveLink, sl.fromName, sl.toName)"
856
+ [attr.title]="fmt(t().ganttRemoveLink, sl.fromName, sl.toName)"
857
+ (click)="removeLink(sl.from, sl.to)"
858
+ >
859
+ <cb-icon name="x" [size]="12" aria-hidden="true" />
860
+ </button>
861
+ }
862
+
863
+ @for (b of bars(); track b.row.task.id) {
864
+ <div
865
+ class="cb-gantt__bar"
866
+ role="button"
867
+ tabindex="0"
868
+ [attr.data-gantt-id]="b.row.task.id"
869
+ [class.cb-gantt__bar--group]="b.row.isGroup"
870
+ [class.cb-gantt__bar--milestone]="b.row.isMilestone"
871
+ [class.cb-gantt__bar--short]="!b.row.isMilestone && !b.row.isGroup && b.width < 72"
872
+ [class.cb-is-selected]="selected() === b.row.task.id"
873
+ [class.cb-is-dragging]="preview()?.id === b.row.task.id"
874
+ [style.left.px]="b.x"
875
+ [style.top.px]="b.y"
876
+ [style.width.px]="b.width"
877
+ [style.--cb-gantt-color]="b.row.task.color || null"
878
+ [attr.aria-label]="barLabel(b)"
879
+ [attr.aria-describedby]="readonly() ? null : uid + '-help'"
880
+ (pointerdown)="onPointerDown($event, b)"
881
+ (keydown)="onBarKeydown($event, b.row)"
882
+ (click)="selected.set(b.row.task.id)"
883
+ (dblclick)="taskOpen.emit(b.row.task)"
884
+ >
885
+ @if (!b.row.isMilestone) {
886
+ <span class="cb-gantt__progress" [style.width.%]="b.progress" aria-hidden="true"></span>
887
+ <span class="cb-gantt__bar-label" aria-hidden="true">{{ b.row.task.name }}</span>
888
+ @if (!readonly() && !b.row.isGroup) {
889
+ <span class="cb-gantt__handle cb-gantt__handle--start" data-mode="start" aria-hidden="true"></span>
890
+ <span class="cb-gantt__handle cb-gantt__handle--end" data-mode="end" aria-hidden="true"></span>
891
+ <span class="cb-gantt__progress-handle" data-mode="progress" [style.left.%]="b.progress" aria-hidden="true"></span>
892
+ }
893
+ } @else {
894
+ <span class="cb-gantt__diamond" aria-hidden="true"></span>
895
+ <span class="cb-gantt__milestone-label" aria-hidden="true">{{ b.row.task.name }}</span>
896
+ }
897
+ @if (!readonly()) {
898
+ <span class="cb-gantt__connector" data-mode="link" aria-hidden="true"></span>
899
+ }
900
+ </div>
901
+ }
902
+ </div>
903
+ </div>
904
+ </div>
905
+ @if (!rows().length) {
906
+ <p class="cb-gantt__empty">{{ emptyMessage() ?? t().noData }}</p>
907
+ }
908
+ </div>
909
+ <p class="cb-gantt__sr" [id]="uid + '-help'">{{ t().ganttInstructions }}</p>
910
+ <div class="cb-gantt__sr" role="status">{{ announcement() }}</div>
911
+ `, isInline: true, styles: [":host{--cb-gantt-row: 40px;--cb-gantt-bar: var(--cb-color-primary-500);--cb-gantt-bar-bg: color-mix(in srgb, var(--cb-gantt-bar) 28%, var(--cb-color-surface));display:flex;flex-direction:column;gap:var(--cb-space-2, .5rem);width:100%;min-width:0;color:var(--cb-color-text);font-family:var(--cb-font-sans);font-size:var(--cb-text-sm, .875rem)}.cb-gantt__toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem}.cb-gantt__scales{display:inline-flex;padding:2px;border-radius:var(--cb-radius-md);background:var(--cb-color-surface-hover)}.cb-gantt__scale,.cb-gantt__btn{padding:.3rem .75rem;border:0;border-radius:calc(var(--cb-radius-md) - 2px);background:transparent;color:var(--cb-color-text-muted);font:inherit;font-size:var(--cb-text-xs, .75rem);font-weight:var(--cb-font-weight-medium, 500);cursor:pointer}.cb-gantt__scale[aria-pressed=true]{background:var(--cb-color-surface-raised);color:var(--cb-color-text-strong, var(--cb-color-text));box-shadow:0 1px 2px #0f172a1f}.cb-gantt__btn{border:1px solid var(--cb-color-border-strong);color:var(--cb-color-text)}.cb-gantt__btn:hover{background:var(--cb-color-surface-hover)}:is(.cb-gantt__scale,.cb-gantt__btn,.cb-gantt__toggle,.cb-gantt__name,.cb-gantt__unlink):focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:1px}.cb-gantt__scroller{position:relative;overflow:auto;container-type:inline-size;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-lg, 12px);background:var(--cb-color-surface)}.cb-gantt__inner{--cb-gantt-grid-w: min(var(--cb-gantt-grid, 30rem), 60cqi);position:relative;width:max-content;min-width:100%}.cb-gantt__head{position:sticky;top:0;z-index:4;display:flex;height:52px;border-bottom:1px solid var(--cb-color-border);background:var(--cb-color-surface-subtle, var(--cb-color-surface))}.cb-gantt__head-grid{position:sticky;left:0;z-index:5;display:flex;flex:none;align-items:flex-end;width:var(--cb-gantt-grid-w);padding-bottom:.45rem;border-right:1px solid var(--cb-color-border);background:inherit}.cb-gantt__head-cell{flex:0 0 auto;box-sizing:border-box;padding:0 .5rem;overflow:hidden;color:var(--cb-color-text-muted);font-size:var(--cb-text-xs, .75rem);font-weight:var(--cb-font-weight-semibold, 600);text-overflow:ellipsis;white-space:nowrap}.cb-gantt__head-cell.cb-gantt__col-name{padding-left:.75rem}.cb-gantt__col-name{flex:1 1 auto!important;min-width:6rem}.cb-gantt__col-start,.cb-gantt__col-end,.cb-gantt__col-duration{width:4.5rem}.cb-gantt__col-progress{width:4rem}.cb-gantt__col-assignee{width:6.5rem}.cb-gantt__head-scale{position:relative;flex:none}.cb-gantt__head-row{position:relative;height:26px}.cb-gantt__unit{position:absolute;top:0;bottom:0;display:flex;align-items:center;justify-content:center;overflow:hidden;border-left:1px solid var(--cb-color-border);color:var(--cb-color-text-muted);font-size:.6875rem;white-space:nowrap}.cb-gantt__unit--top{justify-content:flex-start;padding-left:.5rem;color:var(--cb-color-text);font-weight:var(--cb-font-weight-semibold, 600)}.cb-gantt__unit--weekend{background:var(--cb-color-surface-hover)}@container (max-width: 40rem){.cb-gantt__col-start,.cb-gantt__col-end,.cb-gantt__col-duration,.cb-gantt__col-progress,.cb-gantt__col-assignee{display:none}.cb-gantt__head-grid,.cb-gantt__row{overflow:hidden}}.cb-gantt__body{position:relative;display:flex}.cb-gantt__grid{position:sticky;left:0;z-index:3;flex:none;width:var(--cb-gantt-grid-w);border-right:1px solid var(--cb-color-border);background:var(--cb-color-surface)}.cb-gantt__row{display:flex;align-items:center;height:var(--cb-gantt-row);border-bottom:1px solid var(--cb-color-border)}.cb-gantt__row.cb-is-selected{background:color-mix(in srgb,var(--cb-color-primary-500) 9%,var(--cb-color-surface))}.cb-gantt__cell{flex:0 0 auto;box-sizing:border-box;padding:0 .5rem;overflow:hidden;color:var(--cb-color-text-muted);font-size:var(--cb-text-xs, .75rem);font-variant-numeric:tabular-nums;text-overflow:ellipsis;white-space:nowrap}.cb-gantt__cell.cb-gantt__col-name{display:flex;align-items:center;gap:.25rem;min-width:0;color:var(--cb-color-text);font-size:var(--cb-text-sm, .875rem)}.cb-gantt__toggle,.cb-gantt__toggle-space{display:inline-flex;flex:none;align-items:center;justify-content:center;width:1.4rem;height:1.4rem}.cb-gantt__toggle{padding:0;border:0;border-radius:var(--cb-radius-sm);background:transparent;color:var(--cb-color-text-muted);cursor:pointer}.cb-gantt__toggle:hover{background:var(--cb-color-surface-hover)}.cb-gantt__name{display:inline-flex;align-items:center;gap:.4rem;min-width:0;padding:.15rem .25rem;overflow:hidden;border:0;border-radius:var(--cb-radius-sm);background:transparent;color:inherit;font:inherit;text-align:start;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}.cb-gantt__name--group{font-weight:var(--cb-font-weight-semibold, 600)}.cb-gantt__diamond-mini{flex:none;width:.55rem;height:.55rem;background:var(--cb-color-warning-500, #f59e0b);transform:rotate(45deg)}.cb-gantt__chart{position:relative;flex:none}.cb-gantt__col{position:absolute;top:0;bottom:0;border-left:1px solid color-mix(in srgb,var(--cb-color-border) 60%,transparent)}.cb-gantt__col--weekend{background:color-mix(in srgb,var(--cb-color-surface-hover) 70%,transparent)}.cb-gantt__stripe{position:absolute;left:0;right:0;height:var(--cb-gantt-row);border-bottom:1px solid var(--cb-color-border)}.cb-gantt__stripe.cb-is-selected{background:color-mix(in srgb,var(--cb-color-primary-500) 7%,transparent)}.cb-gantt__today{position:absolute;top:0;bottom:0;z-index:1;width:2px;margin-left:-1px;background:var(--cb-color-danger-500, #ef4444)}.cb-gantt__links{position:absolute;inset:0;z-index:1;overflow:visible;pointer-events:none}.cb-gantt__links marker path{fill:var(--cb-color-text-muted)}.cb-gantt__link{fill:none;stroke:var(--cb-color-text-muted);stroke-width:1.5}.cb-gantt__link.cb-is-selected{stroke:var(--cb-color-primary-600, var(--cb-color-primary-500));stroke-width:2.5}.cb-gantt__link--draft{stroke:var(--cb-color-primary-500);stroke-dasharray:4 3}.cb-gantt__link-hit{fill:none;stroke:transparent;stroke-width:10;pointer-events:stroke;cursor:pointer}:host(.cb-gantt--readonly) .cb-gantt__link-hit{cursor:default}.cb-gantt__unlink{position:absolute;z-index:3;display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;margin:-.625rem 0 0 -.625rem;padding:0;border:1px solid var(--cb-color-border-strong);border-radius:50%;background:var(--cb-color-surface-raised);color:var(--cb-color-danger-600, var(--cb-color-danger));cursor:pointer}.cb-gantt__bar{position:absolute;z-index:2;box-sizing:border-box;height:24px;margin-top:8px;border-radius:6px;background:color-mix(in srgb,var(--cb-gantt-color, var(--cb-gantt-bar)) 28%,var(--cb-color-surface));box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--cb-gantt-color, var(--cb-gantt-bar)) 55%,transparent);cursor:grab;touch-action:none;-webkit-user-select:none;user-select:none}:host(.cb-gantt--readonly) .cb-gantt__bar{cursor:pointer}.cb-gantt__bar:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:2px}.cb-gantt__bar.cb-is-selected{box-shadow:inset 0 0 0 2px var(--cb-gantt-color, var(--cb-gantt-bar))}.cb-gantt__bar.cb-is-dragging{cursor:grabbing;opacity:.85}.cb-gantt__progress{position:absolute;inset:0 auto 0 0;border-radius:6px 0 0 6px;background:var(--cb-gantt-color, var(--cb-gantt-bar))}.cb-gantt__bar-label{position:relative;display:block;padding:0 .5rem;overflow:hidden;color:var(--cb-color-text);font-size:var(--cb-text-xs, .75rem);font-weight:var(--cb-font-weight-medium, 500);line-height:24px;text-overflow:ellipsis;white-space:nowrap;mix-blend-mode:normal}.cb-gantt__bar--short .cb-gantt__bar-label{position:absolute;top:0;left:calc(100% + 18px);padding:0;overflow:visible}.cb-gantt__handle{position:absolute;top:0;bottom:0;width:8px;cursor:ew-resize}.cb-gantt__handle--start{left:0}.cb-gantt__handle--end{right:0}.cb-gantt__progress-handle{position:absolute;bottom:-6px;width:10px;height:10px;margin-left:-5px;border:2px solid var(--cb-color-surface);border-radius:50%;background:var(--cb-gantt-color, var(--cb-gantt-bar));cursor:col-resize;opacity:0}.cb-gantt__connector{position:absolute;top:50%;right:-14px;width:10px;height:10px;margin-top:-5px;border:2px solid var(--cb-gantt-color, var(--cb-gantt-bar));border-radius:50%;background:var(--cb-color-surface);cursor:crosshair;opacity:0}.cb-gantt__bar:hover :is(.cb-gantt__progress-handle,.cb-gantt__connector),.cb-gantt__bar.cb-is-selected :is(.cb-gantt__progress-handle,.cb-gantt__connector){opacity:1}@media(hover:none){.cb-gantt__progress-handle,.cb-gantt__connector{opacity:1}}.cb-gantt__bar--group{height:12px;margin-top:14px;border-radius:2px;background:var(--cb-color-text-muted);box-shadow:none}.cb-gantt__bar--group .cb-gantt__progress{border-radius:2px 0 0 2px;background:var(--cb-color-text)}.cb-gantt__bar--group .cb-gantt__bar-label{position:absolute;bottom:12px;left:0;padding:0;line-height:1.2}.cb-gantt__bar--milestone{width:18px;height:18px;margin-top:11px;background:transparent;box-shadow:none}.cb-gantt__bar--milestone.cb-is-selected{box-shadow:none}.cb-gantt__diamond{position:absolute;inset:2px;border-radius:3px;background:var(--cb-gantt-color, var(--cb-color-warning-500, #f59e0b));transform:rotate(45deg)}.cb-gantt__bar--milestone.cb-is-selected .cb-gantt__diamond{outline:2px solid var(--cb-color-text);outline-offset:1px}.cb-gantt__milestone-label{position:absolute;top:0;left:26px;color:var(--cb-color-text);font-size:var(--cb-text-xs, .75rem);font-weight:var(--cb-font-weight-medium, 500);line-height:18px;white-space:nowrap}.cb-gantt__bar--milestone .cb-gantt__connector{right:-16px}.cb-gantt__empty{margin:0;padding:2rem;color:var(--cb-color-text-muted);text-align:center}.cb-gantt__sr{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap}@media(prefers-reduced-motion:no-preference){.cb-gantt__bar:not(.cb-is-dragging){transition:left .12s ease,width .12s ease}}@media(forced-colors:active){.cb-gantt__bar,.cb-gantt__progress{forced-color-adjust:none;border:1px solid CanvasText}}\n"], dependencies: [{ kind: "component", type: CbIcon, selector: "cb-icon", inputs: ["name", "size", "spin", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
912
+ }
913
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: CbGantt, decorators: [{
914
+ type: Component,
915
+ args: [{ selector: 'cb-gantt', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.Emulated, imports: [CbIcon], host: { class: 'cb-gantt', '[class.cb-gantt--readonly]': 'readonly()' }, template: `
916
+ @if (showToolbar()) {
917
+ <div class="cb-gantt__toolbar">
918
+ <div class="cb-gantt__scales" role="group" [attr.aria-label]="t().ganttScale">
919
+ @for (s of scales; track s) {
920
+ <button type="button" class="cb-gantt__scale" [attr.aria-pressed]="scale() === s" (click)="scale.set(s)">{{ scaleLabel(s) }}</button>
921
+ }
922
+ </div>
923
+ <button type="button" class="cb-gantt__btn" (click)="scrollToToday()">{{ t().today }}</button>
924
+ </div>
925
+ }
926
+
927
+ <div #scroller class="cb-gantt__scroller" [style.height]="height()" (pointermove)="onPointerMove($event)" (pointerup)="onPointerUp($event)" (pointercancel)="cancelDrag()">
928
+ <div class="cb-gantt__inner" [style.--cb-gantt-grid]="gridSize()">
929
+ <!-- Cabecera: columnas de la tabla y escala de tiempo -->
930
+ <div class="cb-gantt__head">
931
+ <div class="cb-gantt__head-grid" aria-hidden="true">
932
+ @for (col of columns(); track col) {
933
+ <span class="cb-gantt__head-cell" [class]="'cb-gantt__head-cell cb-gantt__col-' + col">{{ columnLabel(col) }}</span>
934
+ }
935
+ </div>
936
+ <div class="cb-gantt__head-scale" [style.width.px]="chartWidth()" aria-hidden="true">
937
+ <div class="cb-gantt__head-row">
938
+ @for (u of topUnits(); track u.key) {
939
+ <span class="cb-gantt__unit cb-gantt__unit--top" [style.left.px]="u.x" [style.width.px]="u.width">{{ u.label }}</span>
940
+ }
941
+ </div>
942
+ <div class="cb-gantt__head-row">
943
+ @for (u of units(); track u.key) {
944
+ <span class="cb-gantt__unit" [class.cb-gantt__unit--weekend]="u.weekend" [style.left.px]="u.x" [style.width.px]="u.width">{{ u.label }}</span>
945
+ }
946
+ </div>
947
+ </div>
948
+ </div>
949
+
950
+ <div class="cb-gantt__body" [style.height.px]="rows().length * row">
951
+ <!-- Tabla de tareas (árbol) -->
952
+ <div class="cb-gantt__grid" role="treegrid" [attr.aria-label]="ariaLabel() || t().ganttChart" [attr.aria-rowcount]="rows().length">
953
+ @for (r of rows(); track r.task.id; let i = $index) {
954
+ <div
955
+ class="cb-gantt__row"
956
+ role="row"
957
+ [class.cb-is-selected]="selected() === r.task.id"
958
+ [attr.aria-level]="r.level + 1"
959
+ [attr.aria-expanded]="r.children.length ? !collapsed().has(r.task.id) : null"
960
+ [attr.aria-rowindex]="i + 1"
961
+ [attr.aria-selected]="selected() === r.task.id"
962
+ >
963
+ @for (col of columns(); track col) {
964
+ @if (col === 'name') {
965
+ <span class="cb-gantt__cell cb-gantt__col-name" role="gridcell" [style.padding-left]="0.5 + r.level * 1.1 + 'rem'">
966
+ @if (r.children.length) {
967
+ <button
968
+ type="button"
969
+ class="cb-gantt__toggle"
970
+ [attr.aria-label]="fmt(collapsed().has(r.task.id) ? t().ganttExpand : t().ganttCollapse, r.task.name)"
971
+ [attr.aria-expanded]="!collapsed().has(r.task.id)"
972
+ (click)="toggle(r.task.id)"
973
+ >
974
+ <cb-icon [name]="collapsed().has(r.task.id) ? 'chevron-right' : 'chevron-down'" [size]="14" aria-hidden="true" />
975
+ </button>
976
+ } @else {
977
+ <span class="cb-gantt__toggle-space" aria-hidden="true"></span>
978
+ }
979
+ <button type="button" class="cb-gantt__name" [class.cb-gantt__name--group]="r.isGroup" (click)="focusBar(r.task.id)" (dblclick)="taskOpen.emit(r.task)">
980
+ @if (r.isMilestone) {
981
+ <span class="cb-gantt__diamond-mini" aria-hidden="true"></span>
982
+ }
983
+ {{ r.task.name }}
984
+ </button>
985
+ </span>
986
+ } @else {
987
+ <span [class]="'cb-gantt__cell cb-gantt__col-' + col" role="gridcell">{{ cellText(r, col) }}</span>
988
+ }
989
+ }
990
+ </div>
991
+ }
992
+ </div>
993
+
994
+ <!-- Diagrama -->
995
+ <div #chart class="cb-gantt__chart" [style.width.px]="chartWidth()">
996
+ @for (u of units(); track u.key) {
997
+ <span class="cb-gantt__col" [class.cb-gantt__col--weekend]="u.weekend" [style.left.px]="u.x" [style.width.px]="u.width" aria-hidden="true"></span>
998
+ }
999
+ @for (r of rows(); track r.task.id; let i = $index) {
1000
+ <span class="cb-gantt__stripe" [class.cb-is-selected]="selected() === r.task.id" [style.top.px]="i * row" aria-hidden="true"></span>
1001
+ }
1002
+ @if (todayX() !== null) {
1003
+ <span class="cb-gantt__today" [style.left.px]="todayX()" aria-hidden="true"></span>
1004
+ }
1005
+
1006
+ <svg class="cb-gantt__links" [attr.width]="chartWidth()" [attr.height]="rows().length * row" aria-hidden="true">
1007
+ <defs>
1008
+ <marker [attr.id]="uid + '-arrow'" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
1009
+ <path d="M0 0 L8 4 L0 8 z" />
1010
+ </marker>
1011
+ </defs>
1012
+ @for (l of links(); track l.key) {
1013
+ <path class="cb-gantt__link-hit" [attr.d]="l.path" (click)="selectLink(l)" />
1014
+ <path class="cb-gantt__link" [class.cb-is-selected]="selectedLink()?.key === l.key" [attr.d]="l.path" [attr.marker-end]="'url(#' + uid + '-arrow)'" />
1015
+ }
1016
+ @if (linkLine(); as line) {
1017
+ <path class="cb-gantt__link cb-gantt__link--draft" [attr.d]="line" />
1018
+ }
1019
+ </svg>
1020
+
1021
+ @if (selectedLink(); as sl) {
1022
+ <button
1023
+ type="button"
1024
+ class="cb-gantt__unlink"
1025
+ [style.left.px]="sl.mid.x"
1026
+ [style.top.px]="sl.mid.y"
1027
+ [attr.aria-label]="fmt(t().ganttRemoveLink, sl.fromName, sl.toName)"
1028
+ [attr.title]="fmt(t().ganttRemoveLink, sl.fromName, sl.toName)"
1029
+ (click)="removeLink(sl.from, sl.to)"
1030
+ >
1031
+ <cb-icon name="x" [size]="12" aria-hidden="true" />
1032
+ </button>
1033
+ }
1034
+
1035
+ @for (b of bars(); track b.row.task.id) {
1036
+ <div
1037
+ class="cb-gantt__bar"
1038
+ role="button"
1039
+ tabindex="0"
1040
+ [attr.data-gantt-id]="b.row.task.id"
1041
+ [class.cb-gantt__bar--group]="b.row.isGroup"
1042
+ [class.cb-gantt__bar--milestone]="b.row.isMilestone"
1043
+ [class.cb-gantt__bar--short]="!b.row.isMilestone && !b.row.isGroup && b.width < 72"
1044
+ [class.cb-is-selected]="selected() === b.row.task.id"
1045
+ [class.cb-is-dragging]="preview()?.id === b.row.task.id"
1046
+ [style.left.px]="b.x"
1047
+ [style.top.px]="b.y"
1048
+ [style.width.px]="b.width"
1049
+ [style.--cb-gantt-color]="b.row.task.color || null"
1050
+ [attr.aria-label]="barLabel(b)"
1051
+ [attr.aria-describedby]="readonly() ? null : uid + '-help'"
1052
+ (pointerdown)="onPointerDown($event, b)"
1053
+ (keydown)="onBarKeydown($event, b.row)"
1054
+ (click)="selected.set(b.row.task.id)"
1055
+ (dblclick)="taskOpen.emit(b.row.task)"
1056
+ >
1057
+ @if (!b.row.isMilestone) {
1058
+ <span class="cb-gantt__progress" [style.width.%]="b.progress" aria-hidden="true"></span>
1059
+ <span class="cb-gantt__bar-label" aria-hidden="true">{{ b.row.task.name }}</span>
1060
+ @if (!readonly() && !b.row.isGroup) {
1061
+ <span class="cb-gantt__handle cb-gantt__handle--start" data-mode="start" aria-hidden="true"></span>
1062
+ <span class="cb-gantt__handle cb-gantt__handle--end" data-mode="end" aria-hidden="true"></span>
1063
+ <span class="cb-gantt__progress-handle" data-mode="progress" [style.left.%]="b.progress" aria-hidden="true"></span>
1064
+ }
1065
+ } @else {
1066
+ <span class="cb-gantt__diamond" aria-hidden="true"></span>
1067
+ <span class="cb-gantt__milestone-label" aria-hidden="true">{{ b.row.task.name }}</span>
1068
+ }
1069
+ @if (!readonly()) {
1070
+ <span class="cb-gantt__connector" data-mode="link" aria-hidden="true"></span>
1071
+ }
1072
+ </div>
1073
+ }
1074
+ </div>
1075
+ </div>
1076
+ </div>
1077
+ @if (!rows().length) {
1078
+ <p class="cb-gantt__empty">{{ emptyMessage() ?? t().noData }}</p>
1079
+ }
1080
+ </div>
1081
+ <p class="cb-gantt__sr" [id]="uid + '-help'">{{ t().ganttInstructions }}</p>
1082
+ <div class="cb-gantt__sr" role="status">{{ announcement() }}</div>
1083
+ `, styles: [":host{--cb-gantt-row: 40px;--cb-gantt-bar: var(--cb-color-primary-500);--cb-gantt-bar-bg: color-mix(in srgb, var(--cb-gantt-bar) 28%, var(--cb-color-surface));display:flex;flex-direction:column;gap:var(--cb-space-2, .5rem);width:100%;min-width:0;color:var(--cb-color-text);font-family:var(--cb-font-sans);font-size:var(--cb-text-sm, .875rem)}.cb-gantt__toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem}.cb-gantt__scales{display:inline-flex;padding:2px;border-radius:var(--cb-radius-md);background:var(--cb-color-surface-hover)}.cb-gantt__scale,.cb-gantt__btn{padding:.3rem .75rem;border:0;border-radius:calc(var(--cb-radius-md) - 2px);background:transparent;color:var(--cb-color-text-muted);font:inherit;font-size:var(--cb-text-xs, .75rem);font-weight:var(--cb-font-weight-medium, 500);cursor:pointer}.cb-gantt__scale[aria-pressed=true]{background:var(--cb-color-surface-raised);color:var(--cb-color-text-strong, var(--cb-color-text));box-shadow:0 1px 2px #0f172a1f}.cb-gantt__btn{border:1px solid var(--cb-color-border-strong);color:var(--cb-color-text)}.cb-gantt__btn:hover{background:var(--cb-color-surface-hover)}:is(.cb-gantt__scale,.cb-gantt__btn,.cb-gantt__toggle,.cb-gantt__name,.cb-gantt__unlink):focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:1px}.cb-gantt__scroller{position:relative;overflow:auto;container-type:inline-size;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-lg, 12px);background:var(--cb-color-surface)}.cb-gantt__inner{--cb-gantt-grid-w: min(var(--cb-gantt-grid, 30rem), 60cqi);position:relative;width:max-content;min-width:100%}.cb-gantt__head{position:sticky;top:0;z-index:4;display:flex;height:52px;border-bottom:1px solid var(--cb-color-border);background:var(--cb-color-surface-subtle, var(--cb-color-surface))}.cb-gantt__head-grid{position:sticky;left:0;z-index:5;display:flex;flex:none;align-items:flex-end;width:var(--cb-gantt-grid-w);padding-bottom:.45rem;border-right:1px solid var(--cb-color-border);background:inherit}.cb-gantt__head-cell{flex:0 0 auto;box-sizing:border-box;padding:0 .5rem;overflow:hidden;color:var(--cb-color-text-muted);font-size:var(--cb-text-xs, .75rem);font-weight:var(--cb-font-weight-semibold, 600);text-overflow:ellipsis;white-space:nowrap}.cb-gantt__head-cell.cb-gantt__col-name{padding-left:.75rem}.cb-gantt__col-name{flex:1 1 auto!important;min-width:6rem}.cb-gantt__col-start,.cb-gantt__col-end,.cb-gantt__col-duration{width:4.5rem}.cb-gantt__col-progress{width:4rem}.cb-gantt__col-assignee{width:6.5rem}.cb-gantt__head-scale{position:relative;flex:none}.cb-gantt__head-row{position:relative;height:26px}.cb-gantt__unit{position:absolute;top:0;bottom:0;display:flex;align-items:center;justify-content:center;overflow:hidden;border-left:1px solid var(--cb-color-border);color:var(--cb-color-text-muted);font-size:.6875rem;white-space:nowrap}.cb-gantt__unit--top{justify-content:flex-start;padding-left:.5rem;color:var(--cb-color-text);font-weight:var(--cb-font-weight-semibold, 600)}.cb-gantt__unit--weekend{background:var(--cb-color-surface-hover)}@container (max-width: 40rem){.cb-gantt__col-start,.cb-gantt__col-end,.cb-gantt__col-duration,.cb-gantt__col-progress,.cb-gantt__col-assignee{display:none}.cb-gantt__head-grid,.cb-gantt__row{overflow:hidden}}.cb-gantt__body{position:relative;display:flex}.cb-gantt__grid{position:sticky;left:0;z-index:3;flex:none;width:var(--cb-gantt-grid-w);border-right:1px solid var(--cb-color-border);background:var(--cb-color-surface)}.cb-gantt__row{display:flex;align-items:center;height:var(--cb-gantt-row);border-bottom:1px solid var(--cb-color-border)}.cb-gantt__row.cb-is-selected{background:color-mix(in srgb,var(--cb-color-primary-500) 9%,var(--cb-color-surface))}.cb-gantt__cell{flex:0 0 auto;box-sizing:border-box;padding:0 .5rem;overflow:hidden;color:var(--cb-color-text-muted);font-size:var(--cb-text-xs, .75rem);font-variant-numeric:tabular-nums;text-overflow:ellipsis;white-space:nowrap}.cb-gantt__cell.cb-gantt__col-name{display:flex;align-items:center;gap:.25rem;min-width:0;color:var(--cb-color-text);font-size:var(--cb-text-sm, .875rem)}.cb-gantt__toggle,.cb-gantt__toggle-space{display:inline-flex;flex:none;align-items:center;justify-content:center;width:1.4rem;height:1.4rem}.cb-gantt__toggle{padding:0;border:0;border-radius:var(--cb-radius-sm);background:transparent;color:var(--cb-color-text-muted);cursor:pointer}.cb-gantt__toggle:hover{background:var(--cb-color-surface-hover)}.cb-gantt__name{display:inline-flex;align-items:center;gap:.4rem;min-width:0;padding:.15rem .25rem;overflow:hidden;border:0;border-radius:var(--cb-radius-sm);background:transparent;color:inherit;font:inherit;text-align:start;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}.cb-gantt__name--group{font-weight:var(--cb-font-weight-semibold, 600)}.cb-gantt__diamond-mini{flex:none;width:.55rem;height:.55rem;background:var(--cb-color-warning-500, #f59e0b);transform:rotate(45deg)}.cb-gantt__chart{position:relative;flex:none}.cb-gantt__col{position:absolute;top:0;bottom:0;border-left:1px solid color-mix(in srgb,var(--cb-color-border) 60%,transparent)}.cb-gantt__col--weekend{background:color-mix(in srgb,var(--cb-color-surface-hover) 70%,transparent)}.cb-gantt__stripe{position:absolute;left:0;right:0;height:var(--cb-gantt-row);border-bottom:1px solid var(--cb-color-border)}.cb-gantt__stripe.cb-is-selected{background:color-mix(in srgb,var(--cb-color-primary-500) 7%,transparent)}.cb-gantt__today{position:absolute;top:0;bottom:0;z-index:1;width:2px;margin-left:-1px;background:var(--cb-color-danger-500, #ef4444)}.cb-gantt__links{position:absolute;inset:0;z-index:1;overflow:visible;pointer-events:none}.cb-gantt__links marker path{fill:var(--cb-color-text-muted)}.cb-gantt__link{fill:none;stroke:var(--cb-color-text-muted);stroke-width:1.5}.cb-gantt__link.cb-is-selected{stroke:var(--cb-color-primary-600, var(--cb-color-primary-500));stroke-width:2.5}.cb-gantt__link--draft{stroke:var(--cb-color-primary-500);stroke-dasharray:4 3}.cb-gantt__link-hit{fill:none;stroke:transparent;stroke-width:10;pointer-events:stroke;cursor:pointer}:host(.cb-gantt--readonly) .cb-gantt__link-hit{cursor:default}.cb-gantt__unlink{position:absolute;z-index:3;display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;margin:-.625rem 0 0 -.625rem;padding:0;border:1px solid var(--cb-color-border-strong);border-radius:50%;background:var(--cb-color-surface-raised);color:var(--cb-color-danger-600, var(--cb-color-danger));cursor:pointer}.cb-gantt__bar{position:absolute;z-index:2;box-sizing:border-box;height:24px;margin-top:8px;border-radius:6px;background:color-mix(in srgb,var(--cb-gantt-color, var(--cb-gantt-bar)) 28%,var(--cb-color-surface));box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--cb-gantt-color, var(--cb-gantt-bar)) 55%,transparent);cursor:grab;touch-action:none;-webkit-user-select:none;user-select:none}:host(.cb-gantt--readonly) .cb-gantt__bar{cursor:pointer}.cb-gantt__bar:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:2px}.cb-gantt__bar.cb-is-selected{box-shadow:inset 0 0 0 2px var(--cb-gantt-color, var(--cb-gantt-bar))}.cb-gantt__bar.cb-is-dragging{cursor:grabbing;opacity:.85}.cb-gantt__progress{position:absolute;inset:0 auto 0 0;border-radius:6px 0 0 6px;background:var(--cb-gantt-color, var(--cb-gantt-bar))}.cb-gantt__bar-label{position:relative;display:block;padding:0 .5rem;overflow:hidden;color:var(--cb-color-text);font-size:var(--cb-text-xs, .75rem);font-weight:var(--cb-font-weight-medium, 500);line-height:24px;text-overflow:ellipsis;white-space:nowrap;mix-blend-mode:normal}.cb-gantt__bar--short .cb-gantt__bar-label{position:absolute;top:0;left:calc(100% + 18px);padding:0;overflow:visible}.cb-gantt__handle{position:absolute;top:0;bottom:0;width:8px;cursor:ew-resize}.cb-gantt__handle--start{left:0}.cb-gantt__handle--end{right:0}.cb-gantt__progress-handle{position:absolute;bottom:-6px;width:10px;height:10px;margin-left:-5px;border:2px solid var(--cb-color-surface);border-radius:50%;background:var(--cb-gantt-color, var(--cb-gantt-bar));cursor:col-resize;opacity:0}.cb-gantt__connector{position:absolute;top:50%;right:-14px;width:10px;height:10px;margin-top:-5px;border:2px solid var(--cb-gantt-color, var(--cb-gantt-bar));border-radius:50%;background:var(--cb-color-surface);cursor:crosshair;opacity:0}.cb-gantt__bar:hover :is(.cb-gantt__progress-handle,.cb-gantt__connector),.cb-gantt__bar.cb-is-selected :is(.cb-gantt__progress-handle,.cb-gantt__connector){opacity:1}@media(hover:none){.cb-gantt__progress-handle,.cb-gantt__connector{opacity:1}}.cb-gantt__bar--group{height:12px;margin-top:14px;border-radius:2px;background:var(--cb-color-text-muted);box-shadow:none}.cb-gantt__bar--group .cb-gantt__progress{border-radius:2px 0 0 2px;background:var(--cb-color-text)}.cb-gantt__bar--group .cb-gantt__bar-label{position:absolute;bottom:12px;left:0;padding:0;line-height:1.2}.cb-gantt__bar--milestone{width:18px;height:18px;margin-top:11px;background:transparent;box-shadow:none}.cb-gantt__bar--milestone.cb-is-selected{box-shadow:none}.cb-gantt__diamond{position:absolute;inset:2px;border-radius:3px;background:var(--cb-gantt-color, var(--cb-color-warning-500, #f59e0b));transform:rotate(45deg)}.cb-gantt__bar--milestone.cb-is-selected .cb-gantt__diamond{outline:2px solid var(--cb-color-text);outline-offset:1px}.cb-gantt__milestone-label{position:absolute;top:0;left:26px;color:var(--cb-color-text);font-size:var(--cb-text-xs, .75rem);font-weight:var(--cb-font-weight-medium, 500);line-height:18px;white-space:nowrap}.cb-gantt__bar--milestone .cb-gantt__connector{right:-16px}.cb-gantt__empty{margin:0;padding:2rem;color:var(--cb-color-text-muted);text-align:center}.cb-gantt__sr{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap}@media(prefers-reduced-motion:no-preference){.cb-gantt__bar:not(.cb-is-dragging){transition:left .12s ease,width .12s ease}}@media(forced-colors:active){.cb-gantt__bar,.cb-gantt__progress{forced-color-adjust:none;border:1px solid CanvasText}}\n"] }]
1084
+ }], ctorParameters: () => [], propDecorators: { tasks: [{ type: i0.Input, args: [{ isSignal: true, alias: "tasks", required: false }] }, { type: i0.Output, args: ["tasksChange"] }], scale: [{ type: i0.Input, args: [{ isSignal: true, alias: "scale", required: false }] }, { type: i0.Output, args: ["scaleChange"] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], gridWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "gridWidth", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], autoSchedule: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoSchedule", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], showToolbar: [{ type: i0.Input, args: [{ isSignal: true, alias: "showToolbar", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], taskChange: [{ type: i0.Output, args: ["taskChange"] }], taskOpen: [{ type: i0.Output, args: ["taskOpen"] }], linkAdd: [{ type: i0.Output, args: ["linkAdd"] }], linkRemove: [{ type: i0.Output, args: ["linkRemove"] }], scroller: [{ type: i0.ViewChild, args: ['scroller', { isSignal: true }] }], chart: [{ type: i0.ViewChild, args: ['chart', { isSignal: true }] }] } });
1085
+
1086
+ /*
1087
+ * Public API Surface de @caribeng/ui/gantt
1088
+ */
1089
+
1090
+ /**
1091
+ * Generated bundle index. Do not edit.
1092
+ */
1093
+
1094
+ export { CbGantt, autoSchedule as cbGanttAutoSchedule, canLink as cbGanttCanLink };