@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,638 @@
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
+ constructor() {
73
+ effect(() => {
74
+ const param = this.urlParam();
75
+ if (!param)
76
+ return;
77
+ const urlVal = this._urlState.getParam(param)();
78
+ if (urlVal !== untracked(() => this._value())) {
79
+ this._value.set(urlVal);
80
+ this._onChange(urlVal);
81
+ }
82
+ });
83
+ }
84
+ /** @internal — ID \u00fanico para asociar label con trigger */
85
+ _triggerId = `neu-select-trigger-${_neuSelectIdSeq++}`;
86
+ /** Template personalizado para cada opción del dropdown / Custom template for each dropdown option */
87
+ itemTpl = contentChild(NeuSelectItemDirective, ...(ngDevMode ? [{ debugName: "itemTpl" }] : /* istanbul ignore next */ []));
88
+ /** Template personalizado para el valor seleccionado en el trigger / Custom template for the selected value in the trigger */
89
+ selectedItemTpl = contentChild(NeuSelectSelectedDirective, ...(ngDevMode ? [{ debugName: "selectedItemTpl" }] : /* istanbul ignore next */ []));
90
+ /** Opciones del dropdown / Dropdown options */
91
+ options = input([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
92
+ /** Texto del floating label / Floating label text */
93
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
94
+ /** Placeholder cuando no hay selección / Placeholder when there is no selection */
95
+ placeholder = input('Seleccionar...', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
96
+ /** Mensaje de error / Error message */
97
+ errorMessage = input('', ...(ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []));
98
+ /** Deshabilita el select / Disables the select */
99
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
100
+ /** 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) */
101
+ floatingLabel = input(false, ...(ngDevMode ? [{ debugName: "floatingLabel" }] : /* istanbul ignore next */ []));
102
+ /** Tamaño del campo: 'sm' = 36px | 'md' = 48px | 'lg' = 56px / Field size */
103
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
104
+ /** Activa input de búsqueda/filtro en el panel / Activates the search/filter input in the panel */
105
+ searchable = input(false, ...(ngDevMode ? [{ debugName: "searchable" }] : /* istanbul ignore next */ []));
106
+ /** Placeholder del input de búsqueda / Search input placeholder */
107
+ searchPlaceholder = input('Buscar...', ...(ngDevMode ? [{ debugName: "searchPlaceholder" }] : /* istanbul ignore next */ []));
108
+ /** Muestra un botón para limpiar la selección / Shows a button to clear the selection */
109
+ clearable = input(false, ...(ngDevMode ? [{ debugName: "clearable" }] : /* istanbul ignore next */ []));
110
+ /** Texto cuando no hay opciones tras filtrar / Text when no options remain after filtering */
111
+ noResultsMessage = input('Sin resultados', ...(ngDevMode ? [{ debugName: "noResultsMessage" }] : /* istanbul ignore next */ []));
112
+ /** Aria-label del botón de limpiar / Aria-label for the clear button */
113
+ clearAriaLabel = input('Limpiar selección', ...(ngDevMode ? [{ debugName: "clearAriaLabel" }] : /* istanbul ignore next */ []));
114
+ /**
115
+ * Sincroniza el valor seleccionado con este query param de la URL.
116
+ * Al seleccionar una opción se añade `?{urlParam}=value` a la URL.
117
+ * Pasar `null` (default) deshabilita la sincronización.
118
+ */
119
+ urlParam = input(null, ...(ngDevMode ? [{ debugName: "urlParam" }] : /* istanbul ignore next */ []));
120
+ /**
121
+ * Emite el objeto NeuSelectOption completo (incluyendo data) al seleccionar una opción.
122
+ * Emite null al limpiar la selección.
123
+ * El valor del formControl sigue siendo string.
124
+ */
125
+ selectionChange = output();
126
+ // Estado interno
127
+ _value = signal(null, ...(ngDevMode ? [{ debugName: "_value" }] : /* istanbul ignore next */ []));
128
+ isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
129
+ searchQuery = signal('', ...(ngDevMode ? [{ debugName: "searchQuery" }] : /* istanbul ignore next */ []));
130
+ panelPosition = signal({
131
+ position: null,
132
+ top: null,
133
+ left: null,
134
+ width: null,
135
+ maxHeight: null,
136
+ }, ...(ngDevMode ? [{ debugName: "panelPosition" }] : /* istanbul ignore next */ []));
137
+ hasError = computed(() => !!this.errorMessage(), ...(ngDevMode ? [{ debugName: "hasError" }] : /* istanbul ignore next */ []));
138
+ filteredOptions = computed(() => {
139
+ const q = this.searchQuery().toLowerCase().trim();
140
+ if (!q)
141
+ return this.options();
142
+ return this.options().filter((o) => o.label.toLowerCase().includes(q));
143
+ }, ...(ngDevMode ? [{ debugName: "filteredOptions" }] : /* istanbul ignore next */ []));
144
+ selectedLabel = computed(() => this.options().find((o) => o.value === this._value())?.label ?? null, ...(ngDevMode ? [{ debugName: "selectedLabel" }] : /* istanbul ignore next */ []));
145
+ _selectedOption = computed(() => this.options().find((o) => o.value === this._value()) ?? null, ...(ngDevMode ? [{ debugName: "_selectedOption" }] : /* istanbul ignore next */ []));
146
+ // CVA
147
+ _onChange = () => { };
148
+ _onTouched = () => { };
149
+ writeValue(val) {
150
+ this._value.set(val ?? null);
151
+ }
152
+ registerOnChange(fn) {
153
+ this._onChange = fn;
154
+ }
155
+ registerOnTouched(fn) {
156
+ this._onTouched = fn;
157
+ }
158
+ _cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_cvaDisabled" }] : /* istanbul ignore next */ []));
159
+ setDisabledState(isDisabled) {
160
+ this._cvaDisabled.set(isDisabled);
161
+ }
162
+ isDisabledFinal = computed(() => this.disabled() || this._cvaDisabled(), ...(ngDevMode ? [{ debugName: "isDisabledFinal" }] : /* istanbul ignore next */ []));
163
+ toggle() {
164
+ if (!this.isDisabledFinal())
165
+ this.isOpen.update((v) => !v);
166
+ if (this.isOpen()) {
167
+ this.syncPanelPosition();
168
+ // Foco al primer item cuando abre con teclado
169
+ requestAnimationFrame(() => {
170
+ const first = this.elementRef.nativeElement.querySelector('.neu-select__option:not([aria-disabled="true"])');
171
+ first?.focus();
172
+ });
173
+ }
174
+ else {
175
+ this.resetPanelPosition();
176
+ }
177
+ }
178
+ close() {
179
+ this.isOpen.set(false);
180
+ this.searchQuery.set('');
181
+ this.resetPanelPosition();
182
+ this._onTouched();
183
+ }
184
+ /** Abre el panel y navega con flechas desde el trigger / Opens the panel and navigates with arrows from the trigger */
185
+ onTriggerKey(event) {
186
+ event.preventDefault();
187
+ if (!this.isOpen()) {
188
+ this.isOpen.set(true);
189
+ this.syncPanelPosition();
190
+ requestAnimationFrame(() => {
191
+ const first = this.elementRef.nativeElement.querySelector('.neu-select__option:not([aria-disabled="true"])');
192
+ first?.focus();
193
+ });
194
+ }
195
+ }
196
+ /** Navega entre opciones con flechas / Navigates between options with arrows */
197
+ focusOptionByIndex(event, current, dir) {
198
+ event.preventDefault();
199
+ const opts = this.filteredOptions().filter((o) => !o.disabled);
200
+ const idx = opts.findIndex((o) => o.value === current.value);
201
+ const next = opts[(idx + dir + opts.length) % opts.length];
202
+ if (next) {
203
+ const el = this.elementRef.nativeElement.querySelector(`#neu-select-opt-${next.value}`);
204
+ el?.focus();
205
+ }
206
+ }
207
+ clearValue(event) {
208
+ event.stopPropagation();
209
+ this._value.set(null);
210
+ this._onChange(null);
211
+ const param = this.urlParam();
212
+ if (param)
213
+ this._urlState.setParam(param, null);
214
+ this._onTouched();
215
+ this.selectionChange.emit(null);
216
+ this.close();
217
+ }
218
+ selectOption(option) {
219
+ if (option.disabled)
220
+ return;
221
+ this._value.set(option.value);
222
+ this._onChange(option.value);
223
+ const param = this.urlParam();
224
+ if (param)
225
+ this._urlState.setParam(param, option.value);
226
+ this.selectionChange.emit(option);
227
+ this.close();
228
+ }
229
+ onDocumentClick(event) {
230
+ if (!this.elementRef.nativeElement.contains(event.target)) {
231
+ this.close();
232
+ }
233
+ }
234
+ onWindowResize() {
235
+ if (this.isOpen()) {
236
+ this.syncPanelPosition();
237
+ }
238
+ }
239
+ onWindowScroll() {
240
+ if (this.isOpen()) {
241
+ this.syncPanelPosition();
242
+ }
243
+ }
244
+ syncPanelPosition() {
245
+ requestAnimationFrame(() => {
246
+ const trigger = this.elementRef.nativeElement.querySelector('.neu-select__trigger');
247
+ if (!trigger)
248
+ return;
249
+ if (window.innerWidth > this._mobileViewportMax) {
250
+ this.resetPanelPosition();
251
+ return;
252
+ }
253
+ const triggerRect = trigger.getBoundingClientRect();
254
+ const viewportWidth = window.innerWidth;
255
+ const viewportHeight = window.innerHeight;
256
+ const width = Math.min(triggerRect.width, viewportWidth - this._viewportMargin * 2);
257
+ const left = Math.min(Math.max(triggerRect.left, this._viewportMargin), viewportWidth - this._viewportMargin - width);
258
+ const top = triggerRect.bottom + 6;
259
+ const maxHeight = Math.max(140, viewportHeight - top - this._viewportMargin);
260
+ this.panelPosition.set({
261
+ position: 'fixed',
262
+ top: `${top}px`,
263
+ left: `${left}px`,
264
+ width: `${width}px`,
265
+ maxHeight: `${maxHeight}px`,
266
+ });
267
+ });
268
+ }
269
+ resetPanelPosition() {
270
+ this.panelPosition.set({
271
+ position: null,
272
+ top: null,
273
+ left: null,
274
+ width: null,
275
+ maxHeight: null,
276
+ });
277
+ }
278
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NeuSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
279
+ 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 }, 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: [
280
+ {
281
+ provide: NG_VALUE_ACCESSOR,
282
+ useExisting: forwardRef(() => NeuSelectComponent),
283
+ multi: true,
284
+ },
285
+ ], queries: [{ propertyName: "itemTpl", first: true, predicate: NeuSelectItemDirective, descendants: true, isSignal: true }, { propertyName: "selectedItemTpl", first: true, predicate: NeuSelectSelectedDirective, descendants: true, isSignal: true }], ngImport: i0, template: `
286
+ @if (!floatingLabel() && label()) {
287
+ <label class="neu-select__static-label" [for]="_triggerId">{{ label() }}</label>
288
+ }
289
+ <div
290
+ class="neu-select"
291
+ [class.neu-select--open]="isOpen()"
292
+ [class.neu-select--disabled]="isDisabledFinal()"
293
+ [class.neu-select--error]="hasError()"
294
+ [class.neu-select--has-value]="!!_value()"
295
+ [class.neu-select--has-placeholder]="!!placeholder() && !_value()"
296
+ [class.neu-select--no-float]="!floatingLabel()"
297
+ [class.neu-select--sm]="size() === 'sm'"
298
+ [class.neu-select--lg]="size() === 'lg'"
299
+ >
300
+ <!-- Trigger ------>
301
+ <button
302
+ class="neu-select__trigger"
303
+ type="button"
304
+ [id]="_triggerId"
305
+ [disabled]="isDisabledFinal()"
306
+ [attr.role]="'combobox'"
307
+ [attr.aria-haspopup]="'listbox'"
308
+ [attr.aria-expanded]="isOpen()"
309
+ [attr.aria-invalid]="hasError() ? 'true' : null"
310
+ [attr.aria-activedescendant]="isOpen() && _value() ? 'neu-select-opt-' + _value() : null"
311
+ [attr.aria-label]="label() || placeholder() || 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-select__label">{{ label() }}</span>
319
+ }
320
+
321
+ <span class="neu-select__value">
322
+ @if (selectedLabel()) {
323
+ @if (selectedItemTpl()) {
324
+ <ng-container
325
+ [ngTemplateOutlet]="selectedItemTpl()!.templateRef"
326
+ [ngTemplateOutletContext]="{ $implicit: _selectedOption() }"
327
+ />
328
+ } @else {
329
+ {{ selectedLabel() }}
330
+ }
331
+ } @else {
332
+ <span class="neu-select__placeholder">{{ placeholder() }}</span>
333
+ }
334
+ </span>
335
+
336
+ <!-- Clear button -->
337
+ @if (clearable() && !!_value() && !isDisabledFinal()) {
338
+ <button
339
+ class="neu-select__clear"
340
+ type="button"
341
+ aria-label="Limpiar selección"
342
+ [attr.aria-label]="clearAriaLabel()"
343
+ (click)="clearValue($event)"
344
+ >
345
+ <svg
346
+ viewBox="0 0 24 24"
347
+ fill="none"
348
+ stroke="currentColor"
349
+ stroke-width="2.5"
350
+ stroke-linecap="round"
351
+ aria-hidden="true"
352
+ >
353
+ <line x1="18" y1="6" x2="6" y2="18" />
354
+ <line x1="6" y1="6" x2="18" y2="18" />
355
+ </svg>
356
+ </button>
357
+ }
358
+
359
+ <!-- Chevron -->
360
+ <svg
361
+ class="neu-select__chevron"
362
+ viewBox="0 0 24 24"
363
+ fill="none"
364
+ stroke="currentColor"
365
+ stroke-width="2"
366
+ stroke-linecap="round"
367
+ stroke-linejoin="round"
368
+ aria-hidden="true"
369
+ >
370
+ <polyline points="6 9 12 15 18 9" />
371
+ </svg>
372
+ </button>
373
+
374
+ <!-- Panel ------>
375
+ @if (isOpen()) {
376
+ <div
377
+ class="neu-select__panel"
378
+ role="listbox"
379
+ [attr.aria-label]="label()"
380
+ [style.position]="panelPosition().position"
381
+ [style.top]="panelPosition().top"
382
+ [style.left]="panelPosition().left"
383
+ [style.width]="panelPosition().width"
384
+ [style.max-height]="panelPosition().maxHeight"
385
+ >
386
+ @if (searchable()) {
387
+ <div class="neu-select__search">
388
+ <input
389
+ class="neu-select__search-input"
390
+ type="text"
391
+ [attr.aria-label]="'Search ' + label()"
392
+ [placeholder]="searchPlaceholder()"
393
+ [value]="searchQuery()"
394
+ (input)="searchQuery.set($any($event.target).value)"
395
+ (click)="$event.stopPropagation()"
396
+ />
397
+ </div>
398
+ }
399
+ @for (option of filteredOptions(); track option.value) {
400
+ <div
401
+ class="neu-select__option"
402
+ [class.neu-select__option--selected]="option.value === _value()"
403
+ [class.neu-select__option--disabled]="option.disabled"
404
+ role="option"
405
+ [id]="'neu-select-opt-' + option.value"
406
+ [attr.aria-selected]="option.value === _value()"
407
+ [attr.aria-disabled]="option.disabled"
408
+ [attr.tabindex]="option.disabled ? null : '-1'"
409
+ (click)="selectOption(option)"
410
+ (keydown.enter)="selectOption(option)"
411
+ (keydown.space)="selectOption(option)"
412
+ (keydown.arrowDown)="focusOptionByIndex($any($event), option, 1)"
413
+ (keydown.arrowUp)="focusOptionByIndex($any($event), option, -1)"
414
+ >
415
+ <!-- Checkmark en la seleccionada (siempre reserva el espacio) -->
416
+ <svg
417
+ class="neu-select__check"
418
+ [style.visibility]="option.value === _value() ? 'visible' : 'hidden'"
419
+ viewBox="0 0 24 24"
420
+ fill="none"
421
+ stroke="currentColor"
422
+ stroke-width="2.5"
423
+ stroke-linecap="round"
424
+ stroke-linejoin="round"
425
+ aria-hidden="true"
426
+ >
427
+ <polyline points="20 6 9 17 4 12" />
428
+ </svg>
429
+ @if (itemTpl()) {
430
+ <ng-container
431
+ [ngTemplateOutlet]="itemTpl()!.templateRef"
432
+ [ngTemplateOutletContext]="{ $implicit: option }"
433
+ />
434
+ } @else {
435
+ {{ option.label }}
436
+ }
437
+ </div>
438
+ }
439
+ @if (filteredOptions().length === 0) {
440
+ <div class="neu-select__empty">{{ noResultsMessage() }}</div>
441
+ }
442
+ </div>
443
+ }
444
+ </div>
445
+
446
+ <!-- Error / hint -->
447
+ @if (hasError()) {
448
+ <p class="neu-select__error" role="alert">{{ errorMessage() }}</p>
449
+ }
450
+ `, 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(:disabled){border-color:var(--neu-border-hover)}.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__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__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 });
451
+ }
452
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NeuSelectComponent, decorators: [{
453
+ type: Component,
454
+ args: [{ selector: 'neu-select', imports: [NgTemplateOutlet], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, providers: [
455
+ {
456
+ provide: NG_VALUE_ACCESSOR,
457
+ useExisting: forwardRef(() => NeuSelectComponent),
458
+ multi: true,
459
+ },
460
+ ], host: {
461
+ '(document:click)': 'onDocumentClick($event)',
462
+ '(keydown.escape)': 'close()',
463
+ '(window:resize)': 'onWindowResize()',
464
+ '(window:scroll)': 'onWindowScroll()',
465
+ }, template: `
466
+ @if (!floatingLabel() && label()) {
467
+ <label class="neu-select__static-label" [for]="_triggerId">{{ label() }}</label>
468
+ }
469
+ <div
470
+ class="neu-select"
471
+ [class.neu-select--open]="isOpen()"
472
+ [class.neu-select--disabled]="isDisabledFinal()"
473
+ [class.neu-select--error]="hasError()"
474
+ [class.neu-select--has-value]="!!_value()"
475
+ [class.neu-select--has-placeholder]="!!placeholder() && !_value()"
476
+ [class.neu-select--no-float]="!floatingLabel()"
477
+ [class.neu-select--sm]="size() === 'sm'"
478
+ [class.neu-select--lg]="size() === 'lg'"
479
+ >
480
+ <!-- Trigger ------>
481
+ <button
482
+ class="neu-select__trigger"
483
+ type="button"
484
+ [id]="_triggerId"
485
+ [disabled]="isDisabledFinal()"
486
+ [attr.role]="'combobox'"
487
+ [attr.aria-haspopup]="'listbox'"
488
+ [attr.aria-expanded]="isOpen()"
489
+ [attr.aria-invalid]="hasError() ? 'true' : null"
490
+ [attr.aria-activedescendant]="isOpen() && _value() ? 'neu-select-opt-' + _value() : null"
491
+ [attr.aria-label]="label() || placeholder() || null"
492
+ (click)="toggle()"
493
+ (keydown.arrowDown)="onTriggerKey($any($event))"
494
+ (keydown.arrowUp)="onTriggerKey($any($event))"
495
+ >
496
+ <!-- Floating label -->
497
+ @if (floatingLabel() && label()) {
498
+ <span class="neu-select__label">{{ label() }}</span>
499
+ }
500
+
501
+ <span class="neu-select__value">
502
+ @if (selectedLabel()) {
503
+ @if (selectedItemTpl()) {
504
+ <ng-container
505
+ [ngTemplateOutlet]="selectedItemTpl()!.templateRef"
506
+ [ngTemplateOutletContext]="{ $implicit: _selectedOption() }"
507
+ />
508
+ } @else {
509
+ {{ selectedLabel() }}
510
+ }
511
+ } @else {
512
+ <span class="neu-select__placeholder">{{ placeholder() }}</span>
513
+ }
514
+ </span>
515
+
516
+ <!-- Clear button -->
517
+ @if (clearable() && !!_value() && !isDisabledFinal()) {
518
+ <button
519
+ class="neu-select__clear"
520
+ type="button"
521
+ aria-label="Limpiar selección"
522
+ [attr.aria-label]="clearAriaLabel()"
523
+ (click)="clearValue($event)"
524
+ >
525
+ <svg
526
+ viewBox="0 0 24 24"
527
+ fill="none"
528
+ stroke="currentColor"
529
+ stroke-width="2.5"
530
+ stroke-linecap="round"
531
+ aria-hidden="true"
532
+ >
533
+ <line x1="18" y1="6" x2="6" y2="18" />
534
+ <line x1="6" y1="6" x2="18" y2="18" />
535
+ </svg>
536
+ </button>
537
+ }
538
+
539
+ <!-- Chevron -->
540
+ <svg
541
+ class="neu-select__chevron"
542
+ viewBox="0 0 24 24"
543
+ fill="none"
544
+ stroke="currentColor"
545
+ stroke-width="2"
546
+ stroke-linecap="round"
547
+ stroke-linejoin="round"
548
+ aria-hidden="true"
549
+ >
550
+ <polyline points="6 9 12 15 18 9" />
551
+ </svg>
552
+ </button>
553
+
554
+ <!-- Panel ------>
555
+ @if (isOpen()) {
556
+ <div
557
+ class="neu-select__panel"
558
+ role="listbox"
559
+ [attr.aria-label]="label()"
560
+ [style.position]="panelPosition().position"
561
+ [style.top]="panelPosition().top"
562
+ [style.left]="panelPosition().left"
563
+ [style.width]="panelPosition().width"
564
+ [style.max-height]="panelPosition().maxHeight"
565
+ >
566
+ @if (searchable()) {
567
+ <div class="neu-select__search">
568
+ <input
569
+ class="neu-select__search-input"
570
+ type="text"
571
+ [attr.aria-label]="'Search ' + label()"
572
+ [placeholder]="searchPlaceholder()"
573
+ [value]="searchQuery()"
574
+ (input)="searchQuery.set($any($event.target).value)"
575
+ (click)="$event.stopPropagation()"
576
+ />
577
+ </div>
578
+ }
579
+ @for (option of filteredOptions(); track option.value) {
580
+ <div
581
+ class="neu-select__option"
582
+ [class.neu-select__option--selected]="option.value === _value()"
583
+ [class.neu-select__option--disabled]="option.disabled"
584
+ role="option"
585
+ [id]="'neu-select-opt-' + option.value"
586
+ [attr.aria-selected]="option.value === _value()"
587
+ [attr.aria-disabled]="option.disabled"
588
+ [attr.tabindex]="option.disabled ? null : '-1'"
589
+ (click)="selectOption(option)"
590
+ (keydown.enter)="selectOption(option)"
591
+ (keydown.space)="selectOption(option)"
592
+ (keydown.arrowDown)="focusOptionByIndex($any($event), option, 1)"
593
+ (keydown.arrowUp)="focusOptionByIndex($any($event), option, -1)"
594
+ >
595
+ <!-- Checkmark en la seleccionada (siempre reserva el espacio) -->
596
+ <svg
597
+ class="neu-select__check"
598
+ [style.visibility]="option.value === _value() ? 'visible' : 'hidden'"
599
+ viewBox="0 0 24 24"
600
+ fill="none"
601
+ stroke="currentColor"
602
+ stroke-width="2.5"
603
+ stroke-linecap="round"
604
+ stroke-linejoin="round"
605
+ aria-hidden="true"
606
+ >
607
+ <polyline points="20 6 9 17 4 12" />
608
+ </svg>
609
+ @if (itemTpl()) {
610
+ <ng-container
611
+ [ngTemplateOutlet]="itemTpl()!.templateRef"
612
+ [ngTemplateOutletContext]="{ $implicit: option }"
613
+ />
614
+ } @else {
615
+ {{ option.label }}
616
+ }
617
+ </div>
618
+ }
619
+ @if (filteredOptions().length === 0) {
620
+ <div class="neu-select__empty">{{ noResultsMessage() }}</div>
621
+ }
622
+ </div>
623
+ }
624
+ </div>
625
+
626
+ <!-- Error / hint -->
627
+ @if (hasError()) {
628
+ <p class="neu-select__error" role="alert">{{ errorMessage() }}</p>
629
+ }
630
+ `, 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(:disabled){border-color:var(--neu-border-hover)}.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__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__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"] }]
631
+ }], 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 }] }], 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"] }] } });
632
+
633
+ /**
634
+ * Generated bundle index. Do not edit.
635
+ */
636
+
637
+ export { NeuSelectComponent, NeuSelectItemDirective, NeuSelectSelectedDirective };
638
+ //# sourceMappingURL=neural-ui-core-select.mjs.map