@neural-ui/core 1.2.1 → 1.3.1

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 (239) hide show
  1. package/README.md +56 -88
  2. package/accordion/package.json +4 -0
  3. package/alert/package.json +4 -0
  4. package/autocomplete/package.json +4 -0
  5. package/avatar/package.json +4 -0
  6. package/badge/package.json +4 -0
  7. package/block-ui/package.json +4 -0
  8. package/breadcrumb/package.json +4 -0
  9. package/button/package.json +4 -0
  10. package/card/package.json +4 -0
  11. package/chart/package.json +4 -0
  12. package/checkbox/package.json +4 -0
  13. package/chip/package.json +4 -0
  14. package/code-block/package.json +4 -0
  15. package/color-picker/package.json +4 -0
  16. package/command-palette/package.json +4 -0
  17. package/confirm-dialog/package.json +4 -0
  18. package/context-menu/package.json +4 -0
  19. package/dashboard-grid/package.json +4 -0
  20. package/date-input/package.json +4 -0
  21. package/divider/package.json +4 -0
  22. package/empty-state/package.json +4 -0
  23. package/fesm2022/neural-ui-core-accordion.mjs +162 -0
  24. package/fesm2022/neural-ui-core-accordion.mjs.map +1 -0
  25. package/fesm2022/neural-ui-core-alert.mjs +116 -0
  26. package/fesm2022/neural-ui-core-alert.mjs.map +1 -0
  27. package/fesm2022/neural-ui-core-autocomplete.mjs +406 -0
  28. package/fesm2022/neural-ui-core-autocomplete.mjs.map +1 -0
  29. package/fesm2022/neural-ui-core-avatar.mjs +109 -0
  30. package/fesm2022/neural-ui-core-avatar.mjs.map +1 -0
  31. package/fesm2022/neural-ui-core-badge.mjs +54 -0
  32. package/fesm2022/neural-ui-core-badge.mjs.map +1 -0
  33. package/fesm2022/neural-ui-core-block-ui.mjs +95 -0
  34. package/fesm2022/neural-ui-core-block-ui.mjs.map +1 -0
  35. package/fesm2022/neural-ui-core-breadcrumb.mjs +84 -0
  36. package/fesm2022/neural-ui-core-breadcrumb.mjs.map +1 -0
  37. package/fesm2022/neural-ui-core-button.mjs +125 -0
  38. package/fesm2022/neural-ui-core-button.mjs.map +1 -0
  39. package/fesm2022/neural-ui-core-card.mjs +69 -0
  40. package/fesm2022/neural-ui-core-card.mjs.map +1 -0
  41. package/fesm2022/neural-ui-core-chart.mjs +287 -0
  42. package/fesm2022/neural-ui-core-chart.mjs.map +1 -0
  43. package/fesm2022/neural-ui-core-checkbox.mjs +138 -0
  44. package/fesm2022/neural-ui-core-checkbox.mjs.map +1 -0
  45. package/fesm2022/neural-ui-core-chip.mjs +130 -0
  46. package/fesm2022/neural-ui-core-chip.mjs.map +1 -0
  47. package/fesm2022/neural-ui-core-code-block.mjs +250 -0
  48. package/fesm2022/neural-ui-core-code-block.mjs.map +1 -0
  49. package/fesm2022/neural-ui-core-color-picker.mjs +435 -0
  50. package/fesm2022/neural-ui-core-color-picker.mjs.map +1 -0
  51. package/fesm2022/neural-ui-core-command-palette.mjs +235 -0
  52. package/fesm2022/neural-ui-core-command-palette.mjs.map +1 -0
  53. package/fesm2022/neural-ui-core-confirm-dialog.mjs +118 -0
  54. package/fesm2022/neural-ui-core-confirm-dialog.mjs.map +1 -0
  55. package/fesm2022/neural-ui-core-context-menu.mjs +158 -0
  56. package/fesm2022/neural-ui-core-context-menu.mjs.map +1 -0
  57. package/fesm2022/neural-ui-core-dashboard-grid.mjs +144 -0
  58. package/fesm2022/neural-ui-core-dashboard-grid.mjs.map +1 -0
  59. package/fesm2022/neural-ui-core-date-input.mjs +1332 -0
  60. package/fesm2022/neural-ui-core-date-input.mjs.map +1 -0
  61. package/fesm2022/neural-ui-core-divider.mjs +54 -0
  62. package/fesm2022/neural-ui-core-divider.mjs.map +1 -0
  63. package/fesm2022/neural-ui-core-empty-state.mjs +84 -0
  64. package/fesm2022/neural-ui-core-empty-state.mjs.map +1 -0
  65. package/fesm2022/neural-ui-core-filter-bar.mjs +118 -0
  66. package/fesm2022/neural-ui-core-filter-bar.mjs.map +1 -0
  67. package/fesm2022/neural-ui-core-icon.mjs +50 -0
  68. package/fesm2022/neural-ui-core-icon.mjs.map +1 -0
  69. package/fesm2022/neural-ui-core-image-viewer.mjs +309 -0
  70. package/fesm2022/neural-ui-core-image-viewer.mjs.map +1 -0
  71. package/fesm2022/neural-ui-core-input-otp.mjs +192 -0
  72. package/fesm2022/neural-ui-core-input-otp.mjs.map +1 -0
  73. package/fesm2022/neural-ui-core-input.mjs +320 -0
  74. package/fesm2022/neural-ui-core-input.mjs.map +1 -0
  75. package/fesm2022/neural-ui-core-knob.mjs +323 -0
  76. package/fesm2022/neural-ui-core-knob.mjs.map +1 -0
  77. package/fesm2022/neural-ui-core-meter-group.mjs +122 -0
  78. package/fesm2022/neural-ui-core-meter-group.mjs.map +1 -0
  79. package/fesm2022/neural-ui-core-modal.mjs +156 -0
  80. package/fesm2022/neural-ui-core-modal.mjs.map +1 -0
  81. package/fesm2022/neural-ui-core-multiselect.mjs +825 -0
  82. package/fesm2022/neural-ui-core-multiselect.mjs.map +1 -0
  83. package/fesm2022/neural-ui-core-nav.mjs +952 -0
  84. package/fesm2022/neural-ui-core-nav.mjs.map +1 -0
  85. package/fesm2022/neural-ui-core-notification-center.mjs +264 -0
  86. package/fesm2022/neural-ui-core-notification-center.mjs.map +1 -0
  87. package/fesm2022/neural-ui-core-number-input.mjs +331 -0
  88. package/fesm2022/neural-ui-core-number-input.mjs.map +1 -0
  89. package/fesm2022/neural-ui-core-pagination.mjs +198 -0
  90. package/fesm2022/neural-ui-core-pagination.mjs.map +1 -0
  91. package/fesm2022/neural-ui-core-popover.mjs +207 -0
  92. package/fesm2022/neural-ui-core-popover.mjs.map +1 -0
  93. package/fesm2022/neural-ui-core-progress-bar.mjs +105 -0
  94. package/fesm2022/neural-ui-core-progress-bar.mjs.map +1 -0
  95. package/fesm2022/neural-ui-core-radio.mjs +171 -0
  96. package/fesm2022/neural-ui-core-radio.mjs.map +1 -0
  97. package/fesm2022/neural-ui-core-rating.mjs +151 -0
  98. package/fesm2022/neural-ui-core-rating.mjs.map +1 -0
  99. package/fesm2022/neural-ui-core-select.mjs +710 -0
  100. package/fesm2022/neural-ui-core-select.mjs.map +1 -0
  101. package/fesm2022/neural-ui-core-sidebar.mjs +214 -0
  102. package/fesm2022/neural-ui-core-sidebar.mjs.map +1 -0
  103. package/fesm2022/neural-ui-core-skeleton.mjs +40 -0
  104. package/fesm2022/neural-ui-core-skeleton.mjs.map +1 -0
  105. package/fesm2022/neural-ui-core-slider.mjs +146 -0
  106. package/fesm2022/neural-ui-core-slider.mjs.map +1 -0
  107. package/fesm2022/neural-ui-core-spinner.mjs +113 -0
  108. package/fesm2022/neural-ui-core-spinner.mjs.map +1 -0
  109. package/fesm2022/neural-ui-core-split-button.mjs +252 -0
  110. package/fesm2022/neural-ui-core-split-button.mjs.map +1 -0
  111. package/fesm2022/neural-ui-core-splitter.mjs +174 -0
  112. package/fesm2022/neural-ui-core-splitter.mjs.map +1 -0
  113. package/fesm2022/neural-ui-core-stats-card.mjs +163 -0
  114. package/fesm2022/neural-ui-core-stats-card.mjs.map +1 -0
  115. package/fesm2022/neural-ui-core-stepper.mjs +204 -0
  116. package/fesm2022/neural-ui-core-stepper.mjs.map +1 -0
  117. package/fesm2022/neural-ui-core-switch.mjs +111 -0
  118. package/fesm2022/neural-ui-core-switch.mjs.map +1 -0
  119. package/fesm2022/neural-ui-core-table.mjs +1872 -0
  120. package/fesm2022/neural-ui-core-table.mjs.map +1 -0
  121. package/fesm2022/neural-ui-core-tabs.mjs +338 -0
  122. package/fesm2022/neural-ui-core-tabs.mjs.map +1 -0
  123. package/fesm2022/neural-ui-core-textarea.mjs +188 -0
  124. package/fesm2022/neural-ui-core-textarea.mjs.map +1 -0
  125. package/fesm2022/neural-ui-core-timeline.mjs +117 -0
  126. package/fesm2022/neural-ui-core-timeline.mjs.map +1 -0
  127. package/fesm2022/neural-ui-core-toast.mjs +171 -0
  128. package/fesm2022/neural-ui-core-toast.mjs.map +1 -0
  129. package/fesm2022/neural-ui-core-toggle-button-group.mjs +162 -0
  130. package/fesm2022/neural-ui-core-toggle-button-group.mjs.map +1 -0
  131. package/fesm2022/neural-ui-core-toolbar.mjs +67 -0
  132. package/fesm2022/neural-ui-core-toolbar.mjs.map +1 -0
  133. package/fesm2022/neural-ui-core-tooltip.mjs +151 -0
  134. package/fesm2022/neural-ui-core-tooltip.mjs.map +1 -0
  135. package/fesm2022/neural-ui-core-url-state.mjs +96 -0
  136. package/fesm2022/neural-ui-core-url-state.mjs.map +1 -0
  137. package/fesm2022/neural-ui-core-virtual-list.mjs +126 -0
  138. package/fesm2022/neural-ui-core-virtual-list.mjs.map +1 -0
  139. package/fesm2022/neural-ui-core.mjs +11 -8544
  140. package/fesm2022/neural-ui-core.mjs.map +1 -1
  141. package/filter-bar/package.json +4 -0
  142. package/icon/package.json +4 -0
  143. package/image-viewer/package.json +4 -0
  144. package/input/package.json +4 -0
  145. package/input-otp/package.json +4 -0
  146. package/knob/package.json +4 -0
  147. package/meter-group/package.json +4 -0
  148. package/modal/package.json +4 -0
  149. package/multiselect/package.json +4 -0
  150. package/nav/package.json +4 -0
  151. package/notification-center/package.json +4 -0
  152. package/number-input/package.json +4 -0
  153. package/package.json +252 -5
  154. package/pagination/package.json +4 -0
  155. package/popover/package.json +4 -0
  156. package/progress-bar/package.json +4 -0
  157. package/radio/package.json +4 -0
  158. package/rating/package.json +4 -0
  159. package/select/package.json +4 -0
  160. package/sidebar/package.json +4 -0
  161. package/skeleton/package.json +4 -0
  162. package/slider/package.json +4 -0
  163. package/spinner/package.json +4 -0
  164. package/split-button/package.json +4 -0
  165. package/splitter/package.json +4 -0
  166. package/stats-card/package.json +4 -0
  167. package/stepper/package.json +4 -0
  168. package/styles/_tokens.scss +202 -0
  169. package/styles.scss +1 -0
  170. package/switch/package.json +4 -0
  171. package/table/package.json +4 -0
  172. package/tabs/package.json +4 -0
  173. package/textarea/package.json +4 -0
  174. package/timeline/package.json +4 -0
  175. package/toast/package.json +4 -0
  176. package/toggle-button-group/package.json +4 -0
  177. package/toolbar/package.json +4 -0
  178. package/tooltip/package.json +4 -0
  179. package/types/neural-ui-core-accordion.d.ts +55 -0
  180. package/types/neural-ui-core-alert.d.ts +47 -0
  181. package/types/neural-ui-core-autocomplete.d.ts +75 -0
  182. package/types/neural-ui-core-avatar.d.ts +39 -0
  183. package/types/neural-ui-core-badge.d.ts +36 -0
  184. package/types/neural-ui-core-block-ui.d.ts +46 -0
  185. package/types/neural-ui-core-breadcrumb.d.ts +38 -0
  186. package/types/neural-ui-core-button.d.ts +55 -0
  187. package/types/neural-ui-core-card.d.ts +37 -0
  188. package/types/neural-ui-core-chart.d.ts +236 -0
  189. package/types/neural-ui-core-checkbox.d.ts +33 -0
  190. package/types/neural-ui-core-chip.d.ts +53 -0
  191. package/types/neural-ui-core-code-block.d.ts +55 -0
  192. package/types/neural-ui-core-color-picker.d.ts +55 -0
  193. package/types/neural-ui-core-command-palette.d.ts +56 -0
  194. package/types/neural-ui-core-confirm-dialog.d.ts +50 -0
  195. package/types/neural-ui-core-context-menu.d.ts +66 -0
  196. package/types/neural-ui-core-dashboard-grid.d.ts +41 -0
  197. package/types/neural-ui-core-date-input.d.ts +178 -0
  198. package/types/neural-ui-core-divider.d.ts +20 -0
  199. package/types/neural-ui-core-empty-state.d.ts +32 -0
  200. package/types/neural-ui-core-filter-bar.d.ts +49 -0
  201. package/types/neural-ui-core-icon.d.ts +33 -0
  202. package/types/neural-ui-core-image-viewer.d.ts +67 -0
  203. package/types/neural-ui-core-input-otp.d.ts +49 -0
  204. package/types/neural-ui-core-input.d.ts +86 -0
  205. package/types/neural-ui-core-knob.d.ts +68 -0
  206. package/types/neural-ui-core-meter-group.d.ts +52 -0
  207. package/types/neural-ui-core-modal.d.ts +54 -0
  208. package/types/neural-ui-core-multiselect.d.ts +138 -0
  209. package/types/neural-ui-core-nav.d.ts +69 -0
  210. package/types/neural-ui-core-notification-center.d.ts +60 -0
  211. package/types/neural-ui-core-number-input.d.ts +63 -0
  212. package/types/neural-ui-core-pagination.d.ts +30 -0
  213. package/types/neural-ui-core-popover.d.ts +73 -0
  214. package/types/neural-ui-core-progress-bar.d.ts +35 -0
  215. package/types/neural-ui-core-radio.d.ts +51 -0
  216. package/types/neural-ui-core-rating.d.ts +34 -0
  217. package/types/neural-ui-core-select.d.ts +170 -0
  218. package/types/neural-ui-core-sidebar.d.ts +57 -0
  219. package/types/neural-ui-core-skeleton.d.ts +22 -0
  220. package/types/neural-ui-core-slider.d.ts +42 -0
  221. package/types/neural-ui-core-spinner.d.ts +38 -0
  222. package/types/neural-ui-core-split-button.d.ts +65 -0
  223. package/types/neural-ui-core-splitter.d.ts +28 -0
  224. package/types/neural-ui-core-stats-card.d.ts +39 -0
  225. package/types/neural-ui-core-stepper.d.ts +51 -0
  226. package/types/neural-ui-core-switch.d.ts +34 -0
  227. package/types/neural-ui-core-table.d.ts +285 -0
  228. package/types/neural-ui-core-tabs.d.ts +88 -0
  229. package/types/neural-ui-core-textarea.d.ts +52 -0
  230. package/types/neural-ui-core-timeline.d.ts +33 -0
  231. package/types/neural-ui-core-toast.d.ts +70 -0
  232. package/types/neural-ui-core-toggle-button-group.d.ts +63 -0
  233. package/types/neural-ui-core-toolbar.d.ts +36 -0
  234. package/types/neural-ui-core-tooltip.d.ts +48 -0
  235. package/types/neural-ui-core-url-state.d.ts +58 -0
  236. package/types/neural-ui-core-virtual-list.d.ts +60 -0
  237. package/types/neural-ui-core.d.ts +3 -2105
  238. package/url-state/package.json +4 -0
  239. package/virtual-list/package.json +4 -0
