@neural-ui/core 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +332 -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 +748 -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 +638 -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 +1860 -0
  120. package/fesm2022/neural-ui-core-table.mjs.map +1 -0
  121. package/fesm2022/neural-ui-core-tabs.mjs +246 -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 +69 -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 +129 -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 +161 -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 +282 -0
  228. package/types/neural-ui-core-tabs.d.ts +76 -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,748 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, TemplateRef, Directive, ElementRef, effect, untracked, contentChild, input, output, signal, computed, forwardRef, ChangeDetectionStrategy, ViewEncapsulation, Component } from '@angular/core';
3
+ import { NeuUrlStateService } from '@neural-ui/core/url-state';
4
+ import { NgTemplateOutlet } from '@angular/common';
5
+ import { NG_VALUE_ACCESSOR } from '@angular/forms';
6
+
7
+ /**
8
+ * Directiva para personalizar el template de cada ítem del dropdown de Multiselect. / Directive to customize the template of each Multiselect dropdown item.
9
+ *
10
+ * Uso:
11
+ * ```html
12
+ * <neu-multiselect [options]="opts" [formControl]="valuesCtrl">
13
+ * <ng-template neuMultiselectItem let-item>
14
+ * <span class="flag flag-{{ item.value }}"></span>
15
+ * {{ item.label }}
16
+ * </ng-template>
17
+ * </neu-multiselect>
18
+ * ```
19
+ */
20
+ class NeuMultiselectItemDirective {
21
+ templateRef = inject(TemplateRef);
22
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NeuMultiselectItemDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
23
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.8", type: NeuMultiselectItemDirective, isStandalone: true, selector: "[neuMultiselectItem]", ngImport: i0 });
24
+ }
25
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NeuMultiselectItemDirective, decorators: [{
26
+ type: Directive,
27
+ args: [{ selector: '[neuMultiselectItem]', standalone: true }]
28
+ }] });
29
+
30
+ let _neuMultiselectIdSeq = 0;
31
+ /**
32
+ * NeuralUI Multiselect Component
33
+ *
34
+ * Dropdown de selección múltiple con chips, búsqueda integrada y soporte / Multiple selection dropdown with chips, integrated search and support
35
+ * para ControlValueAccessor y Reactive Forms. Puede usarse dentro de un FormGroup o con un FormControl standalone. / for ControlValueAccessor and Reactive Forms. It can be used inside a FormGroup or with a standalone FormControl.
36
+ *
37
+ * Uso:
38
+ * readonly technologiesCtrl = new FormControl<string[]>([], { nonNullable: true });
39
+ * <neu-multiselect label="Tecnologías" [options]="opts" [formControl]="technologiesCtrl" />
40
+ */
41
+ class NeuMultiselectComponent {
42
+ elementRef = inject(ElementRef);
43
+ _urlState = inject(NeuUrlStateService);
44
+ _mobileViewportMax = 768;
45
+ _viewportMargin = 16;
46
+ constructor() {
47
+ effect(() => {
48
+ const param = this.urlParam();
49
+ if (!param)
50
+ return;
51
+ const urlRaw = this._urlState.getParam(param)();
52
+ const urlVals = urlRaw ? urlRaw.split(',').filter(Boolean) : [];
53
+ const current = untracked(() => this._values());
54
+ if (JSON.stringify(urlVals) !== JSON.stringify(current)) {
55
+ this._values.set(urlVals);
56
+ this._onChange(urlVals);
57
+ }
58
+ });
59
+ }
60
+ /** @internal */
61
+ _triggerId = `neu-multiselect-trigger-${_neuMultiselectIdSeq++}`;
62
+ /** Template personalizado para cada opción del dropdown / Custom template for each dropdown option */
63
+ itemTpl = contentChild(NeuMultiselectItemDirective, ...(ngDevMode ? [{ debugName: "itemTpl" }] : /* istanbul ignore next */ []));
64
+ /** Opciones del dropdown / Dropdown options */
65
+ options = input([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
66
+ /** Etiqueta del componente / Component label */
67
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
68
+ /** Muestra el label como flotante dentro del campo (true) o estático encima (false) / Shows the label as floating inside the field (true) or static above (false) */
69
+ floatingLabel = input(false, ...(ngDevMode ? [{ debugName: "floatingLabel" }] : /* istanbul ignore next */ []));
70
+ /** Tamaño del campo: 'sm' = 36px | 'md' = 48px | 'lg' = 56px / Field size */
71
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
72
+ /** Placeholder cuando no hay selección / Placeholder when there is no selection */
73
+ placeholder = input('Seleccionar...', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
74
+ /** Mensaje de error / Error message */
75
+ errorMessage = input('', ...(ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []));
76
+ /** Deshabilita el componente / Disables the component */
77
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
78
+ /** Activa input de búsqueda/filtro en el panel / Activates the search/filter input in the panel */
79
+ searchable = input(false, ...(ngDevMode ? [{ debugName: "searchable" }] : /* istanbul ignore next */ []));
80
+ /** Placeholder del input de búsqueda / Search input placeholder */
81
+ searchPlaceholder = input('Buscar...', ...(ngDevMode ? [{ debugName: "searchPlaceholder" }] : /* istanbul ignore next */ []));
82
+ /** Texto cuando no hay opciones tras filtrar / Text when no options remain after filtering */
83
+ noResultsMessage = input('Sin resultados', ...(ngDevMode ? [{ debugName: "noResultsMessage" }] : /* istanbul ignore next */ []));
84
+ /** Texto del botón de limpiar todas las selecciones / Button text to clear all selections */
85
+ clearAllLabel = input('Limpiar todo', ...(ngDevMode ? [{ debugName: "clearAllLabel" }] : /* istanbul ignore next */ []));
86
+ /** Muestra un botón × en el trigger para limpiar la selección de una vez / Shows a × button in the trigger to clear the selection at once */
87
+ clearable = input(false, ...(ngDevMode ? [{ debugName: "clearable" }] : /* istanbul ignore next */ []));
88
+ /** Aria-label del botón clear que aparece en el trigger / Aria-label for the clear button shown in the trigger */
89
+ clearAriaLabel = input('Limpiar selección', ...(ngDevMode ? [{ debugName: "clearAriaLabel" }] : /* istanbul ignore next */ []));
90
+ /**
91
+ * Sincroniza los valores seleccionados con este query param de la URL.
92
+ * Los valores se codifican como lista separada por comas: `?{urlParam}=a,b,c`.
93
+ * Pasar `null` (default) deshabilita la sincronización.
94
+ */
95
+ urlParam = input(null, ...(ngDevMode ? [{ debugName: "urlParam" }] : /* istanbul ignore next */ []));
96
+ /**
97
+ * Emite el array de NeuSelectOption completo (incluyendo data) al cambiar la selección.
98
+ * Emite [] al limpiar toda la selección.
99
+ * Los valores del formControl siguen siendo string[].
100
+ */
101
+ selectionChange = output();
102
+ // --- Estado interno ---
103
+ _values = signal([], ...(ngDevMode ? [{ debugName: "_values" }] : /* istanbul ignore next */ []));
104
+ isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
105
+ searchQuery = signal('', ...(ngDevMode ? [{ debugName: "searchQuery" }] : /* istanbul ignore next */ []));
106
+ _chipMode = signal('count', ...(ngDevMode ? [{ debugName: "_chipMode" }] : /* istanbul ignore next */ []));
107
+ panelPosition = signal({
108
+ position: null,
109
+ top: null,
110
+ left: null,
111
+ width: null,
112
+ maxHeight: null,
113
+ }, ...(ngDevMode ? [{ debugName: "panelPosition" }] : /* istanbul ignore next */ []));
114
+ _visibleChips = computed(() => this._values().slice(0, 3), ...(ngDevMode ? [{ debugName: "_visibleChips" }] : /* istanbul ignore next */ []));
115
+ hasError = computed(() => !!this.errorMessage(), ...(ngDevMode ? [{ debugName: "hasError" }] : /* istanbul ignore next */ []));
116
+ filteredOptions = computed(() => {
117
+ const q = this.searchQuery().toLowerCase().trim();
118
+ if (!q)
119
+ return this.options();
120
+ return this.options().filter((o) => o.label.toLowerCase().includes(q));
121
+ }, ...(ngDevMode ? [{ debugName: "filteredOptions" }] : /* istanbul ignore next */ []));
122
+ // --- CVA ---
123
+ _onChange = () => { };
124
+ _onTouched = () => { };
125
+ writeValue(value) {
126
+ this._values.set(Array.isArray(value) ? value : []);
127
+ }
128
+ registerOnChange(fn) {
129
+ this._onChange = fn;
130
+ }
131
+ registerOnTouched(fn) {
132
+ this._onTouched = fn;
133
+ }
134
+ _cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_cvaDisabled" }] : /* istanbul ignore next */ []));
135
+ setDisabledState(isDisabled) {
136
+ this._cvaDisabled.set(isDisabled);
137
+ }
138
+ isDisabledFinal = computed(() => this.disabled() || this._cvaDisabled(), ...(ngDevMode ? [{ debugName: "isDisabledFinal" }] : /* istanbul ignore next */ []));
139
+ // --- Helpers ---
140
+ labelFor(value) {
141
+ return this.options().find((o) => o.value === value)?.label ?? value;
142
+ }
143
+ isSelected(value) {
144
+ return this._values().includes(value);
145
+ }
146
+ toggle() {
147
+ if (this.isDisabledFinal())
148
+ return;
149
+ this.isOpen.update((v) => !v);
150
+ if (!this.isOpen()) {
151
+ this.searchQuery.set('');
152
+ this.resetPanelPosition();
153
+ this._onTouched();
154
+ }
155
+ else {
156
+ this.syncPanelPosition();
157
+ requestAnimationFrame(() => {
158
+ const first = this.elementRef.nativeElement.querySelector('.neu-multiselect__option:not([aria-disabled="true"])');
159
+ first?.focus();
160
+ });
161
+ }
162
+ }
163
+ /** Abre el panel y mueve el foco al primer item / Opens the panel and moves focus to the first item */
164
+ onTriggerKey(event) {
165
+ event.preventDefault();
166
+ if (!this.isOpen()) {
167
+ this.isOpen.set(true);
168
+ this.syncPanelPosition();
169
+ requestAnimationFrame(() => {
170
+ const first = this.elementRef.nativeElement.querySelector('.neu-multiselect__option:not([aria-disabled="true"])');
171
+ first?.focus();
172
+ });
173
+ }
174
+ }
175
+ /** Navega entre opciones con flechas / Navigates between options with arrows */
176
+ focusOptionByIndex(event, current, dir) {
177
+ event.preventDefault();
178
+ const opts = this.filteredOptions().filter((o) => !o.disabled);
179
+ const idx = opts.findIndex((o) => o.value === current.value);
180
+ const next = opts[(idx + dir + opts.length) % opts.length];
181
+ if (next) {
182
+ const el = this.elementRef.nativeElement.querySelector(`#neu-ms-opt-${next.value}`);
183
+ el?.focus();
184
+ }
185
+ }
186
+ close() {
187
+ this.isOpen.set(false);
188
+ this.searchQuery.set('');
189
+ this.resetPanelPosition();
190
+ this._onTouched();
191
+ }
192
+ toggleOption(option) {
193
+ if (option.disabled)
194
+ return;
195
+ const current = this._values();
196
+ const next = current.includes(option.value)
197
+ ? current.filter((v) => v !== option.value)
198
+ : [...current, option.value];
199
+ this._values.set(next);
200
+ this._onChange(next);
201
+ this.selectionChange.emit(this.options().filter((o) => next.includes(o.value)));
202
+ const param = this.urlParam();
203
+ if (param)
204
+ this._urlState.setParam(param, next.length ? next.join(',') : null);
205
+ }
206
+ removeValue(value, event) {
207
+ event.stopPropagation();
208
+ const next = this._values().filter((v) => v !== value);
209
+ this._values.set(next);
210
+ this._onChange(next);
211
+ this._onTouched();
212
+ this.selectionChange.emit(this.options().filter((o) => next.includes(o.value)));
213
+ const param = this.urlParam();
214
+ if (param)
215
+ this._urlState.setParam(param, next.length ? next.join(',') : null);
216
+ }
217
+ clearAll(event) {
218
+ event.stopPropagation();
219
+ this._values.set([]);
220
+ this._onChange([]);
221
+ this._onTouched();
222
+ this.selectionChange.emit([]);
223
+ const param = this.urlParam();
224
+ if (param)
225
+ this._urlState.setParam(param, null);
226
+ }
227
+ toggleChipMode(event) {
228
+ event.stopPropagation();
229
+ this._chipMode.set(this._chipMode() === 'chips' ? 'count' : 'chips');
230
+ }
231
+ onDocumentClick(event) {
232
+ if (!this.elementRef.nativeElement.contains(event.target)) {
233
+ this.close();
234
+ }
235
+ }
236
+ onWindowResize() {
237
+ if (this.isOpen()) {
238
+ this.syncPanelPosition();
239
+ }
240
+ }
241
+ onWindowScroll() {
242
+ if (this.isOpen()) {
243
+ this.syncPanelPosition();
244
+ }
245
+ }
246
+ syncPanelPosition() {
247
+ requestAnimationFrame(() => {
248
+ const trigger = this.elementRef.nativeElement.querySelector('.neu-multiselect__trigger');
249
+ if (!trigger)
250
+ return;
251
+ if (window.innerWidth > this._mobileViewportMax) {
252
+ this.resetPanelPosition();
253
+ return;
254
+ }
255
+ const triggerRect = trigger.getBoundingClientRect();
256
+ const viewportWidth = window.innerWidth;
257
+ const viewportHeight = window.innerHeight;
258
+ const width = Math.min(triggerRect.width, viewportWidth - this._viewportMargin * 2);
259
+ const left = Math.min(Math.max(triggerRect.left, this._viewportMargin), viewportWidth - this._viewportMargin - width);
260
+ const top = triggerRect.bottom + 6;
261
+ const maxHeight = Math.max(180, viewportHeight - top - this._viewportMargin);
262
+ this.panelPosition.set({
263
+ position: 'fixed',
264
+ top: `${top}px`,
265
+ left: `${left}px`,
266
+ width: `${width}px`,
267
+ maxHeight: `${maxHeight}px`,
268
+ });
269
+ });
270
+ }
271
+ resetPanelPosition() {
272
+ this.panelPosition.set({
273
+ position: null,
274
+ top: null,
275
+ left: null,
276
+ width: null,
277
+ maxHeight: null,
278
+ });
279
+ }
280
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NeuMultiselectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
281
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: NeuMultiselectComponent, isStandalone: true, selector: "neu-multiselect", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, floatingLabel: { classPropertyName: "floatingLabel", publicName: "floatingLabel", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", 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 }, noResultsMessage: { classPropertyName: "noResultsMessage", publicName: "noResultsMessage", isSignal: true, isRequired: false, transformFunction: null }, clearAllLabel: { classPropertyName: "clearAllLabel", publicName: "clearAllLabel", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, clearAriaLabel: { classPropertyName: "clearAriaLabel", publicName: "clearAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, urlParam: { classPropertyName: "urlParam", publicName: "urlParam", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange" }, host: { listeners: { "document:click": "onDocumentClick($event)", "keydown.escape": "close()", "window:resize": "onWindowResize()", "window:scroll": "onWindowScroll()" } }, providers: [
282
+ {
283
+ provide: NG_VALUE_ACCESSOR,
284
+ useExisting: forwardRef(() => NeuMultiselectComponent),
285
+ multi: true,
286
+ },
287
+ ], queries: [{ propertyName: "itemTpl", first: true, predicate: NeuMultiselectItemDirective, descendants: true, isSignal: true }], ngImport: i0, template: `
288
+ @if (!floatingLabel() && label()) {
289
+ <label class="neu-multiselect__static-label" [for]="_triggerId">{{ label() }}</label>
290
+ }
291
+ <div
292
+ class="neu-multiselect"
293
+ [class.neu-multiselect--open]="isOpen()"
294
+ [class.neu-multiselect--disabled]="isDisabledFinal()"
295
+ [class.neu-multiselect--error]="hasError()"
296
+ [class.neu-multiselect--has-value]="_values().length > 0"
297
+ [class.neu-multiselect--no-float]="!floatingLabel()"
298
+ [class.neu-multiselect--sm]="size() === 'sm'"
299
+ [class.neu-multiselect--lg]="size() === 'lg'"
300
+ >
301
+ <!-- Trigger -->
302
+ <button
303
+ class="neu-multiselect__trigger"
304
+ type="button"
305
+ [id]="_triggerId"
306
+ [disabled]="isDisabledFinal()"
307
+ [attr.role]="'combobox'"
308
+ [attr.aria-haspopup]="'listbox'"
309
+ [attr.aria-expanded]="isOpen()"
310
+ [attr.aria-invalid]="hasError() ? 'true' : null"
311
+ [attr.aria-label]="label() || null"
312
+ (click)="toggle()"
313
+ (keydown.arrowDown)="onTriggerKey($any($event))"
314
+ (keydown.arrowUp)="onTriggerKey($any($event))"
315
+ >
316
+ <!-- Floating label -->
317
+ @if (floatingLabel() && label()) {
318
+ <span class="neu-multiselect__label">{{ label() }}</span>
319
+ }
320
+ <span class="neu-multiselect__chips">
321
+ @if (_values().length === 0) {
322
+ <span class="neu-multiselect__placeholder">{{ placeholder() }}</span>
323
+ } @else if (_chipMode() === 'count') {
324
+ <span class="neu-multiselect__count-badge">{{ _values().length }} seleccionados</span>
325
+ } @else {
326
+ @for (val of _visibleChips(); track val) {
327
+ <span class="neu-multiselect__chip">
328
+ {{ labelFor(val) }}
329
+ <button
330
+ class="neu-multiselect__chip-remove"
331
+ type="button"
332
+ [attr.aria-label]="'Eliminar ' + labelFor(val)"
333
+ (click)="removeValue(val, $event)"
334
+ >
335
+ <svg
336
+ viewBox="0 0 24 24"
337
+ fill="none"
338
+ stroke="currentColor"
339
+ stroke-width="2.5"
340
+ stroke-linecap="round"
341
+ aria-hidden="true"
342
+ >
343
+ <line x1="18" y1="6" x2="6" y2="18" />
344
+ <line x1="6" y1="6" x2="18" y2="18" />
345
+ </svg>
346
+ </button>
347
+ </span>
348
+ }
349
+ @if (_values().length > 3) {
350
+ <span class="neu-multiselect__chip neu-multiselect__chip--overflow"
351
+ >+{{ _values().length - 3 }}</span
352
+ >
353
+ }
354
+ }
355
+ </span>
356
+
357
+ <!-- Clear button -->
358
+ @if (clearable() && _values().length > 0 && !isDisabledFinal()) {
359
+ <button
360
+ class="neu-multiselect__clear"
361
+ type="button"
362
+ [attr.aria-label]="clearAriaLabel()"
363
+ (click)="clearAll($event)"
364
+ >
365
+ <svg
366
+ viewBox="0 0 24 24"
367
+ fill="none"
368
+ stroke="currentColor"
369
+ stroke-width="2.5"
370
+ stroke-linecap="round"
371
+ aria-hidden="true"
372
+ >
373
+ <line x1="18" y1="6" x2="6" y2="18" />
374
+ <line x1="6" y1="6" x2="18" y2="18" />
375
+ </svg>
376
+ </button>
377
+ }
378
+
379
+ <!-- Chevron -->
380
+ <svg
381
+ class="neu-multiselect__chevron"
382
+ viewBox="0 0 24 24"
383
+ fill="none"
384
+ stroke="currentColor"
385
+ stroke-width="2"
386
+ stroke-linecap="round"
387
+ stroke-linejoin="round"
388
+ aria-hidden="true"
389
+ >
390
+ <polyline points="6 9 12 15 18 9" />
391
+ </svg>
392
+ </button>
393
+
394
+ <!-- Panel -->
395
+ @if (isOpen()) {
396
+ <div
397
+ class="neu-multiselect__panel"
398
+ role="listbox"
399
+ [attr.aria-multiselectable]="true"
400
+ [attr.aria-label]="label() || null"
401
+ [style.position]="panelPosition().position"
402
+ [style.top]="panelPosition().top"
403
+ [style.left]="panelPosition().left"
404
+ [style.width]="panelPosition().width"
405
+ [style.max-height]="panelPosition().maxHeight"
406
+ >
407
+ <!-- Search -->
408
+ @if (searchable()) {
409
+ <div class="neu-multiselect__search">
410
+ <input
411
+ class="neu-multiselect__search-input"
412
+ type="text"
413
+ [attr.aria-label]="'Search ' + label()"
414
+ [placeholder]="searchPlaceholder()"
415
+ [value]="searchQuery()"
416
+ (input)="searchQuery.set($any($event.target).value)"
417
+ (click)="$event.stopPropagation()"
418
+ />
419
+ </div>
420
+ }
421
+
422
+ <!-- Opciones -->
423
+ <div class="neu-multiselect__options">
424
+ @for (option of filteredOptions(); track option.value) {
425
+ <div
426
+ class="neu-multiselect__option"
427
+ [class.neu-multiselect__option--selected]="isSelected(option.value)"
428
+ [class.neu-multiselect__option--disabled]="option.disabled"
429
+ role="option"
430
+ [id]="'neu-ms-opt-' + option.value"
431
+ [attr.aria-selected]="isSelected(option.value)"
432
+ [attr.aria-disabled]="option.disabled"
433
+ [attr.tabindex]="option.disabled ? null : '-1'"
434
+ (click)="toggleOption(option)"
435
+ (keydown.enter)="toggleOption(option)"
436
+ (keydown.space)="toggleOption(option)"
437
+ (keydown.arrowDown)="focusOptionByIndex($any($event), option, 1)"
438
+ (keydown.arrowUp)="focusOptionByIndex($any($event), option, -1)"
439
+ >
440
+ <!-- Checkbox visual -->
441
+ <span
442
+ class="neu-multiselect__checkbox"
443
+ [class.neu-multiselect__checkbox--checked]="isSelected(option.value)"
444
+ >
445
+ <svg
446
+ class="neu-multiselect__checkbox-check"
447
+ viewBox="0 0 12 10"
448
+ fill="none"
449
+ stroke="currentColor"
450
+ stroke-width="2"
451
+ stroke-linecap="round"
452
+ stroke-linejoin="round"
453
+ aria-hidden="true"
454
+ >
455
+ <polyline points="1 5 4.5 9 11 1" />
456
+ </svg>
457
+ </span>
458
+ @if (itemTpl()) {
459
+ <ng-container
460
+ [ngTemplateOutlet]="itemTpl()!.templateRef"
461
+ [ngTemplateOutletContext]="{ $implicit: option }"
462
+ />
463
+ } @else {
464
+ {{ option.label }}
465
+ }
466
+ </div>
467
+ }
468
+
469
+ @if (filteredOptions().length === 0) {
470
+ <div class="neu-multiselect__empty">{{ noResultsMessage() }}</div>
471
+ }
472
+ </div>
473
+
474
+ <!-- Footer: resumen + limpiar + toggle modo -->
475
+ @if (_values().length > 0) {
476
+ <div class="neu-multiselect__footer">
477
+ <span class="neu-multiselect__footer-count"
478
+ >{{ _values().length }} seleccionados</span
479
+ >
480
+ <div class="neu-multiselect__footer-actions">
481
+ <button
482
+ class="neu-multiselect__footer-mode"
483
+ type="button"
484
+ [attr.aria-label]="_chipMode() === 'chips' ? 'Mostrar contador' : 'Mostrar chips'"
485
+ (click)="toggleChipMode($event)"
486
+ >
487
+ {{ _chipMode() === 'chips' ? '#' : '☰' }}
488
+ </button>
489
+ <button
490
+ class="neu-multiselect__footer-clear"
491
+ type="button"
492
+ (click)="clearAll($event)"
493
+ >
494
+ {{ clearAllLabel() }}
495
+ </button>
496
+ </div>
497
+ </div>
498
+ }
499
+ </div>
500
+ }
501
+ </div>
502
+
503
+ @if (hasError()) {
504
+ <p class="neu-multiselect__error" role="alert">{{ errorMessage() }}</p>
505
+ }
506
+ `, isInline: true, styles: [".neu-multiselect__static-label{display:block;font-size:var(--neu-text-sm);font-weight:500;color:var(--neu-text-muted);margin-bottom:var(--neu-space-2)}.neu-multiselect__label{position:absolute;left:var(--neu-space-3);top:50%;transform:translateY(-50%);font-size:var(--neu-text-base);color:var(--neu-text-muted);pointer-events:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:calc(100% - var(--neu-space-8));transition:top var(--neu-transition),font-size var(--neu-transition),color var(--neu-transition),transform var(--neu-transition),padding var(--neu-transition),background var(--neu-transition)}.neu-multiselect--open .neu-multiselect__label,.neu-multiselect--has-value .neu-multiselect__label{top:0;transform:translateY(-50%);font-size:12px;font-weight:600;letter-spacing:.01em;background:var(--neu-surface);padding:0 4px;left:calc(var(--neu-space-3) - 4px)}.neu-multiselect--open .neu-multiselect__label{color:var(--neu-primary)}.neu-multiselect--error .neu-multiselect__label{color:var(--neu-error)}.neu-multiselect--disabled .neu-multiselect__label{background:var(--neu-surface-2)}.neu-multiselect{position:relative;font-family:var(--neu-font-sans)}.neu-multiselect__trigger{display:flex;align-items:center;width:100%;min-height:48px;padding:var(--neu-space-2) var(--neu-space-3);padding-right:36px;background:var(--neu-surface);border:1.5px solid var(--neu-border);border-radius:var(--neu-radius);cursor:pointer;text-align:left;transition:border-color var(--neu-transition),box-shadow var(--neu-transition)}.neu-multiselect__trigger:hover:not(:disabled){border-color:var(--neu-border-hover)}.neu-multiselect__trigger:focus-visible{outline:none;border-color:var(--neu-primary);box-shadow:0 0 0 3px #007aff1f}.neu-multiselect__trigger:disabled{opacity:.6;cursor:not-allowed;background:var(--neu-surface-2)}.neu-multiselect--open .neu-multiselect__trigger{border-color:var(--neu-primary);box-shadow:0 0 0 3px #007aff1f}.neu-multiselect--error .neu-multiselect__trigger{border-color:var(--neu-error)}.neu-multiselect--error .neu-multiselect__trigger:focus-visible{box-shadow:0 0 0 3px #ef44441f}.neu-multiselect__chips{display:flex;flex-wrap:wrap;gap:var(--neu-space-1);flex:1;min-width:0}.neu-multiselect__placeholder{color:var(--neu-text-disabled);font-size:var(--neu-text-base);line-height:1.5}.neu-multiselect:not(.neu-multiselect--no-float):not(.neu-multiselect--open) .neu-multiselect__placeholder{visibility:hidden}.neu-multiselect__chip{display:inline-flex;align-items:center;gap:4px;padding:2px 4px 2px 8px;background:var(--neu-primary-soft, rgba(0, 122, 255, .1));color:var(--neu-primary);border-radius:var(--neu-radius-sm);font-size:var(--neu-text-xs);font-weight:500;max-width:160px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.neu-multiselect__chip-remove{display:flex;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:none;background:transparent;cursor:pointer;color:inherit;opacity:.7;border-radius:2px;transition:opacity var(--neu-transition);flex-shrink:0}.neu-multiselect__chip-remove:hover{opacity:1}.neu-multiselect__chip-remove svg{width:10px;height:10px}.neu-multiselect__clear{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;margin-right:2px;padding:0;border:none;background:transparent;color:var(--neu-text-muted);cursor:pointer;border-radius:var(--neu-radius-sm);flex-shrink:0;transition:color var(--neu-transition),background var(--neu-transition)}.neu-multiselect__clear svg{width:14px;height:14px}.neu-multiselect__clear:hover{color:var(--neu-text);background:var(--neu-surface-3)}.neu-multiselect__chevron{position:absolute;right:var(--neu-space-3);top:50%;transform:translateY(-50%);width:16px;height:16px;color:var(--neu-text-muted);pointer-events:none;transition:transform var(--neu-transition);flex-shrink:0}.neu-multiselect--open .neu-multiselect__chevron{transform:translateY(-50%) rotate(180deg)}.neu-multiselect__panel{position:absolute;top:calc(100% + 6px);left:0;right:0;z-index:200;background:var(--neu-surface);border:1.5px solid var(--neu-border);border-radius:var(--neu-radius);box-shadow:var(--neu-shadow-lg);overflow:hidden;max-height:280px;display:flex;flex-direction:column;animation:neu-multiselect-fade-in .12s ease}@media(max-width:600px){.neu-multiselect__panel{left:auto;right:0;width:min(max(100%,240px),100vw - 2rem);max-width:calc(100vw - 2rem)}}@keyframes neu-multiselect-fade-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.neu-multiselect__search{padding:var(--neu-space-2);border-bottom:1px solid var(--neu-border);flex-shrink:0}.neu-multiselect__search-input{width:100%;height:34px;padding:0 var(--neu-space-3);border:1.5px solid var(--neu-border);border-radius:var(--neu-radius-sm);background:var(--neu-bg);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-multiselect__search-input:focus{border-color:var(--neu-primary);box-shadow:0 0 0 2px #007aff1f}.neu-multiselect__search-input::placeholder{color:var(--neu-text-disabled)}.neu-multiselect__options{flex:1;overflow-y:auto;min-height:0}.neu-multiselect__option{display:flex;align-items:center;gap:var(--neu-space-2);padding:10px var(--neu-space-3);cursor:pointer;font-size:var(--neu-text-sm);color:var(--neu-text);transition:background var(--neu-transition)}.neu-multiselect__option:hover:not(.neu-multiselect__option--disabled){background:var(--neu-surface-2)}.neu-multiselect__option--selected{background:var(--neu-primary-soft, rgba(0, 122, 255, .06));color:var(--neu-primary);font-weight:500}.neu-multiselect__option--disabled{opacity:.4;cursor:not-allowed}.neu-multiselect__checkbox{display:flex;align-items:center;justify-content:center;width:16px;height:16px;border-radius:3px;border:1.5px solid var(--neu-border);background:var(--neu-bg);flex-shrink:0;transition:border-color var(--neu-transition),background var(--neu-transition)}.neu-multiselect__checkbox--checked{background:var(--neu-primary);border-color:var(--neu-primary);color:#fff}.neu-multiselect__checkbox-check{width:10px;height:8px;opacity:0;transform:scale(.6);transition:opacity .12s ease,transform .12s ease}.neu-multiselect__checkbox--checked .neu-multiselect__checkbox-check{opacity:1;transform:scale(1)}.neu-multiselect__empty{padding:var(--neu-space-4);text-align:center;font-size:var(--neu-text-sm);color:var(--neu-text-disabled);font-family:var(--neu-font-sans)}.neu-multiselect__footer{display:flex;align-items:center;justify-content:space-between;padding:var(--neu-space-2) var(--neu-space-3);border-top:1px solid var(--neu-border);background:var(--neu-surface);flex-shrink:0}.neu-multiselect__footer-count{font-size:var(--neu-text-xs);color:var(--neu-text-muted)}.neu-multiselect__footer-actions{display:flex;align-items:center;gap:var(--neu-space-2)}.neu-multiselect__footer-mode{background:none;border:1px solid var(--neu-border);border-radius:var(--neu-radius-sm);padding:2px 6px;font-size:var(--neu-text-xs);font-family:var(--neu-font-sans);color:var(--neu-text-muted);cursor:pointer;line-height:1.4}.neu-multiselect__footer-mode:hover{background:var(--neu-surface-2);color:var(--neu-text)}.neu-multiselect__footer-clear{background:none;border:none;padding:0;font-size:var(--neu-text-xs);font-family:var(--neu-font-sans);color:var(--neu-primary);cursor:pointer;font-weight:500}.neu-multiselect__footer-clear:hover{text-decoration:underline}.neu-multiselect__count-badge{display:inline-flex;align-items:center;padding:2px 10px;background:var(--neu-primary-100, rgba(14, 165, 233, .12));color:var(--neu-primary);border-radius:var(--neu-radius-full);font-size:var(--neu-text-sm);font-weight:500}.neu-multiselect__chip--overflow{background:var(--neu-surface-2);color:var(--neu-text-muted);border:1px dashed var(--neu-border);cursor:default;pointer-events:none}.neu-multiselect__error{margin-top:var(--neu-space-1);font-size:var(--neu-text-xs);color:var(--neu-error-text, var(--neu-error));font-family:var(--neu-font-sans)}.neu-multiselect--sm .neu-multiselect__trigger{min-height:36px;font-size:var(--neu-text-sm);padding:var(--neu-space-1) var(--neu-space-2);padding-right:36px}.neu-multiselect--lg .neu-multiselect__trigger{min-height:56px}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
507
+ }
508
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NeuMultiselectComponent, decorators: [{
509
+ type: Component,
510
+ args: [{ selector: 'neu-multiselect', imports: [NgTemplateOutlet], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, providers: [
511
+ {
512
+ provide: NG_VALUE_ACCESSOR,
513
+ useExisting: forwardRef(() => NeuMultiselectComponent),
514
+ multi: true,
515
+ },
516
+ ], host: {
517
+ '(document:click)': 'onDocumentClick($event)',
518
+ '(keydown.escape)': 'close()',
519
+ '(window:resize)': 'onWindowResize()',
520
+ '(window:scroll)': 'onWindowScroll()',
521
+ }, template: `
522
+ @if (!floatingLabel() && label()) {
523
+ <label class="neu-multiselect__static-label" [for]="_triggerId">{{ label() }}</label>
524
+ }
525
+ <div
526
+ class="neu-multiselect"
527
+ [class.neu-multiselect--open]="isOpen()"
528
+ [class.neu-multiselect--disabled]="isDisabledFinal()"
529
+ [class.neu-multiselect--error]="hasError()"
530
+ [class.neu-multiselect--has-value]="_values().length > 0"
531
+ [class.neu-multiselect--no-float]="!floatingLabel()"
532
+ [class.neu-multiselect--sm]="size() === 'sm'"
533
+ [class.neu-multiselect--lg]="size() === 'lg'"
534
+ >
535
+ <!-- Trigger -->
536
+ <button
537
+ class="neu-multiselect__trigger"
538
+ type="button"
539
+ [id]="_triggerId"
540
+ [disabled]="isDisabledFinal()"
541
+ [attr.role]="'combobox'"
542
+ [attr.aria-haspopup]="'listbox'"
543
+ [attr.aria-expanded]="isOpen()"
544
+ [attr.aria-invalid]="hasError() ? 'true' : null"
545
+ [attr.aria-label]="label() || null"
546
+ (click)="toggle()"
547
+ (keydown.arrowDown)="onTriggerKey($any($event))"
548
+ (keydown.arrowUp)="onTriggerKey($any($event))"
549
+ >
550
+ <!-- Floating label -->
551
+ @if (floatingLabel() && label()) {
552
+ <span class="neu-multiselect__label">{{ label() }}</span>
553
+ }
554
+ <span class="neu-multiselect__chips">
555
+ @if (_values().length === 0) {
556
+ <span class="neu-multiselect__placeholder">{{ placeholder() }}</span>
557
+ } @else if (_chipMode() === 'count') {
558
+ <span class="neu-multiselect__count-badge">{{ _values().length }} seleccionados</span>
559
+ } @else {
560
+ @for (val of _visibleChips(); track val) {
561
+ <span class="neu-multiselect__chip">
562
+ {{ labelFor(val) }}
563
+ <button
564
+ class="neu-multiselect__chip-remove"
565
+ type="button"
566
+ [attr.aria-label]="'Eliminar ' + labelFor(val)"
567
+ (click)="removeValue(val, $event)"
568
+ >
569
+ <svg
570
+ viewBox="0 0 24 24"
571
+ fill="none"
572
+ stroke="currentColor"
573
+ stroke-width="2.5"
574
+ stroke-linecap="round"
575
+ aria-hidden="true"
576
+ >
577
+ <line x1="18" y1="6" x2="6" y2="18" />
578
+ <line x1="6" y1="6" x2="18" y2="18" />
579
+ </svg>
580
+ </button>
581
+ </span>
582
+ }
583
+ @if (_values().length > 3) {
584
+ <span class="neu-multiselect__chip neu-multiselect__chip--overflow"
585
+ >+{{ _values().length - 3 }}</span
586
+ >
587
+ }
588
+ }
589
+ </span>
590
+
591
+ <!-- Clear button -->
592
+ @if (clearable() && _values().length > 0 && !isDisabledFinal()) {
593
+ <button
594
+ class="neu-multiselect__clear"
595
+ type="button"
596
+ [attr.aria-label]="clearAriaLabel()"
597
+ (click)="clearAll($event)"
598
+ >
599
+ <svg
600
+ viewBox="0 0 24 24"
601
+ fill="none"
602
+ stroke="currentColor"
603
+ stroke-width="2.5"
604
+ stroke-linecap="round"
605
+ aria-hidden="true"
606
+ >
607
+ <line x1="18" y1="6" x2="6" y2="18" />
608
+ <line x1="6" y1="6" x2="18" y2="18" />
609
+ </svg>
610
+ </button>
611
+ }
612
+
613
+ <!-- Chevron -->
614
+ <svg
615
+ class="neu-multiselect__chevron"
616
+ viewBox="0 0 24 24"
617
+ fill="none"
618
+ stroke="currentColor"
619
+ stroke-width="2"
620
+ stroke-linecap="round"
621
+ stroke-linejoin="round"
622
+ aria-hidden="true"
623
+ >
624
+ <polyline points="6 9 12 15 18 9" />
625
+ </svg>
626
+ </button>
627
+
628
+ <!-- Panel -->
629
+ @if (isOpen()) {
630
+ <div
631
+ class="neu-multiselect__panel"
632
+ role="listbox"
633
+ [attr.aria-multiselectable]="true"
634
+ [attr.aria-label]="label() || null"
635
+ [style.position]="panelPosition().position"
636
+ [style.top]="panelPosition().top"
637
+ [style.left]="panelPosition().left"
638
+ [style.width]="panelPosition().width"
639
+ [style.max-height]="panelPosition().maxHeight"
640
+ >
641
+ <!-- Search -->
642
+ @if (searchable()) {
643
+ <div class="neu-multiselect__search">
644
+ <input
645
+ class="neu-multiselect__search-input"
646
+ type="text"
647
+ [attr.aria-label]="'Search ' + label()"
648
+ [placeholder]="searchPlaceholder()"
649
+ [value]="searchQuery()"
650
+ (input)="searchQuery.set($any($event.target).value)"
651
+ (click)="$event.stopPropagation()"
652
+ />
653
+ </div>
654
+ }
655
+
656
+ <!-- Opciones -->
657
+ <div class="neu-multiselect__options">
658
+ @for (option of filteredOptions(); track option.value) {
659
+ <div
660
+ class="neu-multiselect__option"
661
+ [class.neu-multiselect__option--selected]="isSelected(option.value)"
662
+ [class.neu-multiselect__option--disabled]="option.disabled"
663
+ role="option"
664
+ [id]="'neu-ms-opt-' + option.value"
665
+ [attr.aria-selected]="isSelected(option.value)"
666
+ [attr.aria-disabled]="option.disabled"
667
+ [attr.tabindex]="option.disabled ? null : '-1'"
668
+ (click)="toggleOption(option)"
669
+ (keydown.enter)="toggleOption(option)"
670
+ (keydown.space)="toggleOption(option)"
671
+ (keydown.arrowDown)="focusOptionByIndex($any($event), option, 1)"
672
+ (keydown.arrowUp)="focusOptionByIndex($any($event), option, -1)"
673
+ >
674
+ <!-- Checkbox visual -->
675
+ <span
676
+ class="neu-multiselect__checkbox"
677
+ [class.neu-multiselect__checkbox--checked]="isSelected(option.value)"
678
+ >
679
+ <svg
680
+ class="neu-multiselect__checkbox-check"
681
+ viewBox="0 0 12 10"
682
+ fill="none"
683
+ stroke="currentColor"
684
+ stroke-width="2"
685
+ stroke-linecap="round"
686
+ stroke-linejoin="round"
687
+ aria-hidden="true"
688
+ >
689
+ <polyline points="1 5 4.5 9 11 1" />
690
+ </svg>
691
+ </span>
692
+ @if (itemTpl()) {
693
+ <ng-container
694
+ [ngTemplateOutlet]="itemTpl()!.templateRef"
695
+ [ngTemplateOutletContext]="{ $implicit: option }"
696
+ />
697
+ } @else {
698
+ {{ option.label }}
699
+ }
700
+ </div>
701
+ }
702
+
703
+ @if (filteredOptions().length === 0) {
704
+ <div class="neu-multiselect__empty">{{ noResultsMessage() }}</div>
705
+ }
706
+ </div>
707
+
708
+ <!-- Footer: resumen + limpiar + toggle modo -->
709
+ @if (_values().length > 0) {
710
+ <div class="neu-multiselect__footer">
711
+ <span class="neu-multiselect__footer-count"
712
+ >{{ _values().length }} seleccionados</span
713
+ >
714
+ <div class="neu-multiselect__footer-actions">
715
+ <button
716
+ class="neu-multiselect__footer-mode"
717
+ type="button"
718
+ [attr.aria-label]="_chipMode() === 'chips' ? 'Mostrar contador' : 'Mostrar chips'"
719
+ (click)="toggleChipMode($event)"
720
+ >
721
+ {{ _chipMode() === 'chips' ? '#' : '☰' }}
722
+ </button>
723
+ <button
724
+ class="neu-multiselect__footer-clear"
725
+ type="button"
726
+ (click)="clearAll($event)"
727
+ >
728
+ {{ clearAllLabel() }}
729
+ </button>
730
+ </div>
731
+ </div>
732
+ }
733
+ </div>
734
+ }
735
+ </div>
736
+
737
+ @if (hasError()) {
738
+ <p class="neu-multiselect__error" role="alert">{{ errorMessage() }}</p>
739
+ }
740
+ `, styles: [".neu-multiselect__static-label{display:block;font-size:var(--neu-text-sm);font-weight:500;color:var(--neu-text-muted);margin-bottom:var(--neu-space-2)}.neu-multiselect__label{position:absolute;left:var(--neu-space-3);top:50%;transform:translateY(-50%);font-size:var(--neu-text-base);color:var(--neu-text-muted);pointer-events:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:calc(100% - var(--neu-space-8));transition:top var(--neu-transition),font-size var(--neu-transition),color var(--neu-transition),transform var(--neu-transition),padding var(--neu-transition),background var(--neu-transition)}.neu-multiselect--open .neu-multiselect__label,.neu-multiselect--has-value .neu-multiselect__label{top:0;transform:translateY(-50%);font-size:12px;font-weight:600;letter-spacing:.01em;background:var(--neu-surface);padding:0 4px;left:calc(var(--neu-space-3) - 4px)}.neu-multiselect--open .neu-multiselect__label{color:var(--neu-primary)}.neu-multiselect--error .neu-multiselect__label{color:var(--neu-error)}.neu-multiselect--disabled .neu-multiselect__label{background:var(--neu-surface-2)}.neu-multiselect{position:relative;font-family:var(--neu-font-sans)}.neu-multiselect__trigger{display:flex;align-items:center;width:100%;min-height:48px;padding:var(--neu-space-2) var(--neu-space-3);padding-right:36px;background:var(--neu-surface);border:1.5px solid var(--neu-border);border-radius:var(--neu-radius);cursor:pointer;text-align:left;transition:border-color var(--neu-transition),box-shadow var(--neu-transition)}.neu-multiselect__trigger:hover:not(:disabled){border-color:var(--neu-border-hover)}.neu-multiselect__trigger:focus-visible{outline:none;border-color:var(--neu-primary);box-shadow:0 0 0 3px #007aff1f}.neu-multiselect__trigger:disabled{opacity:.6;cursor:not-allowed;background:var(--neu-surface-2)}.neu-multiselect--open .neu-multiselect__trigger{border-color:var(--neu-primary);box-shadow:0 0 0 3px #007aff1f}.neu-multiselect--error .neu-multiselect__trigger{border-color:var(--neu-error)}.neu-multiselect--error .neu-multiselect__trigger:focus-visible{box-shadow:0 0 0 3px #ef44441f}.neu-multiselect__chips{display:flex;flex-wrap:wrap;gap:var(--neu-space-1);flex:1;min-width:0}.neu-multiselect__placeholder{color:var(--neu-text-disabled);font-size:var(--neu-text-base);line-height:1.5}.neu-multiselect:not(.neu-multiselect--no-float):not(.neu-multiselect--open) .neu-multiselect__placeholder{visibility:hidden}.neu-multiselect__chip{display:inline-flex;align-items:center;gap:4px;padding:2px 4px 2px 8px;background:var(--neu-primary-soft, rgba(0, 122, 255, .1));color:var(--neu-primary);border-radius:var(--neu-radius-sm);font-size:var(--neu-text-xs);font-weight:500;max-width:160px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.neu-multiselect__chip-remove{display:flex;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:none;background:transparent;cursor:pointer;color:inherit;opacity:.7;border-radius:2px;transition:opacity var(--neu-transition);flex-shrink:0}.neu-multiselect__chip-remove:hover{opacity:1}.neu-multiselect__chip-remove svg{width:10px;height:10px}.neu-multiselect__clear{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;margin-right:2px;padding:0;border:none;background:transparent;color:var(--neu-text-muted);cursor:pointer;border-radius:var(--neu-radius-sm);flex-shrink:0;transition:color var(--neu-transition),background var(--neu-transition)}.neu-multiselect__clear svg{width:14px;height:14px}.neu-multiselect__clear:hover{color:var(--neu-text);background:var(--neu-surface-3)}.neu-multiselect__chevron{position:absolute;right:var(--neu-space-3);top:50%;transform:translateY(-50%);width:16px;height:16px;color:var(--neu-text-muted);pointer-events:none;transition:transform var(--neu-transition);flex-shrink:0}.neu-multiselect--open .neu-multiselect__chevron{transform:translateY(-50%) rotate(180deg)}.neu-multiselect__panel{position:absolute;top:calc(100% + 6px);left:0;right:0;z-index:200;background:var(--neu-surface);border:1.5px solid var(--neu-border);border-radius:var(--neu-radius);box-shadow:var(--neu-shadow-lg);overflow:hidden;max-height:280px;display:flex;flex-direction:column;animation:neu-multiselect-fade-in .12s ease}@media(max-width:600px){.neu-multiselect__panel{left:auto;right:0;width:min(max(100%,240px),100vw - 2rem);max-width:calc(100vw - 2rem)}}@keyframes neu-multiselect-fade-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.neu-multiselect__search{padding:var(--neu-space-2);border-bottom:1px solid var(--neu-border);flex-shrink:0}.neu-multiselect__search-input{width:100%;height:34px;padding:0 var(--neu-space-3);border:1.5px solid var(--neu-border);border-radius:var(--neu-radius-sm);background:var(--neu-bg);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-multiselect__search-input:focus{border-color:var(--neu-primary);box-shadow:0 0 0 2px #007aff1f}.neu-multiselect__search-input::placeholder{color:var(--neu-text-disabled)}.neu-multiselect__options{flex:1;overflow-y:auto;min-height:0}.neu-multiselect__option{display:flex;align-items:center;gap:var(--neu-space-2);padding:10px var(--neu-space-3);cursor:pointer;font-size:var(--neu-text-sm);color:var(--neu-text);transition:background var(--neu-transition)}.neu-multiselect__option:hover:not(.neu-multiselect__option--disabled){background:var(--neu-surface-2)}.neu-multiselect__option--selected{background:var(--neu-primary-soft, rgba(0, 122, 255, .06));color:var(--neu-primary);font-weight:500}.neu-multiselect__option--disabled{opacity:.4;cursor:not-allowed}.neu-multiselect__checkbox{display:flex;align-items:center;justify-content:center;width:16px;height:16px;border-radius:3px;border:1.5px solid var(--neu-border);background:var(--neu-bg);flex-shrink:0;transition:border-color var(--neu-transition),background var(--neu-transition)}.neu-multiselect__checkbox--checked{background:var(--neu-primary);border-color:var(--neu-primary);color:#fff}.neu-multiselect__checkbox-check{width:10px;height:8px;opacity:0;transform:scale(.6);transition:opacity .12s ease,transform .12s ease}.neu-multiselect__checkbox--checked .neu-multiselect__checkbox-check{opacity:1;transform:scale(1)}.neu-multiselect__empty{padding:var(--neu-space-4);text-align:center;font-size:var(--neu-text-sm);color:var(--neu-text-disabled);font-family:var(--neu-font-sans)}.neu-multiselect__footer{display:flex;align-items:center;justify-content:space-between;padding:var(--neu-space-2) var(--neu-space-3);border-top:1px solid var(--neu-border);background:var(--neu-surface);flex-shrink:0}.neu-multiselect__footer-count{font-size:var(--neu-text-xs);color:var(--neu-text-muted)}.neu-multiselect__footer-actions{display:flex;align-items:center;gap:var(--neu-space-2)}.neu-multiselect__footer-mode{background:none;border:1px solid var(--neu-border);border-radius:var(--neu-radius-sm);padding:2px 6px;font-size:var(--neu-text-xs);font-family:var(--neu-font-sans);color:var(--neu-text-muted);cursor:pointer;line-height:1.4}.neu-multiselect__footer-mode:hover{background:var(--neu-surface-2);color:var(--neu-text)}.neu-multiselect__footer-clear{background:none;border:none;padding:0;font-size:var(--neu-text-xs);font-family:var(--neu-font-sans);color:var(--neu-primary);cursor:pointer;font-weight:500}.neu-multiselect__footer-clear:hover{text-decoration:underline}.neu-multiselect__count-badge{display:inline-flex;align-items:center;padding:2px 10px;background:var(--neu-primary-100, rgba(14, 165, 233, .12));color:var(--neu-primary);border-radius:var(--neu-radius-full);font-size:var(--neu-text-sm);font-weight:500}.neu-multiselect__chip--overflow{background:var(--neu-surface-2);color:var(--neu-text-muted);border:1px dashed var(--neu-border);cursor:default;pointer-events:none}.neu-multiselect__error{margin-top:var(--neu-space-1);font-size:var(--neu-text-xs);color:var(--neu-error-text, var(--neu-error));font-family:var(--neu-font-sans)}.neu-multiselect--sm .neu-multiselect__trigger{min-height:36px;font-size:var(--neu-text-sm);padding:var(--neu-space-1) var(--neu-space-2);padding-right:36px}.neu-multiselect--lg .neu-multiselect__trigger{min-height:56px}\n"] }]
741
+ }], ctorParameters: () => [], propDecorators: { itemTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NeuMultiselectItemDirective), { isSignal: true }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], floatingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatingLabel", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], noResultsMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "noResultsMessage", required: false }] }], clearAllLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearAllLabel", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], clearAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearAriaLabel", required: false }] }], urlParam: [{ type: i0.Input, args: [{ isSignal: true, alias: "urlParam", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }] } });
742
+
743
+ /**
744
+ * Generated bundle index. Do not edit.
745
+ */
746
+
747
+ export { NeuMultiselectComponent, NeuMultiselectItemDirective };
748
+ //# sourceMappingURL=neural-ui-core-multiselect.mjs.map