@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,1635 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, Injector, ElementRef, model, input, booleanAttribute, output, signal, computed, effect, untracked, afterNextRender, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { cbFormatAmount, injectCbTranslation, cbFormat, CB_CONFIG, cbSummarize } from '@caribeng/ui';
4
+ import * as i1 from '@angular/cdk/overlay';
5
+ import { OverlayModule } from '@angular/cdk/overlay';
6
+ import { CbIcon } from '@caribeng/ui/icon';
7
+ import { currencySymbol, cbDownloadXlsx } from '@caribeng/ui/xlsx';
8
+
9
+ function rangeOf(a, b) {
10
+ return { r1: Math.min(a.r, b.r), r2: Math.max(a.r, b.r), c1: Math.min(a.c, b.c), c2: Math.max(a.c, b.c) };
11
+ }
12
+ function inRange(range, r, c) {
13
+ return r >= range.r1 && r <= range.r2 && c >= range.c1 && c <= range.c2;
14
+ }
15
+ function isEmpty(value) {
16
+ return value === null || value === undefined || value === '';
17
+ }
18
+ const TRUE_WORDS = new Set(['true', '1', 'x', 'sí', 'si', 'yes', 'y', 's', 'verdadero', '✓']);
19
+ const FALSE_WORDS = new Set(['false', '0', 'no', 'n', 'falso', '']);
20
+ /**
21
+ * Convierte el texto escrito o pegado en el valor de la columna. Devuelve
22
+ * `error` cuando el texto no se puede interpretar (no es un número, una fecha…).
23
+ */
24
+ function parseCell(col, text, texts) {
25
+ const raw = text.trim();
26
+ switch (col.type ?? 'text') {
27
+ case 'number': {
28
+ if (!raw)
29
+ return { value: null };
30
+ // Admite coma decimal y separadores de miles: "1.234,5", "1,234.5", "1234,5".
31
+ let s = raw.replace(/\s/g, '');
32
+ const comma = s.lastIndexOf(',');
33
+ const dot = s.lastIndexOf('.');
34
+ if (comma > dot)
35
+ s = s.replace(/\./g, '').replace(',', '.');
36
+ else
37
+ s = s.replace(/,/g, '');
38
+ const n = Number(s);
39
+ return Number.isFinite(n) ? { value: n } : { value: text, error: texts.number };
40
+ }
41
+ case 'checkbox': {
42
+ const w = raw.toLowerCase();
43
+ if (TRUE_WORDS.has(w))
44
+ return { value: true };
45
+ if (FALSE_WORDS.has(w))
46
+ return { value: false };
47
+ return { value: false };
48
+ }
49
+ case 'select':
50
+ case 'autocomplete': {
51
+ if (!raw)
52
+ return { value: null };
53
+ const opt = findOption(col.options ?? [], raw);
54
+ return opt ? { value: opt.value } : { value: text, error: texts.option };
55
+ }
56
+ case 'date': {
57
+ if (!raw)
58
+ return { value: null };
59
+ const iso = /^(\d{4})-(\d{1,2})-(\d{1,2})/.exec(raw);
60
+ const dmy = /^(\d{1,2})[/.-](\d{1,2})[/.-](\d{4})$/.exec(raw);
61
+ const [y, m, d] = iso ? [+iso[1], +iso[2], +iso[3]] : dmy ? [+dmy[3], +dmy[2], +dmy[1]] : [NaN, NaN, NaN];
62
+ const date = new Date(y, m - 1, d);
63
+ if (Number.isNaN(date.getTime()) || date.getMonth() !== m - 1 || date.getDate() !== d)
64
+ return { value: text, error: texts.date };
65
+ return { value: isoDate(date) };
66
+ }
67
+ default:
68
+ return { value: text };
69
+ }
70
+ }
71
+ /** Minúsculas y sin tildes. */
72
+ function fold(text) {
73
+ return text.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().trim();
74
+ }
75
+ /**
76
+ * Opción que corresponde a un texto escrito o pegado: por valor, por etiqueta
77
+ * exacta o por el código con el que empieza la etiqueta ("110505" → "110505 Caja general").
78
+ */
79
+ function findOption(options, text) {
80
+ const q = fold(text);
81
+ if (!q)
82
+ return undefined;
83
+ const usable = options.filter((o) => !o.disabled);
84
+ return (usable.find((o) => fold(String(o.value)) === q) ??
85
+ usable.find((o) => fold(o.label) === q) ??
86
+ usable.find((o) => fold(o.label).startsWith(q + ' ')));
87
+ }
88
+ /**
89
+ * Opciones de `autocomplete` que coinciden con lo escrito: primero las que
90
+ * empiezan por el texto (por código), después las que lo contienen; sin tildes.
91
+ */
92
+ function searchOptions(options, text, limit = 50) {
93
+ const q = fold(text);
94
+ if (!q)
95
+ return options.slice(0, limit);
96
+ const starts = [];
97
+ const contains = [];
98
+ for (const o of options) {
99
+ const label = fold(o.label);
100
+ if (label.startsWith(q) || fold(String(o.value)).startsWith(q))
101
+ starts.push(o);
102
+ else if (label.includes(q) || (o.description && fold(o.description).includes(q)))
103
+ contains.push(o);
104
+ }
105
+ return [...starts, ...contains].slice(0, limit);
106
+ }
107
+ function isoDate(d) {
108
+ const pad = (n) => String(n).padStart(2, '0');
109
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
110
+ }
111
+ /** Mensaje de error de una celda (obligatoria, límites y validación propia) o `null`. */
112
+ function validateCell(col, value, row, texts) {
113
+ // Vacía: obligatoria, o la validación propia (puede exigirla según la fila).
114
+ if (isEmpty(value))
115
+ return col.required ? texts.required : (col.validate?.(value, row) ?? null);
116
+ const type = col.type ?? 'text';
117
+ if (type === 'number') {
118
+ if (typeof value !== 'number')
119
+ return texts.number;
120
+ if (col.min !== undefined && value < col.min)
121
+ return texts.min.replace('{0}', String(col.min));
122
+ if (col.max !== undefined && value > col.max)
123
+ return texts.max.replace('{0}', String(col.max));
124
+ }
125
+ if ((type === 'select' || type === 'autocomplete') && !(col.options ?? []).some((o) => o.value === value))
126
+ return texts.option;
127
+ if (type === 'date' && (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value)))
128
+ return texts.date;
129
+ return col.validate?.(value, row) ?? null;
130
+ }
131
+ /** Texto plano de una celda: el que se ve, se copia y se ordena. */
132
+ function cellText(col, value, row, locale, yesNo, amount = {}) {
133
+ if (col.format)
134
+ return col.format(value, row);
135
+ if (isEmpty(value))
136
+ return '';
137
+ switch (col.type ?? 'text') {
138
+ case 'number': {
139
+ if (typeof value !== 'number')
140
+ return String(value);
141
+ const currency = typeof col.currency === 'string' ? col.currency : col.currency ? (amount.currency ?? null) : null;
142
+ const negative = col.negative ?? amount.negative ?? 'minus';
143
+ const numberLocale = amount.locale ?? locale;
144
+ if (currency || col.decimals !== undefined || negative === 'parens')
145
+ return cbFormatAmount(value, numberLocale, { currency, decimals: col.decimals, negative });
146
+ return new Intl.NumberFormat(numberLocale).format(value);
147
+ }
148
+ case 'checkbox':
149
+ return value ? yesNo[0] : yesNo[1];
150
+ case 'select':
151
+ case 'autocomplete':
152
+ return (col.options ?? []).find((o) => o.value === value)?.label ?? String(value);
153
+ case 'date': {
154
+ const m = typeof value === 'string' ? /^(\d{4})-(\d{2})-(\d{2})$/.exec(value) : null;
155
+ return m ? new Intl.DateTimeFormat(locale, { dateStyle: 'medium' }).format(new Date(+m[1], +m[2] - 1, +m[3])) : String(value);
156
+ }
157
+ default:
158
+ return String(value);
159
+ }
160
+ }
161
+ /** Texto que se pone en el portapapeles: los números sin separador de miles para que se vuelvan a pegar bien. */
162
+ function copyText(col, value, row, locale, yesNo) {
163
+ if (isEmpty(value))
164
+ return '';
165
+ if ((col.type ?? 'text') === 'number' && typeof value === 'number')
166
+ return String(value);
167
+ if (col.type === 'date' && typeof value === 'string')
168
+ return value;
169
+ return cellText(col, value, row, locale, yesNo);
170
+ }
171
+ /** Compara dos valores de una columna para ordenar (vacíos al final). */
172
+ function compareCells(col, a, b, locale) {
173
+ if (isEmpty(a) || isEmpty(b))
174
+ return isEmpty(a) === isEmpty(b) ? 0 : isEmpty(a) ? 1 : -1;
175
+ if (typeof a === 'number' && typeof b === 'number')
176
+ return a - b;
177
+ if (typeof a === 'boolean' && typeof b === 'boolean')
178
+ return Number(a) - Number(b);
179
+ if (col.type === 'select' || col.type === 'autocomplete') {
180
+ const label = (v) => (col.options ?? []).find((o) => o.value === v)?.label ?? String(v);
181
+ return label(a).localeCompare(label(b), locale, { numeric: true, sensitivity: 'base' });
182
+ }
183
+ return String(a).localeCompare(String(b), locale, { numeric: true, sensitivity: 'base' });
184
+ }
185
+ /** Matriz de textos a TSV (el formato de Excel y Google Sheets al copiar). */
186
+ function toTsv(rows) {
187
+ const quote = (s) => (/[\t\n"\r]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s);
188
+ return rows.map((r) => r.map(quote).join('\t')).join('\n');
189
+ }
190
+ /** TSV (lo que llega al pegar de una hoja de cálculo) a matriz de textos. */
191
+ function fromTsv(text) {
192
+ const out = [];
193
+ let row = [];
194
+ let cell = '';
195
+ let quoted = false;
196
+ const src = text.replace(/\r\n?/g, '\n');
197
+ for (let i = 0; i < src.length; i++) {
198
+ const ch = src[i];
199
+ if (quoted) {
200
+ if (ch === '"' && src[i + 1] === '"') {
201
+ cell += '"';
202
+ i++;
203
+ }
204
+ else if (ch === '"')
205
+ quoted = false;
206
+ else
207
+ cell += ch;
208
+ }
209
+ else if (ch === '"' && cell === '')
210
+ quoted = true;
211
+ else if (ch === '\t') {
212
+ row.push(cell);
213
+ cell = '';
214
+ }
215
+ else if (ch === '\n') {
216
+ row.push(cell);
217
+ out.push(row);
218
+ row = [];
219
+ cell = '';
220
+ }
221
+ else
222
+ cell += ch;
223
+ }
224
+ if (cell !== '' || row.length) {
225
+ row.push(cell);
226
+ out.push(row);
227
+ }
228
+ return out;
229
+ }
230
+
231
+ /** Pasos de RePág / AvPág. */
232
+ const PAGE = 10;
233
+ /** Cambios que se pueden deshacer. */
234
+ const HISTORY = 100;
235
+ /** Posiciones de la lista de `autocomplete`: debajo de la celda o, si no cabe, encima. */
236
+ const LIST_POSITIONS = [
237
+ { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 2 },
238
+ { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -2 },
239
+ ];
240
+ let nextUid = 0;
241
+ let nextKey = 0;
242
+ /**
243
+ * Data Grid editable de CaribeNG: una tabla que se edita como una hoja de cálculo.
244
+ *
245
+ * - Celdas con tipo (texto, número, lista, fecha, casilla), validación por columna
246
+ * (`required`, `min`, `max`, `validate`) y marca de las celdas cambiadas.
247
+ * - Teclado de hoja de cálculo: flechas, Mayús + flechas para seleccionar, Enter o
248
+ * F2 para editar, escribir para reemplazar, Supr para borrar, Ctrl + Z / Y,
249
+ * Ctrl + D para rellenar hacia abajo.
250
+ * - Copiar, cortar y pegar rangos en el formato de Excel y Google Sheets (TSV).
251
+ * - Añadir y eliminar filas, ordenar por columna y deshacer cualquier cambio.
252
+ *
253
+ * @example
254
+ * ```html
255
+ * <cb-data-grid [(rows)]="filas" [columns]="columnas" dataKey="id" (cellChange)="guardar($event)" />
256
+ * ```
257
+ */
258
+ class CbDataGrid {
259
+ constructor() {
260
+ /** Textos de interfaz en el idioma activo (`provideCaribeNG({ locale })`). */
261
+ this.t = injectCbTranslation();
262
+ this.fmt = cbFormat;
263
+ this.injector = inject(Injector);
264
+ this.host = inject(ElementRef);
265
+ this.uid = `cb-data-grid-${++nextUid}`;
266
+ /** Filas (bidireccional: se actualiza con cada edición, pegado, alta o baja). */
267
+ this.rows = model([], /* @ts-ignore */
268
+ ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
269
+ this.columns = input([], /* @ts-ignore */
270
+ ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
271
+ /** Campo que identifica cada fila. */
272
+ this.dataKey = input('id', /* @ts-ignore */
273
+ ...(ngDevMode ? [{ debugName: "dataKey" }] : /* istanbul ignore next */ []));
274
+ /** `false` deja todo el grid de solo lectura (se sigue navegando, seleccionando y copiando). */
275
+ this.editable = input(true, { ...(ngDevMode ? { debugName: "editable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
276
+ /** Columna con el número de fila (y para seleccionar filas enteras). */
277
+ this.rowNumbers = input(true, { ...(ngDevMode ? { debugName: "rowNumbers" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
278
+ /** Barra con Añadir fila, Eliminar, Deshacer, Rehacer y el resumen de errores. */
279
+ this.toolbar = input(true, { ...(ngDevMode ? { debugName: "toolbar" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
280
+ /** Alto máximo del área con scroll (la cabecera y los números de fila quedan fijos). */
281
+ this.scrollHeight = input('28rem', /* @ts-ignore */
282
+ ...(ngDevMode ? [{ debugName: "scrollHeight" }] : /* istanbul ignore next */ []));
283
+ /** Crea la fila nueva al añadir o al pegar más filas de las que hay. */
284
+ this.newRow = input(null, /* @ts-ignore */
285
+ ...(ngDevMode ? [{ debugName: "newRow" }] : /* istanbul ignore next */ []));
286
+ this.emptyMessage = input(undefined, /* @ts-ignore */
287
+ ...(ngDevMode ? [{ debugName: "emptyMessage" }] : /* istanbul ignore next */ []));
288
+ this.ariaLabel = input(undefined, /* @ts-ignore */
289
+ ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
290
+ /** Moneda de las columnas con `currency: true` (si no, la de `provideCaribeNG({ currency })`). */
291
+ this.currency = input(undefined, /* @ts-ignore */
292
+ ...(ngDevMode ? [{ debugName: "currency" }] : /* istanbul ignore next */ []));
293
+ /** Negativos con signo o entre paréntesis (si no, el de `provideCaribeNG({ negativeStyle })`). */
294
+ this.negativeStyle = input(undefined, /* @ts-ignore */
295
+ ...(ngDevMode ? [{ debugName: "negativeStyle" }] : /* istanbul ignore next */ []));
296
+ /** Una celda cambió de valor (al editar, pegar, borrar o rellenar; una vez por celda). */
297
+ this.cellChange = output();
298
+ this.rowAdd = output();
299
+ this.rowsDelete = output();
300
+ this.sort = signal(null, /* @ts-ignore */
301
+ ...(ngDevMode ? [{ debugName: "sort" }] : /* istanbul ignore next */ []));
302
+ this.active = signal({ r: 0, c: 0 }, /* @ts-ignore */
303
+ ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
304
+ this.anchor = signal({ r: 0, c: 0 }, /* @ts-ignore */
305
+ ...(ngDevMode ? [{ debugName: "anchor" }] : /* istanbul ignore next */ []));
306
+ this.editing = signal(null, /* @ts-ignore */
307
+ ...(ngDevMode ? [{ debugName: "editing" }] : /* istanbul ignore next */ []));
308
+ this.announcement = signal('', /* @ts-ignore */
309
+ ...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
310
+ this.dragging = false;
311
+ this.undoStack = signal([], /* @ts-ignore */
312
+ ...(ngDevMode ? [{ debugName: "undoStack" }] : /* istanbul ignore next */ []));
313
+ this.redoStack = signal([], /* @ts-ignore */
314
+ ...(ngDevMode ? [{ debugName: "redoStack" }] : /* istanbul ignore next */ []));
315
+ this.canUndo = computed(() => this.undoStack().length > 0, /* @ts-ignore */
316
+ ...(ngDevMode ? [{ debugName: "canUndo" }] : /* istanbul ignore next */ []));
317
+ this.canRedo = computed(() => this.redoStack().length > 0, /* @ts-ignore */
318
+ ...(ngDevMode ? [{ debugName: "canRedo" }] : /* istanbul ignore next */ []));
319
+ /** Filas tal como estaban al cargar o en el último `markClean()`. */
320
+ this.baseline = signal(null, /* @ts-ignore */
321
+ ...(ngDevMode ? [{ debugName: "baseline" }] : /* istanbul ignore next */ []));
322
+ this.range = computed(() => rangeOf(this.anchor(), this.active()), /* @ts-ignore */
323
+ ...(ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []));
324
+ this.selectedRowCount = computed(() => {
325
+ const r = this.range();
326
+ return this.view().length ? r.r2 - r.r1 + 1 : 0;
327
+ }, /* @ts-ignore */
328
+ ...(ngDevMode ? [{ debugName: "selectedRowCount" }] : /* istanbul ignore next */ []));
329
+ /** Filas en el orden en que se ven. */
330
+ this.view = computed(() => {
331
+ const rows = this.rows();
332
+ const s = this.sort();
333
+ const col = s && this.columns().find((c) => c.field === s.field);
334
+ if (!s || !col)
335
+ return rows;
336
+ const locale = this.t().intlLocale;
337
+ return [...rows].sort((a, b) => compareCells(col, this.get(a, s.field), this.get(b, s.field), locale) * s.order);
338
+ }, /* @ts-ignore */
339
+ ...(ngDevMode ? [{ debugName: "view" }] : /* istanbul ignore next */ []));
340
+ this.texts = computed(() => {
341
+ const t = this.t();
342
+ return { required: t.dataGridRequired, min: t.dataGridMin, max: t.dataGridMax, number: t.dataGridInvalidNumber, date: t.dataGridInvalidDate, option: t.dataGridInvalidOption };
343
+ }, /* @ts-ignore */
344
+ ...(ngDevMode ? [{ debugName: "texts" }] : /* istanbul ignore next */ []));
345
+ /** Errores de validación de todas las celdas, en el orden de la vista. */
346
+ this.errors = computed(() => {
347
+ const texts = this.texts();
348
+ const out = [];
349
+ for (const row of this.view()) {
350
+ for (const col of this.columns()) {
351
+ const message = validateCell(col, this.get(row, col.field), row, texts);
352
+ if (message)
353
+ out.push({ key: this.keyOf(row), field: col.field, message });
354
+ }
355
+ }
356
+ return out;
357
+ }, /* @ts-ignore */
358
+ ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
359
+ this.errorIndex = computed(() => {
360
+ const map = new Map();
361
+ for (const e of this.errors()) {
362
+ if (!map.has(e.key))
363
+ map.set(e.key, new Map());
364
+ map.get(e.key).set(e.field, e.message);
365
+ }
366
+ return map;
367
+ }, /* @ts-ignore */
368
+ ...(ngDevMode ? [{ debugName: "errorIndex" }] : /* istanbul ignore next */ []));
369
+ /** Celdas cambiadas desde la carga (o el último `markClean()`), contando las filas nuevas. */
370
+ this.dirtyCount = computed(() => {
371
+ const base = this.baseline();
372
+ if (!base)
373
+ return 0;
374
+ let n = 0;
375
+ for (const row of this.rows())
376
+ for (const col of this.columns())
377
+ if (this.isDirty(row, col))
378
+ n++;
379
+ return n;
380
+ }, /* @ts-ignore */
381
+ ...(ngDevMode ? [{ debugName: "dirtyCount" }] : /* istanbul ignore next */ []));
382
+ this.config = inject(CB_CONFIG);
383
+ this.amount = computed(() => ({ currency: this.currency() ?? this.config.currency ?? null, negative: this.negativeStyle() ?? this.config.negativeStyle ?? 'minus', locale: this.config.numberLocale }), /* @ts-ignore */
384
+ ...(ngDevMode ? [{ debugName: "amount" }] : /* istanbul ignore next */ []));
385
+ this.hasFooterText = computed(() => this.columns().some((c) => c.footer), /* @ts-ignore */
386
+ ...(ngDevMode ? [{ debugName: "hasFooterText" }] : /* istanbul ignore next */ []));
387
+ this.hasFooter = computed(() => this.hasFooterText() || this.columns().some((c) => c.summary), /* @ts-ignore */
388
+ ...(ngDevMode ? [{ debugName: "hasFooter" }] : /* istanbul ignore next */ []));
389
+ /** Columna de la etiqueta "Total": la primera sin total. */
390
+ this.labelColumn = computed(() => Math.max(0, this.columns().findIndex((c) => !c.summary)), /* @ts-ignore */
391
+ ...(ngDevMode ? [{ debugName: "labelColumn" }] : /* istanbul ignore next */ []));
392
+ /** Totales del pie (columnas con `summary`), sobre todas las filas. */
393
+ this.totals = computed(() => {
394
+ const rows = this.rows();
395
+ const out = {};
396
+ for (const col of this.columns()) {
397
+ if (!col.summary)
398
+ continue;
399
+ out[col.field] = typeof col.summary === 'function' ? col.summary(rows) : cbSummarize(rows.map((r) => this.get(r, col.field)), col.summary);
400
+ }
401
+ return out;
402
+ }, /* @ts-ignore */
403
+ ...(ngDevMode ? [{ debugName: "totals" }] : /* istanbul ignore next */ []));
404
+ this.tableWidth = computed(() => `calc(${[this.rowNumbers() ? '3rem' : '0rem', ...this.columns().map((c) => this.widthOf(c))].join(' + ')})`, /* @ts-ignore */
405
+ ...(ngDevMode ? [{ debugName: "tableWidth" }] : /* istanbul ignore next */ []));
406
+ this.listPositions = LIST_POSITIONS;
407
+ /** Opciones de `autocomplete` que coinciden con lo escrito en el editor. */
408
+ this.matches = computed(() => {
409
+ const e = this.editing();
410
+ const col = e ? this.columns()[e.c] : null;
411
+ if (!e || col?.type !== 'autocomplete')
412
+ return [];
413
+ return searchOptions(col.options ?? [], e.text);
414
+ }, /* @ts-ignore */
415
+ ...(ngDevMode ? [{ debugName: "matches" }] : /* istanbul ignore next */ []));
416
+ // La primera vez que llegan filas se guardan como estado original.
417
+ effect(() => {
418
+ const rows = this.rows();
419
+ if (rows.length && untracked(this.baseline) === null)
420
+ untracked(() => this.markClean());
421
+ });
422
+ // Si cambian las filas o columnas desde fuera, la celda activa sigue dentro.
423
+ effect(() => {
424
+ const rows = this.view().length;
425
+ const cols = this.columns().length;
426
+ untracked(() => {
427
+ const clamp = (p) => ({ r: Math.max(0, Math.min(p.r, rows - 1)), c: Math.max(0, Math.min(p.c, cols - 1)) });
428
+ const a = this.active();
429
+ const b = clamp(a);
430
+ if (a.r !== b.r || a.c !== b.c)
431
+ this.active.set(b);
432
+ const n = this.anchor();
433
+ const m = clamp(n);
434
+ if (n.r !== m.r || n.c !== m.c)
435
+ this.anchor.set(m);
436
+ });
437
+ });
438
+ }
439
+ // --- API pública -------------------------------------------------------------------------
440
+ /** Toma las filas actuales como estado original: borra las marcas de cambio. */
441
+ markClean() {
442
+ this.baseline.set(new Map(this.rows().map((row) => [this.keyOf(row), row])));
443
+ }
444
+ /** Filas añadidas, modificadas y eliminadas desde la carga o el último `markClean()`. */
445
+ getChanges() {
446
+ const base = this.baseline() ?? new Map();
447
+ const keys = new Set();
448
+ const added = [];
449
+ const updated = [];
450
+ for (const row of this.rows()) {
451
+ const key = this.keyOf(row);
452
+ keys.add(key);
453
+ const before = base.get(key);
454
+ if (!before)
455
+ added.push(row);
456
+ else if (before !== row && this.columns().some((c) => this.get(before, c.field) !== this.get(row, c.field)))
457
+ updated.push(row);
458
+ }
459
+ const removed = [...base.entries()].filter(([key]) => !keys.has(key)).map(([, row]) => row);
460
+ return { added, updated, removed };
461
+ }
462
+ /**
463
+ * Hoja de Excel con las filas en el orden en que se ven: números y fechas como
464
+ * valores con formato, listas con su etiqueta, casillas como Sí / No y totales.
465
+ */
466
+ toExcelSheet(options = {}) {
467
+ const t = this.t();
468
+ const amount = this.amount();
469
+ const locale = this.config.numberLocale ?? t.intlLocale;
470
+ const cols = this.columns();
471
+ return {
472
+ name: options.sheetName ?? this.ariaLabel() ?? 'Datos',
473
+ title: options.title,
474
+ columns: cols.map((c) => {
475
+ const currency = typeof c.currency === 'string' ? c.currency : c.currency ? amount.currency : null;
476
+ const type = c.type === 'number' ? (currency ? 'currency' : 'number') : c.type === 'date' ? 'date' : 'text';
477
+ return { header: c.header, type, decimals: c.decimals, negative: c.negative ?? amount.negative, symbol: currency ? currencySymbol(currency, locale) : undefined };
478
+ }),
479
+ rows: this.view().map((row) => cols.map((c) => {
480
+ const v = this.get(row, c.field);
481
+ if (c.type === 'number' || c.type === 'date')
482
+ return typeof v === 'number' || typeof v === 'string' ? v : null;
483
+ return this.text(row, c);
484
+ })),
485
+ totals: this.hasFooter() ? cols.map((c, i) => (c.footer ?? (i === this.labelColumn() && !this.hasFooterText() ? t.tableTotal : (this.totals()[c.field] ?? null)))) : undefined,
486
+ };
487
+ }
488
+ /** Descarga las filas en Excel (`nombre.xlsx`). */
489
+ exportExcel(filename = 'datos', options = {}) {
490
+ cbDownloadXlsx(filename, this.toExcelSheet(options));
491
+ }
492
+ /** `true` si ninguna celda tiene errores de validación. */
493
+ isValid() {
494
+ return this.errors().length === 0;
495
+ }
496
+ undo() {
497
+ const stack = this.undoStack();
498
+ if (!stack.length)
499
+ return;
500
+ this.cancelEdit();
501
+ this.redoStack.update((s) => [...s, this.rows()]);
502
+ this.undoStack.set(stack.slice(0, -1));
503
+ this.rows.set(stack[stack.length - 1]);
504
+ this.say(this.t().dataGridUndone);
505
+ }
506
+ redo() {
507
+ const stack = this.redoStack();
508
+ if (!stack.length)
509
+ return;
510
+ this.cancelEdit();
511
+ this.undoStack.update((s) => [...s, this.rows()]);
512
+ this.redoStack.set(stack.slice(0, -1));
513
+ this.rows.set(stack[stack.length - 1]);
514
+ this.say(this.t().dataGridRedone);
515
+ }
516
+ /** Añade una fila al final y lleva el foco a su primera celda. */
517
+ addRow() {
518
+ if (!this.editable())
519
+ return;
520
+ this.commit(null);
521
+ const row = this.createRow();
522
+ this.pushHistory();
523
+ this.sort.set(null);
524
+ this.rows.update((rows) => [...rows, row]);
525
+ this.rowAdd.emit(row);
526
+ this.select({ r: this.rows().length - 1, c: 0 });
527
+ this.say(this.t().dataGridRowAdded);
528
+ }
529
+ /** Elimina las filas de la selección. */
530
+ deleteRows() {
531
+ if (!this.editable() || !this.view().length)
532
+ return;
533
+ this.commit(null);
534
+ const { r1, r2 } = this.range();
535
+ const gone = this.view().slice(r1, r2 + 1);
536
+ const drop = new Set(gone);
537
+ this.pushHistory();
538
+ this.rows.update((rows) => rows.filter((row) => !drop.has(row)));
539
+ this.rowsDelete.emit(gone);
540
+ this.select({ r: Math.min(r1, this.view().length - 1), c: this.active().c });
541
+ this.say(this.fmt(this.t().dataGridRowsDeleted, gone.length));
542
+ }
543
+ // --- Plantilla ---------------------------------------------------------------------------
544
+ keyOf(row) {
545
+ return this.get(row, this.dataKey());
546
+ }
547
+ get(row, field) {
548
+ return row[field];
549
+ }
550
+ cellId(r, c) {
551
+ return `${this.uid}-${r}-${c}`;
552
+ }
553
+ widthOf(col) {
554
+ return col.width ?? (col.type === 'checkbox' ? '5rem' : col.type === 'number' ? '8rem' : col.type === 'date' ? '10rem' : '10rem');
555
+ }
556
+ alignOf(col) {
557
+ return col.align ?? (col.type === 'number' ? 'end' : col.type === 'checkbox' ? 'center' : 'start');
558
+ }
559
+ canEdit(col) {
560
+ return this.editable() && col.editable !== false;
561
+ }
562
+ isSelected(r, c) {
563
+ return inRange(this.range(), r, c);
564
+ }
565
+ text(row, col) {
566
+ const t = this.t();
567
+ return cellText(col, this.get(row, col.field), row, t.intlLocale, [t.yes, t.no], this.amount());
568
+ }
569
+ isNegative(value, col) {
570
+ return col.type === 'number' && typeof value === 'number' && value < 0;
571
+ }
572
+ totalText(col) {
573
+ const value = this.totals()[col.field];
574
+ if (value === undefined || value === null)
575
+ return '';
576
+ if (col.summary === 'count' || typeof value !== 'number')
577
+ return typeof value === 'number' ? new Intl.NumberFormat(this.config.numberLocale ?? this.t().intlLocale).format(value) : String(value);
578
+ const t = this.t();
579
+ return cellText({ ...col, type: 'number', format: undefined }, value, {}, t.intlLocale, [t.yes, t.no], this.amount());
580
+ }
581
+ errorOf(row, col) {
582
+ return this.errorIndex().get(this.keyOf(row))?.get(col.field) ?? null;
583
+ }
584
+ isNew(row) {
585
+ const base = this.baseline();
586
+ return !!base && !base.has(this.keyOf(row));
587
+ }
588
+ isDirty(row, col) {
589
+ const base = this.baseline();
590
+ if (!base)
591
+ return false;
592
+ const before = base.get(this.keyOf(row));
593
+ if (!before)
594
+ return true;
595
+ return before !== row && this.get(before, col.field) !== this.get(row, col.field);
596
+ }
597
+ setActive(i) {
598
+ this.editing.update((e) => (e ? { ...e, active: i } : e));
599
+ }
600
+ /** Elige una opción de `autocomplete` con el ratón. */
601
+ pick(option) {
602
+ if (option.disabled)
603
+ return;
604
+ const e = this.editing();
605
+ if (!e)
606
+ return;
607
+ this.editing.set({ ...e, text: option.label, active: this.matches().indexOf(option) });
608
+ this.commit('down');
609
+ }
610
+ editorKind(col) {
611
+ if (col.type === 'autocomplete')
612
+ return 'autocomplete';
613
+ const mode = this.editing()?.mode;
614
+ if (col.type === 'select' && mode === 'native')
615
+ return 'select';
616
+ if (col.type === 'date' && mode === 'native')
617
+ return 'date';
618
+ return 'text';
619
+ }
620
+ toggleSort(col) {
621
+ this.commit(null);
622
+ const s = this.sort();
623
+ this.sort.set(s?.field !== col.field ? { field: col.field, order: 1 } : s.order === 1 ? { field: col.field, order: -1 } : null);
624
+ }
625
+ selectAll() {
626
+ this.anchor.set({ r: 0, c: 0 });
627
+ this.active.set({ r: Math.max(0, this.view().length - 1), c: Math.max(0, this.columns().length - 1) });
628
+ this.focusActive();
629
+ }
630
+ selectRow(r, event) {
631
+ event.preventDefault();
632
+ this.commit(null);
633
+ const last = this.columns().length - 1;
634
+ if (event.shiftKey)
635
+ this.anchor.set({ r: this.anchor().r, c: 0 });
636
+ else
637
+ this.anchor.set({ r, c: 0 });
638
+ this.active.set({ r, c: last });
639
+ this.focusActive();
640
+ }
641
+ goToFirstError() {
642
+ const e = this.errors()[0];
643
+ if (!e)
644
+ return;
645
+ const r = this.view().findIndex((row) => this.keyOf(row) === e.key);
646
+ const c = this.columns().findIndex((col) => col.field === e.field);
647
+ if (r >= 0 && c >= 0)
648
+ this.select({ r, c });
649
+ }
650
+ // --- Ratón -------------------------------------------------------------------------------
651
+ onCellPointerDown(event, r, c) {
652
+ if (event.button !== 0 || event.target.closest('.cb-dg__editor'))
653
+ return;
654
+ const e = this.editing();
655
+ if (e && (e.r !== r || e.c !== c))
656
+ this.commit(null);
657
+ if (event.shiftKey)
658
+ this.active.set({ r, c });
659
+ else
660
+ this.select({ r, c }, false);
661
+ this.dragging = true;
662
+ this.focusActive();
663
+ }
664
+ onCellPointerEnter(r, c) {
665
+ if (!this.dragging || this.editing())
666
+ return;
667
+ this.active.set({ r, c });
668
+ }
669
+ toggleCheck(r, c) {
670
+ const col = this.columns()[c];
671
+ const row = this.view()[r];
672
+ if (!col || !row || !this.canEdit(col))
673
+ return;
674
+ this.apply([{ row, field: col.field, value: !this.get(row, col.field) }]);
675
+ }
676
+ // --- Teclado -----------------------------------------------------------------------------
677
+ onKeydown(event) {
678
+ if (event.target.closest('thead, .cb-dg__editor'))
679
+ return;
680
+ if (this.editing()) {
681
+ // Teclas que llegan a la celda antes de que el editor tenga el foco (al escribir rápido).
682
+ if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
683
+ event.preventDefault();
684
+ this.editing.update((e) => (e && e.mode === 'text' ? { ...e, text: e.text + event.key, active: this.firstEnabled(e.c, e.text + event.key) } : e));
685
+ }
686
+ else if (['Enter', 'Tab', 'Escape'].includes(event.key))
687
+ this.onEditorKeydown(event);
688
+ return;
689
+ }
690
+ const rows = this.view().length;
691
+ const cols = this.columns().length;
692
+ if (!rows || !cols)
693
+ return;
694
+ const { r, c } = this.active();
695
+ const mod = event.ctrlKey || event.metaKey;
696
+ const key = event.key;
697
+ let to = null;
698
+ switch (key) {
699
+ case 'ArrowUp':
700
+ to = { r: mod ? 0 : r - 1, c };
701
+ break;
702
+ case 'ArrowDown':
703
+ to = { r: mod ? rows - 1 : r + 1, c };
704
+ break;
705
+ case 'ArrowLeft':
706
+ to = { r, c: mod ? 0 : c - 1 };
707
+ break;
708
+ case 'ArrowRight':
709
+ to = { r, c: mod ? cols - 1 : c + 1 };
710
+ break;
711
+ case 'Home':
712
+ to = { r: mod ? 0 : r, c: 0 };
713
+ break;
714
+ case 'End':
715
+ to = { r: mod ? rows - 1 : r, c: cols - 1 };
716
+ break;
717
+ case 'PageUp':
718
+ to = { r: r - PAGE, c };
719
+ break;
720
+ case 'PageDown':
721
+ to = { r: r + PAGE, c };
722
+ break;
723
+ case 'Tab': {
724
+ const i = r * cols + c + (event.shiftKey ? -1 : 1);
725
+ if (i < 0 || i >= rows * cols)
726
+ return; // En los extremos el Tab sale del grid.
727
+ event.preventDefault();
728
+ this.select({ r: Math.floor(i / cols), c: i % cols });
729
+ return;
730
+ }
731
+ case 'Enter':
732
+ case 'F2':
733
+ event.preventDefault();
734
+ if (this.columns()[c].type === 'checkbox')
735
+ this.toggleCheck(r, c);
736
+ else
737
+ this.startEdit(r, c);
738
+ return;
739
+ case 'Delete':
740
+ case 'Backspace':
741
+ event.preventDefault();
742
+ this.clearRange();
743
+ return;
744
+ case 'Escape':
745
+ if (this.range().r1 !== this.range().r2 || this.range().c1 !== this.range().c2) {
746
+ event.preventDefault();
747
+ this.select({ r, c });
748
+ }
749
+ return;
750
+ }
751
+ if (to) {
752
+ event.preventDefault();
753
+ const next = { r: Math.max(0, Math.min(rows - 1, to.r)), c: Math.max(0, Math.min(cols - 1, to.c)) };
754
+ if (event.shiftKey) {
755
+ this.active.set(next);
756
+ this.focusActive();
757
+ }
758
+ else
759
+ this.select(next);
760
+ return;
761
+ }
762
+ if (key === ' ' && event.shiftKey) {
763
+ event.preventDefault();
764
+ this.anchor.set({ r: this.anchor().r, c: 0 });
765
+ this.active.set({ r, c: cols - 1 });
766
+ return;
767
+ }
768
+ if (key === ' ' && mod) {
769
+ event.preventDefault();
770
+ this.anchor.set({ r: 0, c: this.anchor().c });
771
+ this.active.set({ r: rows - 1, c });
772
+ return;
773
+ }
774
+ if (mod && !event.altKey) {
775
+ const k = key.toLowerCase();
776
+ if (k === 'z' && !event.shiftKey) {
777
+ event.preventDefault();
778
+ this.undo();
779
+ }
780
+ else if (k === 'y' || (k === 'z' && event.shiftKey)) {
781
+ event.preventDefault();
782
+ this.redo();
783
+ }
784
+ else if (k === 'd') {
785
+ event.preventDefault();
786
+ this.fillDown();
787
+ }
788
+ else if (k === 'a') {
789
+ event.preventDefault();
790
+ this.selectAll();
791
+ }
792
+ return;
793
+ }
794
+ if (key === ' ' && this.columns()[c].type === 'checkbox') {
795
+ event.preventDefault();
796
+ this.toggleCheck(r, c);
797
+ return;
798
+ }
799
+ // Escribir un carácter empieza a editar y reemplaza el valor.
800
+ if (key.length === 1 && !event.altKey) {
801
+ const col = this.columns()[c];
802
+ if (!this.canEdit(col) || col.type === 'checkbox')
803
+ return;
804
+ event.preventDefault();
805
+ if (col.type === 'select') {
806
+ const low = key.toLowerCase();
807
+ const opt = (col.options ?? []).find((o) => o.label.toLowerCase().startsWith(low));
808
+ this.startEdit(r, c, opt?.label ?? '', 'native');
809
+ }
810
+ else
811
+ this.startEdit(r, c, key, 'text');
812
+ }
813
+ }
814
+ onEditorKeydown(event) {
815
+ event.stopPropagation();
816
+ const e = this.editing();
817
+ if (!e)
818
+ return;
819
+ switch (event.key) {
820
+ case 'Enter':
821
+ event.preventDefault();
822
+ this.commit(event.shiftKey ? 'up' : 'down');
823
+ break;
824
+ case 'Tab':
825
+ event.preventDefault();
826
+ this.commit(event.shiftKey ? 'left' : 'right');
827
+ break;
828
+ case 'Escape':
829
+ event.preventDefault();
830
+ this.cancelEdit();
831
+ this.focusActive();
832
+ break;
833
+ case 'ArrowUp':
834
+ case 'ArrowDown':
835
+ // En `autocomplete`, las flechas recorren la lista (saltando las deshabilitadas).
836
+ if (this.columns()[e.c]?.type === 'autocomplete') {
837
+ event.preventDefault();
838
+ const list = this.matches();
839
+ if (!list.length)
840
+ break;
841
+ const step = event.key === 'ArrowDown' ? 1 : -1;
842
+ let i = e.active;
843
+ for (let n = 0; n < list.length; n++) {
844
+ i = (i + step + list.length) % list.length;
845
+ if (!list[i].disabled)
846
+ break;
847
+ }
848
+ this.setActive(i);
849
+ this.host.nativeElement.ownerDocument.getElementById(`${this.uid}-opt-${i}`)?.scrollIntoView?.({ block: 'nearest' });
850
+ break;
851
+ }
852
+ // Si se empezó escribiendo, las flechas confirman y se mueven (como en Excel).
853
+ if (e.mode === 'text') {
854
+ event.preventDefault();
855
+ this.commit(event.key === 'ArrowUp' ? 'up' : 'down');
856
+ }
857
+ break;
858
+ }
859
+ }
860
+ editText(event) {
861
+ const value = event.target.value;
862
+ this.editing.update((e) => (e ? { ...e, text: value, error: null, active: this.firstEnabled(e.c, value) } : e));
863
+ }
864
+ /** Primera opción elegible de `autocomplete` para un texto. */
865
+ firstEnabled(c, text) {
866
+ const col = this.columns()[c];
867
+ if (col?.type !== 'autocomplete')
868
+ return 0;
869
+ return Math.max(0, searchOptions(col.options ?? [], text).findIndex((o) => !o.disabled));
870
+ }
871
+ // --- Edición -----------------------------------------------------------------------------
872
+ /** Abre el editor de una celda (con `initial`, reemplaza el valor por ese texto). */
873
+ startEdit(r, c, initial, mode) {
874
+ const col = this.columns()[c];
875
+ const row = this.view()[r];
876
+ if (!col || !row || !this.canEdit(col) || col.type === 'checkbox')
877
+ return;
878
+ const value = this.get(row, col.field);
879
+ let text = initial;
880
+ if (text === undefined) {
881
+ const t = this.t();
882
+ text = col.type === 'date' ? (isEmpty(value) ? '' : String(value)) : copyText(col, value, row, t.intlLocale, [t.yes, t.no]);
883
+ if (col.type === 'number' && typeof value === 'number')
884
+ text = this.localNumber(value);
885
+ }
886
+ this.select({ r, c }, false);
887
+ this.editing.set({ r, c, text, mode: mode ?? (initial === undefined ? 'native' : 'text'), error: null, active: this.firstEnabled(c, initial ?? '') });
888
+ afterNextRender(() => {
889
+ const input = this.host.nativeElement.querySelector(`#${this.cellId(r, c)} .cb-dg__editor`);
890
+ input?.focus();
891
+ if (input instanceof HTMLInputElement && input.type === 'text')
892
+ input.setSelectionRange(input.value.length, input.value.length);
893
+ }, { injector: this.injector });
894
+ }
895
+ /** Confirma la edición en curso y, si se indica, se mueve a la celda vecina. */
896
+ commit(move) {
897
+ const e = this.editing();
898
+ if (!e)
899
+ return;
900
+ const col = this.columns()[e.c];
901
+ const row = this.view()[e.r];
902
+ if (!col || !row) {
903
+ this.editing.set(null);
904
+ return;
905
+ }
906
+ // En `autocomplete`, con Enter o Tab se elige la opción resaltada (al salir, solo si coincide).
907
+ const highlighted = col.type === 'autocomplete' && move !== null ? this.matches()[e.active] : undefined;
908
+ const parsed = highlighted && !highlighted.disabled && e.text.trim() ? { value: highlighted.value } : parseCell(col, e.text, this.texts());
909
+ if ('error' in parsed && parsed.error) {
910
+ // Al salir con un valor que no se entiende, se descarta; con Enter o Tab se avisa.
911
+ if (move === null)
912
+ this.editing.set(null);
913
+ else
914
+ this.editing.set({ ...e, error: parsed.error });
915
+ return;
916
+ }
917
+ this.editing.set(null);
918
+ if (parsed.value !== this.get(row, col.field))
919
+ this.apply([{ row, field: col.field, value: parsed.value }]);
920
+ if (move) {
921
+ const rows = this.view().length;
922
+ const cols = this.columns().length;
923
+ const { r, c } = e;
924
+ const to = move === 'down' ? { r: Math.min(rows - 1, r + 1), c }
925
+ : move === 'up' ? { r: Math.max(0, r - 1), c }
926
+ : move === 'right' ? (c + 1 < cols ? { r, c: c + 1 } : { r: Math.min(rows - 1, r + 1), c: 0 })
927
+ : c > 0 ? { r, c: c - 1 } : { r: Math.max(0, r - 1), c: cols - 1 };
928
+ this.select(to);
929
+ }
930
+ else if (this.host.nativeElement.contains(document.activeElement) || document.activeElement === document.body) {
931
+ this.focusActive();
932
+ }
933
+ }
934
+ cancelEdit() {
935
+ this.editing.set(null);
936
+ }
937
+ /** Borra los valores del rango seleccionado. */
938
+ clearRange() {
939
+ const patches = [];
940
+ this.forRange((row, col) => {
941
+ if (!this.canEdit(col))
942
+ return;
943
+ const value = col.type === 'checkbox' ? false : null;
944
+ if (this.get(row, col.field) !== value && !(isEmpty(value) && isEmpty(this.get(row, col.field))))
945
+ patches.push({ row, field: col.field, value });
946
+ });
947
+ if (!patches.length)
948
+ return;
949
+ this.apply(patches);
950
+ this.say(this.fmt(this.t().dataGridCleared, patches.length));
951
+ }
952
+ /** Ctrl + D: copia la primera fila del rango en las demás (o la de arriba si es una sola fila). */
953
+ fillDown() {
954
+ const { r1, r2, c1, c2 } = this.range();
955
+ const view = this.view();
956
+ const from = r1 === r2 ? r1 - 1 : r1;
957
+ if (from < 0)
958
+ return;
959
+ const source = view[from];
960
+ const patches = [];
961
+ for (let r = r1 === r2 ? r1 : r1 + 1; r <= r2; r++) {
962
+ for (let c = c1; c <= c2; c++) {
963
+ const col = this.columns()[c];
964
+ if (!this.canEdit(col))
965
+ continue;
966
+ const value = this.get(source, col.field);
967
+ if (this.get(view[r], col.field) !== value)
968
+ patches.push({ row: view[r], field: col.field, value });
969
+ }
970
+ }
971
+ if (patches.length)
972
+ this.apply(patches);
973
+ }
974
+ // --- Portapapeles ------------------------------------------------------------------------
975
+ onCopy(event, cut) {
976
+ if (this.editing() || !event.clipboardData || !this.view().length)
977
+ return;
978
+ const { r1, r2, c1, c2 } = this.range();
979
+ const t = this.t();
980
+ const matrix = [];
981
+ for (let r = r1; r <= r2; r++) {
982
+ const row = this.view()[r];
983
+ const line = [];
984
+ for (let c = c1; c <= c2; c++) {
985
+ const col = this.columns()[c];
986
+ line.push(copyText(col, this.get(row, col.field), row, t.intlLocale, [t.yes, t.no]));
987
+ }
988
+ matrix.push(line);
989
+ }
990
+ event.clipboardData.setData('text/plain', toTsv(matrix));
991
+ event.preventDefault();
992
+ const cells = (r2 - r1 + 1) * (c2 - c1 + 1);
993
+ if (cut && this.editable())
994
+ this.clearRange();
995
+ else
996
+ this.say(this.fmt(t.dataGridCopied, cells));
997
+ }
998
+ onPaste(event) {
999
+ if (this.editing() || !this.editable() || !event.clipboardData)
1000
+ return;
1001
+ const text = event.clipboardData.getData('text/plain');
1002
+ if (!text)
1003
+ return;
1004
+ event.preventDefault();
1005
+ this.paste(fromTsv(text));
1006
+ }
1007
+ /** Pega una matriz de textos desde la esquina del rango (un único valor rellena todo el rango). */
1008
+ paste(matrix) {
1009
+ if (!matrix.length)
1010
+ return;
1011
+ const range = this.range();
1012
+ const single = matrix.length === 1 && matrix[0].length === 1;
1013
+ const height = single ? range.r2 - range.r1 + 1 : matrix.length;
1014
+ const width = single ? range.c2 - range.c1 + 1 : Math.max(...matrix.map((m) => m.length));
1015
+ const cols = this.columns();
1016
+ // Si se pegan más filas de las que hay, se añaden (sin orden, para que queden al final).
1017
+ const missing = range.r1 + height - this.view().length;
1018
+ const added = [];
1019
+ if (missing > 0) {
1020
+ for (let i = 0; i < missing; i++)
1021
+ added.push(this.createRow());
1022
+ this.pushHistory();
1023
+ this.sort.set(null);
1024
+ this.rows.update((rows) => [...rows, ...added]);
1025
+ }
1026
+ const view = this.view();
1027
+ const patches = [];
1028
+ for (let i = 0; i < height; i++) {
1029
+ const row = view[range.r1 + i];
1030
+ if (!row)
1031
+ break;
1032
+ for (let j = 0; j < width; j++) {
1033
+ const col = cols[range.c1 + j];
1034
+ if (!col || !this.canEdit(col))
1035
+ continue;
1036
+ const raw = single ? matrix[0][0] : (matrix[i][j] ?? '');
1037
+ const parsed = parseCell(col, raw, this.texts());
1038
+ // Lo que no se entiende se pega tal cual y queda marcado como error.
1039
+ if (parsed.value !== this.get(row, col.field))
1040
+ patches.push({ row, field: col.field, value: parsed.value });
1041
+ }
1042
+ }
1043
+ if (patches.length)
1044
+ this.apply(patches, added.length === 0);
1045
+ for (const row of added)
1046
+ this.rowAdd.emit(row);
1047
+ this.anchor.set({ r: range.r1, c: range.c1 });
1048
+ this.active.set({ r: Math.min(view.length - 1, range.r1 + height - 1), c: Math.min(cols.length - 1, range.c1 + width - 1) });
1049
+ this.focusActive();
1050
+ this.say(this.fmt(this.t().dataGridPasted, height * width));
1051
+ }
1052
+ // --- Internos ----------------------------------------------------------------------------
1053
+ forRange(fn) {
1054
+ const { r1, r2, c1, c2 } = this.range();
1055
+ const view = this.view();
1056
+ for (let r = r1; r <= r2; r++)
1057
+ for (let c = c1; c <= c2; c++)
1058
+ if (view[r] && this.columns()[c])
1059
+ fn(view[r], this.columns()[c]);
1060
+ }
1061
+ /** Aplica cambios de celdas como un único paso de deshacer. */
1062
+ apply(patches, history = true) {
1063
+ if (history)
1064
+ this.pushHistory();
1065
+ const byRow = new Map();
1066
+ for (const p of patches) {
1067
+ const copy = byRow.get(p.row) ?? { ...p.row };
1068
+ copy[p.field] = p.value;
1069
+ // Campos que cambian con esta celda (`onEdit`), en el mismo paso.
1070
+ const extra = this.columns().find((c) => c.field === p.field)?.onEdit?.(p.value, copy);
1071
+ if (extra)
1072
+ Object.assign(copy, extra);
1073
+ byRow.set(p.row, copy);
1074
+ }
1075
+ const next = this.rows().map((row) => byRow.get(row) ?? row);
1076
+ this.rows.set(next);
1077
+ for (const p of patches) {
1078
+ this.cellChange.emit({ row: byRow.get(p.row), field: p.field, value: p.value, previous: this.get(p.row, p.field) });
1079
+ }
1080
+ }
1081
+ pushHistory() {
1082
+ this.undoStack.update((s) => [...s.slice(-(HISTORY - 1)), this.rows()]);
1083
+ this.redoStack.set([]);
1084
+ }
1085
+ createRow() {
1086
+ const factory = this.newRow();
1087
+ if (factory)
1088
+ return factory();
1089
+ const row = { [this.dataKey()]: `${Date.now().toString(36)}-${++nextKey}` };
1090
+ for (const col of this.columns())
1091
+ if (!(col.field in row))
1092
+ row[col.field] = col.type === 'checkbox' ? false : null;
1093
+ return row;
1094
+ }
1095
+ /** Mueve la celda activa (y la selección a esa sola celda). */
1096
+ select(pos, focus = true) {
1097
+ this.anchor.set(pos);
1098
+ this.active.set(pos);
1099
+ if (focus)
1100
+ this.focusActive();
1101
+ }
1102
+ focusActive() {
1103
+ afterNextRender(() => {
1104
+ const { r, c } = this.active();
1105
+ const cell = this.host.nativeElement.querySelector(`#${this.cellId(r, c)}`);
1106
+ if (!cell || cell.contains(document.activeElement))
1107
+ return;
1108
+ cell.focus({ preventScroll: true });
1109
+ cell.scrollIntoView?.({ block: 'nearest', inline: 'nearest' });
1110
+ }, { injector: this.injector });
1111
+ }
1112
+ localNumber(value) {
1113
+ // El separador decimal del idioma, sin separador de miles: se edita como se escribe.
1114
+ const sep = new Intl.NumberFormat(this.t().intlLocale).formatToParts(1.5).find((p) => p.type === 'decimal')?.value ?? '.';
1115
+ return String(value).replace('.', sep);
1116
+ }
1117
+ say(message) {
1118
+ this.announcement.set('');
1119
+ queueMicrotask(() => this.announcement.set(message));
1120
+ }
1121
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: CbDataGrid, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1122
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: CbDataGrid, isStandalone: true, selector: "cb-data-grid", inputs: { rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, dataKey: { classPropertyName: "dataKey", publicName: "dataKey", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, rowNumbers: { classPropertyName: "rowNumbers", publicName: "rowNumbers", isSignal: true, isRequired: false, transformFunction: null }, toolbar: { classPropertyName: "toolbar", publicName: "toolbar", isSignal: true, isRequired: false, transformFunction: null }, scrollHeight: { classPropertyName: "scrollHeight", publicName: "scrollHeight", isSignal: true, isRequired: false, transformFunction: null }, newRow: { classPropertyName: "newRow", publicName: "newRow", 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 }, currency: { classPropertyName: "currency", publicName: "currency", isSignal: true, isRequired: false, transformFunction: null }, negativeStyle: { classPropertyName: "negativeStyle", publicName: "negativeStyle", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rows: "rowsChange", cellChange: "cellChange", rowAdd: "rowAdd", rowsDelete: "rowsDelete" }, host: { listeners: { "document:pointerup": "dragging = false" }, properties: { "class.cb-data-grid--readonly": "!editable()" }, classAttribute: "cb-data-grid" }, ngImport: i0, template: `
1123
+ @if (toolbar()) {
1124
+ <div class="cb-dg__toolbar" role="toolbar" [attr.aria-label]="ariaLabel() || t().dataGridToolbar" [attr.aria-controls]="uid">
1125
+ @if (editable()) {
1126
+ <button type="button" class="cb-dg__btn" (click)="addRow()">
1127
+ <cb-icon name="plus" [size]="16" aria-hidden="true" />
1128
+ {{ t().dataGridAddRow }}
1129
+ </button>
1130
+ <button type="button" class="cb-dg__btn" [disabled]="!view().length" (click)="deleteRows()">
1131
+ <cb-icon name="trash" [size]="16" aria-hidden="true" />
1132
+ {{ selectedRowCount() > 1 ? fmt(t().dataGridDeleteRows, selectedRowCount()) : t().dataGridDeleteRow }}
1133
+ </button>
1134
+ <span class="cb-dg__sep" aria-hidden="true"></span>
1135
+ <button type="button" class="cb-dg__icon-btn" [attr.aria-label]="t().undo" [disabled]="!canUndo()" (click)="undo()">
1136
+ <cb-icon name="undo" [size]="16" aria-hidden="true" />
1137
+ </button>
1138
+ <button type="button" class="cb-dg__icon-btn" [attr.aria-label]="t().redo" [disabled]="!canRedo()" (click)="redo()">
1139
+ <cb-icon name="redo" [size]="16" aria-hidden="true" />
1140
+ </button>
1141
+ }
1142
+ <span class="cb-dg__spacer"></span>
1143
+ @if (errors().length) {
1144
+ <button type="button" class="cb-dg__errors" (click)="goToFirstError()">
1145
+ <cb-icon name="circle-alert" [size]="16" aria-hidden="true" />
1146
+ {{ errors().length === 1 ? t().dataGridOneError : fmt(t().dataGridErrors, errors().length) }}
1147
+ </button>
1148
+ }
1149
+ @if (dirtyCount()) {
1150
+ <span class="cb-dg__dirty">{{ fmt(t().dataGridChanged, dirtyCount()) }}</span>
1151
+ }
1152
+ </div>
1153
+ }
1154
+
1155
+ <div class="cb-dg__scroll" [style.max-height]="scrollHeight()">
1156
+ <table
1157
+ class="cb-dg__table"
1158
+ role="grid"
1159
+ [id]="uid"
1160
+ [style.width]="tableWidth()"
1161
+ [attr.aria-label]="ariaLabel() || null"
1162
+ [attr.aria-rowcount]="view().length + 1"
1163
+ [attr.aria-colcount]="columns().length + (rowNumbers() ? 1 : 0)"
1164
+ [attr.aria-multiselectable]="true"
1165
+ [attr.aria-readonly]="!editable() || null"
1166
+ [attr.aria-describedby]="uid + '-help'"
1167
+ (keydown)="onKeydown($event)"
1168
+ (copy)="onCopy($event, false)"
1169
+ (cut)="onCopy($event, true)"
1170
+ (paste)="onPaste($event)"
1171
+ >
1172
+ <colgroup>
1173
+ @if (rowNumbers()) {
1174
+ <col style="width: 3rem" />
1175
+ }
1176
+ @for (col of columns(); track col.field) {
1177
+ <col [style.width]="widthOf(col)" />
1178
+ }
1179
+ </colgroup>
1180
+ <thead>
1181
+ <tr role="row" aria-rowindex="1">
1182
+ @if (rowNumbers()) {
1183
+ <th class="cb-dg__corner" scope="col">
1184
+ <button type="button" class="cb-dg__corner-btn" tabindex="-1" [attr.aria-label]="t().selectAll" (click)="selectAll()"></button>
1185
+ </th>
1186
+ }
1187
+ @for (col of columns(); track col.field; let c = $index) {
1188
+ <th
1189
+ class="cb-dg__head"
1190
+ scope="col"
1191
+ [class.cb-dg__head--active]="range().c1 <= c && c <= range().c2"
1192
+ [class]="'cb-dg__align--' + alignOf(col)"
1193
+ [attr.aria-sort]="sort()?.field === col.field ? (sort()!.order === 1 ? 'ascending' : 'descending') : null"
1194
+ >
1195
+ @if (col.sortable !== false) {
1196
+ <button type="button" class="cb-dg__sort" (click)="toggleSort(col)">
1197
+ <span class="cb-dg__head-text">{{ col.header }}@if (col.required) {<span class="cb-dg__req" aria-hidden="true"> *</span>}</span>
1198
+ <cb-icon
1199
+ class="cb-dg__sort-icon"
1200
+ [class.cb-dg__sort-icon--on]="sort()?.field === col.field"
1201
+ [name]="sort()?.field === col.field && sort()!.order === -1 ? 'arrow-down' : 'arrow-up'"
1202
+ [size]="14"
1203
+ aria-hidden="true"
1204
+ />
1205
+ </button>
1206
+ } @else {
1207
+ <span class="cb-dg__head-text">{{ col.header }}@if (col.required) {<span class="cb-dg__req" aria-hidden="true"> *</span>}</span>
1208
+ }
1209
+ </th>
1210
+ }
1211
+ </tr>
1212
+ </thead>
1213
+ <tbody>
1214
+ @for (row of view(); track keyOf(row); let r = $index) {
1215
+ <tr role="row" [attr.aria-rowindex]="r + 2" [class.cb-dg__row--active]="active().r === r">
1216
+ @if (rowNumbers()) {
1217
+ <th
1218
+ class="cb-dg__num"
1219
+ scope="row"
1220
+ [class.cb-dg__num--active]="range().r1 <= r && r <= range().r2"
1221
+ [class.cb-dg__num--new]="isNew(row)"
1222
+ (pointerdown)="selectRow(r, $event)"
1223
+ >
1224
+ {{ r + 1 }}
1225
+ </th>
1226
+ }
1227
+ @for (col of columns(); track col.field; let c = $index) {
1228
+ <td
1229
+ role="gridcell"
1230
+ class="cb-dg__cell"
1231
+ [id]="cellId(r, c)"
1232
+ [class]="'cb-dg__align--' + alignOf(col)"
1233
+ [class.cb-dg__cell--active]="active().r === r && active().c === c"
1234
+ [class.cb-dg__cell--selected]="isSelected(r, c)"
1235
+ [class.cb-dg__cell--readonly]="!canEdit(col)"
1236
+ [class.cb-dg__cell--dirty]="isDirty(row, col)"
1237
+ [class.cb-dg__cell--invalid]="!!errorOf(row, col)"
1238
+ [class.cb-dg__cell--editing]="editing()?.r === r && editing()?.c === c"
1239
+ [class.cb-dg__cell--negative]="isNegative(get(row, col.field), col)"
1240
+ [attr.tabindex]="active().r === r && active().c === c ? 0 : -1"
1241
+ [attr.aria-selected]="isSelected(r, c)"
1242
+ [attr.aria-readonly]="!canEdit(col) || null"
1243
+ [attr.aria-invalid]="errorOf(row, col) ? true : null"
1244
+ [attr.aria-colindex]="c + (rowNumbers() ? 2 : 1)"
1245
+ [attr.title]="errorOf(row, col)"
1246
+ (pointerdown)="onCellPointerDown($event, r, c)"
1247
+ (pointerenter)="onCellPointerEnter(r, c)"
1248
+ (dblclick)="startEdit(r, c)"
1249
+ >
1250
+ @if (editing()?.r === r && editing()?.c === c) {
1251
+ @switch (editorKind(col)) {
1252
+ @case ('autocomplete') {
1253
+ <input
1254
+ #acInput
1255
+ class="cb-dg__editor"
1256
+ type="text"
1257
+ role="combobox"
1258
+ autocomplete="off"
1259
+ aria-autocomplete="list"
1260
+ aria-expanded="true"
1261
+ [attr.aria-label]="col.header"
1262
+ [attr.aria-controls]="uid + '-list'"
1263
+ [attr.aria-activedescendant]="matches().length ? uid + '-opt-' + editing()!.active : null"
1264
+ [attr.aria-invalid]="editing()!.error ? true : null"
1265
+ [attr.aria-describedby]="editing()!.error ? uid + '-editor-error' : null"
1266
+ [value]="editing()!.text"
1267
+ (input)="editText($event)"
1268
+ (keydown)="onEditorKeydown($event)"
1269
+ (blur)="commit(null)"
1270
+ />
1271
+ <ng-template cdkConnectedOverlay [cdkConnectedOverlayOrigin]="acInput" [cdkConnectedOverlayOpen]="true" [cdkConnectedOverlayPositions]="listPositions" [cdkConnectedOverlayMinWidth]="acInput.offsetWidth">
1272
+ <ul class="cb-dg__list" role="listbox" [id]="uid + '-list'" [attr.aria-label]="col.header">
1273
+ @for (o of matches(); track $index; let i = $index) {
1274
+ <li
1275
+ class="cb-dg__option"
1276
+ role="option"
1277
+ [id]="uid + '-opt-' + i"
1278
+ [class.cb-dg__option--active]="i === editing()!.active"
1279
+ [attr.aria-selected]="i === editing()!.active"
1280
+ [attr.aria-disabled]="o.disabled || null"
1281
+ (mousedown)="$event.preventDefault()"
1282
+ (mouseenter)="setActive(i)"
1283
+ (click)="pick(o)"
1284
+ >
1285
+ <span class="cb-dg__option-label">{{ o.label }}</span>
1286
+ @if (o.description) {
1287
+ <span class="cb-dg__option-desc">{{ o.description }}</span>
1288
+ }
1289
+ </li>
1290
+ } @empty {
1291
+ <li class="cb-dg__option cb-dg__option--empty" role="option" aria-disabled="true">{{ t().noResults }}</li>
1292
+ }
1293
+ </ul>
1294
+ </ng-template>
1295
+ }
1296
+ @case ('select') {
1297
+ <select class="cb-dg__editor" [attr.aria-label]="col.header" (change)="editText($event)" (keydown)="onEditorKeydown($event)" (blur)="commit(null)">
1298
+ <option value="" [selected]="editing()!.text === ''"></option>
1299
+ @for (o of col.options ?? []; track $index) {
1300
+ <option [value]="o.label" [selected]="o.label === editing()!.text">{{ o.label }}</option>
1301
+ }
1302
+ </select>
1303
+ }
1304
+ @default {
1305
+ <input
1306
+ class="cb-dg__editor"
1307
+ [type]="editorKind(col) === 'date' ? 'date' : 'text'"
1308
+ [attr.inputmode]="col.type === 'number' ? 'decimal' : null"
1309
+ [attr.aria-label]="col.header"
1310
+ [attr.aria-invalid]="editing()!.error ? true : null"
1311
+ [attr.aria-describedby]="editing()!.error ? uid + '-editor-error' : null"
1312
+ [value]="editing()!.text"
1313
+ (input)="editText($event)"
1314
+ (keydown)="onEditorKeydown($event)"
1315
+ (blur)="commit(null)"
1316
+ />
1317
+ }
1318
+ }
1319
+ @if (editing()!.error) {
1320
+ <span class="cb-dg__editor-error" [id]="uid + '-editor-error'" role="alert">{{ editing()!.error }}</span>
1321
+ }
1322
+ } @else if (col.type === 'checkbox') {
1323
+ <span class="cb-dg__check" [class.cb-dg__check--on]="!!get(row, col.field)" aria-hidden="true" (click)="toggleCheck(r, c)">
1324
+ @if (get(row, col.field)) {
1325
+ <cb-icon name="check" [size]="12" />
1326
+ }
1327
+ </span>
1328
+ <span class="cb-dg__sr">{{ text(row, col) }}</span>
1329
+ } @else {
1330
+ <span class="cb-dg__text">{{ text(row, col) }}</span>
1331
+ @if ((col.type === 'select' || col.type === 'autocomplete') && canEdit(col)) {
1332
+ <cb-icon class="cb-dg__caret" name="chevron-down" [size]="14" aria-hidden="true" />
1333
+ }
1334
+ }
1335
+ @if (errorOf(row, col); as err) {
1336
+ @if (!(editing()?.r === r && editing()?.c === c)) {
1337
+ <span class="cb-dg__sr">. {{ err }}</span>
1338
+ }
1339
+ }
1340
+ </td>
1341
+ }
1342
+ </tr>
1343
+ } @empty {
1344
+ <tr>
1345
+ <td class="cb-dg__empty" [attr.colspan]="columns().length + (rowNumbers() ? 1 : 0)">{{ emptyMessage() || t().noData }}</td>
1346
+ </tr>
1347
+ }
1348
+ </tbody>
1349
+ @if (hasFooter() && view().length) {
1350
+ <tfoot>
1351
+ <tr class="cb-dg__foot">
1352
+ @if (rowNumbers()) {
1353
+ <td class="cb-dg__num cb-dg__num--foot"></td>
1354
+ }
1355
+ @for (col of columns(); track col.field; let c = $index) {
1356
+ @if (col.footer || (c === labelColumn() && !hasFooterText())) {
1357
+ <th scope="row" class="cb-dg__total" [class]="'cb-dg__align--' + alignOf(col)">{{ col.footer || t().tableTotal }}</th>
1358
+ } @else {
1359
+ <td class="cb-dg__total" [class]="'cb-dg__align--' + alignOf(col)" [class.cb-dg__cell--negative]="isNegative(totals()[col.field], col)">{{ totalText(col) }}</td>
1360
+ }
1361
+ }
1362
+ </tr>
1363
+ </tfoot>
1364
+ }
1365
+ </table>
1366
+ </div>
1367
+ <p class="cb-dg__sr" [id]="uid + '-help'">{{ editable() ? t().dataGridInstructions : t().dataGridReadonlyInstructions }}</p>
1368
+ <div class="cb-dg__sr" role="status">{{ announcement() }}</div>
1369
+ `, isInline: true, styles: [":host{--cb-dg-row: 2.25rem;--cb-dg-accent: var(--cb-color-primary-500);--cb-dg-selected: color-mix(in srgb, var(--cb-color-primary-500) 12%, transparent);--cb-dg-invalid: var(--cb-color-danger-500, #ef4444);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-dg__toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:.35rem}.cb-dg__btn,.cb-dg__icon-btn,.cb-dg__errors{display:inline-flex;align-items:center;justify-content:center;gap:.35rem;height:2rem;padding:0 .75rem;border:1px solid var(--cb-color-border-strong);border-radius:var(--cb-radius-md);background:transparent;color:var(--cb-color-text);font:inherit;cursor:pointer}.cb-dg__icon-btn{width:2rem;padding:0;border-color:transparent}.cb-dg__btn:hover:not(:disabled),.cb-dg__icon-btn:hover:not(:disabled){background:var(--cb-color-surface-hover)}.cb-dg__btn:disabled,.cb-dg__icon-btn:disabled{opacity:.45;cursor:default}.cb-dg__sep{width:1px;height:1.25rem;margin:0 .25rem;background:var(--cb-color-border)}.cb-dg__spacer{flex:1}.cb-dg__errors{border-color:color-mix(in srgb,var(--cb-dg-invalid) 45%,transparent);background:color-mix(in srgb,var(--cb-dg-invalid) 8%,transparent);color:var(--cb-color-danger-text, #dc2626)}.cb-dg__dirty{color:var(--cb-color-text-muted)}:is(.cb-dg__btn,.cb-dg__icon-btn,.cb-dg__errors,.cb-dg__sort):focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:2px}.cb-dg__scroll{position:relative;overflow:auto;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-md);background:var(--cb-color-bg);scroll-padding-top:var(--cb-dg-row);scroll-padding-left:3rem}.cb-dg__table{min-width:100%;table-layout:fixed;border-collapse:separate;border-spacing:0}.cb-dg__table th,.cb-dg__table td{height:var(--cb-dg-row);padding:0 .6rem;border-right:1px solid var(--cb-color-border);border-bottom:1px solid var(--cb-color-border);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cb-dg__table thead th{position:sticky;top:0;z-index:2;background:var(--cb-color-surface-subtle);font-weight:var(--cb-font-weight-semibold, 600);text-align:start}.cb-dg__head--active{box-shadow:inset 0 -2px 0 var(--cb-dg-accent)}.cb-dg__sort{display:flex;align-items:center;gap:.3rem;width:100%;height:100%;padding:0;border:0;background:none;color:inherit;font:inherit;text-align:inherit;cursor:pointer}.cb-dg__align--end .cb-dg__sort{flex-direction:row-reverse}.cb-dg__align--center .cb-dg__sort{justify-content:center}.cb-dg__head-text{min-width:0;overflow:hidden;text-overflow:ellipsis}.cb-dg__req{color:var(--cb-dg-invalid)}.cb-dg__sort-icon{flex:none;opacity:0;color:var(--cb-color-text-muted)}.cb-dg__sort:hover .cb-dg__sort-icon{opacity:.6}.cb-dg__sort-icon--on,.cb-dg__sort:hover .cb-dg__sort-icon--on{opacity:1;color:var(--cb-dg-accent)}.cb-dg__corner,.cb-dg__num{position:sticky;left:0;z-index:1;padding:0!important;background:var(--cb-color-surface-subtle);color:var(--cb-color-text-muted);font-size:var(--cb-text-xs, .75rem);font-weight:400;text-align:center!important}.cb-dg__corner{z-index:3!important}.cb-dg__corner-btn{display:block;width:100%;height:100%;border:0;background:none;cursor:pointer}.cb-dg__num{cursor:pointer;-webkit-user-select:none;user-select:none}.cb-dg__num--active{background:color-mix(in srgb,var(--cb-dg-accent) 14%,var(--cb-color-surface-subtle));color:var(--cb-color-text)}.cb-dg__num--new{box-shadow:inset 3px 0 0 var(--cb-color-success-500, #10b981)}.cb-dg__cell{position:relative;outline:none;background:var(--cb-color-bg);cursor:cell;-webkit-user-select:none;user-select:none}.cb-dg__align--end{text-align:end!important}.cb-dg__align--center{text-align:center!important}.cb-dg__cell--readonly{color:var(--cb-color-text-muted)}.cb-dg__cell--selected{background:var(--cb-dg-selected)}.cb-dg__cell--active{box-shadow:inset 0 0 0 2px var(--cb-dg-accent)}.cb-dg__table:not(:focus-within) .cb-dg__cell--active:not(.cb-dg__cell--editing){box-shadow:inset 0 0 0 1px var(--cb-color-border-strong)}.cb-dg__table:not(:focus-within) .cb-dg__cell--selected:not(.cb-dg__cell--invalid){background:color-mix(in srgb,var(--cb-color-primary-500) 6%,transparent)}.cb-dg__cell--negative{color:var(--cb-color-danger-text, #dc2626)}.cb-dg__table tfoot th,.cb-dg__table tfoot td{position:sticky;bottom:0;z-index:2;border-top:2px solid var(--cb-color-border-strong);border-bottom:0;background:var(--cb-color-surface-subtle);font-weight:var(--cb-font-weight-semibold, 600);font-variant-numeric:tabular-nums}.cb-dg__total{text-align:start}.cb-dg__table tfoot .cb-dg__num--foot{z-index:3}.cb-dg__list{max-height:16rem;margin:0;padding:.25rem;overflow:auto;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-md);background:var(--cb-color-surface-raised, var(--cb-color-bg));box-shadow:var(--cb-shadow-lg, 0 10px 24px rgb(0 0 0 / .14));list-style:none;color:var(--cb-color-text);font-family:var(--cb-font-sans);font-size:var(--cb-text-sm, .875rem)}.cb-dg__option{display:grid;gap:.1rem;padding:.4rem .6rem;border-radius:var(--cb-radius-sm, 4px);cursor:pointer}.cb-dg__option--active{background:color-mix(in srgb,var(--cb-color-primary-500) 12%,transparent)}.cb-dg__option[aria-disabled=true]{color:var(--cb-color-text-muted);cursor:default}.cb-dg__option-desc{color:var(--cb-color-text-muted);font-size:var(--cb-text-xs, .75rem)}.cb-dg__cell--dirty:before{position:absolute;top:0;left:0;border-top:7px solid var(--cb-dg-accent);border-right:7px solid transparent;content:\"\"}.cb-dg__cell--invalid{background:color-mix(in srgb,var(--cb-dg-invalid) 7%,var(--cb-color-bg))}.cb-dg__cell--invalid:after{position:absolute;top:0;right:0;border-top:8px solid var(--cb-dg-invalid);border-left:8px solid transparent;content:\"\"}.cb-dg__cell--selected.cb-dg__cell--invalid{background:color-mix(in srgb,var(--cb-dg-invalid) 7%,var(--cb-dg-selected))}.cb-dg__cell--editing{padding:0!important;overflow:visible!important;box-shadow:inset 0 0 0 2px var(--cb-dg-accent)}.cb-dg__caret{position:absolute;top:50%;right:.35rem;opacity:0;color:var(--cb-color-text-muted);transform:translateY(-50%)}.cb-dg__cell:hover .cb-dg__caret,.cb-dg__cell--active .cb-dg__caret{opacity:1}.cb-dg__check{display:inline-flex;align-items:center;justify-content:center;width:1rem;height:1rem;border:1.5px solid var(--cb-color-border-strong);border-radius:4px;background:var(--cb-color-bg);color:var(--cb-color-primary-contrast, #fff);vertical-align:middle}.cb-dg__check--on{border-color:var(--cb-dg-accent);background:var(--cb-dg-accent)}.cb-dg__editor{box-sizing:border-box;width:100%;height:var(--cb-dg-row);padding:0 .6rem;border:0;outline:none;background:var(--cb-color-bg);color:var(--cb-color-text);font:inherit;text-align:inherit}.cb-dg__editor[aria-invalid=true]{box-shadow:inset 0 0 0 2px var(--cb-dg-invalid)}.cb-dg__editor-error{position:absolute;top:100%;left:0;z-index:4;padding:.25rem .5rem;border-radius:var(--cb-radius-sm, 4px);background:var(--cb-dg-invalid);color:#fff;font-size:var(--cb-text-xs, .75rem);white-space:nowrap}.cb-dg__empty{height:6rem!important;color:var(--cb-color-text-muted);text-align:center}.cb-dg__sr{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap}@media(forced-colors:active){.cb-dg__cell--active,.cb-dg__cell--editing{outline:2px solid Highlight;outline-offset:-2px}.cb-dg__cell--selected{background:Highlight;color:HighlightText}.cb-dg__cell--invalid{text-decoration:wavy underline}}\n"], dependencies: [{ kind: "component", type: CbIcon, selector: "cb-icon", inputs: ["name", "size", "spin", "ariaLabel"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation", "cdkConnectedOverlayUsePopover", "cdkConnectedOverlayMatchWidth", "cdkConnectedOverlay"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1370
+ }
1371
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: CbDataGrid, decorators: [{
1372
+ type: Component,
1373
+ args: [{ selector: 'cb-data-grid', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.Emulated, imports: [CbIcon, OverlayModule], host: {
1374
+ class: 'cb-data-grid',
1375
+ '[class.cb-data-grid--readonly]': '!editable()',
1376
+ '(document:pointerup)': 'dragging = false',
1377
+ }, template: `
1378
+ @if (toolbar()) {
1379
+ <div class="cb-dg__toolbar" role="toolbar" [attr.aria-label]="ariaLabel() || t().dataGridToolbar" [attr.aria-controls]="uid">
1380
+ @if (editable()) {
1381
+ <button type="button" class="cb-dg__btn" (click)="addRow()">
1382
+ <cb-icon name="plus" [size]="16" aria-hidden="true" />
1383
+ {{ t().dataGridAddRow }}
1384
+ </button>
1385
+ <button type="button" class="cb-dg__btn" [disabled]="!view().length" (click)="deleteRows()">
1386
+ <cb-icon name="trash" [size]="16" aria-hidden="true" />
1387
+ {{ selectedRowCount() > 1 ? fmt(t().dataGridDeleteRows, selectedRowCount()) : t().dataGridDeleteRow }}
1388
+ </button>
1389
+ <span class="cb-dg__sep" aria-hidden="true"></span>
1390
+ <button type="button" class="cb-dg__icon-btn" [attr.aria-label]="t().undo" [disabled]="!canUndo()" (click)="undo()">
1391
+ <cb-icon name="undo" [size]="16" aria-hidden="true" />
1392
+ </button>
1393
+ <button type="button" class="cb-dg__icon-btn" [attr.aria-label]="t().redo" [disabled]="!canRedo()" (click)="redo()">
1394
+ <cb-icon name="redo" [size]="16" aria-hidden="true" />
1395
+ </button>
1396
+ }
1397
+ <span class="cb-dg__spacer"></span>
1398
+ @if (errors().length) {
1399
+ <button type="button" class="cb-dg__errors" (click)="goToFirstError()">
1400
+ <cb-icon name="circle-alert" [size]="16" aria-hidden="true" />
1401
+ {{ errors().length === 1 ? t().dataGridOneError : fmt(t().dataGridErrors, errors().length) }}
1402
+ </button>
1403
+ }
1404
+ @if (dirtyCount()) {
1405
+ <span class="cb-dg__dirty">{{ fmt(t().dataGridChanged, dirtyCount()) }}</span>
1406
+ }
1407
+ </div>
1408
+ }
1409
+
1410
+ <div class="cb-dg__scroll" [style.max-height]="scrollHeight()">
1411
+ <table
1412
+ class="cb-dg__table"
1413
+ role="grid"
1414
+ [id]="uid"
1415
+ [style.width]="tableWidth()"
1416
+ [attr.aria-label]="ariaLabel() || null"
1417
+ [attr.aria-rowcount]="view().length + 1"
1418
+ [attr.aria-colcount]="columns().length + (rowNumbers() ? 1 : 0)"
1419
+ [attr.aria-multiselectable]="true"
1420
+ [attr.aria-readonly]="!editable() || null"
1421
+ [attr.aria-describedby]="uid + '-help'"
1422
+ (keydown)="onKeydown($event)"
1423
+ (copy)="onCopy($event, false)"
1424
+ (cut)="onCopy($event, true)"
1425
+ (paste)="onPaste($event)"
1426
+ >
1427
+ <colgroup>
1428
+ @if (rowNumbers()) {
1429
+ <col style="width: 3rem" />
1430
+ }
1431
+ @for (col of columns(); track col.field) {
1432
+ <col [style.width]="widthOf(col)" />
1433
+ }
1434
+ </colgroup>
1435
+ <thead>
1436
+ <tr role="row" aria-rowindex="1">
1437
+ @if (rowNumbers()) {
1438
+ <th class="cb-dg__corner" scope="col">
1439
+ <button type="button" class="cb-dg__corner-btn" tabindex="-1" [attr.aria-label]="t().selectAll" (click)="selectAll()"></button>
1440
+ </th>
1441
+ }
1442
+ @for (col of columns(); track col.field; let c = $index) {
1443
+ <th
1444
+ class="cb-dg__head"
1445
+ scope="col"
1446
+ [class.cb-dg__head--active]="range().c1 <= c && c <= range().c2"
1447
+ [class]="'cb-dg__align--' + alignOf(col)"
1448
+ [attr.aria-sort]="sort()?.field === col.field ? (sort()!.order === 1 ? 'ascending' : 'descending') : null"
1449
+ >
1450
+ @if (col.sortable !== false) {
1451
+ <button type="button" class="cb-dg__sort" (click)="toggleSort(col)">
1452
+ <span class="cb-dg__head-text">{{ col.header }}@if (col.required) {<span class="cb-dg__req" aria-hidden="true"> *</span>}</span>
1453
+ <cb-icon
1454
+ class="cb-dg__sort-icon"
1455
+ [class.cb-dg__sort-icon--on]="sort()?.field === col.field"
1456
+ [name]="sort()?.field === col.field && sort()!.order === -1 ? 'arrow-down' : 'arrow-up'"
1457
+ [size]="14"
1458
+ aria-hidden="true"
1459
+ />
1460
+ </button>
1461
+ } @else {
1462
+ <span class="cb-dg__head-text">{{ col.header }}@if (col.required) {<span class="cb-dg__req" aria-hidden="true"> *</span>}</span>
1463
+ }
1464
+ </th>
1465
+ }
1466
+ </tr>
1467
+ </thead>
1468
+ <tbody>
1469
+ @for (row of view(); track keyOf(row); let r = $index) {
1470
+ <tr role="row" [attr.aria-rowindex]="r + 2" [class.cb-dg__row--active]="active().r === r">
1471
+ @if (rowNumbers()) {
1472
+ <th
1473
+ class="cb-dg__num"
1474
+ scope="row"
1475
+ [class.cb-dg__num--active]="range().r1 <= r && r <= range().r2"
1476
+ [class.cb-dg__num--new]="isNew(row)"
1477
+ (pointerdown)="selectRow(r, $event)"
1478
+ >
1479
+ {{ r + 1 }}
1480
+ </th>
1481
+ }
1482
+ @for (col of columns(); track col.field; let c = $index) {
1483
+ <td
1484
+ role="gridcell"
1485
+ class="cb-dg__cell"
1486
+ [id]="cellId(r, c)"
1487
+ [class]="'cb-dg__align--' + alignOf(col)"
1488
+ [class.cb-dg__cell--active]="active().r === r && active().c === c"
1489
+ [class.cb-dg__cell--selected]="isSelected(r, c)"
1490
+ [class.cb-dg__cell--readonly]="!canEdit(col)"
1491
+ [class.cb-dg__cell--dirty]="isDirty(row, col)"
1492
+ [class.cb-dg__cell--invalid]="!!errorOf(row, col)"
1493
+ [class.cb-dg__cell--editing]="editing()?.r === r && editing()?.c === c"
1494
+ [class.cb-dg__cell--negative]="isNegative(get(row, col.field), col)"
1495
+ [attr.tabindex]="active().r === r && active().c === c ? 0 : -1"
1496
+ [attr.aria-selected]="isSelected(r, c)"
1497
+ [attr.aria-readonly]="!canEdit(col) || null"
1498
+ [attr.aria-invalid]="errorOf(row, col) ? true : null"
1499
+ [attr.aria-colindex]="c + (rowNumbers() ? 2 : 1)"
1500
+ [attr.title]="errorOf(row, col)"
1501
+ (pointerdown)="onCellPointerDown($event, r, c)"
1502
+ (pointerenter)="onCellPointerEnter(r, c)"
1503
+ (dblclick)="startEdit(r, c)"
1504
+ >
1505
+ @if (editing()?.r === r && editing()?.c === c) {
1506
+ @switch (editorKind(col)) {
1507
+ @case ('autocomplete') {
1508
+ <input
1509
+ #acInput
1510
+ class="cb-dg__editor"
1511
+ type="text"
1512
+ role="combobox"
1513
+ autocomplete="off"
1514
+ aria-autocomplete="list"
1515
+ aria-expanded="true"
1516
+ [attr.aria-label]="col.header"
1517
+ [attr.aria-controls]="uid + '-list'"
1518
+ [attr.aria-activedescendant]="matches().length ? uid + '-opt-' + editing()!.active : null"
1519
+ [attr.aria-invalid]="editing()!.error ? true : null"
1520
+ [attr.aria-describedby]="editing()!.error ? uid + '-editor-error' : null"
1521
+ [value]="editing()!.text"
1522
+ (input)="editText($event)"
1523
+ (keydown)="onEditorKeydown($event)"
1524
+ (blur)="commit(null)"
1525
+ />
1526
+ <ng-template cdkConnectedOverlay [cdkConnectedOverlayOrigin]="acInput" [cdkConnectedOverlayOpen]="true" [cdkConnectedOverlayPositions]="listPositions" [cdkConnectedOverlayMinWidth]="acInput.offsetWidth">
1527
+ <ul class="cb-dg__list" role="listbox" [id]="uid + '-list'" [attr.aria-label]="col.header">
1528
+ @for (o of matches(); track $index; let i = $index) {
1529
+ <li
1530
+ class="cb-dg__option"
1531
+ role="option"
1532
+ [id]="uid + '-opt-' + i"
1533
+ [class.cb-dg__option--active]="i === editing()!.active"
1534
+ [attr.aria-selected]="i === editing()!.active"
1535
+ [attr.aria-disabled]="o.disabled || null"
1536
+ (mousedown)="$event.preventDefault()"
1537
+ (mouseenter)="setActive(i)"
1538
+ (click)="pick(o)"
1539
+ >
1540
+ <span class="cb-dg__option-label">{{ o.label }}</span>
1541
+ @if (o.description) {
1542
+ <span class="cb-dg__option-desc">{{ o.description }}</span>
1543
+ }
1544
+ </li>
1545
+ } @empty {
1546
+ <li class="cb-dg__option cb-dg__option--empty" role="option" aria-disabled="true">{{ t().noResults }}</li>
1547
+ }
1548
+ </ul>
1549
+ </ng-template>
1550
+ }
1551
+ @case ('select') {
1552
+ <select class="cb-dg__editor" [attr.aria-label]="col.header" (change)="editText($event)" (keydown)="onEditorKeydown($event)" (blur)="commit(null)">
1553
+ <option value="" [selected]="editing()!.text === ''"></option>
1554
+ @for (o of col.options ?? []; track $index) {
1555
+ <option [value]="o.label" [selected]="o.label === editing()!.text">{{ o.label }}</option>
1556
+ }
1557
+ </select>
1558
+ }
1559
+ @default {
1560
+ <input
1561
+ class="cb-dg__editor"
1562
+ [type]="editorKind(col) === 'date' ? 'date' : 'text'"
1563
+ [attr.inputmode]="col.type === 'number' ? 'decimal' : null"
1564
+ [attr.aria-label]="col.header"
1565
+ [attr.aria-invalid]="editing()!.error ? true : null"
1566
+ [attr.aria-describedby]="editing()!.error ? uid + '-editor-error' : null"
1567
+ [value]="editing()!.text"
1568
+ (input)="editText($event)"
1569
+ (keydown)="onEditorKeydown($event)"
1570
+ (blur)="commit(null)"
1571
+ />
1572
+ }
1573
+ }
1574
+ @if (editing()!.error) {
1575
+ <span class="cb-dg__editor-error" [id]="uid + '-editor-error'" role="alert">{{ editing()!.error }}</span>
1576
+ }
1577
+ } @else if (col.type === 'checkbox') {
1578
+ <span class="cb-dg__check" [class.cb-dg__check--on]="!!get(row, col.field)" aria-hidden="true" (click)="toggleCheck(r, c)">
1579
+ @if (get(row, col.field)) {
1580
+ <cb-icon name="check" [size]="12" />
1581
+ }
1582
+ </span>
1583
+ <span class="cb-dg__sr">{{ text(row, col) }}</span>
1584
+ } @else {
1585
+ <span class="cb-dg__text">{{ text(row, col) }}</span>
1586
+ @if ((col.type === 'select' || col.type === 'autocomplete') && canEdit(col)) {
1587
+ <cb-icon class="cb-dg__caret" name="chevron-down" [size]="14" aria-hidden="true" />
1588
+ }
1589
+ }
1590
+ @if (errorOf(row, col); as err) {
1591
+ @if (!(editing()?.r === r && editing()?.c === c)) {
1592
+ <span class="cb-dg__sr">. {{ err }}</span>
1593
+ }
1594
+ }
1595
+ </td>
1596
+ }
1597
+ </tr>
1598
+ } @empty {
1599
+ <tr>
1600
+ <td class="cb-dg__empty" [attr.colspan]="columns().length + (rowNumbers() ? 1 : 0)">{{ emptyMessage() || t().noData }}</td>
1601
+ </tr>
1602
+ }
1603
+ </tbody>
1604
+ @if (hasFooter() && view().length) {
1605
+ <tfoot>
1606
+ <tr class="cb-dg__foot">
1607
+ @if (rowNumbers()) {
1608
+ <td class="cb-dg__num cb-dg__num--foot"></td>
1609
+ }
1610
+ @for (col of columns(); track col.field; let c = $index) {
1611
+ @if (col.footer || (c === labelColumn() && !hasFooterText())) {
1612
+ <th scope="row" class="cb-dg__total" [class]="'cb-dg__align--' + alignOf(col)">{{ col.footer || t().tableTotal }}</th>
1613
+ } @else {
1614
+ <td class="cb-dg__total" [class]="'cb-dg__align--' + alignOf(col)" [class.cb-dg__cell--negative]="isNegative(totals()[col.field], col)">{{ totalText(col) }}</td>
1615
+ }
1616
+ }
1617
+ </tr>
1618
+ </tfoot>
1619
+ }
1620
+ </table>
1621
+ </div>
1622
+ <p class="cb-dg__sr" [id]="uid + '-help'">{{ editable() ? t().dataGridInstructions : t().dataGridReadonlyInstructions }}</p>
1623
+ <div class="cb-dg__sr" role="status">{{ announcement() }}</div>
1624
+ `, styles: [":host{--cb-dg-row: 2.25rem;--cb-dg-accent: var(--cb-color-primary-500);--cb-dg-selected: color-mix(in srgb, var(--cb-color-primary-500) 12%, transparent);--cb-dg-invalid: var(--cb-color-danger-500, #ef4444);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-dg__toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:.35rem}.cb-dg__btn,.cb-dg__icon-btn,.cb-dg__errors{display:inline-flex;align-items:center;justify-content:center;gap:.35rem;height:2rem;padding:0 .75rem;border:1px solid var(--cb-color-border-strong);border-radius:var(--cb-radius-md);background:transparent;color:var(--cb-color-text);font:inherit;cursor:pointer}.cb-dg__icon-btn{width:2rem;padding:0;border-color:transparent}.cb-dg__btn:hover:not(:disabled),.cb-dg__icon-btn:hover:not(:disabled){background:var(--cb-color-surface-hover)}.cb-dg__btn:disabled,.cb-dg__icon-btn:disabled{opacity:.45;cursor:default}.cb-dg__sep{width:1px;height:1.25rem;margin:0 .25rem;background:var(--cb-color-border)}.cb-dg__spacer{flex:1}.cb-dg__errors{border-color:color-mix(in srgb,var(--cb-dg-invalid) 45%,transparent);background:color-mix(in srgb,var(--cb-dg-invalid) 8%,transparent);color:var(--cb-color-danger-text, #dc2626)}.cb-dg__dirty{color:var(--cb-color-text-muted)}:is(.cb-dg__btn,.cb-dg__icon-btn,.cb-dg__errors,.cb-dg__sort):focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:2px}.cb-dg__scroll{position:relative;overflow:auto;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-md);background:var(--cb-color-bg);scroll-padding-top:var(--cb-dg-row);scroll-padding-left:3rem}.cb-dg__table{min-width:100%;table-layout:fixed;border-collapse:separate;border-spacing:0}.cb-dg__table th,.cb-dg__table td{height:var(--cb-dg-row);padding:0 .6rem;border-right:1px solid var(--cb-color-border);border-bottom:1px solid var(--cb-color-border);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cb-dg__table thead th{position:sticky;top:0;z-index:2;background:var(--cb-color-surface-subtle);font-weight:var(--cb-font-weight-semibold, 600);text-align:start}.cb-dg__head--active{box-shadow:inset 0 -2px 0 var(--cb-dg-accent)}.cb-dg__sort{display:flex;align-items:center;gap:.3rem;width:100%;height:100%;padding:0;border:0;background:none;color:inherit;font:inherit;text-align:inherit;cursor:pointer}.cb-dg__align--end .cb-dg__sort{flex-direction:row-reverse}.cb-dg__align--center .cb-dg__sort{justify-content:center}.cb-dg__head-text{min-width:0;overflow:hidden;text-overflow:ellipsis}.cb-dg__req{color:var(--cb-dg-invalid)}.cb-dg__sort-icon{flex:none;opacity:0;color:var(--cb-color-text-muted)}.cb-dg__sort:hover .cb-dg__sort-icon{opacity:.6}.cb-dg__sort-icon--on,.cb-dg__sort:hover .cb-dg__sort-icon--on{opacity:1;color:var(--cb-dg-accent)}.cb-dg__corner,.cb-dg__num{position:sticky;left:0;z-index:1;padding:0!important;background:var(--cb-color-surface-subtle);color:var(--cb-color-text-muted);font-size:var(--cb-text-xs, .75rem);font-weight:400;text-align:center!important}.cb-dg__corner{z-index:3!important}.cb-dg__corner-btn{display:block;width:100%;height:100%;border:0;background:none;cursor:pointer}.cb-dg__num{cursor:pointer;-webkit-user-select:none;user-select:none}.cb-dg__num--active{background:color-mix(in srgb,var(--cb-dg-accent) 14%,var(--cb-color-surface-subtle));color:var(--cb-color-text)}.cb-dg__num--new{box-shadow:inset 3px 0 0 var(--cb-color-success-500, #10b981)}.cb-dg__cell{position:relative;outline:none;background:var(--cb-color-bg);cursor:cell;-webkit-user-select:none;user-select:none}.cb-dg__align--end{text-align:end!important}.cb-dg__align--center{text-align:center!important}.cb-dg__cell--readonly{color:var(--cb-color-text-muted)}.cb-dg__cell--selected{background:var(--cb-dg-selected)}.cb-dg__cell--active{box-shadow:inset 0 0 0 2px var(--cb-dg-accent)}.cb-dg__table:not(:focus-within) .cb-dg__cell--active:not(.cb-dg__cell--editing){box-shadow:inset 0 0 0 1px var(--cb-color-border-strong)}.cb-dg__table:not(:focus-within) .cb-dg__cell--selected:not(.cb-dg__cell--invalid){background:color-mix(in srgb,var(--cb-color-primary-500) 6%,transparent)}.cb-dg__cell--negative{color:var(--cb-color-danger-text, #dc2626)}.cb-dg__table tfoot th,.cb-dg__table tfoot td{position:sticky;bottom:0;z-index:2;border-top:2px solid var(--cb-color-border-strong);border-bottom:0;background:var(--cb-color-surface-subtle);font-weight:var(--cb-font-weight-semibold, 600);font-variant-numeric:tabular-nums}.cb-dg__total{text-align:start}.cb-dg__table tfoot .cb-dg__num--foot{z-index:3}.cb-dg__list{max-height:16rem;margin:0;padding:.25rem;overflow:auto;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-md);background:var(--cb-color-surface-raised, var(--cb-color-bg));box-shadow:var(--cb-shadow-lg, 0 10px 24px rgb(0 0 0 / .14));list-style:none;color:var(--cb-color-text);font-family:var(--cb-font-sans);font-size:var(--cb-text-sm, .875rem)}.cb-dg__option{display:grid;gap:.1rem;padding:.4rem .6rem;border-radius:var(--cb-radius-sm, 4px);cursor:pointer}.cb-dg__option--active{background:color-mix(in srgb,var(--cb-color-primary-500) 12%,transparent)}.cb-dg__option[aria-disabled=true]{color:var(--cb-color-text-muted);cursor:default}.cb-dg__option-desc{color:var(--cb-color-text-muted);font-size:var(--cb-text-xs, .75rem)}.cb-dg__cell--dirty:before{position:absolute;top:0;left:0;border-top:7px solid var(--cb-dg-accent);border-right:7px solid transparent;content:\"\"}.cb-dg__cell--invalid{background:color-mix(in srgb,var(--cb-dg-invalid) 7%,var(--cb-color-bg))}.cb-dg__cell--invalid:after{position:absolute;top:0;right:0;border-top:8px solid var(--cb-dg-invalid);border-left:8px solid transparent;content:\"\"}.cb-dg__cell--selected.cb-dg__cell--invalid{background:color-mix(in srgb,var(--cb-dg-invalid) 7%,var(--cb-dg-selected))}.cb-dg__cell--editing{padding:0!important;overflow:visible!important;box-shadow:inset 0 0 0 2px var(--cb-dg-accent)}.cb-dg__caret{position:absolute;top:50%;right:.35rem;opacity:0;color:var(--cb-color-text-muted);transform:translateY(-50%)}.cb-dg__cell:hover .cb-dg__caret,.cb-dg__cell--active .cb-dg__caret{opacity:1}.cb-dg__check{display:inline-flex;align-items:center;justify-content:center;width:1rem;height:1rem;border:1.5px solid var(--cb-color-border-strong);border-radius:4px;background:var(--cb-color-bg);color:var(--cb-color-primary-contrast, #fff);vertical-align:middle}.cb-dg__check--on{border-color:var(--cb-dg-accent);background:var(--cb-dg-accent)}.cb-dg__editor{box-sizing:border-box;width:100%;height:var(--cb-dg-row);padding:0 .6rem;border:0;outline:none;background:var(--cb-color-bg);color:var(--cb-color-text);font:inherit;text-align:inherit}.cb-dg__editor[aria-invalid=true]{box-shadow:inset 0 0 0 2px var(--cb-dg-invalid)}.cb-dg__editor-error{position:absolute;top:100%;left:0;z-index:4;padding:.25rem .5rem;border-radius:var(--cb-radius-sm, 4px);background:var(--cb-dg-invalid);color:#fff;font-size:var(--cb-text-xs, .75rem);white-space:nowrap}.cb-dg__empty{height:6rem!important;color:var(--cb-color-text-muted);text-align:center}.cb-dg__sr{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap}@media(forced-colors:active){.cb-dg__cell--active,.cb-dg__cell--editing{outline:2px solid Highlight;outline-offset:-2px}.cb-dg__cell--selected{background:Highlight;color:HighlightText}.cb-dg__cell--invalid{text-decoration:wavy underline}}\n"] }]
1625
+ }], ctorParameters: () => [], propDecorators: { rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }, { type: i0.Output, args: ["rowsChange"] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], dataKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataKey", required: false }] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }], rowNumbers: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowNumbers", required: false }] }], toolbar: [{ type: i0.Input, args: [{ isSignal: true, alias: "toolbar", required: false }] }], scrollHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollHeight", required: false }] }], newRow: [{ type: i0.Input, args: [{ isSignal: true, alias: "newRow", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], currency: [{ type: i0.Input, args: [{ isSignal: true, alias: "currency", required: false }] }], negativeStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "negativeStyle", required: false }] }], cellChange: [{ type: i0.Output, args: ["cellChange"] }], rowAdd: [{ type: i0.Output, args: ["rowAdd"] }], rowsDelete: [{ type: i0.Output, args: ["rowsDelete"] }] } });
1626
+
1627
+ /*
1628
+ * Public API Surface de @caribeng/ui/datagrid
1629
+ */
1630
+
1631
+ /**
1632
+ * Generated bundle index. Do not edit.
1633
+ */
1634
+
1635
+ export { CbDataGrid };