@@ -0,0 +1,1872 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, TemplateRef, Directive, DestroyRef, PLATFORM_ID, contentChild, input, output, signal, computed, effect, ChangeDetectionStrategy, ViewEncapsulation, Component } from '@angular/core';
3
+ import { isPlatformBrowser, NgTemplateOutlet } from '@angular/common';
4
+ import * as i1 from '@angular/forms';
5
+ import { FormControl, ReactiveFormsModule } from '@angular/forms';
6
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
7
+ import { NeuUrlStateService } from '@neural-ui/core/url-state';
8
+ import { NeuInputComponent } from '@neural-ui/core/input';
9
+ import { NeuSelectComponent } from '@neural-ui/core/select';
10
+ import { NeuDateInputComponent } from '@neural-ui/core/date-input';
11
+ import { NeuIconComponent } from '@neural-ui/core/icon';
12
+
13
+ /**
14
+ * Directiva para definir el template de expansión de fila en NeuTable.
15
+ *
16
+ * Uso:
17
+ * ```html
18
+ * <neu-table [expandable]="true" [columns]="cols" [data]="rows">
19
+ * <ng-template neuTableExpand let-row>
20
+ * <div>{{ row.details }}</div>
21
+ * </ng-template>
22
+ * </neu-table>
23
+ * ```
24
+ */
25
+ class NeuTableExpandDirective {
26
+ templateRef = inject(TemplateRef);
27
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NeuTableExpandDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
28
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.8", type: NeuTableExpandDirective, isStandalone: true, selector: "ng-template[neuTableExpand]", ngImport: i0 });
29
+ }
30
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NeuTableExpandDirective, decorators: [{
31
+ type: Directive,
32
+ args: [{
33
+ selector: 'ng-template[neuTableExpand]',
34
+ standalone: true,
35
+ }]
36
+ }] });
37
+
38
+ // Conversor interno — el input acepta object[] para compatibilidad con
39
+ // interfaces de usuario concretas (User, Product…). Internamente casteamos a Row.
40
+ function asRows(data) {
41
+ return data;
42
+ }
43
+ /**
44
+ * NeuralUI Table — Componente Estrella
45
+ *
46
+ * Tabla de datos con:
47
+ * - Búsqueda/filtrado → ?q=...
48
+ * - Paginación → ?page=...
49
+ * - Ordenación → ?sort=col&sortDir=asc|desc
50
+ * - Selección de filas (múltiple) opcional
51
+ * - Skeleton de carga animado
52
+ * - Scroll horizontal elegante en mobile (<400px)
53
+ *
54
+ * Uso básico:
55
+ * <neu-table [columns]="cols" [data]="rows" />
56
+ *
57
+ * Múltiples tablas por página:
58
+ * <neu-table pageParam="userPage" searchParam="userQ" sortParam="userSort" ... />
59
+ */
60
+ class NeuTableComponent {
61
+ _destroyRef = inject(DestroyRef);
62
+ _urlState = inject(NeuUrlStateService);
63
+ _platformId = inject(PLATFORM_ID);
64
+ expandTemplate = contentChild(NeuTableExpandDirective, ...(ngDevMode ? [{ debugName: "expandTemplate" }] : /* istanbul ignore next */ []));
65
+ // ── Inputs de datos ─────────────────────────────────────────────────
66
+ columns = input([], ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
67
+ data = input([], ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
68
+ pageSize = input(10, ...(ngDevMode ? [{ debugName: "pageSize" }] : /* istanbul ignore next */ []));
69
+ loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
70
+ title = input('', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
71
+ emptyMessage = input('No results found', ...(ngDevMode ? [{ debugName: "emptyMessage" }] : /* istanbul ignore next */ []));
72
+ skeletonRows = input([1, 2, 3, 4, 5], ...(ngDevMode ? [{ debugName: "skeletonRows" }] : /* istanbul ignore next */ []));
73
+ // ── Inputs de funcionalidad ──────────────────────────────────────────
74
+ searchable = input(true, ...(ngDevMode ? [{ debugName: "searchable" }] : /* istanbul ignore next */ []));
75
+ searchPlaceholder = input('Search...', ...(ngDevMode ? [{ debugName: "searchPlaceholder" }] : /* istanbul ignore next */ []));
76
+ exactMatchable = input(false, ...(ngDevMode ? [{ debugName: "exactMatchable" }] : /* istanbul ignore next */ []));
77
+ exactMatchLabel = input('Exact match', ...(ngDevMode ? [{ debugName: "exactMatchLabel" }] : /* istanbul ignore next */ []));
78
+ searchAriaLabel = input('Search table', ...(ngDevMode ? [{ debugName: "searchAriaLabel" }] : /* istanbul ignore next */ []));
79
+ clearSearchAriaLabel = input('Clear search', ...(ngDevMode ? [{ debugName: "clearSearchAriaLabel" }] : /* istanbul ignore next */ []));
80
+ clearFilterLabel = input('Clear filter', ...(ngDevMode ? [{ debugName: "clearFilterLabel" }] : /* istanbul ignore next */ []));
81
+ previousPageAriaLabel = input('Previous page', ...(ngDevMode ? [{ debugName: "previousPageAriaLabel" }] : /* istanbul ignore next */ []));
82
+ nextPageAriaLabel = input('Next page', ...(ngDevMode ? [{ debugName: "nextPageAriaLabel" }] : /* istanbul ignore next */ []));
83
+ pageSizeLabel = input('Rows:', ...(ngDevMode ? [{ debugName: "pageSizeLabel" }] : /* istanbul ignore next */ []));
84
+ pageSizeAriaLabel = input('Rows per page', ...(ngDevMode ? [{ debugName: "pageSizeAriaLabel" }] : /* istanbul ignore next */ []));
85
+ paginationAriaLabel = input('Pagination', ...(ngDevMode ? [{ debugName: "paginationAriaLabel" }] : /* istanbul ignore next */ []));
86
+ exportCsvTitle = input('Export CSV', ...(ngDevMode ? [{ debugName: "exportCsvTitle" }] : /* istanbul ignore next */ []));
87
+ exportJsonTitle = input('Export JSON', ...(ngDevMode ? [{ debugName: "exportJsonTitle" }] : /* istanbul ignore next */ []));
88
+ clearSelectionLabel = input('Clear selection', ...(ngDevMode ? [{ debugName: "clearSelectionLabel" }] : /* istanbul ignore next */ []));
89
+ selectionSummaryLabel = input('selected', ...(ngDevMode ? [{ debugName: "selectionSummaryLabel" }] : /* istanbul ignore next */ []));
90
+ tableAriaLabel = input('Data table', ...(ngDevMode ? [{ debugName: "tableAriaLabel" }] : /* istanbul ignore next */ []));
91
+ selectAllAriaLabel = input('Select all filtered rows', ...(ngDevMode ? [{ debugName: "selectAllAriaLabel" }] : /* istanbul ignore next */ []));
92
+ selectRowAriaLabel = input('Select row', ...(ngDevMode ? [{ debugName: "selectRowAriaLabel" }] : /* istanbul ignore next */ []));
93
+ expandRowAriaLabel = input('Expand row', ...(ngDevMode ? [{ debugName: "expandRowAriaLabel" }] : /* istanbul ignore next */ []));
94
+ filterPlaceholder = input('Filter...', ...(ngDevMode ? [{ debugName: "filterPlaceholder" }] : /* istanbul ignore next */ []));
95
+ filterAriaPrefix = input('Filter by', ...(ngDevMode ? [{ debugName: "filterAriaPrefix" }] : /* istanbul ignore next */ []));
96
+ allFilterOptionLabel = input('All', ...(ngDevMode ? [{ debugName: "allFilterOptionLabel" }] : /* istanbul ignore next */ []));
97
+ ofLabel = input('of', ...(ngDevMode ? [{ debugName: "ofLabel" }] : /* istanbul ignore next */ []));
98
+ resultLabelSingular = input('result', ...(ngDevMode ? [{ debugName: "resultLabelSingular" }] : /* istanbul ignore next */ []));
99
+ resultLabelPlural = input('results', ...(ngDevMode ? [{ debugName: "resultLabelPlural" }] : /* istanbul ignore next */ []));
100
+ sortable = input(false, ...(ngDevMode ? [{ debugName: "sortable" }] : /* istanbul ignore next */ []));
101
+ selectable = input(false, ...(ngDevMode ? [{ debugName: "selectable" }] : /* istanbul ignore next */ []));
102
+ expandable = input(false, ...(ngDevMode ? [{ debugName: "expandable" }] : /* istanbul ignore next */ []));
103
+ exportable = input(false, ...(ngDevMode ? [{ debugName: "exportable" }] : /* istanbul ignore next */ []));
104
+ exportFileName = input('export', ...(ngDevMode ? [{ debugName: "exportFileName" }] : /* istanbul ignore next */ []));
105
+ pageSizeOptions = input([], ...(ngDevMode ? [{ debugName: "pageSizeOptions" }] : /* istanbul ignore next */ []));
106
+ stickyHeader = input(false, ...(ngDevMode ? [{ debugName: "stickyHeader" }] : /* istanbul ignore next */ []));
107
+ rowKey = input('id', ...(ngDevMode ? [{ debugName: "rowKey" }] : /* istanbul ignore next */ []));
108
+ // ── Inputs nuevos v1.3.0 ──────────────────────────────────────────────
109
+ density = input('normal', ...(ngDevMode ? [{ debugName: "density" }] : /* istanbul ignore next */ []));
110
+ showRowNumbers = input(false, ...(ngDevMode ? [{ debugName: "showRowNumbers" }] : /* istanbul ignore next */ []));
111
+ rowClass = input(undefined, ...(ngDevMode ? [{ debugName: "rowClass" }] : /* istanbul ignore next */ []));
112
+ footerRow = input(undefined, ...(ngDevMode ? [{ debugName: "footerRow" }] : /* istanbul ignore next */ []));
113
+ emptyStateTemplate = input(undefined, ...(ngDevMode ? [{ debugName: "emptyStateTemplate" }] : /* istanbul ignore next */ []));
114
+ serverSide = input(false, ...(ngDevMode ? [{ debugName: "serverSide" }] : /* istanbul ignore next */ []));
115
+ totalItems = input(undefined, ...(ngDevMode ? [{ debugName: "totalItems" }] : /* istanbul ignore next */ []));
116
+ multiSort = input(false, ...(ngDevMode ? [{ debugName: "multiSort" }] : /* istanbul ignore next */ []));
117
+ exportFormats = input(['csv'], ...(ngDevMode ? [{ debugName: "exportFormats" }] : /* istanbul ignore next */ []));
118
+ exportColumns = input([], ...(ngDevMode ? [{ debugName: "exportColumns" }] : /* istanbul ignore next */ []));
119
+ // ── URL params ────────────────────────────────────────────────────────
120
+ pageParam = input('page', ...(ngDevMode ? [{ debugName: "pageParam" }] : /* istanbul ignore next */ []));
121
+ searchParam = input('q', ...(ngDevMode ? [{ debugName: "searchParam" }] : /* istanbul ignore next */ []));
122
+ sortParam = input('sort', ...(ngDevMode ? [{ debugName: "sortParam" }] : /* istanbul ignore next */ []));
123
+ sortDirParam = input('sortDir', ...(ngDevMode ? [{ debugName: "sortDirParam" }] : /* istanbul ignore next */ []));
124
+ /** Param used to persist multi-sort state in the URL / Param para persistir multisort en la URL */
125
+ multiSortParam = input('msort', ...(ngDevMode ? [{ debugName: "multiSortParam" }] : /* istanbul ignore next */ []));
126
+ /**
127
+ * Activa o desactiva la sincronización de estado con los queryParams de la URL.
128
+ * Cuando es false, la paginación, búsqueda y ordenación se gestionan con
129
+ * señales internas sin tocar la URL del navegador.
130
+ *
131
+ * Enables or disables URL query-param synchronization.
132
+ * When false, pagination, search and sort are managed with internal signals
133
+ * and the browser URL is never modified.
134
+ */
135
+ useUrlState = input(true, ...(ngDevMode ? [{ debugName: "useUrlState" }] : /* istanbul ignore next */ []));
136
+ // ── Outputs ───────────────────────────────────────────────────────────
137
+ selectionChange = output();
138
+ rowClick = output();
139
+ rowDblClick = output();
140
+ actionClick = output();
141
+ serverStateChange = output();
142
+ searchChange = output();
143
+ // ── Estado interno (usado cuando useUrlState = false) ─────────────────
144
+ // Internal state signals (used when useUrlState = false)
145
+ _internalPage = signal(1, ...(ngDevMode ? [{ debugName: "_internalPage" }] : /* istanbul ignore next */ []));
146
+ _internalSearch = signal('', ...(ngDevMode ? [{ debugName: "_internalSearch" }] : /* istanbul ignore next */ []));
147
+ _internalSortKey = signal('', ...(ngDevMode ? [{ debugName: "_internalSortKey" }] : /* istanbul ignore next */ []));
148
+ _internalSortDir = signal('asc', ...(ngDevMode ? [{ debugName: "_internalSortDir" }] : /* istanbul ignore next */ []));
149
+ _internalMultiSort = signal('', ...(ngDevMode ? [{ debugName: "_internalMultiSort" }] : /* istanbul ignore next */ []));
150
+ // ── URL State ─────────────────────────────────────────────────────────
151
+ _urlParamSignals = new Map();
152
+ _getUrlParamSignal(key) {
153
+ let paramSignal = this._urlParamSignals.get(key);
154
+ if (!paramSignal) {
155
+ paramSignal = this._urlState.getParam(key);
156
+ this._urlParamSignals.set(key, paramSignal);
157
+ }
158
+ return paramSignal;
159
+ }
160
+ _readUrlParam(key) {
161
+ return this._getUrlParamSignal(key)();
162
+ }
163
+ currentPage = computed(() => {
164
+ if (!this.useUrlState())
165
+ return this._internalPage();
166
+ const raw = this._readUrlParam(this.pageParam());
167
+ const n = Number(raw);
168
+ return !raw || isNaN(n) || n < 1 ? 1 : n;
169
+ }, ...(ngDevMode ? [{ debugName: "currentPage" }] : /* istanbul ignore next */ []));
170
+ searchQuery = computed(() => {
171
+ if (!this.useUrlState())
172
+ return this._internalSearch();
173
+ return this._readUrlParam(this.searchParam()) ?? '';
174
+ }, ...(ngDevMode ? [{ debugName: "searchQuery" }] : /* istanbul ignore next */ []));
175
+ sortKey = computed(() => {
176
+ if (!this.useUrlState())
177
+ return this._internalSortKey();
178
+ return this._readUrlParam(this.sortParam()) ?? '';
179
+ }, ...(ngDevMode ? [{ debugName: "sortKey" }] : /* istanbul ignore next */ []));
180
+ sortDir = computed(() => {
181
+ if (!this.useUrlState())
182
+ return this._internalSortDir();
183
+ const d = this._readUrlParam(this.sortDirParam());
184
+ return d === 'desc' ? 'desc' : 'asc';
185
+ }, ...(ngDevMode ? [{ debugName: "sortDir" }] : /* istanbul ignore next */ []));
186
+ // ---- Pipeline de datos / Data pipeline ----
187
+ rows = computed(() => asRows(this.data()), ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
188
+ // ── Estado interno ────────────────────────────────────────────────────
189
+ _exactMatch = signal(false, ...(ngDevMode ? [{ debugName: "_exactMatch" }] : /* istanbul ignore next */ []));
190
+ exactMatch = this._exactMatch.asReadonly();
191
+ /**
192
+ * Multi-sort entries derived from URL param or internal state.
193
+ * Entradas de multisort derivadas del param de URL o del estado interno.
194
+ */
195
+ _sortEntries = computed(() => {
196
+ if (!this.multiSort())
197
+ return [];
198
+ const param = this.useUrlState()
199
+ ? this._readUrlParam(this.multiSortParam())
200
+ : this._internalMultiSort();
201
+ if (!param)
202
+ return [];
203
+ return param.split(',').flatMap((chunk) => {
204
+ const [key, dir] = chunk.split(':');
205
+ if (!key)
206
+ return [];
207
+ return [{ key, dir: (dir === 'desc' ? 'desc' : 'asc') }];
208
+ });
209
+ }, ...(ngDevMode ? [{ debugName: "_sortEntries" }] : /* istanbul ignore next */ []));
210
+ _columnFilters = signal({}, ...(ngDevMode ? [{ debugName: "_columnFilters" }] : /* istanbul ignore next */ []));
211
+ _pageSizeControl = new FormControl('', { nonNullable: true });
212
+ _columnFilterControls = new Map();
213
+ /** True when at least one column has filterable:true / True si alguna columna tiene filterable:true */
214
+ _hasFilterableCol = computed(() => this.columns().some((c) => c.filterable), ...(ngDevMode ? [{ debugName: "_hasFilterableCol" }] : /* istanbul ignore next */ []));
215
+ /** Convierte filterOptions de string[] a NeuSelectOption[] con opción "Todos" al inicio.
216
+ * Converts filterOptions from string[] to NeuSelectOption[] with a leading "All" option. */
217
+ _filterOpts(col) {
218
+ return [
219
+ { label: this.allFilterOptionLabel(), value: '' },
220
+ ...(col.filterOptions ?? []).map((o) => ({ label: o, value: o })),
221
+ ];
222
+ }
223
+ _pageSizeOptions = computed(() => this.pageSizeOptions().map((size) => ({ label: String(size), value: String(size) })), ...(ngDevMode ? [{ debugName: "_pageSizeOptions" }] : /* istanbul ignore next */ []));
224
+ constructor() {
225
+ this._pageSizeControl.valueChanges
226
+ .pipe(takeUntilDestroyed(this._destroyRef))
227
+ .subscribe((value) => this.onPageSizeChange(value));
228
+ effect(() => {
229
+ const nextSize = String(this.effectivePageSize());
230
+ if (this._pageSizeControl.value !== nextSize) {
231
+ this._pageSizeControl.setValue(nextSize, { emitEvent: false });
232
+ }
233
+ });
234
+ effect(() => {
235
+ const filters = this._columnFilters();
236
+ for (const col of this.columns()) {
237
+ if (!col.filterable)
238
+ continue;
239
+ const control = this.columnFilterControl(col.key);
240
+ const nextValue = String(filters[col.key] ?? '');
241
+ if (control.value !== nextValue) {
242
+ control.setValue(nextValue, { emitEvent: false });
243
+ }
244
+ }
245
+ });
246
+ }
247
+ _confirmPending = signal(null, ...(ngDevMode ? [{ debugName: "_confirmPending" }] : /* istanbul ignore next */ []));
248
+ // ── Pipeline de datos ─────────────────────────────────────────────────
249
+ _rows = computed(() => asRows(this.data()), ...(ngDevMode ? [{ debugName: "_rows" }] : /* istanbul ignore next */ []));
250
+ filteredData = computed(() => {
251
+ if (this.serverSide())
252
+ return this._rows();
253
+ const rows = this._rows();
254
+ const colFilters = this._columnFilters();
255
+ const q = this.searchQuery().trim().toLowerCase();
256
+ const exact = this._exactMatch();
257
+ const matches = (text) => exact ? text.toLowerCase() === q : text.toLowerCase().includes(q);
258
+ return rows.filter((row) => {
259
+ const passesGlobal = !q ||
260
+ this.columns().some((col) => {
261
+ const rawVal = String(row[col.key] ?? '');
262
+ const displayVal = col.cell
263
+ ? col.cell(row)
264
+ : col.type === 'badge' && col.badgeMap?.[rawVal]?.label
265
+ ? col.badgeMap[rawVal].label
266
+ : rawVal;
267
+ return matches(displayVal) || matches(rawVal);
268
+ });
269
+ if (!passesGlobal)
270
+ return false;
271
+ for (const [colKey, filterVal] of Object.entries(colFilters)) {
272
+ if (filterVal === null || filterVal === '' || filterVal === undefined)
273
+ continue;
274
+ if (!String(row[colKey] ?? '')
275
+ .toLowerCase()
276
+ .includes(String(filterVal).toLowerCase()))
277
+ return false;
278
+ }
279
+ return true;
280
+ });
281
+ }, ...(ngDevMode ? [{ debugName: "filteredData" }] : /* istanbul ignore next */ []));
282
+ sortedData = computed(() => {
283
+ if (this.serverSide())
284
+ return this.filteredData();
285
+ const data = [...this.filteredData()];
286
+ if (!this.sortable())
287
+ return data;
288
+ if (this.multiSort() && this._sortEntries().length > 0) {
289
+ const entries = this._sortEntries();
290
+ return data.sort((a, b) => {
291
+ for (const entry of entries) {
292
+ const cmp = String(a[entry.key] ?? '').localeCompare(String(b[entry.key] ?? ''), undefined, { numeric: true });
293
+ if (cmp !== 0)
294
+ return entry.dir === 'asc' ? cmp : -cmp;
295
+ }
296
+ return 0;
297
+ });
298
+ }
299
+ const key = this.sortKey();
300
+ if (!key)
301
+ return data;
302
+ const dir = this.sortDir();
303
+ return data.sort((a, b) => {
304
+ const cmp = String(a[key] ?? '').localeCompare(String(b[key] ?? ''), undefined, {
305
+ numeric: true,
306
+ });
307
+ return dir === 'asc' ? cmp : -cmp;
308
+ });
309
+ }, ...(ngDevMode ? [{ debugName: "sortedData" }] : /* istanbul ignore next */ []));
310
+ _dynamicPageSize = signal(null, ...(ngDevMode ? [{ debugName: "_dynamicPageSize" }] : /* istanbul ignore next */ []));
311
+ effectivePageSize = computed(() => this._dynamicPageSize() ?? this.pageSize(), ...(ngDevMode ? [{ debugName: "effectivePageSize" }] : /* istanbul ignore next */ []));
312
+ totalPages = computed(() => {
313
+ const total = this.serverSide() && this.totalItems() != null
314
+ ? this.totalItems()
315
+ : this.filteredData().length;
316
+ return Math.max(1, Math.ceil(total / this.effectivePageSize()));
317
+ }, ...(ngDevMode ? [{ debugName: "totalPages" }] : /* istanbul ignore next */ []));
318
+ paginatedData = computed(() => {
319
+ if (this.serverSide())
320
+ return this._rows();
321
+ const page = Math.min(this.currentPage(), this.totalPages());
322
+ const size = this.effectivePageSize();
323
+ return this.sortedData().slice((page - 1) * size, page * size);
324
+ }, ...(ngDevMode ? [{ debugName: "paginatedData" }] : /* istanbul ignore next */ []));
325
+ pageNumbers = computed(() => {
326
+ const total = this.totalPages();
327
+ const current = this.currentPage();
328
+ let start = Math.max(1, current - 2);
329
+ const end = Math.min(total, start + 4);
330
+ start = Math.max(1, end - 4);
331
+ return Array.from({ length: end - start + 1 }, (_, i) => start + i);
332
+ }, ...(ngDevMode ? [{ debugName: "pageNumbers" }] : /* istanbul ignore next */ []));
333
+ paginationInfo = computed(() => {
334
+ const total = this.serverSide() && this.totalItems() != null
335
+ ? this.totalItems()
336
+ : this.filteredData().length;
337
+ const page = Math.min(this.currentPage(), this.totalPages());
338
+ const size = this.effectivePageSize();
339
+ const from = Math.min((page - 1) * size + 1, total);
340
+ const to = Math.min(page * size, total);
341
+ return `${from}\u2013${to} ${this.ofLabel()} ${total} ${total === 1 ? this.resultLabelSingular() : this.resultLabelPlural()}`;
342
+ }, ...(ngDevMode ? [{ debugName: "paginationInfo" }] : /* istanbul ignore next */ []));
343
+ totalColspan = computed(() => {
344
+ let cols = this.columns().length;
345
+ if (this.selectable())
346
+ cols++;
347
+ if (this.expandable())
348
+ cols++;
349
+ if (this.showRowNumbers())
350
+ cols++;
351
+ return cols;
352
+ }, ...(ngDevMode ? [{ debugName: "totalColspan" }] : /* istanbul ignore next */ []));
353
+ /** Calcula el offset izquierdo acumulado para columnas frozen-left múltiples.
354
+ * Calculates cumulative left offset for multiple frozen-left columns. */
355
+ _frozenLeftOffsets = computed(() => {
356
+ const offsets = new Map();
357
+ let leftPx = 0;
358
+ for (const col of this.columns()) {
359
+ if (col.frozen === 'left') {
360
+ offsets.set(col.key, leftPx);
361
+ if (col.width && col.width !== 'auto') {
362
+ const px = parseFloat(col.width);
363
+ if (!isNaN(px))
364
+ leftPx += px;
365
+ }
366
+ }
367
+ }
368
+ return offsets;
369
+ }, ...(ngDevMode ? [{ debugName: "_frozenLeftOffsets" }] : /* istanbul ignore next */ []));
370
+ _lastFrozenLeftKey = computed(() => {
371
+ const frozenLeft = this.columns().filter((col) => col.frozen === 'left');
372
+ return frozenLeft.at(-1)?.key ?? null;
373
+ }, ...(ngDevMode ? [{ debugName: "_lastFrozenLeftKey" }] : /* istanbul ignore next */ []));
374
+ _firstFrozenRightKey = computed(() => {
375
+ const frozenRight = this.columns().filter((col) => col.frozen === 'right');
376
+ return frozenRight[0]?.key ?? null;
377
+ }, ...(ngDevMode ? [{ debugName: "_firstFrozenRightKey" }] : /* istanbul ignore next */ []));
378
+ isLastFrozenLeftColumn(key) {
379
+ return this._lastFrozenLeftKey() === key;
380
+ }
381
+ isFirstFrozenRightColumn(key) {
382
+ return this._firstFrozenRightKey() === key;
383
+ }
384
+ // ── Expansión de filas ────────────────────────────────────────────────
385
+ _expandedKeys = signal(new Set(), ...(ngDevMode ? [{ debugName: "_expandedKeys" }] : /* istanbul ignore next */ []));
386
+ isRowExpanded(row) {
387
+ return this._expandedKeys().has(this.getRowKey(row));
388
+ }
389
+ toggleExpand(row) {
390
+ const key = this.getRowKey(row);
391
+ const set = new Set(this._expandedKeys());
392
+ if (set.has(key))
393
+ set.delete(key);
394
+ else
395
+ set.add(key);
396
+ this._expandedKeys.set(set);
397
+ }
398
+ // ── Selección ─────────────────────────────────────────────────────────
399
+ _selectedKeys = signal(new Set(), ...(ngDevMode ? [{ debugName: "_selectedKeys" }] : /* istanbul ignore next */ []));
400
+ selectedCount = computed(() => this._selectedKeys().size, ...(ngDevMode ? [{ debugName: "selectedCount" }] : /* istanbul ignore next */ []));
401
+ selectedRowsInfo = computed(() => `${this.selectedCount()} ${this.selectionSummaryLabel()}`, ...(ngDevMode ? [{ debugName: "selectedRowsInfo" }] : /* istanbul ignore next */ []));
402
+ /**
403
+ * TRUE cuando TODOS los registros que pasan el filtro activo están seleccionados.
404
+ * A diferencia de una selección global, actúa solo sobre el subconjunto filtrado.
405
+ */
406
+ isAllFilteredSelected = computed(() => {
407
+ const filtered = this.filteredData();
408
+ return (filtered.length > 0 && filtered.every((r) => this._selectedKeys().has(this.getRowKey(r))));
409
+ }, ...(ngDevMode ? [{ debugName: "isAllFilteredSelected" }] : /* istanbul ignore next */ []));
410
+ isSomeFilteredSelected = computed(() => this.filteredData().some((r) => this._selectedKeys().has(this.getRowKey(r))) &&
411
+ !this.isAllFilteredSelected(), ...(ngDevMode ? [{ debugName: "isSomeFilteredSelected" }] : /* istanbul ignore next */ []));
412
+ isRowSelected(row) {
413
+ return this._selectedKeys().has(this.getRowKey(row));
414
+ }
415
+ toggleRow(row) {
416
+ const key = this.getRowKey(row);
417
+ const set = new Set(this._selectedKeys());
418
+ if (set.has(key))
419
+ set.delete(key);
420
+ else
421
+ set.add(key);
422
+ this._selectedKeys.set(set);
423
+ this._emitSelection(set);
424
+ }
425
+ /** Selecciona/deselecciona SOLO los datos filtrados activos. */
426
+ toggleAll() {
427
+ const set = new Set(this._selectedKeys());
428
+ if (this.isAllFilteredSelected()) {
429
+ this.filteredData().forEach((r) => set.delete(this.getRowKey(r)));
430
+ }
431
+ else {
432
+ this.filteredData().forEach((r) => set.add(this.getRowKey(r)));
433
+ }
434
+ this._selectedKeys.set(set);
435
+ this._emitSelection(set);
436
+ }
437
+ clearSelection() {
438
+ this._selectedKeys.set(new Set());
439
+ this.selectionChange.emit([]);
440
+ }
441
+ _emitSelection(keys) {
442
+ const selected = this._rows().filter((r) => keys.has(this.getRowKey(r)));
443
+ this.selectionChange.emit(selected);
444
+ }
445
+ // ── Navegación ────────────────────────────────────────────────────────
446
+ goToPage(page) {
447
+ const clamped = Math.max(1, Math.min(page, this.totalPages()));
448
+ if (this.serverSide()) {
449
+ this._emitServerState({ page: clamped });
450
+ }
451
+ else if (this.useUrlState()) {
452
+ this._urlState.setParam(this.pageParam(), String(clamped));
453
+ }
454
+ else {
455
+ this._internalPage.set(clamped);
456
+ }
457
+ }
458
+ sortBy(key, event) {
459
+ if (this.multiSort() && event?.shiftKey) {
460
+ // Shift+click: toggle or add column / Shift+click: alterna o añade columna
461
+ const entries = [...this._sortEntries()];
462
+ const idx = entries.findIndex((e) => e.key === key);
463
+ if (idx >= 0) {
464
+ entries[idx] = { key, dir: entries[idx].dir === 'asc' ? 'desc' : 'asc' };
465
+ }
466
+ else {
467
+ entries.push({ key, dir: 'asc' });
468
+ }
469
+ const val = entries.map((e) => `${e.key}:${e.dir}`).join(',') || null;
470
+ if (this.useUrlState()) {
471
+ this._urlState.setParam(this.multiSortParam(), val);
472
+ }
473
+ else {
474
+ this._internalMultiSort.set(val ?? '');
475
+ }
476
+ }
477
+ else if (this.multiSort()) {
478
+ // Single click: replace sort / Click simple: reemplaza el orden
479
+ const current = this._sortEntries().find((e) => e.key === key);
480
+ const dir = current?.dir === 'asc' ? 'desc' : 'asc';
481
+ const val = `${key}:${dir}`;
482
+ if (this.useUrlState()) {
483
+ this._urlState.setParam(this.multiSortParam(), val);
484
+ }
485
+ else {
486
+ this._internalMultiSort.set(val);
487
+ }
488
+ }
489
+ else {
490
+ const dir = this.sortKey() === key && this.sortDir() === 'asc' ? 'desc' : 'asc';
491
+ if (this.serverSide()) {
492
+ this._emitServerState({ sortKey: key, sortDir: dir, page: 1 });
493
+ }
494
+ else if (this.useUrlState()) {
495
+ this._urlState.patchParams({
496
+ [this.sortParam()]: key,
497
+ [this.sortDirParam()]: dir,
498
+ [this.pageParam()]: '1',
499
+ });
500
+ }
501
+ else {
502
+ this._internalSortKey.set(key);
503
+ this._internalSortDir.set(dir);
504
+ this._internalPage.set(1);
505
+ }
506
+ }
507
+ }
508
+ onRowClick(row, _event) {
509
+ if (this.selectable())
510
+ this.toggleRow(row);
511
+ this.rowClick.emit(row);
512
+ }
513
+ onSearch(event) {
514
+ const q = event.target.value;
515
+ this.searchChange.emit(q);
516
+ if (this.serverSide()) {
517
+ this._emitServerState({ search: q, page: 1 });
518
+ }
519
+ else if (this.useUrlState()) {
520
+ this._urlState.patchParams({ [this.searchParam()]: q || null, [this.pageParam()]: '1' });
521
+ }
522
+ else {
523
+ this._internalSearch.set(q);
524
+ this._internalPage.set(1);
525
+ }
526
+ }
527
+ clearSearch() {
528
+ if (this.serverSide()) {
529
+ this._emitServerState({ search: '', page: 1 });
530
+ }
531
+ else if (this.useUrlState()) {
532
+ this._urlState.patchParams({ [this.searchParam()]: null, [this.pageParam()]: '1' });
533
+ }
534
+ else {
535
+ this._internalSearch.set('');
536
+ this._internalPage.set(1);
537
+ }
538
+ }
539
+ onPageSizeChange(value) {
540
+ const normalizedValue = value && typeof value === 'object' ? (value.target?.value ?? null) : value;
541
+ const size = Number(normalizedValue);
542
+ if (!Number.isFinite(size) || size <= 0)
543
+ return;
544
+ this._dynamicPageSize.set(size);
545
+ if (this.serverSide()) {
546
+ this._emitServerState({ pageSize: size, page: 1 });
547
+ }
548
+ else if (this.useUrlState()) {
549
+ this._urlState.patchParams({ [this.pageParam()]: '1' });
550
+ }
551
+ else {
552
+ this._internalPage.set(1);
553
+ }
554
+ }
555
+ setExactMatch(value) {
556
+ this._exactMatch.set(value);
557
+ }
558
+ setColumnFilter(colKey, value) {
559
+ // Column filters are local signal — no URL update avoids scroll jumps.
560
+ // paginatedData already clamps currentPage to totalPages automatically.
561
+ // Los filtros de columna son locales — sin URL para evitar saltos de scroll.
562
+ // paginatedData ya adapta currentPage a totalPages automáticamente.
563
+ this._columnFilters.update((f) => ({ ...f, [colKey]: value }));
564
+ if (this.serverSide()) {
565
+ this._emitServerState({
566
+ columnFilters: { ...this._columnFilters(), [colKey]: value },
567
+ page: 1,
568
+ });
569
+ }
570
+ }
571
+ clearColumnFilters() {
572
+ this._columnFilters.set({});
573
+ }
574
+ // ── Actions ───────────────────────────────────────────────────────────
575
+ isConfirmPending(row, action) {
576
+ const p = this._confirmPending();
577
+ return p !== null && p.rowKey === this.getRowKey(row) && p.actionKey === action.key;
578
+ }
579
+ handleAction(row, action) {
580
+ if (action.confirm) {
581
+ const p = this._confirmPending();
582
+ const rowKey = this.getRowKey(row);
583
+ if (p && p.rowKey === rowKey && p.actionKey === action.key) {
584
+ this._confirmPending.set(null);
585
+ this.actionClick.emit({ action, row });
586
+ }
587
+ else {
588
+ this._confirmPending.set({ rowKey, actionKey: action.key });
589
+ }
590
+ }
591
+ else {
592
+ this.actionClick.emit({ action, row });
593
+ }
594
+ }
595
+ cancelConfirm() {
596
+ this._confirmPending.set(null);
597
+ }
598
+ // ── Export ────────────────────────────────────────────────────────────
599
+ exportCsv() {
600
+ if (!isPlatformBrowser(this._platformId))
601
+ return;
602
+ const cols = this._getExportColumns();
603
+ const headers = cols.map((c) => `"${c.header.replace(/"/g, '""')}"`);
604
+ const rows = this.filteredData().map((row) => cols.map((col) => `"${this.getCellValue(row, col).replace(/"/g, '""')}"`));
605
+ const csv = [headers, ...rows].map((r) => r.join(',')).join('\r\n');
606
+ this._downloadBlob(new Blob([csv], { type: 'text/csv;charset=utf-8;' }), `${this.exportFileName()}.csv`);
607
+ }
608
+ exportJson() {
609
+ if (!isPlatformBrowser(this._platformId))
610
+ return;
611
+ const cols = this._getExportColumns();
612
+ const data = this.filteredData().map((row) => {
613
+ const obj = {};
614
+ cols.forEach((col) => (obj[col.key] = this.getCellValue(row, col)));
615
+ return obj;
616
+ });
617
+ this._downloadBlob(new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }), `${this.exportFileName()}.json`);
618
+ }
619
+ _getExportColumns() {
620
+ const keys = this.exportColumns();
621
+ const all = this.columns().filter((c) => c.type !== 'actions');
622
+ return keys.length ? all.filter((c) => keys.includes(c.key)) : all;
623
+ }
624
+ _downloadBlob(blob, filename) {
625
+ const url = URL.createObjectURL(blob);
626
+ const a = document.createElement('a');
627
+ a.href = url;
628
+ a.download = filename;
629
+ document.body.appendChild(a);
630
+ a.click();
631
+ document.body.removeChild(a);
632
+ URL.revokeObjectURL(url);
633
+ }
634
+ // ── Utilidades ────────────────────────────────────────────────────────
635
+ getRowKey(row) {
636
+ return row[this.rowKey()] ?? JSON.stringify(row);
637
+ }
638
+ getRowClass(row) {
639
+ return this.rowClass()?.(row) ?? '';
640
+ }
641
+ getCellValue(row, col) {
642
+ if (col.cell)
643
+ return col.cell(row);
644
+ const val = row[col.key];
645
+ if (val == null)
646
+ return '—';
647
+ const locale = col.locale ?? 'es-ES';
648
+ switch (col.type) {
649
+ case 'date': {
650
+ const d = val instanceof Date ? val : new Date(String(val));
651
+ return isNaN(d.getTime())
652
+ ? String(val)
653
+ : d.toLocaleDateString(locale, col.dateFormat ?? { day: '2-digit', month: '2-digit', year: 'numeric' });
654
+ }
655
+ case 'number':
656
+ return new Intl.NumberFormat(locale, col.numberFormat).format(Number(val));
657
+ case 'currency':
658
+ return new Intl.NumberFormat(locale, {
659
+ style: 'currency',
660
+ currency: col.currencyCode ?? 'EUR',
661
+ ...col.numberFormat,
662
+ }).format(Number(val));
663
+ default:
664
+ return String(val);
665
+ }
666
+ }
667
+ getSortPriority(key) {
668
+ if (!this.multiSort())
669
+ return -1;
670
+ return this._sortEntries().findIndex((e) => e.key === key);
671
+ }
672
+ columnFilterControl(key) {
673
+ let control = this._columnFilterControls.get(key);
674
+ if (!control) {
675
+ control = new FormControl(String(this.getColumnFilterValue(key)), {
676
+ nonNullable: true,
677
+ });
678
+ control.valueChanges
679
+ .pipe(takeUntilDestroyed(this._destroyRef))
680
+ .subscribe((value) => this.setColumnFilter(key, value));
681
+ this._columnFilterControls.set(key, control);
682
+ }
683
+ return control;
684
+ }
685
+ getColumnFilterValue(key) {
686
+ return this._columnFilters()[key] ?? '';
687
+ }
688
+ _emitServerState(patch) {
689
+ const current = {
690
+ page: this.currentPage(),
691
+ pageSize: this.effectivePageSize(),
692
+ search: this.searchQuery(),
693
+ sortKey: this.sortKey(),
694
+ sortDir: this.sortDir(),
695
+ sortEntries: this._sortEntries(),
696
+ columnFilters: this._columnFilters(),
697
+ };
698
+ this.serverStateChange.emit({ ...current, ...patch });
699
+ }
700
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NeuTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
701
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: NeuTableComponent, isStandalone: true, selector: "neu-table", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, skeletonRows: { classPropertyName: "skeletonRows", publicName: "skeletonRows", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, exactMatchable: { classPropertyName: "exactMatchable", publicName: "exactMatchable", isSignal: true, isRequired: false, transformFunction: null }, exactMatchLabel: { classPropertyName: "exactMatchLabel", publicName: "exactMatchLabel", isSignal: true, isRequired: false, transformFunction: null }, searchAriaLabel: { classPropertyName: "searchAriaLabel", publicName: "searchAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, clearSearchAriaLabel: { classPropertyName: "clearSearchAriaLabel", publicName: "clearSearchAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, clearFilterLabel: { classPropertyName: "clearFilterLabel", publicName: "clearFilterLabel", isSignal: true, isRequired: false, transformFunction: null }, previousPageAriaLabel: { classPropertyName: "previousPageAriaLabel", publicName: "previousPageAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, nextPageAriaLabel: { classPropertyName: "nextPageAriaLabel", publicName: "nextPageAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, pageSizeLabel: { classPropertyName: "pageSizeLabel", publicName: "pageSizeLabel", isSignal: true, isRequired: false, transformFunction: null }, pageSizeAriaLabel: { classPropertyName: "pageSizeAriaLabel", publicName: "pageSizeAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, paginationAriaLabel: { classPropertyName: "paginationAriaLabel", publicName: "paginationAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, exportCsvTitle: { classPropertyName: "exportCsvTitle", publicName: "exportCsvTitle", isSignal: true, isRequired: false, transformFunction: null }, exportJsonTitle: { classPropertyName: "exportJsonTitle", publicName: "exportJsonTitle", isSignal: true, isRequired: false, transformFunction: null }, clearSelectionLabel: { classPropertyName: "clearSelectionLabel", publicName: "clearSelectionLabel", isSignal: true, isRequired: false, transformFunction: null }, selectionSummaryLabel: { classPropertyName: "selectionSummaryLabel", publicName: "selectionSummaryLabel", isSignal: true, isRequired: false, transformFunction: null }, tableAriaLabel: { classPropertyName: "tableAriaLabel", publicName: "tableAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, selectAllAriaLabel: { classPropertyName: "selectAllAriaLabel", publicName: "selectAllAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, selectRowAriaLabel: { classPropertyName: "selectRowAriaLabel", publicName: "selectRowAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, expandRowAriaLabel: { classPropertyName: "expandRowAriaLabel", publicName: "expandRowAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, filterPlaceholder: { classPropertyName: "filterPlaceholder", publicName: "filterPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, filterAriaPrefix: { classPropertyName: "filterAriaPrefix", publicName: "filterAriaPrefix", isSignal: true, isRequired: false, transformFunction: null }, allFilterOptionLabel: { classPropertyName: "allFilterOptionLabel", publicName: "allFilterOptionLabel", isSignal: true, isRequired: false, transformFunction: null }, ofLabel: { classPropertyName: "ofLabel", publicName: "ofLabel", isSignal: true, isRequired: false, transformFunction: null }, resultLabelSingular: { classPropertyName: "resultLabelSingular", publicName: "resultLabelSingular", isSignal: true, isRequired: false, transformFunction: null }, resultLabelPlural: { classPropertyName: "resultLabelPlural", publicName: "resultLabelPlural", isSignal: true, isRequired: false, transformFunction: null }, sortable: { classPropertyName: "sortable", publicName: "sortable", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, expandable: { classPropertyName: "expandable", publicName: "expandable", isSignal: true, isRequired: false, transformFunction: null }, exportable: { classPropertyName: "exportable", publicName: "exportable", isSignal: true, isRequired: false, transformFunction: null }, exportFileName: { classPropertyName: "exportFileName", publicName: "exportFileName", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, stickyHeader: { classPropertyName: "stickyHeader", publicName: "stickyHeader", isSignal: true, isRequired: false, transformFunction: null }, rowKey: { classPropertyName: "rowKey", publicName: "rowKey", isSignal: true, isRequired: false, transformFunction: null }, density: { classPropertyName: "density", publicName: "density", isSignal: true, isRequired: false, transformFunction: null }, showRowNumbers: { classPropertyName: "showRowNumbers", publicName: "showRowNumbers", isSignal: true, isRequired: false, transformFunction: null }, rowClass: { classPropertyName: "rowClass", publicName: "rowClass", isSignal: true, isRequired: false, transformFunction: null }, footerRow: { classPropertyName: "footerRow", publicName: "footerRow", isSignal: true, isRequired: false, transformFunction: null }, emptyStateTemplate: { classPropertyName: "emptyStateTemplate", publicName: "emptyStateTemplate", isSignal: true, isRequired: false, transformFunction: null }, serverSide: { classPropertyName: "serverSide", publicName: "serverSide", isSignal: true, isRequired: false, transformFunction: null }, totalItems: { classPropertyName: "totalItems", publicName: "totalItems", isSignal: true, isRequired: false, transformFunction: null }, multiSort: { classPropertyName: "multiSort", publicName: "multiSort", isSignal: true, isRequired: false, transformFunction: null }, exportFormats: { classPropertyName: "exportFormats", publicName: "exportFormats", isSignal: true, isRequired: false, transformFunction: null }, exportColumns: { classPropertyName: "exportColumns", publicName: "exportColumns", isSignal: true, isRequired: false, transformFunction: null }, pageParam: { classPropertyName: "pageParam", publicName: "pageParam", isSignal: true, isRequired: false, transformFunction: null }, searchParam: { classPropertyName: "searchParam", publicName: "searchParam", isSignal: true, isRequired: false, transformFunction: null }, sortParam: { classPropertyName: "sortParam", publicName: "sortParam", isSignal: true, isRequired: false, transformFunction: null }, sortDirParam: { classPropertyName: "sortDirParam", publicName: "sortDirParam", isSignal: true, isRequired: false, transformFunction: null }, multiSortParam: { classPropertyName: "multiSortParam", publicName: "multiSortParam", isSignal: true, isRequired: false, transformFunction: null }, useUrlState: { classPropertyName: "useUrlState", publicName: "useUrlState", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange", rowClick: "rowClick", rowDblClick: "rowDblClick", actionClick: "actionClick", serverStateChange: "serverStateChange", searchChange: "searchChange" }, host: { properties: { "class.neu-table__host": "true", "class.neu-table__host--compact": "density() === \"compact\"", "class.neu-table__host--relaxed": "density() === \"relaxed\"" } }, queries: [{ propertyName: "expandTemplate", first: true, predicate: NeuTableExpandDirective, descendants: true, isSignal: true }], ngImport: i0, template: `
702
+ <div class="neu-table-container" [class.neu-table--sticky-header]="stickyHeader()">
703
+ <!-- ---- Toolbar ---- -->
704
+ @if (searchable() || title() || exportable()) {
705
+ <div class="neu-table__toolbar">
706
+ @if (title()) {
707
+ <h3 class="neu-table__title">{{ title() }}</h3>
708
+ }
709
+ @if (searchable()) {
710
+ <div class="neu-table__search-group">
711
+ <div class="neu-table__search-wrapper">
712
+ <svg
713
+ class="neu-table__search-icon"
714
+ viewBox="0 0 24 24"
715
+ fill="none"
716
+ stroke="currentColor"
717
+ stroke-width="2"
718
+ stroke-linecap="round"
719
+ stroke-linejoin="round"
720
+ aria-hidden="true"
721
+ >
722
+ <circle cx="11" cy="11" r="8" />
723
+ <line x1="21" y1="21" x2="16.65" y2="16.65" />
724
+ </svg>
725
+ <input
726
+ class="neu-table__search"
727
+ type="search"
728
+ [placeholder]="searchPlaceholder()"
729
+ [value]="searchQuery()"
730
+ (input)="onSearch($event)"
731
+ [attr.aria-label]="searchAriaLabel()"
732
+ />
733
+ @if (searchQuery()) {
734
+ <button
735
+ class="neu-table__search-clear"
736
+ type="button"
737
+ [attr.aria-label]="clearSearchAriaLabel()"
738
+ (click)="clearSearch()"
739
+ >
740
+ <svg
741
+ viewBox="0 0 24 24"
742
+ fill="none"
743
+ stroke="currentColor"
744
+ stroke-width="2.5"
745
+ stroke-linecap="round"
746
+ stroke-linejoin="round"
747
+ >
748
+ <line x1="18" y1="6" x2="6" y2="18" />
749
+ <line x1="6" y1="6" x2="18" y2="18" />
750
+ </svg>
751
+ </button>
752
+ }
753
+ </div>
754
+ @if (exactMatchable()) {
755
+ <label class="neu-table__exact-label">
756
+ <input
757
+ class="neu-table__exact-checkbox"
758
+ type="checkbox"
759
+ [checked]="exactMatch()"
760
+ (change)="setExactMatch($any($event.target).checked)"
761
+ />
762
+ {{ exactMatchLabel() }}
763
+ </label>
764
+ }
765
+ </div>
766
+ }
767
+ @if (exportable() && !loading()) {
768
+ <div class="neu-table__export-group">
769
+ @if (exportFormats().includes('csv')) {
770
+ <button
771
+ class="neu-table__export-btn"
772
+ type="button"
773
+ (click)="exportCsv()"
774
+ [title]="exportCsvTitle()"
775
+ >
776
+ <svg
777
+ viewBox="0 0 24 24"
778
+ fill="none"
779
+ stroke="currentColor"
780
+ stroke-width="2"
781
+ stroke-linecap="round"
782
+ stroke-linejoin="round"
783
+ aria-hidden="true"
784
+ width="15"
785
+ height="15"
786
+ >
787
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
788
+ <polyline points="7 10 12 15 17 10" />
789
+ <line x1="12" y1="15" x2="12" y2="3" />
790
+ </svg>
791
+ CSV
792
+ </button>
793
+ }
794
+ @if (exportFormats().includes('json')) {
795
+ <button
796
+ class="neu-table__export-btn"
797
+ type="button"
798
+ (click)="exportJson()"
799
+ [title]="exportJsonTitle()"
800
+ >
801
+ <svg
802
+ viewBox="0 0 24 24"
803
+ fill="none"
804
+ stroke="currentColor"
805
+ stroke-width="2"
806
+ stroke-linecap="round"
807
+ stroke-linejoin="round"
808
+ aria-hidden="true"
809
+ width="15"
810
+ height="15"
811
+ >
812
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
813
+ <polyline points="7 10 12 15 17 10" />
814
+ <line x1="12" y1="15" x2="12" y2="3" />
815
+ </svg>
816
+ JSON
817
+ </button>
818
+ }
819
+ </div>
820
+ }
821
+ </div>
822
+ }
823
+
824
+ <!-- Barra de selección (aparece al seleccionar) -->
825
+ @if (selectable() && selectedCount() > 0) {
826
+ <div class="neu-table__selection-bar">
827
+ <span>{{ selectedRowsInfo() }}</span>
828
+ <button class="neu-table__selection-clear" type="button" (click)="clearSelection()">
829
+ {{ clearSelectionLabel() }}
830
+ </button>
831
+ </div>
832
+ }
833
+
834
+ <!-- ---- Scroll container ---- -->
835
+ <div
836
+ class="neu-table__scroll-container"
837
+ role="region"
838
+ [attr.aria-label]="title() || tableAriaLabel()"
839
+ >
840
+ <table class="neu-table" [attr.aria-rowcount]="filteredData().length">
841
+ <thead class="neu-table__head">
842
+ <tr>
843
+ <!-- Checkbox de selección global -->
844
+ @if (expandable()) {
845
+ <th class="neu-table__th neu-table__th--expand-col" scope="col"></th>
846
+ }
847
+ @if (selectable()) {
848
+ <th class="neu-table__th neu-table__th--check" scope="col">
849
+ <input
850
+ type="checkbox"
851
+ class="neu-table__checkbox"
852
+ [checked]="isAllFilteredSelected()"
853
+ [indeterminate]="isSomeFilteredSelected()"
854
+ (change)="toggleAll()"
855
+ [attr.aria-label]="selectAllAriaLabel()"
856
+ />
857
+ </th>
858
+ }
859
+ @if (showRowNumbers()) {
860
+ <th class="neu-table__th neu-table__th--rn" scope="col">#</th>
861
+ }
862
+ @for (col of columns(); track col.key) {
863
+ <th
864
+ class="neu-table__th"
865
+ [class.neu-table__th--sortable]="sortable() && col.sortable !== false"
866
+ [class.neu-table__th--sorted-asc]="
867
+ !multiSort() && sortKey() === col.key && sortDir() === 'asc'
868
+ "
869
+ [class.neu-table__th--sorted-desc]="
870
+ !multiSort() && sortKey() === col.key && sortDir() === 'desc'
871
+ "
872
+ [class]="col.cellClass ?? ''"
873
+ [class.neu-table__th--frozen-left]="col.frozen === 'left'"
874
+ [class.neu-table__th--frozen-right]="col.frozen === 'right'"
875
+ [class.neu-table__th--frozen-left-boundary]="isLastFrozenLeftColumn(col.key)"
876
+ [class.neu-table__th--frozen-right-boundary]="isFirstFrozenRightColumn(col.key)"
877
+ [style.left]="
878
+ col.frozen === 'left' ? (_frozenLeftOffsets().get(col.key) ?? 0) + 'px' : null
879
+ "
880
+ [style.width]="col.width ?? 'auto'"
881
+ [style.text-align]="col.align ?? 'left'"
882
+ scope="col"
883
+ (click)="sortable() && col.sortable !== false ? sortBy(col.key, $event) : null"
884
+ >
885
+ @if (col.headerTemplate) {
886
+ <ng-container
887
+ [ngTemplateOutlet]="col.headerTemplate"
888
+ [ngTemplateOutletContext]="{ $implicit: col }"
889
+ />
890
+ } @else {
891
+ {{ col.header }}
892
+ }
893
+ @if (sortable() && col.sortable !== false) {
894
+ <span class="neu-table__sort-icon" aria-hidden="true">
895
+ @if (multiSort()) {
896
+ @let pri = getSortPriority(col.key);
897
+ @if (pri >= 0) {
898
+ <span class="neu-table__sort-priority">{{ pri + 1 }}</span>
899
+ {{ _sortEntries()[pri]?.dir === 'asc' ? '↑' : '↓' }}
900
+ } @else {
901
+
902
+ }
903
+ } @else {
904
+ @if (sortKey() === col.key) {
905
+ {{ sortDir() === 'asc' ? '↑' : '↓' }}
906
+ } @else {
907
+
908
+ }
909
+ }
910
+ </span>
911
+ }
912
+ </th>
913
+ }
914
+ </tr>
915
+ <!-- ── Fila de filtros de columna / Column filter row ── -->
916
+ @if (_hasFilterableCol()) {
917
+ <tr class="neu-table__filter-row">
918
+ @if (expandable()) {
919
+ <th class="neu-table__th neu-table__th--filter-placeholder" scope="col"></th>
920
+ }
921
+ @if (selectable()) {
922
+ <th class="neu-table__th neu-table__th--filter-placeholder" scope="col"></th>
923
+ }
924
+ @if (showRowNumbers()) {
925
+ <th class="neu-table__th neu-table__th--filter-placeholder" scope="col"></th>
926
+ }
927
+ @for (col of columns(); track col.key) {
928
+ <th
929
+ class="neu-table__th neu-table__th--filter"
930
+ scope="col"
931
+ [style.width]="col.width ?? 'auto'"
932
+ >
933
+ @if (col.filterable) {
934
+ @if (col.filterType === 'select') {
935
+ <neu-select
936
+ [floatingLabel]="false"
937
+ [options]="_filterOpts(col)"
938
+ [formControl]="columnFilterControl(col.key)"
939
+ [attr.aria-label]="filterAriaPrefix() + ' ' + col.header"
940
+ />
941
+ } @else if (col.filterType === 'date') {
942
+ <neu-date-input
943
+ [formControl]="columnFilterControl(col.key)"
944
+ [attr.aria-label]="filterAriaPrefix() + ' ' + col.header"
945
+ />
946
+ } @else {
947
+ <neu-input
948
+ [floatingLabel]="false"
949
+ [placeholder]="filterPlaceholder()"
950
+ [formControl]="columnFilterControl(col.key)"
951
+ [attr.aria-label]="filterAriaPrefix() + ' ' + col.header"
952
+ />
953
+ }
954
+ }
955
+ </th>
956
+ }
957
+ </tr>
958
+ }
959
+ </thead>
960
+
961
+ <tbody class="neu-table__body">
962
+ @if (loading()) {
963
+ <tr class="neu-table__row--loading">
964
+ <td [attr.colspan]="totalColspan()" class="neu-table__td neu-table__td--center">
965
+ <div class="neu-table__skeleton-rows">
966
+ @for (_ of skeletonRows(); track $index) {
967
+ <div class="neu-table__skeleton-row">
968
+ @for (__ of columns(); track $index) {
969
+ <div class="neu-table__skeleton-cell"></div>
970
+ }
971
+ </div>
972
+ }
973
+ </div>
974
+ </td>
975
+ </tr>
976
+ } @else if (paginatedData().length === 0) {
977
+ <tr>
978
+ <td [attr.colspan]="totalColspan()" class="neu-table__td neu-table__td--empty">
979
+ @if (emptyStateTemplate()) {
980
+ <ng-container [ngTemplateOutlet]="emptyStateTemplate()!" />
981
+ } @else {
982
+ <div class="neu-table__empty">
983
+ <svg
984
+ viewBox="0 0 24 24"
985
+ fill="none"
986
+ stroke="currentColor"
987
+ stroke-width="1.5"
988
+ stroke-linecap="round"
989
+ stroke-linejoin="round"
990
+ >
991
+ <circle cx="11" cy="11" r="8" />
992
+ <line x1="21" y1="21" x2="16.65" y2="16.65" />
993
+ </svg>
994
+ <p>{{ emptyMessage() }}</p>
995
+ @if (searchQuery()) {
996
+ <button
997
+ class="neu-table__clear-filter"
998
+ type="button"
999
+ (click)="clearSearch()"
1000
+ >
1001
+ {{ clearFilterLabel() }}
1002
+ </button>
1003
+ }
1004
+ </div>
1005
+ }
1006
+ </td>
1007
+ </tr>
1008
+ } @else {
1009
+ @for (row of paginatedData(); track getRowKey(row); let rowIdx = $index) {
1010
+ <tr
1011
+ class="neu-table__row"
1012
+ [class]="getRowClass(row)"
1013
+ [class.neu-table__row--selected]="isRowSelected(row)"
1014
+ [class.neu-table__row--clickable]="selectable()"
1015
+ (click)="onRowClick(row, $event)"
1016
+ (dblclick)="rowDblClick.emit(row)"
1017
+ >
1018
+ @if (expandable()) {
1019
+ <td
1020
+ class="neu-table__td neu-table__td--expand-col"
1021
+ (click)="$event.stopPropagation()"
1022
+ >
1023
+ <button
1024
+ class="neu-table__expand-btn"
1025
+ type="button"
1026
+ (click)="toggleExpand(row)"
1027
+ [class.neu-table__expand-btn--open]="isRowExpanded(row)"
1028
+ [attr.aria-expanded]="isRowExpanded(row)"
1029
+ [attr.aria-label]="expandRowAriaLabel()"
1030
+ >
1031
+ <svg
1032
+ viewBox="0 0 24 24"
1033
+ fill="none"
1034
+ stroke="currentColor"
1035
+ stroke-width="2.5"
1036
+ stroke-linecap="round"
1037
+ stroke-linejoin="round"
1038
+ >
1039
+ <polyline points="9 18 15 12 9 6" />
1040
+ </svg>
1041
+ </button>
1042
+ </td>
1043
+ }
1044
+ @if (selectable()) {
1045
+ <td
1046
+ class="neu-table__td neu-table__th--check"
1047
+ (click)="$event.stopPropagation()"
1048
+ >
1049
+ <input
1050
+ type="checkbox"
1051
+ class="neu-table__checkbox"
1052
+ [checked]="isRowSelected(row)"
1053
+ (change)="toggleRow(row)"
1054
+ [attr.aria-label]="selectRowAriaLabel()"
1055
+ />
1056
+ </td>
1057
+ }
1058
+ @if (showRowNumbers()) {
1059
+ <td class="neu-table__td neu-table__td--rn">
1060
+ {{ (currentPage() - 1) * effectivePageSize() + rowIdx + 1 }}
1061
+ </td>
1062
+ }
1063
+ @for (col of columns(); track col.key) {
1064
+ <td
1065
+ class="neu-table__td"
1066
+ [class]="col.cellClass ?? ''"
1067
+ [class.neu-table__td--frozen-left]="col.frozen === 'left'"
1068
+ [class.neu-table__td--frozen-right]="col.frozen === 'right'"
1069
+ [class.neu-table__td--frozen-left-boundary]="isLastFrozenLeftColumn(col.key)"
1070
+ [class.neu-table__td--frozen-right-boundary]="
1071
+ isFirstFrozenRightColumn(col.key)
1072
+ "
1073
+ [style.left]="
1074
+ col.frozen === 'left'
1075
+ ? (_frozenLeftOffsets().get(col.key) ?? 0) + 'px'
1076
+ : null
1077
+ "
1078
+ [style.text-align]="col.align ?? 'left'"
1079
+ >
1080
+ @if (col.cellTemplate) {
1081
+ <ng-container
1082
+ [ngTemplateOutlet]="col.cellTemplate"
1083
+ [ngTemplateOutletContext]="{ $implicit: row, row: row, column: col }"
1084
+ />
1085
+ } @else if (col.type === 'badge') {
1086
+ @let badgeVal = getCellValue(row, col);
1087
+ @let badgeCfg = col.badgeMap?.[badgeVal];
1088
+ <span
1089
+ class="neu-table__cell-badge neu-table__cell-badge--{{
1090
+ badgeCfg?.variant ?? 'default'
1091
+ }}"
1092
+ >
1093
+ {{ badgeCfg?.label ?? badgeVal }}
1094
+ </span>
1095
+ } @else if (col.type === 'link') {
1096
+ <a
1097
+ class="neu-table__cell-link"
1098
+ [href]="col.linkHref ? col.linkHref(row) : '#'"
1099
+ [target]="col.linkTarget ?? '_self'"
1100
+ (click)="$event.stopPropagation()"
1101
+ >{{ getCellValue(row, col) }}</a
1102
+ >
1103
+ } @else if (col.type === 'actions') {
1104
+ <div class="neu-table__actions">
1105
+ @for (action of col.actions ?? []; track action.key) {
1106
+ @if (action.show === undefined || action.show(row)) {
1107
+ @if (isConfirmPending(row, action)) {
1108
+ <span class="neu-table__action-confirm">
1109
+ <span>{{ action.confirm }}</span>
1110
+ <button
1111
+ class="neu-table__action-btn neu-table__action-btn--danger"
1112
+ type="button"
1113
+ (click)="handleAction(row, action); $event.stopPropagation()"
1114
+ >
1115
+
1116
+ </button>
1117
+ <button
1118
+ class="neu-table__action-btn"
1119
+ type="button"
1120
+ (click)="cancelConfirm(); $event.stopPropagation()"
1121
+ >
1122
+ No
1123
+ </button>
1124
+ </span>
1125
+ } @else {
1126
+ <button
1127
+ class="neu-table__action-btn neu-table__action-btn--{{
1128
+ action.variant ?? 'ghost'
1129
+ }}"
1130
+ type="button"
1131
+ [disabled]="action.disabled ? action.disabled(row) : false"
1132
+ [title]="action.label"
1133
+ (click)="handleAction(row, action); $event.stopPropagation()"
1134
+ >
1135
+ @if (action.icon.startsWith('lucide')) {
1136
+ <neu-icon
1137
+ class="neu-table__action-icon"
1138
+ [name]="action.icon"
1139
+ size="1rem"
1140
+ aria-hidden="true"
1141
+ />
1142
+ } @else {
1143
+ <span class="neu-table__action-icon" aria-hidden="true">{{
1144
+ action.icon
1145
+ }}</span>
1146
+ }
1147
+ <span class="sr-only">{{ action.label }}</span>
1148
+ </button>
1149
+ }
1150
+ }
1151
+ }
1152
+ </div>
1153
+ } @else {
1154
+ {{ getCellValue(row, col) }}
1155
+ }
1156
+ </td>
1157
+ }
1158
+ </tr>
1159
+ @if (expandable() && isRowExpanded(row)) {
1160
+ <tr class="neu-table__row-expand-detail">
1161
+ <td [attr.colspan]="totalColspan()" class="neu-table__td--expand-panel">
1162
+ <div class="neu-table__expand-content">
1163
+ @if (expandTemplate(); as tpl) {
1164
+ <ng-container
1165
+ [ngTemplateOutlet]="tpl.templateRef"
1166
+ [ngTemplateOutletContext]="{ $implicit: row }"
1167
+ />
1168
+ }
1169
+ </div>
1170
+ </td>
1171
+ </tr>
1172
+ }
1173
+ }
1174
+ }
1175
+ </tbody>
1176
+ @if (footerRow()) {
1177
+ <tfoot class="neu-table__foot">
1178
+ <tr class="neu-table__footer-row">
1179
+ @if (expandable()) {
1180
+ <td class="neu-table__td"></td>
1181
+ }
1182
+ @if (selectable()) {
1183
+ <td class="neu-table__td"></td>
1184
+ }
1185
+ @if (showRowNumbers()) {
1186
+ <td class="neu-table__td"></td>
1187
+ }
1188
+ @for (col of columns(); track col.key) {
1189
+ <td
1190
+ class="neu-table__td neu-table__td--footer"
1191
+ [style.text-align]="col.align ?? 'left'"
1192
+ >
1193
+ {{ footerRow()![col.key] !== undefined ? footerRow()![col.key] : '' }}
1194
+ </td>
1195
+ }
1196
+ </tr>
1197
+ </tfoot>
1198
+ }
1199
+ </table>
1200
+ </div>
1201
+
1202
+ <!-- ---- Footer ---- -->
1203
+ @if (!loading() && filteredData().length > 0) {
1204
+ <div class="neu-table__footer">
1205
+ @if (pageSizeOptions().length > 0) {
1206
+ <div class="neu-table__page-size">
1207
+ <label class="neu-table__page-size-label">{{ pageSizeLabel() }}</label>
1208
+ <neu-select
1209
+ class="neu-table__page-size-select"
1210
+ [floatingLabel]="false"
1211
+ [options]="_pageSizeOptions()"
1212
+ [formControl]="_pageSizeControl"
1213
+ [attr.aria-label]="pageSizeAriaLabel()"
1214
+ size="sm"
1215
+ />
1216
+ </div>
1217
+ }
1218
+ <span class="neu-table__info">
1219
+ {{ paginationInfo() }}
1220
+ </span>
1221
+ @if (totalPages() > 1) {
1222
+ <nav class="neu-table__pagination" [attr.aria-label]="paginationAriaLabel()">
1223
+ <button
1224
+ class="neu-table__page-btn"
1225
+ [disabled]="currentPage() === 1"
1226
+ (click)="goToPage(currentPage() - 1)"
1227
+ [attr.aria-label]="previousPageAriaLabel()"
1228
+ type="button"
1229
+ >
1230
+ <svg
1231
+ viewBox="0 0 24 24"
1232
+ fill="none"
1233
+ stroke="currentColor"
1234
+ stroke-width="2.5"
1235
+ stroke-linecap="round"
1236
+ stroke-linejoin="round"
1237
+ >
1238
+ <polyline points="15 18 9 12 15 6" />
1239
+ </svg>
1240
+ </button>
1241
+ @for (page of pageNumbers(); track page) {
1242
+ <button
1243
+ class="neu-table__page-btn"
1244
+ [class.neu-table__page-btn--active]="page === currentPage()"
1245
+ (click)="goToPage(page)"
1246
+ [attr.aria-current]="page === currentPage() ? 'page' : null"
1247
+ type="button"
1248
+ >
1249
+ {{ page }}
1250
+ </button>
1251
+ }
1252
+ <button
1253
+ class="neu-table__page-btn"
1254
+ [disabled]="currentPage() === totalPages()"
1255
+ (click)="goToPage(currentPage() + 1)"
1256
+ [attr.aria-label]="nextPageAriaLabel()"
1257
+ type="button"
1258
+ >
1259
+ <svg
1260
+ viewBox="0 0 24 24"
1261
+ fill="none"
1262
+ stroke="currentColor"
1263
+ stroke-width="2.5"
1264
+ stroke-linecap="round"
1265
+ stroke-linejoin="round"
1266
+ >
1267
+ <polyline points="9 18 15 12 9 6" />
1268
+ </svg>
1269
+ </button>
1270
+ </nav>
1271
+ }
1272
+ </div>
1273
+ }
1274
+ </div>
1275
+ `, isInline: true, styles: [".neu-table-container{background:var(--neu-surface);border:1px solid var(--neu-border);border-radius:var(--neu-radius-lg);overflow:hidden;box-shadow:var(--neu-shadow-sm)}.neu-table__toolbar{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:var(--neu-space-3);padding:var(--neu-space-4) var(--neu-space-5);border-bottom:1px solid var(--neu-border);background:var(--neu-surface)}.neu-table__title{font-size:var(--neu-text-base);font-weight:600;color:var(--neu-text);margin:0;letter-spacing:-.01em}.neu-table__search-group{display:flex;align-items:center;gap:var(--neu-space-3);flex:1;max-width:420px;min-width:0}.neu-table__search-wrapper{position:relative;display:flex;align-items:center;flex:1;min-width:0}.neu-table__search-icon{position:absolute;left:var(--neu-space-3);width:15px;height:15px;color:var(--neu-text-muted);pointer-events:none;flex-shrink:0}.neu-table__search{width:100%;height:36px;padding:0 var(--neu-space-3) 0 36px;border:1.5px solid var(--neu-border);border-radius:var(--neu-radius);background:var(--neu-surface-2);font-family:var(--neu-font-sans);font-size:var(--neu-text-sm);color:var(--neu-text);outline:none;transition:border-color var(--neu-transition),box-shadow var(--neu-transition)}.neu-table__search::placeholder{color:var(--neu-text-muted)}.neu-table__search:focus{border-color:var(--neu-primary);box-shadow:var(--neu-focus-ring);background:var(--neu-surface)}.neu-table__search::-webkit-search-cancel-button{display:none}.neu-table__search-clear{position:absolute;right:var(--neu-space-2);display:flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:none;background:transparent;color:var(--neu-text-muted);cursor:pointer;border-radius:var(--neu-radius-sm);transition:color var(--neu-transition),background-color var(--neu-transition)}.neu-table__search-clear svg{width:13px;height:13px}.neu-table__search-clear:hover{color:var(--neu-error);background:var(--neu-error-bg)}.neu-table__exact-label{display:inline-flex;align-items:center;gap:6px;font-size:var(--neu-text-sm);color:var(--neu-text-muted);cursor:pointer;white-space:nowrap;-webkit-user-select:none;user-select:none}.neu-table__exact-label:hover{color:var(--neu-text)}.neu-table__exact-checkbox{width:14px;height:14px;accent-color:var(--neu-primary);cursor:pointer}.neu-table__scroll-container{position:relative;isolation:isolate;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:thin;scrollbar-color:var(--neu-surface-3) transparent}.neu-table__scroll-container::-webkit-scrollbar{height:4px}.neu-table__scroll-container::-webkit-scrollbar-track{background:transparent}.neu-table__scroll-container::-webkit-scrollbar-thumb{background:var(--neu-surface-3);border-radius:99px}@media(max-width:399px){.neu-table__scroll-container{background-image:linear-gradient(to right,var(--neu-surface),var(--neu-surface)),linear-gradient(to right,var(--neu-surface),var(--neu-surface)),linear-gradient(to right,rgba(15,23,42,.08),transparent),linear-gradient(to left,rgba(15,23,42,.08),transparent);background-position:left center,right center,left center,right center;background-repeat:no-repeat;background-color:var(--neu-surface);background-size:20px 100%,20px 100%,10px 100%,10px 100%;background-attachment:local,local,scroll,scroll}}.neu-table{width:100%;border-collapse:separate;border-spacing:0;font-size:var(--neu-text-sm);min-width:400px}.neu-table--sticky-header .neu-table__scroll-container{overflow-y:auto;max-height:var(--neu-table-max-height, 480px)}.neu-table--sticky-header .neu-table__head tr:first-child .neu-table__th{position:sticky;top:0;z-index:2;background:var(--neu-surface-2);box-shadow:inset 0 -1px 0 var(--neu-border)}.neu-table--sticky-header .neu-table__th--filter,.neu-table--sticky-header .neu-table__th--filter-placeholder{position:sticky;top:var(--neu-table-header-height, 44px);z-index:1;background:var(--neu-surface-2)}.neu-table__head{background:var(--neu-surface-2)}.neu-table__th{padding:var(--neu-space-3) var(--neu-space-4);font-size:var(--neu-text-xs);font-weight:600;color:var(--neu-text-muted);text-transform:uppercase;letter-spacing:.06em;border-bottom:1px solid var(--neu-border);white-space:nowrap;-webkit-user-select:none;user-select:none;background-clip:padding-box}.neu-table__th--check{width:40px;text-align:center!important;padding:var(--neu-space-3) var(--neu-space-3)}.neu-table__th--sortable{cursor:pointer;transition:color var(--neu-transition)}.neu-table__th--sortable:hover{color:var(--neu-text)}.neu-table__th--sorted-asc,.neu-table__th--sorted-desc{color:var(--neu-primary)}.neu-table__sort-icon{margin-left:4px;font-style:normal;font-size:10px;opacity:.5}.neu-table__th--sorted-asc .neu-table__sort-icon,.neu-table__th--sorted-desc .neu-table__sort-icon{opacity:1}.neu-table__checkbox{width:16px;height:16px;cursor:pointer;accent-color:var(--neu-primary)}.neu-table__selection-bar{display:flex;align-items:center;justify-content:space-between;padding:var(--neu-space-2) var(--neu-space-5);background:var(--neu-primary-50);border-bottom:1px solid var(--neu-primary-100);font-size:var(--neu-text-sm);color:var(--neu-primary);font-weight:500;animation:neu-tab-fade .15s ease}.neu-table__selection-clear{background:none;border:none;color:var(--neu-primary);font-size:var(--neu-text-xs);font-weight:500;cursor:pointer;text-decoration:underline;text-underline-offset:2px;padding:0;font-family:var(--neu-font-sans)}.neu-table__row{transition:background-color var(--neu-transition)}.neu-table__row:hover{background:var(--neu-surface-2)}.neu-table__row:not(:last-child) .neu-table__td{border-bottom:1px solid var(--neu-border)}.neu-table__row--selected{background:var(--neu-primary-50)!important}.neu-table__row--selected .neu-table__td{color:var(--neu-primary)}.neu-table__row--clickable{cursor:pointer}.neu-table__td{padding:var(--neu-space-3) var(--neu-space-4);color:var(--neu-text);line-height:1.5;vertical-align:middle;background-clip:padding-box}.neu-table__td--center{text-align:center}.neu-table__td--empty{padding:var(--neu-space-8) 0}.neu-table__skeleton-rows{display:flex;flex-direction:column;gap:1px}.neu-table__skeleton-row{display:flex;align-items:center;padding:var(--neu-space-3) var(--neu-space-4);gap:var(--neu-space-4);border-bottom:1px solid var(--neu-border)}.neu-table__skeleton-cell{height:14px;flex:1;border-radius:var(--neu-radius-sm);background:linear-gradient(90deg,var(--neu-surface-2) 0%,var(--neu-surface-3) 50%,var(--neu-surface-2) 100%);background-size:200% 100%;animation:neu-skeleton 1.4s ease infinite}.neu-table__skeleton-cell:first-child{max-width:60px}.neu-table__skeleton-cell:nth-child(2){max-width:160px}.neu-table__skeleton-cell:last-child{max-width:80px}@keyframes neu-skeleton{0%{background-position:200% 0}to{background-position:-200% 0}}.neu-table__empty{display:flex;flex-direction:column;align-items:center;gap:var(--neu-space-3);padding:var(--neu-space-8) var(--neu-space-4);color:var(--neu-text-muted)}.neu-table__empty svg{width:40px;height:40px;opacity:.4}.neu-table__empty p{font-size:var(--neu-text-sm);margin:0}.neu-table__clear-filter{background:none;border:none;color:var(--neu-primary);font-size:var(--neu-text-xs);font-weight:500;cursor:pointer;padding:0;text-decoration:underline;text-underline-offset:2px}.neu-table__clear-filter:hover{color:var(--neu-primary-dark)}.neu-table__footer{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:var(--neu-space-3);padding:var(--neu-space-3) var(--neu-space-5);border-top:1px solid var(--neu-border);background:var(--neu-surface-2)}.neu-table__info{font-size:var(--neu-text-xs);color:var(--neu-text-muted)}.neu-table__pagination{display:flex;align-items:center;gap:2px}.neu-table__page-btn{display:inline-flex;align-items:center;justify-content:center;min-width:32px;height:32px;padding:0 var(--neu-space-2);border:1px solid transparent;border-radius:var(--neu-radius);background:transparent;font-family:var(--neu-font-sans);font-size:var(--neu-text-xs);font-weight:500;color:var(--neu-text-muted);cursor:pointer;transition:all var(--neu-transition)}.neu-table__page-btn svg{width:14px;height:14px}.neu-table__page-btn:hover:not(:disabled){background:var(--neu-surface-3);color:var(--neu-text);border-color:var(--neu-border)}.neu-table__page-btn:disabled{opacity:.35;cursor:not-allowed}.neu-table__page-btn--active{background:var(--neu-primary);color:var(--neu-primary-fg);border-color:var(--neu-primary)}.neu-table__page-btn--active:hover{background:var(--neu-primary-dark);border-color:var(--neu-primary-dark)}.neu-table__export-btn{display:inline-flex;align-items:center;gap:6px;height:36px;padding:0 var(--neu-space-3);border:1.5px solid var(--neu-border);border-radius:var(--neu-radius);background:var(--neu-surface);font-family:var(--neu-font-sans);font-size:var(--neu-text-sm);font-weight:500;color:var(--neu-text-muted);cursor:pointer;white-space:nowrap;transition:all var(--neu-transition);flex-shrink:0}.neu-table__export-btn svg{flex-shrink:0}.neu-table__export-btn:hover{color:var(--neu-primary);border-color:var(--neu-primary);background:var(--neu-primary-50)}.neu-table__page-size{display:flex;align-items:center;gap:var(--neu-space-2);min-width:0}.neu-table__page-size-label{font-size:var(--neu-text-xs);color:var(--neu-text-muted)}.neu-table__page-size-select{display:block;min-width:78px}.neu-table__page-size-select .neu-select__trigger{min-width:78px}.neu-table__th--expand-col,.neu-table__td--expand-col{width:36px;padding:var(--neu-space-2) var(--neu-space-2);text-align:center}.neu-table__expand-btn{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border:none;background:transparent;color:var(--neu-text-muted);cursor:pointer;border-radius:var(--neu-radius-sm);transition:all var(--neu-transition);padding:0}.neu-table__expand-btn svg{width:14px;height:14px;transition:transform .2s ease}.neu-table__expand-btn:hover{color:var(--neu-primary);background:var(--neu-primary-50)}.neu-table__expand-btn--open svg{transform:rotate(90deg)}.neu-table__row-expand-detail{background:var(--neu-surface-2)}.neu-table__td--expand-panel{padding:0}.neu-table__expand-content{padding:var(--neu-space-4) var(--neu-space-5);border-bottom:1px solid var(--neu-border);animation:neu-tab-fade .18s ease}.neu-table__cell-badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:var(--neu-radius-full);font-size:var(--neu-text-xs);font-weight:600;letter-spacing:.02em;white-space:nowrap}.neu-table__cell-badge--primary{background:var(--neu-primary-100);color:var(--neu-primary-dark)}.neu-table__cell-badge--success{background:var(--neu-table-badge-success-bg);color:var(--neu-table-badge-success-text)}.neu-table__cell-badge--warning{background:var(--neu-table-badge-warning-bg);color:var(--neu-table-badge-warning-text)}.neu-table__cell-badge--danger{background:var(--neu-table-badge-danger-bg);color:var(--neu-table-badge-danger-text)}.neu-table__cell-badge--info{background:var(--neu-primary-50);color:var(--neu-primary)}.neu-table__cell-badge--default{background:var(--neu-surface-3);color:var(--neu-text-muted)}@keyframes neu-tab-fade{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.neu-table__host--compact .neu-table__th,.neu-table__host--compact .neu-table__td{padding:var(--neu-table-density-compact-padding, 4px 8px)}.neu-table__host--relaxed .neu-table__th,.neu-table__host--relaxed .neu-table__td{padding:var(--neu-table-density-relaxed-padding, 16px 20px)}.neu-table__th--rn,.neu-table__td--rn{width:44px;text-align:right;padding-right:var(--neu-space-4);color:var(--neu-text-muted);font-variant-numeric:tabular-nums;font-size:var(--neu-text-xs)}.neu-table__th--frozen-left,.neu-table__th--frozen-right{position:sticky;z-index:3;background:var(--neu-surface-2)}.neu-table__th--frozen-left{left:0}.neu-table__th--frozen-right{right:0}.neu-table__th--frozen-left-boundary{box-shadow:inset -1px 0 0 var(--neu-border),6px 0 8px -8px #0f172a52}.neu-table__th--frozen-right-boundary{box-shadow:inset 1px 0 0 var(--neu-border),-6px 0 8px -8px #0f172a52}.neu-table__td--frozen-left,.neu-table__td--frozen-right{position:sticky;z-index:2;background:var(--neu-surface)}.neu-table__td--frozen-left{left:0}.neu-table__td--frozen-right{right:0}.neu-table__td--frozen-left-boundary{box-shadow:inset -1px 0 0 var(--neu-border),6px 0 8px -8px #0f172a52}.neu-table__td--frozen-right-boundary{box-shadow:inset 1px 0 0 var(--neu-border),-6px 0 8px -8px #0f172a52}.neu-table__foot{background:var(--neu-surface-2)}.neu-table__footer-row .neu-table__td--footer{padding:var(--neu-space-3) var(--neu-space-4);font-weight:600;font-size:var(--neu-text-sm);border-top:2px solid var(--neu-border);color:var(--neu-text)}.neu-table__sort-priority{display:inline-flex;align-items:center;justify-content:center;width:14px;height:14px;border-radius:50%;background:var(--neu-primary);color:var(--neu-primary-fg);font-size:9px;font-weight:700;margin-right:2px}.neu-table__cell-link{color:var(--neu-primary);text-decoration:none;text-underline-offset:2px}.neu-table__cell-link:hover{text-decoration:underline}.neu-table__actions{display:flex;align-items:center;gap:var(--neu-space-1);flex-wrap:nowrap}.neu-table__action-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;padding:0;border:none;border-radius:var(--neu-radius-sm);background:transparent;font-size:14px;cursor:pointer;transition:all var(--neu-transition);color:var(--neu-text-muted)}.neu-table__action-btn:disabled{opacity:.35;cursor:not-allowed}.neu-table__action-btn--ghost:hover:not(:disabled){background:var(--neu-surface-3);color:var(--neu-text)}.neu-table__action-btn--primary{background:var(--neu-primary-50);color:var(--neu-primary)}.neu-table__action-btn--primary:hover:not(:disabled){background:var(--neu-primary);color:var(--neu-primary-fg)}.neu-table__action-btn--danger{color:var(--neu-error)}.neu-table__action-btn--danger:hover:not(:disabled){background:var(--neu-error-bg)}.neu-table__action-icon{line-height:1}.neu-table__action-confirm{display:inline-flex;align-items:center;gap:var(--neu-space-1);font-size:var(--neu-text-xs);color:var(--neu-text-muted);white-space:nowrap}.neu-table__action-confirm .neu-table__action-btn{width:auto;padding:0 var(--neu-space-2);font-size:var(--neu-text-xs);font-family:var(--neu-font-sans);font-weight:500;border:1px solid var(--neu-border)}.neu-table__export-group{display:flex;align-items:center;gap:var(--neu-space-2);flex-shrink:0}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.neu-table__filter-row .neu-table__th--filter,.neu-table__filter-row .neu-table__th--filter-placeholder{padding:var(--neu-space-2) var(--neu-space-3);background:var(--neu-surface-2);border-bottom:2px solid var(--neu-border);font-weight:400;text-transform:none;letter-spacing:0;vertical-align:middle}.neu-table__col-filter-input,.neu-table__col-filter-select{display:block;width:100%;height:28px;padding:0 var(--neu-space-2);border:1px solid var(--neu-border);border-radius:var(--neu-radius-sm);background:var(--neu-surface);font-family:var(--neu-font-sans);font-size:var(--neu-text-xs);color:var(--neu-text);font-weight:400;outline:none;transition:border-color var(--neu-transition),box-shadow var(--neu-transition)}.neu-table__col-filter-input::placeholder,.neu-table__col-filter-select::placeholder{color:var(--neu-text-muted)}.neu-table__col-filter-input:focus,.neu-table__col-filter-select:focus{border-color:var(--neu-primary);box-shadow:0 0 0 2px #007aff1a;background:var(--neu-surface)}.neu-table__col-filter-select{cursor:pointer}.neu-table--scrollable .neu-table__scroll-container{max-height:480px;overflow-y:auto}.neu-table--scrollable .neu-table__head .neu-table__th{position:sticky;top:0;z-index:2;background:var(--neu-surface-2);box-shadow:0 1px 0 var(--neu-border)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: NeuInputComponent, selector: "neu-input", inputs: ["type", "label", "placeholder", "floatingLabel", "size", "hint", "errorMessage", "disabled", "autocomplete", "startIcon", "endIcon", "icon", "iconPosition", "inputId", "name", "required", "readonly", "maxlength", "minlength", "min", "max", "pattern"] }, { kind: "component", type: NeuSelectComponent, selector: "neu-select", inputs: ["options", "label", "placeholder", "errorMessage", "hint", "disabled", "floatingLabel", "size", "searchable", "searchPlaceholder", "clearable", "noResultsMessage", "clearAriaLabel", "urlParam"], outputs: ["selectionChange"] }, { kind: "component", type: NeuDateInputComponent, selector: "neu-date-input", inputs: ["type", "label", "hint", "errorMessage", "disabled", "size", "readonly", "name", "inputId", "required", "min", "max", "step", "locale", "placeholder", "dateFormat"], outputs: ["rangeChange"] }, { kind: "component", type: NeuIconComponent, selector: "neu-icon", inputs: ["name", "strokeWidth", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1276
+ }
1277
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NeuTableComponent, decorators: [{
1278
+ type: Component,
1279
+ args: [{ selector: 'neu-table', imports: [
1280
+ NgTemplateOutlet,
1281
+ ReactiveFormsModule,
1282
+ NeuInputComponent,
1283
+ NeuSelectComponent,
1284
+ NeuDateInputComponent,
1285
+ NeuIconComponent,
1286
+ ], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: {
1287
+ '[class.neu-table__host]': 'true',
1288
+ '[class.neu-table__host--compact]': 'density() === "compact"',
1289
+ '[class.neu-table__host--relaxed]': 'density() === "relaxed"',
1290
+ }, template: `
1291
+ <div class="neu-table-container" [class.neu-table--sticky-header]="stickyHeader()">
1292
+ <!-- ---- Toolbar ---- -->
1293
+ @if (searchable() || title() || exportable()) {
1294
+ <div class="neu-table__toolbar">
1295
+ @if (title()) {
1296
+ <h3 class="neu-table__title">{{ title() }}</h3>
1297
+ }
1298
+ @if (searchable()) {
1299
+ <div class="neu-table__search-group">
1300
+ <div class="neu-table__search-wrapper">
1301
+ <svg
1302
+ class="neu-table__search-icon"
1303
+ viewBox="0 0 24 24"
1304
+ fill="none"
1305
+ stroke="currentColor"
1306
+ stroke-width="2"
1307
+ stroke-linecap="round"
1308
+ stroke-linejoin="round"
1309
+ aria-hidden="true"
1310
+ >
1311
+ <circle cx="11" cy="11" r="8" />
1312
+ <line x1="21" y1="21" x2="16.65" y2="16.65" />
1313
+ </svg>
1314
+ <input
1315
+ class="neu-table__search"
1316
+ type="search"
1317
+ [placeholder]="searchPlaceholder()"
1318
+ [value]="searchQuery()"
1319
+ (input)="onSearch($event)"
1320
+ [attr.aria-label]="searchAriaLabel()"
1321
+ />
1322
+ @if (searchQuery()) {
1323
+ <button
1324
+ class="neu-table__search-clear"
1325
+ type="button"
1326
+ [attr.aria-label]="clearSearchAriaLabel()"
1327
+ (click)="clearSearch()"
1328
+ >
1329
+ <svg
1330
+ viewBox="0 0 24 24"
1331
+ fill="none"
1332
+ stroke="currentColor"
1333
+ stroke-width="2.5"
1334
+ stroke-linecap="round"
1335
+ stroke-linejoin="round"
1336
+ >
1337
+ <line x1="18" y1="6" x2="6" y2="18" />
1338
+ <line x1="6" y1="6" x2="18" y2="18" />
1339
+ </svg>
1340
+ </button>
1341
+ }
1342
+ </div>
1343
+ @if (exactMatchable()) {
1344
+ <label class="neu-table__exact-label">
1345
+ <input
1346
+ class="neu-table__exact-checkbox"
1347
+ type="checkbox"
1348
+ [checked]="exactMatch()"
1349
+ (change)="setExactMatch($any($event.target).checked)"
1350
+ />
1351
+ {{ exactMatchLabel() }}
1352
+ </label>
1353
+ }
1354
+ </div>
1355
+ }
1356
+ @if (exportable() && !loading()) {
1357
+ <div class="neu-table__export-group">
1358
+ @if (exportFormats().includes('csv')) {
1359
+ <button
1360
+ class="neu-table__export-btn"
1361
+ type="button"
1362
+ (click)="exportCsv()"
1363
+ [title]="exportCsvTitle()"
1364
+ >
1365
+ <svg
1366
+ viewBox="0 0 24 24"
1367
+ fill="none"
1368
+ stroke="currentColor"
1369
+ stroke-width="2"
1370
+ stroke-linecap="round"
1371
+ stroke-linejoin="round"
1372
+ aria-hidden="true"
1373
+ width="15"
1374
+ height="15"
1375
+ >
1376
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
1377
+ <polyline points="7 10 12 15 17 10" />
1378
+ <line x1="12" y1="15" x2="12" y2="3" />
1379
+ </svg>
1380
+ CSV
1381
+ </button>
1382
+ }
1383
+ @if (exportFormats().includes('json')) {
1384
+ <button
1385
+ class="neu-table__export-btn"
1386
+ type="button"
1387
+ (click)="exportJson()"
1388
+ [title]="exportJsonTitle()"
1389
+ >
1390
+ <svg
1391
+ viewBox="0 0 24 24"
1392
+ fill="none"
1393
+ stroke="currentColor"
1394
+ stroke-width="2"
1395
+ stroke-linecap="round"
1396
+ stroke-linejoin="round"
1397
+ aria-hidden="true"
1398
+ width="15"
1399
+ height="15"
1400
+ >
1401
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
1402
+ <polyline points="7 10 12 15 17 10" />
1403
+ <line x1="12" y1="15" x2="12" y2="3" />
1404
+ </svg>
1405
+ JSON
1406
+ </button>
1407
+ }
1408
+ </div>
1409
+ }
1410
+ </div>
1411
+ }
1412
+
1413
+ <!-- Barra de selección (aparece al seleccionar) -->
1414
+ @if (selectable() && selectedCount() > 0) {
1415
+ <div class="neu-table__selection-bar">
1416
+ <span>{{ selectedRowsInfo() }}</span>
1417
+ <button class="neu-table__selection-clear" type="button" (click)="clearSelection()">
1418
+ {{ clearSelectionLabel() }}
1419
+ </button>
1420
+ </div>
1421
+ }
1422
+
1423
+ <!-- ---- Scroll container ---- -->
1424
+ <div
1425
+ class="neu-table__scroll-container"
1426
+ role="region"
1427
+ [attr.aria-label]="title() || tableAriaLabel()"
1428
+ >
1429
+ <table class="neu-table" [attr.aria-rowcount]="filteredData().length">
1430
+ <thead class="neu-table__head">
1431
+ <tr>
1432
+ <!-- Checkbox de selección global -->
1433
+ @if (expandable()) {
1434
+ <th class="neu-table__th neu-table__th--expand-col" scope="col"></th>
1435
+ }
1436
+ @if (selectable()) {
1437
+ <th class="neu-table__th neu-table__th--check" scope="col">
1438
+ <input
1439
+ type="checkbox"
1440
+ class="neu-table__checkbox"
1441
+ [checked]="isAllFilteredSelected()"
1442
+ [indeterminate]="isSomeFilteredSelected()"
1443
+ (change)="toggleAll()"
1444
+ [attr.aria-label]="selectAllAriaLabel()"
1445
+ />
1446
+ </th>
1447
+ }
1448
+ @if (showRowNumbers()) {
1449
+ <th class="neu-table__th neu-table__th--rn" scope="col">#</th>
1450
+ }
1451
+ @for (col of columns(); track col.key) {
1452
+ <th
1453
+ class="neu-table__th"
1454
+ [class.neu-table__th--sortable]="sortable() && col.sortable !== false"
1455
+ [class.neu-table__th--sorted-asc]="
1456
+ !multiSort() && sortKey() === col.key && sortDir() === 'asc'
1457
+ "
1458
+ [class.neu-table__th--sorted-desc]="
1459
+ !multiSort() && sortKey() === col.key && sortDir() === 'desc'
1460
+ "
1461
+ [class]="col.cellClass ?? ''"
1462
+ [class.neu-table__th--frozen-left]="col.frozen === 'left'"
1463
+ [class.neu-table__th--frozen-right]="col.frozen === 'right'"
1464
+ [class.neu-table__th--frozen-left-boundary]="isLastFrozenLeftColumn(col.key)"
1465
+ [class.neu-table__th--frozen-right-boundary]="isFirstFrozenRightColumn(col.key)"
1466
+ [style.left]="
1467
+ col.frozen === 'left' ? (_frozenLeftOffsets().get(col.key) ?? 0) + 'px' : null
1468
+ "
1469
+ [style.width]="col.width ?? 'auto'"
1470
+ [style.text-align]="col.align ?? 'left'"
1471
+ scope="col"
1472
+ (click)="sortable() && col.sortable !== false ? sortBy(col.key, $event) : null"
1473
+ >
1474
+ @if (col.headerTemplate) {
1475
+ <ng-container
1476
+ [ngTemplateOutlet]="col.headerTemplate"
1477
+ [ngTemplateOutletContext]="{ $implicit: col }"
1478
+ />
1479
+ } @else {
1480
+ {{ col.header }}
1481
+ }
1482
+ @if (sortable() && col.sortable !== false) {
1483
+ <span class="neu-table__sort-icon" aria-hidden="true">
1484
+ @if (multiSort()) {
1485
+ @let pri = getSortPriority(col.key);
1486
+ @if (pri >= 0) {
1487
+ <span class="neu-table__sort-priority">{{ pri + 1 }}</span>
1488
+ {{ _sortEntries()[pri]?.dir === 'asc' ? '↑' : '↓' }}
1489
+ } @else {
1490
+
1491
+ }
1492
+ } @else {
1493
+ @if (sortKey() === col.key) {
1494
+ {{ sortDir() === 'asc' ? '↑' : '↓' }}
1495
+ } @else {
1496
+
1497
+ }
1498
+ }
1499
+ </span>
1500
+ }
1501
+ </th>
1502
+ }
1503
+ </tr>
1504
+ <!-- ── Fila de filtros de columna / Column filter row ── -->
1505
+ @if (_hasFilterableCol()) {
1506
+ <tr class="neu-table__filter-row">
1507
+ @if (expandable()) {
1508
+ <th class="neu-table__th neu-table__th--filter-placeholder" scope="col"></th>
1509
+ }
1510
+ @if (selectable()) {
1511
+ <th class="neu-table__th neu-table__th--filter-placeholder" scope="col"></th>
1512
+ }
1513
+ @if (showRowNumbers()) {
1514
+ <th class="neu-table__th neu-table__th--filter-placeholder" scope="col"></th>
1515
+ }
1516
+ @for (col of columns(); track col.key) {
1517
+ <th
1518
+ class="neu-table__th neu-table__th--filter"
1519
+ scope="col"
1520
+ [style.width]="col.width ?? 'auto'"
1521
+ >
1522
+ @if (col.filterable) {
1523
+ @if (col.filterType === 'select') {
1524
+ <neu-select
1525
+ [floatingLabel]="false"
1526
+ [options]="_filterOpts(col)"
1527
+ [formControl]="columnFilterControl(col.key)"
1528
+ [attr.aria-label]="filterAriaPrefix() + ' ' + col.header"
1529
+ />
1530
+ } @else if (col.filterType === 'date') {
1531
+ <neu-date-input
1532
+ [formControl]="columnFilterControl(col.key)"
1533
+ [attr.aria-label]="filterAriaPrefix() + ' ' + col.header"
1534
+ />
1535
+ } @else {
1536
+ <neu-input
1537
+ [floatingLabel]="false"
1538
+ [placeholder]="filterPlaceholder()"
1539
+ [formControl]="columnFilterControl(col.key)"
1540
+ [attr.aria-label]="filterAriaPrefix() + ' ' + col.header"
1541
+ />
1542
+ }
1543
+ }
1544
+ </th>
1545
+ }
1546
+ </tr>
1547
+ }
1548
+ </thead>
1549
+
1550
+ <tbody class="neu-table__body">
1551
+ @if (loading()) {
1552
+ <tr class="neu-table__row--loading">
1553
+ <td [attr.colspan]="totalColspan()" class="neu-table__td neu-table__td--center">
1554
+ <div class="neu-table__skeleton-rows">
1555
+ @for (_ of skeletonRows(); track $index) {
1556
+ <div class="neu-table__skeleton-row">
1557
+ @for (__ of columns(); track $index) {
1558
+ <div class="neu-table__skeleton-cell"></div>
1559
+ }
1560
+ </div>
1561
+ }
1562
+ </div>
1563
+ </td>
1564
+ </tr>
1565
+ } @else if (paginatedData().length === 0) {
1566
+ <tr>
1567
+ <td [attr.colspan]="totalColspan()" class="neu-table__td neu-table__td--empty">
1568
+ @if (emptyStateTemplate()) {
1569
+ <ng-container [ngTemplateOutlet]="emptyStateTemplate()!" />
1570
+ } @else {
1571
+ <div class="neu-table__empty">
1572
+ <svg
1573
+ viewBox="0 0 24 24"
1574
+ fill="none"
1575
+ stroke="currentColor"
1576
+ stroke-width="1.5"
1577
+ stroke-linecap="round"
1578
+ stroke-linejoin="round"
1579
+ >
1580
+ <circle cx="11" cy="11" r="8" />
1581
+ <line x1="21" y1="21" x2="16.65" y2="16.65" />
1582
+ </svg>
1583
+ <p>{{ emptyMessage() }}</p>
1584
+ @if (searchQuery()) {
1585
+ <button
1586
+ class="neu-table__clear-filter"
1587
+ type="button"
1588
+ (click)="clearSearch()"
1589
+ >
1590
+ {{ clearFilterLabel() }}
1591
+ </button>
1592
+ }
1593
+ </div>
1594
+ }
1595
+ </td>
1596
+ </tr>
1597
+ } @else {
1598
+ @for (row of paginatedData(); track getRowKey(row); let rowIdx = $index) {
1599
+ <tr
1600
+ class="neu-table__row"
1601
+ [class]="getRowClass(row)"
1602
+ [class.neu-table__row--selected]="isRowSelected(row)"
1603
+ [class.neu-table__row--clickable]="selectable()"
1604
+ (click)="onRowClick(row, $event)"
1605
+ (dblclick)="rowDblClick.emit(row)"
1606
+ >
1607
+ @if (expandable()) {
1608
+ <td
1609
+ class="neu-table__td neu-table__td--expand-col"
1610
+ (click)="$event.stopPropagation()"
1611
+ >
1612
+ <button
1613
+ class="neu-table__expand-btn"
1614
+ type="button"
1615
+ (click)="toggleExpand(row)"
1616
+ [class.neu-table__expand-btn--open]="isRowExpanded(row)"
1617
+ [attr.aria-expanded]="isRowExpanded(row)"
1618
+ [attr.aria-label]="expandRowAriaLabel()"
1619
+ >
1620
+ <svg
1621
+ viewBox="0 0 24 24"
1622
+ fill="none"
1623
+ stroke="currentColor"
1624
+ stroke-width="2.5"
1625
+ stroke-linecap="round"
1626
+ stroke-linejoin="round"
1627
+ >
1628
+ <polyline points="9 18 15 12 9 6" />
1629
+ </svg>
1630
+ </button>
1631
+ </td>
1632
+ }
1633
+ @if (selectable()) {
1634
+ <td
1635
+ class="neu-table__td neu-table__th--check"
1636
+ (click)="$event.stopPropagation()"
1637
+ >
1638
+ <input
1639
+ type="checkbox"
1640
+ class="neu-table__checkbox"
1641
+ [checked]="isRowSelected(row)"
1642
+ (change)="toggleRow(row)"
1643
+ [attr.aria-label]="selectRowAriaLabel()"
1644
+ />
1645
+ </td>
1646
+ }
1647
+ @if (showRowNumbers()) {
1648
+ <td class="neu-table__td neu-table__td--rn">
1649
+ {{ (currentPage() - 1) * effectivePageSize() + rowIdx + 1 }}
1650
+ </td>
1651
+ }
1652
+ @for (col of columns(); track col.key) {
1653
+ <td
1654
+ class="neu-table__td"
1655
+ [class]="col.cellClass ?? ''"
1656
+ [class.neu-table__td--frozen-left]="col.frozen === 'left'"
1657
+ [class.neu-table__td--frozen-right]="col.frozen === 'right'"
1658
+ [class.neu-table__td--frozen-left-boundary]="isLastFrozenLeftColumn(col.key)"
1659
+ [class.neu-table__td--frozen-right-boundary]="
1660
+ isFirstFrozenRightColumn(col.key)
1661
+ "
1662
+ [style.left]="
1663
+ col.frozen === 'left'
1664
+ ? (_frozenLeftOffsets().get(col.key) ?? 0) + 'px'
1665
+ : null
1666
+ "
1667
+ [style.text-align]="col.align ?? 'left'"
1668
+ >
1669
+ @if (col.cellTemplate) {
1670
+ <ng-container
1671
+ [ngTemplateOutlet]="col.cellTemplate"
1672
+ [ngTemplateOutletContext]="{ $implicit: row, row: row, column: col }"
1673
+ />
1674
+ } @else if (col.type === 'badge') {
1675
+ @let badgeVal = getCellValue(row, col);
1676
+ @let badgeCfg = col.badgeMap?.[badgeVal];
1677
+ <span
1678
+ class="neu-table__cell-badge neu-table__cell-badge--{{
1679
+ badgeCfg?.variant ?? 'default'
1680
+ }}"
1681
+ >
1682
+ {{ badgeCfg?.label ?? badgeVal }}
1683
+ </span>
1684
+ } @else if (col.type === 'link') {
1685
+ <a
1686
+ class="neu-table__cell-link"
1687
+ [href]="col.linkHref ? col.linkHref(row) : '#'"
1688
+ [target]="col.linkTarget ?? '_self'"
1689
+ (click)="$event.stopPropagation()"
1690
+ >{{ getCellValue(row, col) }}</a
1691
+ >
1692
+ } @else if (col.type === 'actions') {
1693
+ <div class="neu-table__actions">
1694
+ @for (action of col.actions ?? []; track action.key) {
1695
+ @if (action.show === undefined || action.show(row)) {
1696
+ @if (isConfirmPending(row, action)) {
1697
+ <span class="neu-table__action-confirm">
1698
+ <span>{{ action.confirm }}</span>
1699
+ <button
1700
+ class="neu-table__action-btn neu-table__action-btn--danger"
1701
+ type="button"
1702
+ (click)="handleAction(row, action); $event.stopPropagation()"
1703
+ >
1704
+
1705
+ </button>
1706
+ <button
1707
+ class="neu-table__action-btn"
1708
+ type="button"
1709
+ (click)="cancelConfirm(); $event.stopPropagation()"
1710
+ >
1711
+ No
1712
+ </button>
1713
+ </span>
1714
+ } @else {
1715
+ <button
1716
+ class="neu-table__action-btn neu-table__action-btn--{{
1717
+ action.variant ?? 'ghost'
1718
+ }}"
1719
+ type="button"
1720
+ [disabled]="action.disabled ? action.disabled(row) : false"
1721
+ [title]="action.label"
1722
+ (click)="handleAction(row, action); $event.stopPropagation()"
1723
+ >
1724
+ @if (action.icon.startsWith('lucide')) {
1725
+ <neu-icon
1726
+ class="neu-table__action-icon"
1727
+ [name]="action.icon"
1728
+ size="1rem"
1729
+ aria-hidden="true"
1730
+ />
1731
+ } @else {
1732
+ <span class="neu-table__action-icon" aria-hidden="true">{{
1733
+ action.icon
1734
+ }}</span>
1735
+ }
1736
+ <span class="sr-only">{{ action.label }}</span>
1737
+ </button>
1738
+ }
1739
+ }
1740
+ }
1741
+ </div>
1742
+ } @else {
1743
+ {{ getCellValue(row, col) }}
1744
+ }
1745
+ </td>
1746
+ }
1747
+ </tr>
1748
+ @if (expandable() && isRowExpanded(row)) {
1749
+ <tr class="neu-table__row-expand-detail">
1750
+ <td [attr.colspan]="totalColspan()" class="neu-table__td--expand-panel">
1751
+ <div class="neu-table__expand-content">
1752
+ @if (expandTemplate(); as tpl) {
1753
+ <ng-container
1754
+ [ngTemplateOutlet]="tpl.templateRef"
1755
+ [ngTemplateOutletContext]="{ $implicit: row }"
1756
+ />
1757
+ }
1758
+ </div>
1759
+ </td>
1760
+ </tr>
1761
+ }
1762
+ }
1763
+ }
1764
+ </tbody>
1765
+ @if (footerRow()) {
1766
+ <tfoot class="neu-table__foot">
1767
+ <tr class="neu-table__footer-row">
1768
+ @if (expandable()) {
1769
+ <td class="neu-table__td"></td>
1770
+ }
1771
+ @if (selectable()) {
1772
+ <td class="neu-table__td"></td>
1773
+ }
1774
+ @if (showRowNumbers()) {
1775
+ <td class="neu-table__td"></td>
1776
+ }
1777
+ @for (col of columns(); track col.key) {
1778
+ <td
1779
+ class="neu-table__td neu-table__td--footer"
1780
+ [style.text-align]="col.align ?? 'left'"
1781
+ >
1782
+ {{ footerRow()![col.key] !== undefined ? footerRow()![col.key] : '' }}
1783
+ </td>
1784
+ }
1785
+ </tr>
1786
+ </tfoot>
1787
+ }
1788
+ </table>
1789
+ </div>
1790
+
1791
+ <!-- ---- Footer ---- -->
1792
+ @if (!loading() && filteredData().length > 0) {
1793
+ <div class="neu-table__footer">
1794
+ @if (pageSizeOptions().length > 0) {
1795
+ <div class="neu-table__page-size">
1796
+ <label class="neu-table__page-size-label">{{ pageSizeLabel() }}</label>
1797
+ <neu-select
1798
+ class="neu-table__page-size-select"
1799
+ [floatingLabel]="false"
1800
+ [options]="_pageSizeOptions()"
1801
+ [formControl]="_pageSizeControl"
1802
+ [attr.aria-label]="pageSizeAriaLabel()"
1803
+ size="sm"
1804
+ />
1805
+ </div>
1806
+ }
1807
+ <span class="neu-table__info">
1808
+ {{ paginationInfo() }}
1809
+ </span>
1810
+ @if (totalPages() > 1) {
1811
+ <nav class="neu-table__pagination" [attr.aria-label]="paginationAriaLabel()">
1812
+ <button
1813
+ class="neu-table__page-btn"
1814
+ [disabled]="currentPage() === 1"
1815
+ (click)="goToPage(currentPage() - 1)"
1816
+ [attr.aria-label]="previousPageAriaLabel()"
1817
+ type="button"
1818
+ >
1819
+ <svg
1820
+ viewBox="0 0 24 24"
1821
+ fill="none"
1822
+ stroke="currentColor"
1823
+ stroke-width="2.5"
1824
+ stroke-linecap="round"
1825
+ stroke-linejoin="round"
1826
+ >
1827
+ <polyline points="15 18 9 12 15 6" />
1828
+ </svg>
1829
+ </button>
1830
+ @for (page of pageNumbers(); track page) {
1831
+ <button
1832
+ class="neu-table__page-btn"
1833
+ [class.neu-table__page-btn--active]="page === currentPage()"
1834
+ (click)="goToPage(page)"
1835
+ [attr.aria-current]="page === currentPage() ? 'page' : null"
1836
+ type="button"
1837
+ >
1838
+ {{ page }}
1839
+ </button>
1840
+ }
1841
+ <button
1842
+ class="neu-table__page-btn"
1843
+ [disabled]="currentPage() === totalPages()"
1844
+ (click)="goToPage(currentPage() + 1)"
1845
+ [attr.aria-label]="nextPageAriaLabel()"
1846
+ type="button"
1847
+ >
1848
+ <svg
1849
+ viewBox="0 0 24 24"
1850
+ fill="none"
1851
+ stroke="currentColor"
1852
+ stroke-width="2.5"
1853
+ stroke-linecap="round"
1854
+ stroke-linejoin="round"
1855
+ >
1856
+ <polyline points="9 18 15 12 9 6" />
1857
+ </svg>
1858
+ </button>
1859
+ </nav>
1860
+ }
1861
+ </div>
1862
+ }
1863
+ </div>
1864
+ `, styles: [".neu-table-container{background:var(--neu-surface);border:1px solid var(--neu-border);border-radius:var(--neu-radius-lg);overflow:hidden;box-shadow:var(--neu-shadow-sm)}.neu-table__toolbar{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:var(--neu-space-3);padding:var(--neu-space-4) var(--neu-space-5);border-bottom:1px solid var(--neu-border);background:var(--neu-surface)}.neu-table__title{font-size:var(--neu-text-base);font-weight:600;color:var(--neu-text);margin:0;letter-spacing:-.01em}.neu-table__search-group{display:flex;align-items:center;gap:var(--neu-space-3);flex:1;max-width:420px;min-width:0}.neu-table__search-wrapper{position:relative;display:flex;align-items:center;flex:1;min-width:0}.neu-table__search-icon{position:absolute;left:var(--neu-space-3);width:15px;height:15px;color:var(--neu-text-muted);pointer-events:none;flex-shrink:0}.neu-table__search{width:100%;height:36px;padding:0 var(--neu-space-3) 0 36px;border:1.5px solid var(--neu-border);border-radius:var(--neu-radius);background:var(--neu-surface-2);font-family:var(--neu-font-sans);font-size:var(--neu-text-sm);color:var(--neu-text);outline:none;transition:border-color var(--neu-transition),box-shadow var(--neu-transition)}.neu-table__search::placeholder{color:var(--neu-text-muted)}.neu-table__search:focus{border-color:var(--neu-primary);box-shadow:var(--neu-focus-ring);background:var(--neu-surface)}.neu-table__search::-webkit-search-cancel-button{display:none}.neu-table__search-clear{position:absolute;right:var(--neu-space-2);display:flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:none;background:transparent;color:var(--neu-text-muted);cursor:pointer;border-radius:var(--neu-radius-sm);transition:color var(--neu-transition),background-color var(--neu-transition)}.neu-table__search-clear svg{width:13px;height:13px}.neu-table__search-clear:hover{color:var(--neu-error);background:var(--neu-error-bg)}.neu-table__exact-label{display:inline-flex;align-items:center;gap:6px;font-size:var(--neu-text-sm);color:var(--neu-text-muted);cursor:pointer;white-space:nowrap;-webkit-user-select:none;user-select:none}.neu-table__exact-label:hover{color:var(--neu-text)}.neu-table__exact-checkbox{width:14px;height:14px;accent-color:var(--neu-primary);cursor:pointer}.neu-table__scroll-container{position:relative;isolation:isolate;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:thin;scrollbar-color:var(--neu-surface-3) transparent}.neu-table__scroll-container::-webkit-scrollbar{height:4px}.neu-table__scroll-container::-webkit-scrollbar-track{background:transparent}.neu-table__scroll-container::-webkit-scrollbar-thumb{background:var(--neu-surface-3);border-radius:99px}@media(max-width:399px){.neu-table__scroll-container{background-image:linear-gradient(to right,var(--neu-surface),var(--neu-surface)),linear-gradient(to right,var(--neu-surface),var(--neu-surface)),linear-gradient(to right,rgba(15,23,42,.08),transparent),linear-gradient(to left,rgba(15,23,42,.08),transparent);background-position:left center,right center,left center,right center;background-repeat:no-repeat;background-color:var(--neu-surface);background-size:20px 100%,20px 100%,10px 100%,10px 100%;background-attachment:local,local,scroll,scroll}}.neu-table{width:100%;border-collapse:separate;border-spacing:0;font-size:var(--neu-text-sm);min-width:400px}.neu-table--sticky-header .neu-table__scroll-container{overflow-y:auto;max-height:var(--neu-table-max-height, 480px)}.neu-table--sticky-header .neu-table__head tr:first-child .neu-table__th{position:sticky;top:0;z-index:2;background:var(--neu-surface-2);box-shadow:inset 0 -1px 0 var(--neu-border)}.neu-table--sticky-header .neu-table__th--filter,.neu-table--sticky-header .neu-table__th--filter-placeholder{position:sticky;top:var(--neu-table-header-height, 44px);z-index:1;background:var(--neu-surface-2)}.neu-table__head{background:var(--neu-surface-2)}.neu-table__th{padding:var(--neu-space-3) var(--neu-space-4);font-size:var(--neu-text-xs);font-weight:600;color:var(--neu-text-muted);text-transform:uppercase;letter-spacing:.06em;border-bottom:1px solid var(--neu-border);white-space:nowrap;-webkit-user-select:none;user-select:none;background-clip:padding-box}.neu-table__th--check{width:40px;text-align:center!important;padding:var(--neu-space-3) var(--neu-space-3)}.neu-table__th--sortable{cursor:pointer;transition:color var(--neu-transition)}.neu-table__th--sortable:hover{color:var(--neu-text)}.neu-table__th--sorted-asc,.neu-table__th--sorted-desc{color:var(--neu-primary)}.neu-table__sort-icon{margin-left:4px;font-style:normal;font-size:10px;opacity:.5}.neu-table__th--sorted-asc .neu-table__sort-icon,.neu-table__th--sorted-desc .neu-table__sort-icon{opacity:1}.neu-table__checkbox{width:16px;height:16px;cursor:pointer;accent-color:var(--neu-primary)}.neu-table__selection-bar{display:flex;align-items:center;justify-content:space-between;padding:var(--neu-space-2) var(--neu-space-5);background:var(--neu-primary-50);border-bottom:1px solid var(--neu-primary-100);font-size:var(--neu-text-sm);color:var(--neu-primary);font-weight:500;animation:neu-tab-fade .15s ease}.neu-table__selection-clear{background:none;border:none;color:var(--neu-primary);font-size:var(--neu-text-xs);font-weight:500;cursor:pointer;text-decoration:underline;text-underline-offset:2px;padding:0;font-family:var(--neu-font-sans)}.neu-table__row{transition:background-color var(--neu-transition)}.neu-table__row:hover{background:var(--neu-surface-2)}.neu-table__row:not(:last-child) .neu-table__td{border-bottom:1px solid var(--neu-border)}.neu-table__row--selected{background:var(--neu-primary-50)!important}.neu-table__row--selected .neu-table__td{color:var(--neu-primary)}.neu-table__row--clickable{cursor:pointer}.neu-table__td{padding:var(--neu-space-3) var(--neu-space-4);color:var(--neu-text);line-height:1.5;vertical-align:middle;background-clip:padding-box}.neu-table__td--center{text-align:center}.neu-table__td--empty{padding:var(--neu-space-8) 0}.neu-table__skeleton-rows{display:flex;flex-direction:column;gap:1px}.neu-table__skeleton-row{display:flex;align-items:center;padding:var(--neu-space-3) var(--neu-space-4);gap:var(--neu-space-4);border-bottom:1px solid var(--neu-border)}.neu-table__skeleton-cell{height:14px;flex:1;border-radius:var(--neu-radius-sm);background:linear-gradient(90deg,var(--neu-surface-2) 0%,var(--neu-surface-3) 50%,var(--neu-surface-2) 100%);background-size:200% 100%;animation:neu-skeleton 1.4s ease infinite}.neu-table__skeleton-cell:first-child{max-width:60px}.neu-table__skeleton-cell:nth-child(2){max-width:160px}.neu-table__skeleton-cell:last-child{max-width:80px}@keyframes neu-skeleton{0%{background-position:200% 0}to{background-position:-200% 0}}.neu-table__empty{display:flex;flex-direction:column;align-items:center;gap:var(--neu-space-3);padding:var(--neu-space-8) var(--neu-space-4);color:var(--neu-text-muted)}.neu-table__empty svg{width:40px;height:40px;opacity:.4}.neu-table__empty p{font-size:var(--neu-text-sm);margin:0}.neu-table__clear-filter{background:none;border:none;color:var(--neu-primary);font-size:var(--neu-text-xs);font-weight:500;cursor:pointer;padding:0;text-decoration:underline;text-underline-offset:2px}.neu-table__clear-filter:hover{color:var(--neu-primary-dark)}.neu-table__footer{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:var(--neu-space-3);padding:var(--neu-space-3) var(--neu-space-5);border-top:1px solid var(--neu-border);background:var(--neu-surface-2)}.neu-table__info{font-size:var(--neu-text-xs);color:var(--neu-text-muted)}.neu-table__pagination{display:flex;align-items:center;gap:2px}.neu-table__page-btn{display:inline-flex;align-items:center;justify-content:center;min-width:32px;height:32px;padding:0 var(--neu-space-2);border:1px solid transparent;border-radius:var(--neu-radius);background:transparent;font-family:var(--neu-font-sans);font-size:var(--neu-text-xs);font-weight:500;color:var(--neu-text-muted);cursor:pointer;transition:all var(--neu-transition)}.neu-table__page-btn svg{width:14px;height:14px}.neu-table__page-btn:hover:not(:disabled){background:var(--neu-surface-3);color:var(--neu-text);border-color:var(--neu-border)}.neu-table__page-btn:disabled{opacity:.35;cursor:not-allowed}.neu-table__page-btn--active{background:var(--neu-primary);color:var(--neu-primary-fg);border-color:var(--neu-primary)}.neu-table__page-btn--active:hover{background:var(--neu-primary-dark);border-color:var(--neu-primary-dark)}.neu-table__export-btn{display:inline-flex;align-items:center;gap:6px;height:36px;padding:0 var(--neu-space-3);border:1.5px solid var(--neu-border);border-radius:var(--neu-radius);background:var(--neu-surface);font-family:var(--neu-font-sans);font-size:var(--neu-text-sm);font-weight:500;color:var(--neu-text-muted);cursor:pointer;white-space:nowrap;transition:all var(--neu-transition);flex-shrink:0}.neu-table__export-btn svg{flex-shrink:0}.neu-table__export-btn:hover{color:var(--neu-primary);border-color:var(--neu-primary);background:var(--neu-primary-50)}.neu-table__page-size{display:flex;align-items:center;gap:var(--neu-space-2);min-width:0}.neu-table__page-size-label{font-size:var(--neu-text-xs);color:var(--neu-text-muted)}.neu-table__page-size-select{display:block;min-width:78px}.neu-table__page-size-select .neu-select__trigger{min-width:78px}.neu-table__th--expand-col,.neu-table__td--expand-col{width:36px;padding:var(--neu-space-2) var(--neu-space-2);text-align:center}.neu-table__expand-btn{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border:none;background:transparent;color:var(--neu-text-muted);cursor:pointer;border-radius:var(--neu-radius-sm);transition:all var(--neu-transition);padding:0}.neu-table__expand-btn svg{width:14px;height:14px;transition:transform .2s ease}.neu-table__expand-btn:hover{color:var(--neu-primary);background:var(--neu-primary-50)}.neu-table__expand-btn--open svg{transform:rotate(90deg)}.neu-table__row-expand-detail{background:var(--neu-surface-2)}.neu-table__td--expand-panel{padding:0}.neu-table__expand-content{padding:var(--neu-space-4) var(--neu-space-5);border-bottom:1px solid var(--neu-border);animation:neu-tab-fade .18s ease}.neu-table__cell-badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:var(--neu-radius-full);font-size:var(--neu-text-xs);font-weight:600;letter-spacing:.02em;white-space:nowrap}.neu-table__cell-badge--primary{background:var(--neu-primary-100);color:var(--neu-primary-dark)}.neu-table__cell-badge--success{background:var(--neu-table-badge-success-bg);color:var(--neu-table-badge-success-text)}.neu-table__cell-badge--warning{background:var(--neu-table-badge-warning-bg);color:var(--neu-table-badge-warning-text)}.neu-table__cell-badge--danger{background:var(--neu-table-badge-danger-bg);color:var(--neu-table-badge-danger-text)}.neu-table__cell-badge--info{background:var(--neu-primary-50);color:var(--neu-primary)}.neu-table__cell-badge--default{background:var(--neu-surface-3);color:var(--neu-text-muted)}@keyframes neu-tab-fade{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.neu-table__host--compact .neu-table__th,.neu-table__host--compact .neu-table__td{padding:var(--neu-table-density-compact-padding, 4px 8px)}.neu-table__host--relaxed .neu-table__th,.neu-table__host--relaxed .neu-table__td{padding:var(--neu-table-density-relaxed-padding, 16px 20px)}.neu-table__th--rn,.neu-table__td--rn{width:44px;text-align:right;padding-right:var(--neu-space-4);color:var(--neu-text-muted);font-variant-numeric:tabular-nums;font-size:var(--neu-text-xs)}.neu-table__th--frozen-left,.neu-table__th--frozen-right{position:sticky;z-index:3;background:var(--neu-surface-2)}.neu-table__th--frozen-left{left:0}.neu-table__th--frozen-right{right:0}.neu-table__th--frozen-left-boundary{box-shadow:inset -1px 0 0 var(--neu-border),6px 0 8px -8px #0f172a52}.neu-table__th--frozen-right-boundary{box-shadow:inset 1px 0 0 var(--neu-border),-6px 0 8px -8px #0f172a52}.neu-table__td--frozen-left,.neu-table__td--frozen-right{position:sticky;z-index:2;background:var(--neu-surface)}.neu-table__td--frozen-left{left:0}.neu-table__td--frozen-right{right:0}.neu-table__td--frozen-left-boundary{box-shadow:inset -1px 0 0 var(--neu-border),6px 0 8px -8px #0f172a52}.neu-table__td--frozen-right-boundary{box-shadow:inset 1px 0 0 var(--neu-border),-6px 0 8px -8px #0f172a52}.neu-table__foot{background:var(--neu-surface-2)}.neu-table__footer-row .neu-table__td--footer{padding:var(--neu-space-3) var(--neu-space-4);font-weight:600;font-size:var(--neu-text-sm);border-top:2px solid var(--neu-border);color:var(--neu-text)}.neu-table__sort-priority{display:inline-flex;align-items:center;justify-content:center;width:14px;height:14px;border-radius:50%;background:var(--neu-primary);color:var(--neu-primary-fg);font-size:9px;font-weight:700;margin-right:2px}.neu-table__cell-link{color:var(--neu-primary);text-decoration:none;text-underline-offset:2px}.neu-table__cell-link:hover{text-decoration:underline}.neu-table__actions{display:flex;align-items:center;gap:var(--neu-space-1);flex-wrap:nowrap}.neu-table__action-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;padding:0;border:none;border-radius:var(--neu-radius-sm);background:transparent;font-size:14px;cursor:pointer;transition:all var(--neu-transition);color:var(--neu-text-muted)}.neu-table__action-btn:disabled{opacity:.35;cursor:not-allowed}.neu-table__action-btn--ghost:hover:not(:disabled){background:var(--neu-surface-3);color:var(--neu-text)}.neu-table__action-btn--primary{background:var(--neu-primary-50);color:var(--neu-primary)}.neu-table__action-btn--primary:hover:not(:disabled){background:var(--neu-primary);color:var(--neu-primary-fg)}.neu-table__action-btn--danger{color:var(--neu-error)}.neu-table__action-btn--danger:hover:not(:disabled){background:var(--neu-error-bg)}.neu-table__action-icon{line-height:1}.neu-table__action-confirm{display:inline-flex;align-items:center;gap:var(--neu-space-1);font-size:var(--neu-text-xs);color:var(--neu-text-muted);white-space:nowrap}.neu-table__action-confirm .neu-table__action-btn{width:auto;padding:0 var(--neu-space-2);font-size:var(--neu-text-xs);font-family:var(--neu-font-sans);font-weight:500;border:1px solid var(--neu-border)}.neu-table__export-group{display:flex;align-items:center;gap:var(--neu-space-2);flex-shrink:0}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.neu-table__filter-row .neu-table__th--filter,.neu-table__filter-row .neu-table__th--filter-placeholder{padding:var(--neu-space-2) var(--neu-space-3);background:var(--neu-surface-2);border-bottom:2px solid var(--neu-border);font-weight:400;text-transform:none;letter-spacing:0;vertical-align:middle}.neu-table__col-filter-input,.neu-table__col-filter-select{display:block;width:100%;height:28px;padding:0 var(--neu-space-2);border:1px solid var(--neu-border);border-radius:var(--neu-radius-sm);background:var(--neu-surface);font-family:var(--neu-font-sans);font-size:var(--neu-text-xs);color:var(--neu-text);font-weight:400;outline:none;transition:border-color var(--neu-transition),box-shadow var(--neu-transition)}.neu-table__col-filter-input::placeholder,.neu-table__col-filter-select::placeholder{color:var(--neu-text-muted)}.neu-table__col-filter-input:focus,.neu-table__col-filter-select:focus{border-color:var(--neu-primary);box-shadow:0 0 0 2px #007aff1a;background:var(--neu-surface)}.neu-table__col-filter-select{cursor:pointer}.neu-table--scrollable .neu-table__scroll-container{max-height:480px;overflow-y:auto}.neu-table--scrollable .neu-table__head .neu-table__th{position:sticky;top:0;z-index:2;background:var(--neu-surface-2);box-shadow:0 1px 0 var(--neu-border)}\n"] }]
1865
+ }], ctorParameters: () => [], propDecorators: { expandTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NeuTableExpandDirective), { isSignal: true }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], skeletonRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "skeletonRows", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], exactMatchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "exactMatchable", required: false }] }], exactMatchLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "exactMatchLabel", required: false }] }], searchAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchAriaLabel", required: false }] }], clearSearchAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearSearchAriaLabel", required: false }] }], clearFilterLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearFilterLabel", required: false }] }], previousPageAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "previousPageAriaLabel", required: false }] }], nextPageAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "nextPageAriaLabel", required: false }] }], pageSizeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizeLabel", required: false }] }], pageSizeAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizeAriaLabel", required: false }] }], paginationAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "paginationAriaLabel", required: false }] }], exportCsvTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "exportCsvTitle", required: false }] }], exportJsonTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "exportJsonTitle", required: false }] }], clearSelectionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearSelectionLabel", required: false }] }], selectionSummaryLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionSummaryLabel", required: false }] }], tableAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "tableAriaLabel", required: false }] }], selectAllAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectAllAriaLabel", required: false }] }], selectRowAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectRowAriaLabel", required: false }] }], expandRowAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandRowAriaLabel", required: false }] }], filterPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterPlaceholder", required: false }] }], filterAriaPrefix: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterAriaPrefix", required: false }] }], allFilterOptionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "allFilterOptionLabel", required: false }] }], ofLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ofLabel", required: false }] }], resultLabelSingular: [{ type: i0.Input, args: [{ isSignal: true, alias: "resultLabelSingular", required: false }] }], resultLabelPlural: [{ type: i0.Input, args: [{ isSignal: true, alias: "resultLabelPlural", required: false }] }], sortable: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortable", required: false }] }], selectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectable", required: false }] }], expandable: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandable", required: false }] }], exportable: [{ type: i0.Input, args: [{ isSignal: true, alias: "exportable", required: false }] }], exportFileName: [{ type: i0.Input, args: [{ isSignal: true, alias: "exportFileName", required: false }] }], pageSizeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizeOptions", required: false }] }], stickyHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "stickyHeader", required: false }] }], rowKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowKey", required: false }] }], density: [{ type: i0.Input, args: [{ isSignal: true, alias: "density", required: false }] }], showRowNumbers: [{ type: i0.Input, args: [{ isSignal: true, alias: "showRowNumbers", required: false }] }], rowClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowClass", required: false }] }], footerRow: [{ type: i0.Input, args: [{ isSignal: true, alias: "footerRow", required: false }] }], emptyStateTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyStateTemplate", required: false }] }], serverSide: [{ type: i0.Input, args: [{ isSignal: true, alias: "serverSide", required: false }] }], totalItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalItems", required: false }] }], multiSort: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiSort", required: false }] }], exportFormats: [{ type: i0.Input, args: [{ isSignal: true, alias: "exportFormats", required: false }] }], exportColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "exportColumns", required: false }] }], pageParam: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageParam", required: false }] }], searchParam: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchParam", required: false }] }], sortParam: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortParam", required: false }] }], sortDirParam: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortDirParam", required: false }] }], multiSortParam: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiSortParam", required: false }] }], useUrlState: [{ type: i0.Input, args: [{ isSignal: true, alias: "useUrlState", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], rowDblClick: [{ type: i0.Output, args: ["rowDblClick"] }], actionClick: [{ type: i0.Output, args: ["actionClick"] }], serverStateChange: [{ type: i0.Output, args: ["serverStateChange"] }], searchChange: [{ type: i0.Output, args: ["searchChange"] }] } });
1866
+
1867
+ /**
1868
+ * Generated bundle index. Do not edit.
1869
+ */
1870
+
1871
+ export { NeuTableComponent, NeuTableExpandDirective };
1872
+ //# sourceMappingURL=neural-ui-core-table.mjs.map