@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,710 @@
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.
9
+ *
10
+ * Uso:
11
+ * ```html
12
+ * <neu-select [options]="opts" [formControl]="valueCtrl">
13
+ * <ng-template neuSelectItem let-item>
14
+ * <span class="flag flag-{{ item.value }}"></span>
15
+ * {{ item.label }}
16
+ * </ng-template>
17
+ * </neu-select>
18
+ * ```
19
+ */
20
+ class NeuSelectItemDirective {
21
+ templateRef = inject(TemplateRef);
22
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NeuSelectItemDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
23
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.8", type: NeuSelectItemDirective, isStandalone: true, selector: "[neuSelectItem]", ngImport: i0 });
24
+ }
25
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NeuSelectItemDirective, decorators: [{
26
+ type: Directive,
27
+ args: [{ selector: '[neuSelectItem]', standalone: true }]
28
+ }] });
29
+ /**
30
+ * Directiva para personalizar el template del ítem seleccionado (trigger).
31
+ *
32
+ * Uso:
33
+ * ```html
34
+ * <neu-select [options]="opts" [formControl]="valueCtrl">
35
+ * <ng-template neuSelectSelected let-item>
36
+ * <strong>{{ item?.label }}</strong>
37
+ * </ng-template>
38
+ * </neu-select>
39
+ * ```
40
+ */
41
+ class NeuSelectSelectedDirective {
42
+ templateRef = inject(TemplateRef);
43
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NeuSelectSelectedDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
44
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.8", type: NeuSelectSelectedDirective, isStandalone: true, selector: "[neuSelectSelected]", ngImport: i0 });
45
+ }
46
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NeuSelectSelectedDirective, decorators: [{
47
+ type: Directive,
48
+ args: [{ selector: '[neuSelectSelected]', standalone: true }]
49
+ }] });
50
+
51
+ let _neuSelectIdSeq = 0;
52
+ /**
53
+ * NeuralUI Select Component
54
+ *
55
+ * Dropdown personalizado con soporte para ControlValueAccessor y Reactive Forms.
56
+ * Puede usarse dentro de un FormGroup o con un FormControl standalone.
57
+ * Cierra automáticamente al hacer clic fuera del componente.
58
+ *
59
+ * Uso:
60
+ * readonly countryCtrl = new FormControl<string | null>(null);
61
+ * <neu-select label="País" [options]="paises" [formControl]="countryCtrl" />
62
+ *
63
+ * Uso standalone fuera de un formulario completo:
64
+ * readonly sortCtrl = new FormControl<string | null>('name');
65
+ * <neu-select label="Orden" [options]="sortOptions" [formControl]="sortCtrl" />
66
+ */
67
+ class NeuSelectComponent {
68
+ elementRef = inject(ElementRef);
69
+ _urlState = inject(NeuUrlStateService);
70
+ _mobileViewportMax = 768;
71
+ _viewportMargin = 16;
72
+ _urlParamSignals = new Map();
73
+ _getUrlParamSignal(key) {
74
+ let paramSignal = this._urlParamSignals.get(key);
75
+ if (!paramSignal) {
76
+ paramSignal = this._urlState.getParam(key);
77
+ this._urlParamSignals.set(key, paramSignal);
78
+ }
79
+ return paramSignal;
80
+ }
81
+ constructor() {
82
+ effect(() => {
83
+ const param = this.urlParam();
84
+ if (!param)
85
+ return;
86
+ const urlVal = this._getUrlParamSignal(param)();
87
+ if (urlVal !== untracked(() => this._value())) {
88
+ this._value.set(urlVal);
89
+ this._onChange(urlVal);
90
+ }
91
+ });
92
+ }
93
+ /** @internal — ID \u00fanico para asociar label con trigger */
94
+ _triggerId = `neu-select-trigger-${_neuSelectIdSeq++}`;
95
+ _panelId = `${this._triggerId}-panel`;
96
+ /** Template personalizado para cada opción del dropdown / Custom template for each dropdown option */
97
+ itemTpl = contentChild(NeuSelectItemDirective, ...(ngDevMode ? [{ debugName: "itemTpl" }] : /* istanbul ignore next */ []));
98
+ /** Template personalizado para el valor seleccionado en el trigger / Custom template for the selected value in the trigger */
99
+ selectedItemTpl = contentChild(NeuSelectSelectedDirective, ...(ngDevMode ? [{ debugName: "selectedItemTpl" }] : /* istanbul ignore next */ []));
100
+ /** Opciones del dropdown / Dropdown options */
101
+ options = input([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
102
+ /** Texto del floating label / Floating label text */
103
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
104
+ /** Placeholder cuando no hay selección / Placeholder when there is no selection */
105
+ placeholder = input('Seleccionar...', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
106
+ /** Mensaje de error / Error message */
107
+ errorMessage = input('', ...(ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []));
108
+ /** Texto de ayuda bajo el campo / Helper text below the field */
109
+ hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
110
+ /** Deshabilita el select / Disables the select */
111
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
112
+ /** Muestra el label como flotante (true) o como label estático encima (false, por defecto) / Shows the label as floating (true) or static above (false, default) */
113
+ floatingLabel = input(false, ...(ngDevMode ? [{ debugName: "floatingLabel" }] : /* istanbul ignore next */ []));
114
+ /** Tamaño del campo: 'sm' = 36px | 'md' = 48px | 'lg' = 56px / Field size */
115
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
116
+ /** Activa input de búsqueda/filtro en el panel / Activates the search/filter input in the panel */
117
+ searchable = input(false, ...(ngDevMode ? [{ debugName: "searchable" }] : /* istanbul ignore next */ []));
118
+ /** Placeholder del input de búsqueda / Search input placeholder */
119
+ searchPlaceholder = input('Buscar...', ...(ngDevMode ? [{ debugName: "searchPlaceholder" }] : /* istanbul ignore next */ []));
120
+ /** Muestra un botón para limpiar la selección / Shows a button to clear the selection */
121
+ clearable = input(false, ...(ngDevMode ? [{ debugName: "clearable" }] : /* istanbul ignore next */ []));
122
+ /** Texto cuando no hay opciones tras filtrar / Text when no options remain after filtering */
123
+ noResultsMessage = input('Sin resultados', ...(ngDevMode ? [{ debugName: "noResultsMessage" }] : /* istanbul ignore next */ []));
124
+ /** Aria-label del botón de limpiar / Aria-label for the clear button */
125
+ clearAriaLabel = input('Limpiar selección', ...(ngDevMode ? [{ debugName: "clearAriaLabel" }] : /* istanbul ignore next */ []));
126
+ /**
127
+ * Sincroniza el valor seleccionado con este query param de la URL.
128
+ * Al seleccionar una opción se añade `?{urlParam}=value` a la URL.
129
+ * Pasar `null` (default) deshabilita la sincronización.
130
+ */
131
+ urlParam = input(null, ...(ngDevMode ? [{ debugName: "urlParam" }] : /* istanbul ignore next */ []));
132
+ /**
133
+ * Emite el objeto NeuSelectOption completo (incluyendo data) al seleccionar una opción.
134
+ * Emite null al limpiar la selección.
135
+ * El valor del formControl sigue siendo string.
136
+ */
137
+ selectionChange = output();
138
+ // Estado interno
139
+ _value = signal(null, ...(ngDevMode ? [{ debugName: "_value" }] : /* istanbul ignore next */ []));
140
+ isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
141
+ searchQuery = signal('', ...(ngDevMode ? [{ debugName: "searchQuery" }] : /* istanbul ignore next */ []));
142
+ panelPosition = signal({
143
+ position: null,
144
+ top: null,
145
+ left: null,
146
+ width: null,
147
+ maxHeight: null,
148
+ }, ...(ngDevMode ? [{ debugName: "panelPosition" }] : /* istanbul ignore next */ []));
149
+ hasError = computed(() => !!this.errorMessage(), ...(ngDevMode ? [{ debugName: "hasError" }] : /* istanbul ignore next */ []));
150
+ describedBy = computed(() => {
151
+ if (this.hasError()) {
152
+ return `${this._triggerId}-error`;
153
+ }
154
+ if (this.hint()) {
155
+ return `${this._triggerId}-hint`;
156
+ }
157
+ return null;
158
+ }, ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
159
+ filteredOptions = computed(() => {
160
+ const q = this.searchQuery().toLowerCase().trim();
161
+ if (!q)
162
+ return this.options();
163
+ return this.options().filter((o) => o.label.toLowerCase().includes(q));
164
+ }, ...(ngDevMode ? [{ debugName: "filteredOptions" }] : /* istanbul ignore next */ []));
165
+ selectedLabel = computed(() => this.options().find((o) => o.value === this._value())?.label ?? null, ...(ngDevMode ? [{ debugName: "selectedLabel" }] : /* istanbul ignore next */ []));
166
+ _selectedOption = computed(() => this.options().find((o) => o.value === this._value()) ?? null, ...(ngDevMode ? [{ debugName: "_selectedOption" }] : /* istanbul ignore next */ []));
167
+ resultsAnnouncement = computed(() => {
168
+ if (!this.isOpen()) {
169
+ return '';
170
+ }
171
+ const total = this.filteredOptions().length;
172
+ if (!total) {
173
+ return this.noResultsMessage();
174
+ }
175
+ return total === 1 ? '1 opción disponible' : `${total} opciones disponibles`;
176
+ }, ...(ngDevMode ? [{ debugName: "resultsAnnouncement" }] : /* istanbul ignore next */ []));
177
+ // CVA
178
+ _onChange = () => { };
179
+ _onTouched = () => { };
180
+ writeValue(val) {
181
+ this._value.set(val ?? null);
182
+ }
183
+ registerOnChange(fn) {
184
+ this._onChange = fn;
185
+ }
186
+ registerOnTouched(fn) {
187
+ this._onTouched = fn;
188
+ }
189
+ _cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_cvaDisabled" }] : /* istanbul ignore next */ []));
190
+ setDisabledState(isDisabled) {
191
+ this._cvaDisabled.set(isDisabled);
192
+ }
193
+ isDisabledFinal = computed(() => this.disabled() || this._cvaDisabled(), ...(ngDevMode ? [{ debugName: "isDisabledFinal" }] : /* istanbul ignore next */ []));
194
+ toggle() {
195
+ if (!this.isDisabledFinal())
196
+ this.isOpen.update((v) => !v);
197
+ if (this.isOpen()) {
198
+ this.syncPanelPosition();
199
+ // Foco al primer item cuando abre con teclado
200
+ requestAnimationFrame(() => {
201
+ const first = this.elementRef.nativeElement.querySelector('.neu-select__option:not([aria-disabled="true"])');
202
+ first?.focus();
203
+ });
204
+ }
205
+ else {
206
+ this.resetPanelPosition();
207
+ }
208
+ }
209
+ close() {
210
+ this.isOpen.set(false);
211
+ this.searchQuery.set('');
212
+ this.resetPanelPosition();
213
+ this._onTouched();
214
+ }
215
+ /** Abre el panel y navega con flechas desde el trigger / Opens the panel and navigates with arrows from the trigger */
216
+ onTriggerKey(event) {
217
+ if (event.target !== event.currentTarget) {
218
+ return;
219
+ }
220
+ event.preventDefault();
221
+ if (!this.isOpen()) {
222
+ this.isOpen.set(true);
223
+ this.syncPanelPosition();
224
+ requestAnimationFrame(() => {
225
+ const first = this.elementRef.nativeElement.querySelector('.neu-select__option:not([aria-disabled="true"])');
226
+ first?.focus();
227
+ });
228
+ }
229
+ }
230
+ onTriggerActionKey(event) {
231
+ if (event.target !== event.currentTarget) {
232
+ return;
233
+ }
234
+ event.preventDefault();
235
+ this.toggle();
236
+ }
237
+ focusTrigger() {
238
+ this.elementRef.nativeElement.querySelector('.neu-select__trigger')?.focus();
239
+ }
240
+ /** Navega entre opciones con flechas / Navigates between options with arrows */
241
+ focusOptionByIndex(event, current, dir) {
242
+ event.preventDefault();
243
+ const opts = this.filteredOptions().filter((o) => !o.disabled);
244
+ const idx = opts.findIndex((o) => o.value === current.value);
245
+ const next = opts[(idx + dir + opts.length) % opts.length];
246
+ if (next) {
247
+ const el = this.elementRef.nativeElement.querySelector(`#neu-select-opt-${next.value}`);
248
+ el?.focus();
249
+ }
250
+ }
251
+ clearValue(event) {
252
+ event.stopPropagation();
253
+ this._value.set(null);
254
+ this._onChange(null);
255
+ const param = this.urlParam();
256
+ if (param)
257
+ this._urlState.setParam(param, null);
258
+ this._onTouched();
259
+ this.selectionChange.emit(null);
260
+ this.close();
261
+ }
262
+ selectOption(option) {
263
+ if (option.disabled)
264
+ return;
265
+ this._value.set(option.value);
266
+ this._onChange(option.value);
267
+ const param = this.urlParam();
268
+ if (param)
269
+ this._urlState.setParam(param, option.value);
270
+ this.selectionChange.emit(option);
271
+ this.close();
272
+ }
273
+ onDocumentClick(event) {
274
+ if (!this.elementRef.nativeElement.contains(event.target)) {
275
+ this.close();
276
+ }
277
+ }
278
+ onWindowResize() {
279
+ if (this.isOpen()) {
280
+ this.syncPanelPosition();
281
+ }
282
+ }
283
+ onWindowScroll() {
284
+ if (this.isOpen()) {
285
+ this.syncPanelPosition();
286
+ }
287
+ }
288
+ syncPanelPosition() {
289
+ requestAnimationFrame(() => {
290
+ const trigger = this.elementRef.nativeElement.querySelector('.neu-select__trigger');
291
+ if (!trigger)
292
+ return;
293
+ if (window.innerWidth > this._mobileViewportMax) {
294
+ this.resetPanelPosition();
295
+ return;
296
+ }
297
+ const triggerRect = trigger.getBoundingClientRect();
298
+ const viewportWidth = window.innerWidth;
299
+ const viewportHeight = window.innerHeight;
300
+ const width = Math.min(triggerRect.width, viewportWidth - this._viewportMargin * 2);
301
+ const left = Math.min(Math.max(triggerRect.left, this._viewportMargin), viewportWidth - this._viewportMargin - width);
302
+ const top = triggerRect.bottom + 6;
303
+ const maxHeight = Math.max(140, viewportHeight - top - this._viewportMargin);
304
+ this.panelPosition.set({
305
+ position: 'fixed',
306
+ top: `${top}px`,
307
+ left: `${left}px`,
308
+ width: `${width}px`,
309
+ maxHeight: `${maxHeight}px`,
310
+ });
311
+ });
312
+ }
313
+ resetPanelPosition() {
314
+ this.panelPosition.set({
315
+ position: null,
316
+ top: null,
317
+ left: null,
318
+ width: null,
319
+ maxHeight: null,
320
+ });
321
+ }
322
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NeuSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
323
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: NeuSelectComponent, isStandalone: true, selector: "neu-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", 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 }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", 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 }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, noResultsMessage: { classPropertyName: "noResultsMessage", publicName: "noResultsMessage", 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: [
324
+ {
325
+ provide: NG_VALUE_ACCESSOR,
326
+ useExisting: forwardRef(() => NeuSelectComponent),
327
+ multi: true,
328
+ },
329
+ ], queries: [{ propertyName: "itemTpl", first: true, predicate: NeuSelectItemDirective, descendants: true, isSignal: true }, { propertyName: "selectedItemTpl", first: true, predicate: NeuSelectSelectedDirective, descendants: true, isSignal: true }], ngImport: i0, template: `
330
+ @if (!floatingLabel() && label()) {
331
+ <label class="neu-select__static-label" [for]="_triggerId" (click)="focusTrigger()">{{
332
+ label()
333
+ }}</label>
334
+ }
335
+ <div
336
+ class="neu-select"
337
+ [class.neu-select--open]="isOpen()"
338
+ [class.neu-select--disabled]="isDisabledFinal()"
339
+ [class.neu-select--error]="hasError()"
340
+ [class.neu-select--has-value]="!!_value()"
341
+ [class.neu-select--has-placeholder]="!!placeholder() && !_value()"
342
+ [class.neu-select--no-float]="!floatingLabel()"
343
+ [class.neu-select--sm]="size() === 'sm'"
344
+ [class.neu-select--lg]="size() === 'lg'"
345
+ >
346
+ <!-- Trigger ------>
347
+ <div
348
+ class="neu-select__trigger"
349
+ [id]="_triggerId"
350
+ [attr.tabindex]="isDisabledFinal() ? '-1' : '0'"
351
+ [attr.role]="'combobox'"
352
+ [attr.aria-haspopup]="'listbox'"
353
+ [attr.aria-expanded]="isOpen() ? 'true' : 'false'"
354
+ [attr.aria-controls]="_panelId"
355
+ [attr.aria-disabled]="isDisabledFinal() ? 'true' : null"
356
+ [attr.aria-invalid]="hasError() ? 'true' : null"
357
+ [attr.aria-describedby]="describedBy()"
358
+ [attr.aria-activedescendant]="isOpen() && _value() ? 'neu-select-opt-' + _value() : null"
359
+ [attr.aria-label]="label() || placeholder() || null"
360
+ (click)="toggle()"
361
+ (keydown.arrowDown)="onTriggerKey($any($event))"
362
+ (keydown.arrowUp)="onTriggerKey($any($event))"
363
+ (keydown.enter)="onTriggerActionKey($any($event))"
364
+ (keydown.space)="onTriggerActionKey($any($event))"
365
+ >
366
+ <!-- Floating label -->
367
+ @if (floatingLabel() && label()) {
368
+ <span class="neu-select__label">{{ label() }}</span>
369
+ }
370
+
371
+ <span class="neu-select__value">
372
+ @if (selectedLabel()) {
373
+ @if (selectedItemTpl()) {
374
+ <ng-container
375
+ [ngTemplateOutlet]="selectedItemTpl()!.templateRef"
376
+ [ngTemplateOutletContext]="{ $implicit: _selectedOption() }"
377
+ />
378
+ } @else {
379
+ {{ selectedLabel() }}
380
+ }
381
+ } @else {
382
+ <span class="neu-select__placeholder">{{ placeholder() }}</span>
383
+ }
384
+ </span>
385
+
386
+ <!-- Clear button -->
387
+ @if (clearable() && !!_value() && !isDisabledFinal()) {
388
+ <button
389
+ class="neu-select__clear"
390
+ type="button"
391
+ aria-label="Limpiar selección"
392
+ [attr.aria-label]="clearAriaLabel()"
393
+ (click)="clearValue($event)"
394
+ >
395
+ <svg
396
+ viewBox="0 0 24 24"
397
+ fill="none"
398
+ stroke="currentColor"
399
+ stroke-width="2.5"
400
+ stroke-linecap="round"
401
+ aria-hidden="true"
402
+ >
403
+ <line x1="18" y1="6" x2="6" y2="18" />
404
+ <line x1="6" y1="6" x2="18" y2="18" />
405
+ </svg>
406
+ </button>
407
+ }
408
+
409
+ <!-- Chevron -->
410
+ <svg
411
+ class="neu-select__chevron"
412
+ viewBox="0 0 24 24"
413
+ fill="none"
414
+ stroke="currentColor"
415
+ stroke-width="2"
416
+ stroke-linecap="round"
417
+ stroke-linejoin="round"
418
+ aria-hidden="true"
419
+ >
420
+ <polyline points="6 9 12 15 18 9" />
421
+ </svg>
422
+ </div>
423
+
424
+ <!-- Panel ------>
425
+ @if (isOpen()) {
426
+ <div
427
+ class="neu-select__panel"
428
+ role="listbox"
429
+ [id]="_panelId"
430
+ [attr.aria-label]="label()"
431
+ [style.position]="panelPosition().position"
432
+ [style.top]="panelPosition().top"
433
+ [style.left]="panelPosition().left"
434
+ [style.width]="panelPosition().width"
435
+ [style.max-height]="panelPosition().maxHeight"
436
+ >
437
+ @if (searchable()) {
438
+ <div class="neu-select__search">
439
+ <input
440
+ class="neu-select__search-input"
441
+ type="text"
442
+ [attr.aria-label]="'Search ' + label()"
443
+ [placeholder]="searchPlaceholder()"
444
+ [value]="searchQuery()"
445
+ (input)="searchQuery.set($any($event.target).value)"
446
+ (click)="$event.stopPropagation()"
447
+ />
448
+ </div>
449
+ }
450
+ @for (option of filteredOptions(); track option.value) {
451
+ <div
452
+ class="neu-select__option"
453
+ [class.neu-select__option--selected]="option.value === _value()"
454
+ [class.neu-select__option--disabled]="option.disabled"
455
+ role="option"
456
+ [id]="'neu-select-opt-' + option.value"
457
+ [attr.aria-selected]="option.value === _value()"
458
+ [attr.aria-disabled]="option.disabled"
459
+ [attr.tabindex]="option.disabled ? null : '-1'"
460
+ (click)="selectOption(option)"
461
+ (keydown.enter)="selectOption(option)"
462
+ (keydown.space)="selectOption(option)"
463
+ (keydown.arrowDown)="focusOptionByIndex($any($event), option, 1)"
464
+ (keydown.arrowUp)="focusOptionByIndex($any($event), option, -1)"
465
+ >
466
+ <!-- Checkmark en la seleccionada (siempre reserva el espacio) -->
467
+ <svg
468
+ class="neu-select__check"
469
+ [style.visibility]="option.value === _value() ? 'visible' : 'hidden'"
470
+ viewBox="0 0 24 24"
471
+ fill="none"
472
+ stroke="currentColor"
473
+ stroke-width="2.5"
474
+ stroke-linecap="round"
475
+ stroke-linejoin="round"
476
+ aria-hidden="true"
477
+ >
478
+ <polyline points="20 6 9 17 4 12" />
479
+ </svg>
480
+ @if (itemTpl()) {
481
+ <ng-container
482
+ [ngTemplateOutlet]="itemTpl()!.templateRef"
483
+ [ngTemplateOutletContext]="{ $implicit: option }"
484
+ />
485
+ } @else {
486
+ {{ option.label }}
487
+ }
488
+ </div>
489
+ }
490
+ @if (filteredOptions().length === 0) {
491
+ <div class="neu-select__empty">{{ noResultsMessage() }}</div>
492
+ }
493
+ </div>
494
+ }
495
+ <div class="neu-select__sr-status" aria-live="polite" aria-atomic="true">
496
+ {{ resultsAnnouncement() }}
497
+ </div>
498
+ </div>
499
+
500
+ <!-- Error / hint -->
501
+ @if (hasError()) {
502
+ <p class="neu-select__error" [id]="_triggerId + '-error'" role="alert">
503
+ {{ errorMessage() }}
504
+ </p>
505
+ } @else if (hint()) {
506
+ <p class="neu-select__hint" [id]="_triggerId + '-hint'">{{ hint() }}</p>
507
+ }
508
+ `, isInline: true, styles: [".neu-select{position:relative;display:block}.neu-select--disabled{opacity:.6;pointer-events:none}.neu-select__trigger{display:flex;align-items:center;width:100%;height:48px;padding:0 var(--neu-space-3);padding-right:36px;background:var(--neu-surface);border:1.5px solid var(--neu-border);border-radius:var(--neu-radius);font-family:var(--neu-font-sans);font-size:var(--neu-text-base);color:var(--neu-text);cursor:pointer;text-align:left;outline:none;transition:border-color var(--neu-transition),box-shadow var(--neu-transition);position:relative}.neu-select__trigger:hover:not([aria-disabled=true]){border-color:var(--neu-border-hover)}.neu-select__trigger[aria-disabled=true]{cursor:not-allowed;background:var(--neu-surface-2)}.neu-select__trigger:focus-visible{border-color:var(--neu-primary);box-shadow:0 0 0 3px #007aff1f}.neu-select--open .neu-select__trigger{border-color:var(--neu-primary);box-shadow:0 0 0 3px #007aff1f;border-bottom-left-radius:0;border-bottom-right-radius:0}.neu-select--error .neu-select__trigger{border-color:var(--neu-error)}.neu-select--error .neu-select__trigger:focus-visible{box-shadow:0 0 0 3px #ef44441f}.neu-select__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-6));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-select--open .neu-select__label,.neu-select--has-value .neu-select__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-select--open .neu-select__label{color:var(--neu-primary)}.neu-select--error .neu-select__label{color:var(--neu-error)}.neu-select--disabled .neu-select__label{background:var(--neu-surface-2)}.neu-select__value{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.neu-select--no-float .neu-select__value{padding-top:0}.neu-select__placeholder{color:var(--neu-text-disabled)}.neu-select:not(.neu-select--no-float):not(.neu-select--open) .neu-select__placeholder{visibility:hidden}.neu-select__chevron{position:absolute;right:var(--neu-space-3);top:50%;transform:translateY(-50%);width:18px;height:18px;color:var(--neu-text-muted);flex-shrink:0;transition:transform var(--neu-transition)}.neu-select--open .neu-select__chevron{transform:translateY(-50%) rotate(180deg);color:var(--neu-primary)}.neu-select__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-select__clear svg{width:14px;height:14px}.neu-select__clear:hover{color:var(--neu-text);background:var(--neu-surface-3)}.neu-select__clear:focus-visible{outline:2px solid var(--neu-primary);outline-offset:2px}.neu-select__panel{position:absolute;top:100%;left:0;right:0;z-index:var(--neu-z-dropdown);background:var(--neu-surface);border:1.5px solid var(--neu-primary);border-top:none;border-bottom-left-radius:var(--neu-radius);border-bottom-right-radius:var(--neu-radius);box-shadow:var(--neu-shadow-lg);max-height:240px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--neu-surface-3) transparent;animation:neu-select-open .15s ease forwards}.neu-select__panel::-webkit-scrollbar{width:4px}.neu-select__panel::-webkit-scrollbar-thumb{background:var(--neu-surface-3);border-radius:99px}@media(max-width:600px){.neu-select__panel{left:auto;right:0;width:min(max(100%,220px),100vw - 2rem);max-width:calc(100vw - 2rem)}}@keyframes neu-select-open{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.neu-select__option{display:flex;align-items:center;gap:var(--neu-space-2);padding:var(--neu-space-3) var(--neu-space-4);font-size:var(--neu-text-sm);color:var(--neu-text);cursor:pointer;transition:background-color var(--neu-transition)}.neu-select__option:hover:not(.neu-select__option--disabled){background:var(--neu-primary-50);color:var(--neu-primary)}.neu-select__option--selected{color:var(--neu-primary);font-weight:600;background:var(--neu-primary-50)}.neu-select__option--disabled{opacity:.4;cursor:not-allowed}.neu-select__check{width:14px;height:14px;flex-shrink:0}.neu-select__error{margin-top:var(--neu-space-1);font-size:var(--neu-text-xs);color:var(--neu-error-text);font-family:var(--neu-font-sans)}.neu-select__hint{margin-top:var(--neu-space-1);font-size:var(--neu-text-xs);color:var(--neu-text-muted);font-family:var(--neu-font-sans)}.neu-select__sr-status{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.neu-select__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-select__search{padding:var(--neu-space-2);border-bottom:1px solid var(--neu-border);position:sticky;top:0;background:var(--neu-surface);z-index:1}.neu-select__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-select__search-input:focus{border-color:var(--neu-primary);box-shadow:0 0 0 2px #007aff1f}.neu-select__search-input::placeholder{color:var(--neu-text-disabled)}.neu-select__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-select--sm .neu-select__trigger{height:36px;font-size:var(--neu-text-sm)}.neu-select--lg .neu-select__trigger{height:56px}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
509
+ }
510
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NeuSelectComponent, decorators: [{
511
+ type: Component,
512
+ args: [{ selector: 'neu-select', imports: [NgTemplateOutlet], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, providers: [
513
+ {
514
+ provide: NG_VALUE_ACCESSOR,
515
+ useExisting: forwardRef(() => NeuSelectComponent),
516
+ multi: true,
517
+ },
518
+ ], host: {
519
+ '(document:click)': 'onDocumentClick($event)',
520
+ '(keydown.escape)': 'close()',
521
+ '(window:resize)': 'onWindowResize()',
522
+ '(window:scroll)': 'onWindowScroll()',
523
+ }, template: `
524
+ @if (!floatingLabel() && label()) {
525
+ <label class="neu-select__static-label" [for]="_triggerId" (click)="focusTrigger()">{{
526
+ label()
527
+ }}</label>
528
+ }
529
+ <div
530
+ class="neu-select"
531
+ [class.neu-select--open]="isOpen()"
532
+ [class.neu-select--disabled]="isDisabledFinal()"
533
+ [class.neu-select--error]="hasError()"
534
+ [class.neu-select--has-value]="!!_value()"
535
+ [class.neu-select--has-placeholder]="!!placeholder() && !_value()"
536
+ [class.neu-select--no-float]="!floatingLabel()"
537
+ [class.neu-select--sm]="size() === 'sm'"
538
+ [class.neu-select--lg]="size() === 'lg'"
539
+ >
540
+ <!-- Trigger ------>
541
+ <div
542
+ class="neu-select__trigger"
543
+ [id]="_triggerId"
544
+ [attr.tabindex]="isDisabledFinal() ? '-1' : '0'"
545
+ [attr.role]="'combobox'"
546
+ [attr.aria-haspopup]="'listbox'"
547
+ [attr.aria-expanded]="isOpen() ? 'true' : 'false'"
548
+ [attr.aria-controls]="_panelId"
549
+ [attr.aria-disabled]="isDisabledFinal() ? 'true' : null"
550
+ [attr.aria-invalid]="hasError() ? 'true' : null"
551
+ [attr.aria-describedby]="describedBy()"
552
+ [attr.aria-activedescendant]="isOpen() && _value() ? 'neu-select-opt-' + _value() : null"
553
+ [attr.aria-label]="label() || placeholder() || null"
554
+ (click)="toggle()"
555
+ (keydown.arrowDown)="onTriggerKey($any($event))"
556
+ (keydown.arrowUp)="onTriggerKey($any($event))"
557
+ (keydown.enter)="onTriggerActionKey($any($event))"
558
+ (keydown.space)="onTriggerActionKey($any($event))"
559
+ >
560
+ <!-- Floating label -->
561
+ @if (floatingLabel() && label()) {
562
+ <span class="neu-select__label">{{ label() }}</span>
563
+ }
564
+
565
+ <span class="neu-select__value">
566
+ @if (selectedLabel()) {
567
+ @if (selectedItemTpl()) {
568
+ <ng-container
569
+ [ngTemplateOutlet]="selectedItemTpl()!.templateRef"
570
+ [ngTemplateOutletContext]="{ $implicit: _selectedOption() }"
571
+ />
572
+ } @else {
573
+ {{ selectedLabel() }}
574
+ }
575
+ } @else {
576
+ <span class="neu-select__placeholder">{{ placeholder() }}</span>
577
+ }
578
+ </span>
579
+
580
+ <!-- Clear button -->
581
+ @if (clearable() && !!_value() && !isDisabledFinal()) {
582
+ <button
583
+ class="neu-select__clear"
584
+ type="button"
585
+ aria-label="Limpiar selección"
586
+ [attr.aria-label]="clearAriaLabel()"
587
+ (click)="clearValue($event)"
588
+ >
589
+ <svg
590
+ viewBox="0 0 24 24"
591
+ fill="none"
592
+ stroke="currentColor"
593
+ stroke-width="2.5"
594
+ stroke-linecap="round"
595
+ aria-hidden="true"
596
+ >
597
+ <line x1="18" y1="6" x2="6" y2="18" />
598
+ <line x1="6" y1="6" x2="18" y2="18" />
599
+ </svg>
600
+ </button>
601
+ }
602
+
603
+ <!-- Chevron -->
604
+ <svg
605
+ class="neu-select__chevron"
606
+ viewBox="0 0 24 24"
607
+ fill="none"
608
+ stroke="currentColor"
609
+ stroke-width="2"
610
+ stroke-linecap="round"
611
+ stroke-linejoin="round"
612
+ aria-hidden="true"
613
+ >
614
+ <polyline points="6 9 12 15 18 9" />
615
+ </svg>
616
+ </div>
617
+
618
+ <!-- Panel ------>
619
+ @if (isOpen()) {
620
+ <div
621
+ class="neu-select__panel"
622
+ role="listbox"
623
+ [id]="_panelId"
624
+ [attr.aria-label]="label()"
625
+ [style.position]="panelPosition().position"
626
+ [style.top]="panelPosition().top"
627
+ [style.left]="panelPosition().left"
628
+ [style.width]="panelPosition().width"
629
+ [style.max-height]="panelPosition().maxHeight"
630
+ >
631
+ @if (searchable()) {
632
+ <div class="neu-select__search">
633
+ <input
634
+ class="neu-select__search-input"
635
+ type="text"
636
+ [attr.aria-label]="'Search ' + label()"
637
+ [placeholder]="searchPlaceholder()"
638
+ [value]="searchQuery()"
639
+ (input)="searchQuery.set($any($event.target).value)"
640
+ (click)="$event.stopPropagation()"
641
+ />
642
+ </div>
643
+ }
644
+ @for (option of filteredOptions(); track option.value) {
645
+ <div
646
+ class="neu-select__option"
647
+ [class.neu-select__option--selected]="option.value === _value()"
648
+ [class.neu-select__option--disabled]="option.disabled"
649
+ role="option"
650
+ [id]="'neu-select-opt-' + option.value"
651
+ [attr.aria-selected]="option.value === _value()"
652
+ [attr.aria-disabled]="option.disabled"
653
+ [attr.tabindex]="option.disabled ? null : '-1'"
654
+ (click)="selectOption(option)"
655
+ (keydown.enter)="selectOption(option)"
656
+ (keydown.space)="selectOption(option)"
657
+ (keydown.arrowDown)="focusOptionByIndex($any($event), option, 1)"
658
+ (keydown.arrowUp)="focusOptionByIndex($any($event), option, -1)"
659
+ >
660
+ <!-- Checkmark en la seleccionada (siempre reserva el espacio) -->
661
+ <svg
662
+ class="neu-select__check"
663
+ [style.visibility]="option.value === _value() ? 'visible' : 'hidden'"
664
+ viewBox="0 0 24 24"
665
+ fill="none"
666
+ stroke="currentColor"
667
+ stroke-width="2.5"
668
+ stroke-linecap="round"
669
+ stroke-linejoin="round"
670
+ aria-hidden="true"
671
+ >
672
+ <polyline points="20 6 9 17 4 12" />
673
+ </svg>
674
+ @if (itemTpl()) {
675
+ <ng-container
676
+ [ngTemplateOutlet]="itemTpl()!.templateRef"
677
+ [ngTemplateOutletContext]="{ $implicit: option }"
678
+ />
679
+ } @else {
680
+ {{ option.label }}
681
+ }
682
+ </div>
683
+ }
684
+ @if (filteredOptions().length === 0) {
685
+ <div class="neu-select__empty">{{ noResultsMessage() }}</div>
686
+ }
687
+ </div>
688
+ }
689
+ <div class="neu-select__sr-status" aria-live="polite" aria-atomic="true">
690
+ {{ resultsAnnouncement() }}
691
+ </div>
692
+ </div>
693
+
694
+ <!-- Error / hint -->
695
+ @if (hasError()) {
696
+ <p class="neu-select__error" [id]="_triggerId + '-error'" role="alert">
697
+ {{ errorMessage() }}
698
+ </p>
699
+ } @else if (hint()) {
700
+ <p class="neu-select__hint" [id]="_triggerId + '-hint'">{{ hint() }}</p>
701
+ }
702
+ `, styles: [".neu-select{position:relative;display:block}.neu-select--disabled{opacity:.6;pointer-events:none}.neu-select__trigger{display:flex;align-items:center;width:100%;height:48px;padding:0 var(--neu-space-3);padding-right:36px;background:var(--neu-surface);border:1.5px solid var(--neu-border);border-radius:var(--neu-radius);font-family:var(--neu-font-sans);font-size:var(--neu-text-base);color:var(--neu-text);cursor:pointer;text-align:left;outline:none;transition:border-color var(--neu-transition),box-shadow var(--neu-transition);position:relative}.neu-select__trigger:hover:not([aria-disabled=true]){border-color:var(--neu-border-hover)}.neu-select__trigger[aria-disabled=true]{cursor:not-allowed;background:var(--neu-surface-2)}.neu-select__trigger:focus-visible{border-color:var(--neu-primary);box-shadow:0 0 0 3px #007aff1f}.neu-select--open .neu-select__trigger{border-color:var(--neu-primary);box-shadow:0 0 0 3px #007aff1f;border-bottom-left-radius:0;border-bottom-right-radius:0}.neu-select--error .neu-select__trigger{border-color:var(--neu-error)}.neu-select--error .neu-select__trigger:focus-visible{box-shadow:0 0 0 3px #ef44441f}.neu-select__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-6));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-select--open .neu-select__label,.neu-select--has-value .neu-select__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-select--open .neu-select__label{color:var(--neu-primary)}.neu-select--error .neu-select__label{color:var(--neu-error)}.neu-select--disabled .neu-select__label{background:var(--neu-surface-2)}.neu-select__value{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.neu-select--no-float .neu-select__value{padding-top:0}.neu-select__placeholder{color:var(--neu-text-disabled)}.neu-select:not(.neu-select--no-float):not(.neu-select--open) .neu-select__placeholder{visibility:hidden}.neu-select__chevron{position:absolute;right:var(--neu-space-3);top:50%;transform:translateY(-50%);width:18px;height:18px;color:var(--neu-text-muted);flex-shrink:0;transition:transform var(--neu-transition)}.neu-select--open .neu-select__chevron{transform:translateY(-50%) rotate(180deg);color:var(--neu-primary)}.neu-select__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-select__clear svg{width:14px;height:14px}.neu-select__clear:hover{color:var(--neu-text);background:var(--neu-surface-3)}.neu-select__clear:focus-visible{outline:2px solid var(--neu-primary);outline-offset:2px}.neu-select__panel{position:absolute;top:100%;left:0;right:0;z-index:var(--neu-z-dropdown);background:var(--neu-surface);border:1.5px solid var(--neu-primary);border-top:none;border-bottom-left-radius:var(--neu-radius);border-bottom-right-radius:var(--neu-radius);box-shadow:var(--neu-shadow-lg);max-height:240px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--neu-surface-3) transparent;animation:neu-select-open .15s ease forwards}.neu-select__panel::-webkit-scrollbar{width:4px}.neu-select__panel::-webkit-scrollbar-thumb{background:var(--neu-surface-3);border-radius:99px}@media(max-width:600px){.neu-select__panel{left:auto;right:0;width:min(max(100%,220px),100vw - 2rem);max-width:calc(100vw - 2rem)}}@keyframes neu-select-open{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.neu-select__option{display:flex;align-items:center;gap:var(--neu-space-2);padding:var(--neu-space-3) var(--neu-space-4);font-size:var(--neu-text-sm);color:var(--neu-text);cursor:pointer;transition:background-color var(--neu-transition)}.neu-select__option:hover:not(.neu-select__option--disabled){background:var(--neu-primary-50);color:var(--neu-primary)}.neu-select__option--selected{color:var(--neu-primary);font-weight:600;background:var(--neu-primary-50)}.neu-select__option--disabled{opacity:.4;cursor:not-allowed}.neu-select__check{width:14px;height:14px;flex-shrink:0}.neu-select__error{margin-top:var(--neu-space-1);font-size:var(--neu-text-xs);color:var(--neu-error-text);font-family:var(--neu-font-sans)}.neu-select__hint{margin-top:var(--neu-space-1);font-size:var(--neu-text-xs);color:var(--neu-text-muted);font-family:var(--neu-font-sans)}.neu-select__sr-status{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.neu-select__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-select__search{padding:var(--neu-space-2);border-bottom:1px solid var(--neu-border);position:sticky;top:0;background:var(--neu-surface);z-index:1}.neu-select__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-select__search-input:focus{border-color:var(--neu-primary);box-shadow:0 0 0 2px #007aff1f}.neu-select__search-input::placeholder{color:var(--neu-text-disabled)}.neu-select__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-select--sm .neu-select__trigger{height:36px;font-size:var(--neu-text-sm)}.neu-select--lg .neu-select__trigger{height:56px}\n"] }]
703
+ }], ctorParameters: () => [], propDecorators: { itemTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NeuSelectItemDirective), { isSignal: true }] }], selectedItemTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NeuSelectSelectedDirective), { isSignal: true }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], floatingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatingLabel", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], noResultsMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "noResultsMessage", 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"] }] } });
704
+
705
+ /**
706
+ * Generated bundle index. Do not edit.
707
+ */
708
+
709
+ export { NeuSelectComponent, NeuSelectItemDirective, NeuSelectSelectedDirective };
710
+ //# sourceMappingURL=neural-ui-core-select.mjs.map