@raintonic/formaui 0.4.0 → 0.9.2

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 (238) hide show
  1. package/CHANGELOG.md +80 -35
  2. package/README.md +22 -26
  3. package/fesm2022/raintonic-formaui-cdk-drag-drop.mjs +39 -41
  4. package/fesm2022/raintonic-formaui-cdk-drag-drop.mjs.map +1 -1
  5. package/fesm2022/raintonic-formaui-cdk-form-field.mjs +207 -3
  6. package/fesm2022/raintonic-formaui-cdk-form-field.mjs.map +1 -1
  7. package/fesm2022/raintonic-formaui-cdk-overlay.mjs +19 -1
  8. package/fesm2022/raintonic-formaui-cdk-overlay.mjs.map +1 -1
  9. package/fesm2022/raintonic-formaui-cdk-virtual-scroll.mjs +5 -12
  10. package/fesm2022/raintonic-formaui-cdk-virtual-scroll.mjs.map +1 -1
  11. package/fesm2022/raintonic-formaui-components-accordion.mjs +8 -5
  12. package/fesm2022/raintonic-formaui-components-accordion.mjs.map +1 -1
  13. package/fesm2022/raintonic-formaui-components-alert.mjs +16 -2
  14. package/fesm2022/raintonic-formaui-components-alert.mjs.map +1 -1
  15. package/fesm2022/raintonic-formaui-components-autocomplete.mjs +255 -462
  16. package/fesm2022/raintonic-formaui-components-autocomplete.mjs.map +1 -1
  17. package/fesm2022/raintonic-formaui-components-avatar.mjs +34 -59
  18. package/fesm2022/raintonic-formaui-components-avatar.mjs.map +1 -1
  19. package/fesm2022/raintonic-formaui-components-badge.mjs +2 -2
  20. package/fesm2022/raintonic-formaui-components-badge.mjs.map +1 -1
  21. package/fesm2022/raintonic-formaui-components-breadcrumb.mjs +4 -4
  22. package/fesm2022/raintonic-formaui-components-breadcrumb.mjs.map +1 -1
  23. package/fesm2022/raintonic-formaui-components-button-group.mjs +2 -2
  24. package/fesm2022/raintonic-formaui-components-button-group.mjs.map +1 -1
  25. package/fesm2022/raintonic-formaui-components-button.mjs +15 -20
  26. package/fesm2022/raintonic-formaui-components-button.mjs.map +1 -1
  27. package/fesm2022/raintonic-formaui-components-card.mjs +2 -2
  28. package/fesm2022/raintonic-formaui-components-card.mjs.map +1 -1
  29. package/fesm2022/raintonic-formaui-components-checkbox.mjs +2 -2
  30. package/fesm2022/raintonic-formaui-components-checkbox.mjs.map +1 -1
  31. package/fesm2022/raintonic-formaui-components-chip.mjs +97 -0
  32. package/fesm2022/raintonic-formaui-components-chip.mjs.map +1 -0
  33. package/fesm2022/raintonic-formaui-components-data-table.mjs +69 -29
  34. package/fesm2022/raintonic-formaui-components-data-table.mjs.map +1 -1
  35. package/fesm2022/raintonic-formaui-components-date-picker.mjs +223 -144
  36. package/fesm2022/raintonic-formaui-components-date-picker.mjs.map +1 -1
  37. package/fesm2022/raintonic-formaui-components-divider.mjs +2 -2
  38. package/fesm2022/raintonic-formaui-components-divider.mjs.map +1 -1
  39. package/fesm2022/raintonic-formaui-components-drawer.mjs +2 -2
  40. package/fesm2022/raintonic-formaui-components-drawer.mjs.map +1 -1
  41. package/fesm2022/raintonic-formaui-components-dropdown-menu.mjs +888 -0
  42. package/fesm2022/raintonic-formaui-components-dropdown-menu.mjs.map +1 -0
  43. package/fesm2022/raintonic-formaui-components-dual-tier-navigation.mjs +774 -0
  44. package/fesm2022/raintonic-formaui-components-dual-tier-navigation.mjs.map +1 -0
  45. package/fesm2022/raintonic-formaui-components-empty-state.mjs +2 -2
  46. package/fesm2022/raintonic-formaui-components-empty-state.mjs.map +1 -1
  47. package/fesm2022/raintonic-formaui-components-file-upload.mjs +2 -2
  48. package/fesm2022/raintonic-formaui-components-file-upload.mjs.map +1 -1
  49. package/fesm2022/raintonic-formaui-components-form-field.mjs +81 -50
  50. package/fesm2022/raintonic-formaui-components-form-field.mjs.map +1 -1
  51. package/fesm2022/raintonic-formaui-components-icon.mjs +2 -2
  52. package/fesm2022/raintonic-formaui-components-icon.mjs.map +1 -1
  53. package/fesm2022/raintonic-formaui-components-input.mjs +47 -12
  54. package/fesm2022/raintonic-formaui-components-input.mjs.map +1 -1
  55. package/fesm2022/raintonic-formaui-components-list.mjs +4 -4
  56. package/fesm2022/raintonic-formaui-components-list.mjs.map +1 -1
  57. package/fesm2022/raintonic-formaui-components-number-input.mjs +20 -12
  58. package/fesm2022/raintonic-formaui-components-number-input.mjs.map +1 -1
  59. package/fesm2022/raintonic-formaui-components-paginator.mjs +2 -2
  60. package/fesm2022/raintonic-formaui-components-paginator.mjs.map +1 -1
  61. package/fesm2022/raintonic-formaui-components-password-input.mjs +35 -110
  62. package/fesm2022/raintonic-formaui-components-password-input.mjs.map +1 -1
  63. package/fesm2022/raintonic-formaui-components-popover.mjs +3 -2
  64. package/fesm2022/raintonic-formaui-components-popover.mjs.map +1 -1
  65. package/fesm2022/raintonic-formaui-components-progressbar.mjs +3 -2
  66. package/fesm2022/raintonic-formaui-components-progressbar.mjs.map +1 -1
  67. package/fesm2022/raintonic-formaui-components-radio.mjs +5 -6
  68. package/fesm2022/raintonic-formaui-components-radio.mjs.map +1 -1
  69. package/fesm2022/raintonic-formaui-components-select.mjs +257 -412
  70. package/fesm2022/raintonic-formaui-components-select.mjs.map +1 -1
  71. package/fesm2022/raintonic-formaui-components-side-panel.mjs +2 -2
  72. package/fesm2022/raintonic-formaui-components-side-panel.mjs.map +1 -1
  73. package/fesm2022/raintonic-formaui-components-sidebar-nav-menu.mjs +525 -0
  74. package/fesm2022/raintonic-formaui-components-sidebar-nav-menu.mjs.map +1 -0
  75. package/fesm2022/raintonic-formaui-components-skeleton.mjs +2 -2
  76. package/fesm2022/raintonic-formaui-components-skeleton.mjs.map +1 -1
  77. package/fesm2022/raintonic-formaui-components-slider.mjs +2 -2
  78. package/fesm2022/raintonic-formaui-components-slider.mjs.map +1 -1
  79. package/fesm2022/raintonic-formaui-components-spinner.mjs +2 -2
  80. package/fesm2022/raintonic-formaui-components-spinner.mjs.map +1 -1
  81. package/fesm2022/raintonic-formaui-components-stepper.mjs +50 -45
  82. package/fesm2022/raintonic-formaui-components-stepper.mjs.map +1 -1
  83. package/fesm2022/raintonic-formaui-components-strength-meter.mjs +149 -0
  84. package/fesm2022/raintonic-formaui-components-strength-meter.mjs.map +1 -0
  85. package/fesm2022/raintonic-formaui-components-tab.mjs +2 -2
  86. package/fesm2022/raintonic-formaui-components-tab.mjs.map +1 -1
  87. package/fesm2022/raintonic-formaui-components-time-picker.mjs +194 -154
  88. package/fesm2022/raintonic-formaui-components-time-picker.mjs.map +1 -1
  89. package/fesm2022/raintonic-formaui-components-toggle-group.mjs +302 -0
  90. package/fesm2022/raintonic-formaui-components-toggle-group.mjs.map +1 -0
  91. package/fesm2022/raintonic-formaui-components-toggle.mjs +2 -2
  92. package/fesm2022/raintonic-formaui-components-toggle.mjs.map +1 -1
  93. package/fesm2022/raintonic-formaui-components-toolbar.mjs +2 -2
  94. package/fesm2022/raintonic-formaui-components-toolbar.mjs.map +1 -1
  95. package/fesm2022/raintonic-formaui-components-tooltip.mjs +10 -4
  96. package/fesm2022/raintonic-formaui-components-tooltip.mjs.map +1 -1
  97. package/fesm2022/raintonic-formaui-components-topbar.mjs +60 -0
  98. package/fesm2022/raintonic-formaui-components-topbar.mjs.map +1 -0
  99. package/fesm2022/raintonic-formaui-components-tree-select.mjs +59 -69
  100. package/fesm2022/raintonic-formaui-components-tree-select.mjs.map +1 -1
  101. package/fesm2022/raintonic-formaui-components-tree-table.mjs +2 -2
  102. package/fesm2022/raintonic-formaui-components-tree-table.mjs.map +1 -1
  103. package/fesm2022/raintonic-formaui-components-tree.mjs +31 -5
  104. package/fesm2022/raintonic-formaui-components-tree.mjs.map +1 -1
  105. package/fesm2022/raintonic-formaui-core.mjs +279 -1
  106. package/fesm2022/raintonic-formaui-core.mjs.map +1 -1
  107. package/fesm2022/raintonic-formaui-services-breakpoint.mjs +93 -0
  108. package/fesm2022/raintonic-formaui-services-breakpoint.mjs.map +1 -0
  109. package/fesm2022/raintonic-formaui-services-dialog.mjs +314 -16
  110. package/fesm2022/raintonic-formaui-services-dialog.mjs.map +1 -1
  111. package/fesm2022/raintonic-formaui-services-notification.mjs +93 -29
  112. package/fesm2022/raintonic-formaui-services-notification.mjs.map +1 -1
  113. package/fesm2022/raintonic-formaui-services-theme.mjs +46 -196
  114. package/fesm2022/raintonic-formaui-services-theme.mjs.map +1 -1
  115. package/fesm2022/raintonic-formaui.mjs +1 -1
  116. package/fesm2022/raintonic-formaui.mjs.map +1 -1
  117. package/llms-full.txt +2329 -450
  118. package/llms.txt +36 -33
  119. package/package.json +42 -19
  120. package/styles/fonts/Geist-Bold.woff2 +0 -0
  121. package/styles/fonts/Geist-Italic.woff2 +0 -0
  122. package/styles/fonts/Geist-Light.woff2 +0 -0
  123. package/styles/fonts/Geist-Medium.woff2 +0 -0
  124. package/styles/fonts/Geist-Regular.woff2 +0 -0
  125. package/styles/fonts/Geist-SemiBold.woff2 +0 -0
  126. package/styles/fonts/GeistMono-Regular.woff2 +0 -0
  127. package/styles/generated/_tokens.scss +906 -0
  128. package/styles/index.scss +11 -10
  129. package/styles/partials/_brand.scss +46 -0
  130. package/styles/partials/_constants.scss +22 -20
  131. package/styles/partials/_fonts.scss +54 -10
  132. package/styles/partials/_grid.scss +29 -18
  133. package/styles/partials/_mixins.scss +69 -27
  134. package/styles/partials/_motion.scss +28 -33
  135. package/styles/partials/_theme.scss +28 -255
  136. package/styles/partials/_type.scss +117 -0
  137. package/styles/partials/_typography.scss +45 -45
  138. package/styles/partials/_utilities.scss +198 -98
  139. package/styles/partials/components/_button.scss +144 -75
  140. package/styles/partials/components/_dialog.scss +181 -180
  141. package/styles/partials/components/_overlay.scss +87 -87
  142. package/styles/partials/themes/_dark.scss +3 -268
  143. package/styles/partials/themes/_light.scss +4 -268
  144. package/styles/styles.css +7744 -0
  145. package/styles/styles.entry.scss +3 -0
  146. package/styles/utilities.css +4802 -0
  147. package/styles/utilities.entry.scss +3 -0
  148. package/types/raintonic-formaui-cdk-drag-drop.d.ts +0 -1
  149. package/types/raintonic-formaui-cdk-drag-drop.d.ts.map +1 -1
  150. package/types/raintonic-formaui-cdk-form-field.d.ts +118 -2
  151. package/types/raintonic-formaui-cdk-form-field.d.ts.map +1 -1
  152. package/types/raintonic-formaui-cdk-overlay.d.ts +2 -0
  153. package/types/raintonic-formaui-cdk-overlay.d.ts.map +1 -1
  154. package/types/raintonic-formaui-cdk-virtual-scroll.d.ts +0 -1
  155. package/types/raintonic-formaui-cdk-virtual-scroll.d.ts.map +1 -1
  156. package/types/raintonic-formaui-components-accordion.d.ts +1 -1
  157. package/types/raintonic-formaui-components-accordion.d.ts.map +1 -1
  158. package/types/raintonic-formaui-components-alert.d.ts +6 -1
  159. package/types/raintonic-formaui-components-alert.d.ts.map +1 -1
  160. package/types/raintonic-formaui-components-autocomplete.d.ts +73 -116
  161. package/types/raintonic-formaui-components-autocomplete.d.ts.map +1 -1
  162. package/types/raintonic-formaui-components-avatar.d.ts +13 -31
  163. package/types/raintonic-formaui-components-avatar.d.ts.map +1 -1
  164. package/types/raintonic-formaui-components-button.d.ts +4 -10
  165. package/types/raintonic-formaui-components-button.d.ts.map +1 -1
  166. package/types/raintonic-formaui-components-chip.d.ts +43 -0
  167. package/types/raintonic-formaui-components-chip.d.ts.map +1 -0
  168. package/types/raintonic-formaui-components-data-table.d.ts +48 -11
  169. package/types/raintonic-formaui-components-data-table.d.ts.map +1 -1
  170. package/types/raintonic-formaui-components-date-picker.d.ts +59 -23
  171. package/types/raintonic-formaui-components-date-picker.d.ts.map +1 -1
  172. package/types/raintonic-formaui-components-dropdown-menu.d.ts +394 -0
  173. package/types/raintonic-formaui-components-dropdown-menu.d.ts.map +1 -0
  174. package/types/raintonic-formaui-components-dual-tier-navigation.d.ts +87 -0
  175. package/types/raintonic-formaui-components-dual-tier-navigation.d.ts.map +1 -0
  176. package/types/raintonic-formaui-components-form-field.d.ts +51 -21
  177. package/types/raintonic-formaui-components-form-field.d.ts.map +1 -1
  178. package/types/raintonic-formaui-components-input.d.ts +20 -11
  179. package/types/raintonic-formaui-components-input.d.ts.map +1 -1
  180. package/types/raintonic-formaui-components-number-input.d.ts +5 -3
  181. package/types/raintonic-formaui-components-number-input.d.ts.map +1 -1
  182. package/types/raintonic-formaui-components-password-input.d.ts +18 -32
  183. package/types/raintonic-formaui-components-password-input.d.ts.map +1 -1
  184. package/types/raintonic-formaui-components-popover.d.ts.map +1 -1
  185. package/types/raintonic-formaui-components-progressbar.d.ts +1 -1
  186. package/types/raintonic-formaui-components-progressbar.d.ts.map +1 -1
  187. package/types/raintonic-formaui-components-radio.d.ts +1 -2
  188. package/types/raintonic-formaui-components-radio.d.ts.map +1 -1
  189. package/types/raintonic-formaui-components-select.d.ts +107 -76
  190. package/types/raintonic-formaui-components-select.d.ts.map +1 -1
  191. package/types/raintonic-formaui-components-sidebar-nav-menu.d.ts +223 -0
  192. package/types/raintonic-formaui-components-sidebar-nav-menu.d.ts.map +1 -0
  193. package/types/raintonic-formaui-components-stepper.d.ts +4 -2
  194. package/types/raintonic-formaui-components-stepper.d.ts.map +1 -1
  195. package/types/raintonic-formaui-components-strength-meter.d.ts +78 -0
  196. package/types/raintonic-formaui-components-strength-meter.d.ts.map +1 -0
  197. package/types/raintonic-formaui-components-time-picker.d.ts +44 -24
  198. package/types/raintonic-formaui-components-time-picker.d.ts.map +1 -1
  199. package/types/raintonic-formaui-components-toggle-group.d.ts +100 -0
  200. package/types/raintonic-formaui-components-toggle-group.d.ts.map +1 -0
  201. package/types/raintonic-formaui-components-tooltip.d.ts +2 -1
  202. package/types/raintonic-formaui-components-tooltip.d.ts.map +1 -1
  203. package/types/raintonic-formaui-components-topbar.d.ts +48 -0
  204. package/types/raintonic-formaui-components-topbar.d.ts.map +1 -0
  205. package/types/raintonic-formaui-components-tree-select.d.ts +25 -9
  206. package/types/raintonic-formaui-components-tree-select.d.ts.map +1 -1
  207. package/types/raintonic-formaui-components-tree.d.ts +12 -1
  208. package/types/raintonic-formaui-components-tree.d.ts.map +1 -1
  209. package/types/raintonic-formaui-core.d.ts +243 -5
  210. package/types/raintonic-formaui-core.d.ts.map +1 -1
  211. package/types/raintonic-formaui-services-breakpoint.d.ts +44 -0
  212. package/types/raintonic-formaui-services-breakpoint.d.ts.map +1 -0
  213. package/types/raintonic-formaui-services-dialog.d.ts +141 -2
  214. package/types/raintonic-formaui-services-dialog.d.ts.map +1 -1
  215. package/types/raintonic-formaui-services-notification.d.ts +24 -2
  216. package/types/raintonic-formaui-services-notification.d.ts.map +1 -1
  217. package/types/raintonic-formaui-services-theme.d.ts +13 -103
  218. package/types/raintonic-formaui-services-theme.d.ts.map +1 -1
  219. package/types/raintonic-formaui.d.ts +1 -1
  220. package/fesm2022/raintonic-formaui-components-big-menu.mjs +0 -86
  221. package/fesm2022/raintonic-formaui-components-big-menu.mjs.map +0 -1
  222. package/fesm2022/raintonic-formaui-components-menu.mjs +0 -896
  223. package/fesm2022/raintonic-formaui-components-menu.mjs.map +0 -1
  224. package/fesm2022/raintonic-formaui-components-sidebar.mjs +0 -275
  225. package/fesm2022/raintonic-formaui-components-sidebar.mjs.map +0 -1
  226. package/fesm2022/raintonic-formaui-components-tag.mjs +0 -95
  227. package/fesm2022/raintonic-formaui-components-tag.mjs.map +0 -1
  228. package/styles/_fonts-entry.scss +0 -3
  229. package/styles/fonts/inter-tight-latin-italic.woff2 +0 -0
  230. package/styles/fonts/inter-tight-latin.woff2 +0 -0
  231. package/types/raintonic-formaui-components-big-menu.d.ts +0 -73
  232. package/types/raintonic-formaui-components-big-menu.d.ts.map +0 -1
  233. package/types/raintonic-formaui-components-menu.d.ts +0 -403
  234. package/types/raintonic-formaui-components-menu.d.ts.map +0 -1
  235. package/types/raintonic-formaui-components-sidebar.d.ts +0 -185
  236. package/types/raintonic-formaui-components-sidebar.d.ts.map +0 -1
  237. package/types/raintonic-formaui-components-tag.d.ts +0 -43
  238. package/types/raintonic-formaui-components-tag.d.ts.map +0 -1
@@ -1,58 +1,43 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, input, output, signal, inject, ElementRef, HostListener, ViewEncapsulation, ChangeDetectionStrategy, Component, computed, contentChildren, NgZone, effect, ViewChild } from '@angular/core';
3
- import { Subject, Subscription, fromEvent } from 'rxjs';
4
- import { NgForm, FormGroupDirective, ReactiveFormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
5
- import { DOCUMENT } from '@angular/common';
6
- import { filter } from 'rxjs/operators';
7
- import { injectNgControl, updateErrorState, syncRequiredState, syncNgControlDisabled } from '@raintonic/formaui/cdk/form-field';
8
- import { DefaultErrorStateMatcher, FUI_FORM_FIELD_CONTROL } from '@raintonic/formaui/core';
9
- import { FuiOverlayService } from '@raintonic/formaui/cdk/overlay';
2
+ import { InjectionToken, input, output, signal, inject, ElementRef, HostListener, Directive, computed, ViewEncapsulation, ChangeDetectionStrategy, Component, contentChildren, effect, ViewChild } from '@angular/core';
3
+ import { Subject } from 'rxjs';
4
+ import { FuiCheckboxComponent } from '@raintonic/formaui/components/checkbox';
5
+ import { ReactiveFormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
6
+ import * as i1 from '@raintonic/formaui/cdk/form-field';
7
+ import { FuiPopupOverlayDirective, FuiFormControlSyncDirective, injectNgControl } from '@raintonic/formaui/cdk/form-field';
8
+ import { isEmpty, computeMultiDisplayValue, syncMultiOptions, applyOptionSelection, computePagedActiveOptionIndex, computeNextActiveOptionIndex, scrollOptionIntoView, findInitialActiveOptionIndex, getEnabledOptions, computeActiveDescendant, announceMessage, FUI_FORM_FIELD_CONTROL } from '@raintonic/formaui/core';
9
+ import { FuiIconComponent } from '@raintonic/formaui/components/icon';
10
+ import { FUI_FORM_FIELD } from '@raintonic/formaui/components/form-field';
10
11
 
11
12
  /**
12
13
  * Injection token used to provide the parent select to options.
13
14
  */
14
15
  const FUI_SELECT = new InjectionToken('FUI_SELECT');
16
+ /**
17
+ * Vertical offset (px) between the select trigger bottom and overlay panel top.
18
+ * The default popup positions use 4px, but the select needs 16px for better
19
+ * visual spacing between the trigger and the dropdown panel.
20
+ */
21
+ const FUI_SELECT_OVERLAY_OFFSET = 16;
15
22
 
16
23
  /**
17
- * # FuiOption Component
18
- *
19
- * Individual option component for use within fui-select.
20
- * Works like Angular Material's mat-option with full accessibility support.
21
- *
22
- * ## Features
23
- * - Disabled state support
24
- * - Selection state management
25
- * - Full accessibility support (ARIA attributes)
26
- * - Keyboard navigation support
27
- * - Custom content projection
28
- * - Smooth hover animations
24
+ * Injection token for querying all option-like components
25
+ * via contentChildren from parent components.
29
26
  *
30
- * ## Usage
27
+ * FuiOptionComponent provides this token so a single
28
+ * contentChildren(FUI_OPTION) query captures all options.
29
+ */
30
+ const FUI_OPTION = new InjectionToken('FUI_OPTION');
31
+ /**
32
+ * Abstract base class for FuiOptionComponent.
31
33
  *
32
- * ### Basic Option
33
- * ```html
34
- * <fui-select placeholder="Select a status">
35
- * <fui-option value="active">Active</fui-option>
36
- * <fui-option value="inactive">Inactive</fui-option>
37
- * <fui-option value="pending" [disabled]="true">Pending (Disabled)</fui-option>
38
- * </fui-select>
39
- * ```
34
+ * Defines the contract that parent components rely on.
35
+ * FuiOptionComponent provides FUI_OPTION for DI-based querying.
40
36
  *
41
- * ### Option with Custom Content
42
- * ```html
43
- * <fui-select placeholder="Select a country">
44
- * <fui-option value="us">
45
- * <fui-icon name="flag-us"></fui-icon>
46
- * United States
47
- * </fui-option>
48
- * <fui-option value="ca">
49
- * <fui-icon name="flag-ca"></fui-icon>
50
- * Canada
51
- * </fui-option>
52
- * </fui-select>
53
- * ```
37
+ * Uses @Directive() so Angular recognises input()/output()/signal()/HostListener
38
+ * calls as valid (they are only valid on @Component or @Directive classes).
54
39
  */
55
- class FuiOptionComponent {
40
+ class FuiOptionBase {
56
41
  static nextId = 0;
57
42
  /**
58
43
  * The value of the option
@@ -73,16 +58,10 @@ class FuiOptionComponent {
73
58
  stateChanges = new Subject();
74
59
  // Element reference
75
60
  _element = inject(ElementRef);
76
- // Parent select (optional - may not exist if used standalone)
61
+ // Parent select (optional may not exist if used standalone)
77
62
  _parentSelect = inject(FUI_SELECT, { optional: true });
78
63
  // Unique ID
79
- id = `fui-option-${FuiOptionComponent.nextId++}`;
80
- // Show checkmark only in multiple mode when selected
81
- _showCheckmark = signal(false, ...(ngDevMode ? [{ debugName: "_showCheckmark" }] : /* istanbul ignore next */ []));
82
- ngAfterViewInit() {
83
- // Update checkmark visibility based on parent select's multiple mode
84
- this._updateCheckmarkVisibility();
85
- }
64
+ id = `fui-option-${FuiOptionBase.nextId++}`;
86
65
  ngOnDestroy() {
87
66
  this.stateChanges.complete();
88
67
  }
@@ -99,7 +78,7 @@ class FuiOptionComponent {
99
78
  this._parentSelect._onOptionSelected(this);
100
79
  }
101
80
  else {
102
- // Standalone usage - emit event
81
+ // Standalone usage emit event
103
82
  this._emitSelectionChangeEvent();
104
83
  }
105
84
  }
@@ -118,7 +97,6 @@ class FuiOptionComponent {
118
97
  select() {
119
98
  if (!this._selected()) {
120
99
  this._selected.set(true);
121
- this._updateCheckmarkVisibility();
122
100
  this.stateChanges.next();
123
101
  }
124
102
  }
@@ -128,7 +106,6 @@ class FuiOptionComponent {
128
106
  deselect() {
129
107
  if (this._selected()) {
130
108
  this._selected.set(false);
131
- this._updateCheckmarkVisibility();
132
109
  this.stateChanges.next();
133
110
  }
134
111
  }
@@ -171,43 +148,121 @@ class FuiOptionComponent {
171
148
  _getHostElement() {
172
149
  return this._element.nativeElement;
173
150
  }
174
- /** Update checkmark visibility based on selection and multiple mode */
175
- _updateCheckmarkVisibility() {
176
- const isMultiple = this._parentSelect?.multiple() ?? false;
177
- this._showCheckmark.set(isMultiple && this._selected());
178
- }
179
151
  /** Emits the selection change event */
180
152
  _emitSelectionChangeEvent() {
181
153
  this.selectionChange.emit({ source: this, value: this.value() });
182
154
  }
183
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiOptionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
184
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: FuiOptionComponent, isStandalone: true, selector: "fui-option", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange" }, host: { attributes: { "role": "option" }, listeners: { "click": "_handleClick($event)", "mouseenter": "_handleMouseEnter()", "mouseleave": "_handleMouseLeave()" }, properties: { "class.fui-option--selected": "_selected()", "class.fui-option--disabled": "disabled()", "class.fui-option--active": "_active()", "attr.id": "id", "attr.aria-selected": "_selected()", "attr.aria-disabled": "disabled()", "attr.tabindex": "-1" }, classAttribute: "fui-option" }, ngImport: i0, template: `
185
- @if (_showCheckmark()) {
186
- <span class="fui-option__checkmark">
187
- <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
188
- <path d="M6.5 11.5L3 8l1-1 2.5 2.5L12 4l1 1z" />
189
- </svg>
190
- </span>
191
- }
155
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiOptionBase, deps: [], target: i0.ɵɵFactoryTarget.Directive });
156
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.6", type: FuiOptionBase, isStandalone: true, inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange" }, host: { listeners: { "click": "_handleClick($event)", "mouseenter": "_handleMouseEnter()", "mouseleave": "_handleMouseLeave()" } }, ngImport: i0 });
157
+ }
158
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiOptionBase, decorators: [{
159
+ type: Directive
160
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], _handleClick: [{
161
+ type: HostListener,
162
+ args: ['click', ['$event']]
163
+ }], _handleMouseEnter: [{
164
+ type: HostListener,
165
+ args: ['mouseenter']
166
+ }], _handleMouseLeave: [{
167
+ type: HostListener,
168
+ args: ['mouseleave']
169
+ }] } });
170
+
171
+ /**
172
+ * # FuiOption Component
173
+ *
174
+ * Individual option component for use within fui-select.
175
+ * Works like Angular Material's mat-option with full accessibility support.
176
+ *
177
+ * ## Features
178
+ * - Disabled state support
179
+ * - Selection state management
180
+ * - Full accessibility support (ARIA attributes)
181
+ * - Keyboard navigation support
182
+ * - Custom content projection
183
+ * - Smooth hover animations
184
+ *
185
+ * ## Usage
186
+ *
187
+ * ### Basic Option
188
+ * ```html
189
+ * <fui-select placeholder="Select a status">
190
+ * <fui-option value="active">Active</fui-option>
191
+ * <fui-option value="inactive">Inactive</fui-option>
192
+ * <fui-option value="pending" [disabled]="true">Pending (Disabled)</fui-option>
193
+ * </fui-select>
194
+ * ```
195
+ *
196
+ * ### Option with Custom Content
197
+ * ```html
198
+ * <fui-select placeholder="Select a country">
199
+ * <fui-option value="us">
200
+ * <fui-icon name="flag-us"></fui-icon>
201
+ * United States
202
+ * </fui-option>
203
+ * <fui-option value="ca">
204
+ * <fui-icon name="flag-ca"></fui-icon>
205
+ * Canada
206
+ * </fui-option>
207
+ * </fui-select>
208
+ * ```
209
+ *
210
+ * ### Multi-Select Options
211
+ * When `fui-select` has `[multiple]="true"`, `<fui-option>` automatically renders
212
+ * a checkbox indicator:
213
+ * ```html
214
+ * <fui-select [multiple]="true" placeholder="Select skills">
215
+ * <fui-option value="js">JavaScript</fui-option>
216
+ * <fui-option value="ts">TypeScript</fui-option>
217
+ * <fui-option value="py">Python</fui-option>
218
+ * </fui-select>
219
+ * ```
220
+ */
221
+ class FuiOptionComponent extends FuiOptionBase {
222
+ /** Whether the parent select is in multi-select mode */
223
+ _isMultiParent = computed(() => this._parentSelect?.multiple() ?? false, ...(ngDevMode ? [{ debugName: "_isMultiParent" }] : /* istanbul ignore next */ []));
224
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiOptionComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
225
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: FuiOptionComponent, isStandalone: true, selector: "fui-option", host: { attributes: { "role": "option" }, properties: { "class.fui-option--selected": "_selected()", "class.fui-option--disabled": "disabled()", "class.fui-option--active": "_active()", "attr.id": "id", "attr.aria-selected": "_selected()", "attr.aria-disabled": "disabled()", "attr.tabindex": "-1" }, classAttribute: "fui-option" }, providers: [
226
+ {
227
+ provide: FUI_OPTION,
228
+ useExisting: FuiOptionComponent,
229
+ },
230
+ ], usesInheritance: true, ngImport: i0, template: `
231
+ @if (_isMultiParent()) {
232
+ <fui-checkbox
233
+ class="fui-option__checkbox"
234
+ [checked]="_selected()"
235
+ [disabled]="disabled()"
236
+ [tabIndex]="-1"
237
+ aria-hidden="true"
238
+ />
239
+ } @else if (_selected()) {}
192
240
  <span class="fui-option__content">
193
241
  <ng-content></ng-content>
194
242
  </span>
195
- `, isInline: true, styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition:opacity var(--fui-duration-fast-02) var(--fui-ease-entrance) 0ms}.fui-motion-fade-out{transition:opacity var(--fui-duration-fast-01) var(--fui-ease-exit) 0ms}.fui-motion-slide-in-bottom{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition:transform,opacity var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-moderate-01) var(--fui-ease-entrance)}@keyframes fui-popover-enter{0%{opacity:0;transform:translateY(-14px)}60%{opacity:1}to{opacity:1;transform:translateY(0)}}.fui-option{position:relative;display:flex;align-items:center;min-height:2.5rem;margin:0 var(--fui-spacing-02);padding:0 1rem;cursor:pointer;-webkit-user-select:none;user-select:none;outline:none;border-radius:var(--fui-border-radius-sm);color:var(--fui-text-primary);background-color:transparent;transition:background-color,color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-option:hover:not(.fui-option--disabled){background-color:var(--fui-surface-02)}.fui-option--selected{color:var(--fui-primary);background-color:var(--fui-primary-10)}.fui-option--selected:hover:not(.fui-option--disabled){background-color:var(--fui-primary-20)}.fui-option--disabled{opacity:.5;cursor:not-allowed;color:var(--fui-text-secondary)}.fui-option:focus-visible,.fui-option:focus,.fui-option--active{background-color:var(--fui-surface-01)}.fui-option__checkmark{display:flex;align-items:center;justify-content:center;width:1rem;height:1rem;margin-right:.5rem;flex-shrink:0;color:var(--fui-primary);transition:transform,opacity var(--fui-duration-fast-02) var(--fui-ease-expressive) 0ms}.fui-option__checkmark svg{width:100%;height:100%}.fui-option__content{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fui-option--group-header{font-size:var(--fui-font-size-01);font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--fui-text-secondary);padding:.5rem 1rem;min-height:2rem;cursor:default}.fui-option--group-header:hover{background-color:transparent}.fui-theme-dark .fui-option--selected{background-color:rgba(var(--fui-primary-rgb),.15)}.fui-theme-dark .fui-option--selected:hover:not(.fui-option--disabled){background-color:rgba(var(--fui-primary-rgb),.25)}@media(prefers-contrast:high){.fui-option--selected{outline:2px solid var(--fui-primary);outline-offset:-2px}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
243
+ `, isInline: true, styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition-property:opacity;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-out);transition-delay:0ms}.fui-motion-fade-out{transition-property:opacity;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-in);transition-delay:0ms}.fui-motion-slide-in-bottom{transition-property:transform;transition-duration:var(--fui-duration-base);transition-timing-function:var(--fui-ease-out);transition-delay:0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition-property:transform;transition-duration:var(--fui-duration-base);transition-timing-function:var(--fui-ease-out);transition-delay:0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition-property:transform,opacity;transition-duration:var(--fui-duration-base);transition-timing-function:var(--fui-ease-out);transition-delay:0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-base) var(--fui-ease-out)}@keyframes fui-popover-enter{0%{opacity:0;transform:translateY(-14px)}60%{opacity:1}to{opacity:1;transform:translateY(0)}}.fui-option{position:relative;display:flex;font-size:var(--fui-text-base);align-items:center;margin:0 var(--fui-spacing-2);padding:var(--fui-spacing-3) var(--fui-spacing-4);cursor:pointer;-webkit-user-select:none;user-select:none;outline:none;border-radius:var(--fui-radius-xs);color:var(--fui-text-primary);background-color:transparent;transition-property:background-color,color;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-in-out);transition-delay:0ms}.fui-option:hover:not(.fui-option--disabled){background-color:var(--fui-primary-muted)}.fui-option--disabled{opacity:.5;cursor:not-allowed;color:var(--fui-text-secondary)}.fui-option:focus-visible,.fui-option:focus{background-color:var(--fui-bg-default)}.fui-option--active:not(.fui-option--disabled){background-color:var(--fui-primary-muted)}.fui-option--selected:not(.fui-option--disabled){background-color:var(--fui-bg-subtle)}.fui-option--selected:not(.fui-option--disabled):hover{background-color:color-mix(in srgb,var(--fui-bg-subtle) 100%,var(--fui-primary-muted) 100%)}.fui-option.fui-option--selected.fui-option--active{background-color:color-mix(in srgb,var(--fui-bg-subtle) 100%,var(--fui-primary-muted) 100%)}.fui-option__checkmark{display:flex;align-items:center;justify-content:center;width:1rem;height:1rem;margin-right:.5rem;flex-shrink:0;color:var(--fui-primary-fg);transition-property:transform,opacity;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-emphasized);transition-delay:0ms}.fui-option__checkmark svg{width:100%;height:100%}.fui-option__content{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fui-option--group-header{font-size:var(--fui-text-sm);font-weight:var(--fui-weight-semibold);text-transform:uppercase;letter-spacing:.05em;color:var(--fui-text-secondary);padding:.5rem 1rem;min-height:2rem;cursor:default}.fui-option--group-header:hover{background-color:transparent}@media(prefers-contrast:high){.fui-option--selected{outline:2px solid var(--fui-border-primary);outline-offset:-2px}}\n"], dependencies: [{ kind: "component", type: FuiCheckboxComponent, selector: "fui-checkbox", inputs: ["readonly", "checked", "disabled", "indeterminate", "required", "labelPosition", "name", "value", "aria-label", "aria-labelledby", "aria-describedby", "disableRipple", "tabIndex", "errorStateMatcher"], outputs: ["change", "indeterminateChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
196
244
  }
197
245
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiOptionComponent, decorators: [{
198
246
  type: Component,
199
- args: [{ selector: 'fui-option', standalone: true, imports: [], template: `
200
- @if (_showCheckmark()) {
201
- <span class="fui-option__checkmark">
202
- <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
203
- <path d="M6.5 11.5L3 8l1-1 2.5 2.5L12 4l1 1z" />
204
- </svg>
205
- </span>
206
- }
247
+ args: [{ selector: 'fui-option', standalone: true, imports: [FuiCheckboxComponent], template: `
248
+ @if (_isMultiParent()) {
249
+ <fui-checkbox
250
+ class="fui-option__checkbox"
251
+ [checked]="_selected()"
252
+ [disabled]="disabled()"
253
+ [tabIndex]="-1"
254
+ aria-hidden="true"
255
+ />
256
+ } @else if (_selected()) {}
207
257
  <span class="fui-option__content">
208
258
  <ng-content></ng-content>
209
259
  </span>
210
- `, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
260
+ `, providers: [
261
+ {
262
+ provide: FUI_OPTION,
263
+ useExisting: FuiOptionComponent,
264
+ },
265
+ ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
211
266
  class: 'fui-option',
212
267
  '[class.fui-option--selected]': '_selected()',
213
268
  '[class.fui-option--disabled]': 'disabled()',
@@ -217,17 +272,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImpor
217
272
  '[attr.aria-selected]': '_selected()',
218
273
  '[attr.aria-disabled]': 'disabled()',
219
274
  '[attr.tabindex]': '-1',
220
- }, styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition:opacity var(--fui-duration-fast-02) var(--fui-ease-entrance) 0ms}.fui-motion-fade-out{transition:opacity var(--fui-duration-fast-01) var(--fui-ease-exit) 0ms}.fui-motion-slide-in-bottom{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition:transform,opacity var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-moderate-01) var(--fui-ease-entrance)}@keyframes fui-popover-enter{0%{opacity:0;transform:translateY(-14px)}60%{opacity:1}to{opacity:1;transform:translateY(0)}}.fui-option{position:relative;display:flex;align-items:center;min-height:2.5rem;margin:0 var(--fui-spacing-02);padding:0 1rem;cursor:pointer;-webkit-user-select:none;user-select:none;outline:none;border-radius:var(--fui-border-radius-sm);color:var(--fui-text-primary);background-color:transparent;transition:background-color,color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-option:hover:not(.fui-option--disabled){background-color:var(--fui-surface-02)}.fui-option--selected{color:var(--fui-primary);background-color:var(--fui-primary-10)}.fui-option--selected:hover:not(.fui-option--disabled){background-color:var(--fui-primary-20)}.fui-option--disabled{opacity:.5;cursor:not-allowed;color:var(--fui-text-secondary)}.fui-option:focus-visible,.fui-option:focus,.fui-option--active{background-color:var(--fui-surface-01)}.fui-option__checkmark{display:flex;align-items:center;justify-content:center;width:1rem;height:1rem;margin-right:.5rem;flex-shrink:0;color:var(--fui-primary);transition:transform,opacity var(--fui-duration-fast-02) var(--fui-ease-expressive) 0ms}.fui-option__checkmark svg{width:100%;height:100%}.fui-option__content{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fui-option--group-header{font-size:var(--fui-font-size-01);font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--fui-text-secondary);padding:.5rem 1rem;min-height:2rem;cursor:default}.fui-option--group-header:hover{background-color:transparent}.fui-theme-dark .fui-option--selected{background-color:rgba(var(--fui-primary-rgb),.15)}.fui-theme-dark .fui-option--selected:hover:not(.fui-option--disabled){background-color:rgba(var(--fui-primary-rgb),.25)}@media(prefers-contrast:high){.fui-option--selected{outline:2px solid var(--fui-primary);outline-offset:-2px}}\n"] }]
221
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], _handleClick: [{
222
- type: HostListener,
223
- args: ['click', ['$event']]
224
- }], _handleMouseEnter: [{
225
- type: HostListener,
226
- args: ['mouseenter']
227
- }], _handleMouseLeave: [{
228
- type: HostListener,
229
- args: ['mouseleave']
230
- }] } });
275
+ }, styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition-property:opacity;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-out);transition-delay:0ms}.fui-motion-fade-out{transition-property:opacity;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-in);transition-delay:0ms}.fui-motion-slide-in-bottom{transition-property:transform;transition-duration:var(--fui-duration-base);transition-timing-function:var(--fui-ease-out);transition-delay:0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition-property:transform;transition-duration:var(--fui-duration-base);transition-timing-function:var(--fui-ease-out);transition-delay:0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition-property:transform,opacity;transition-duration:var(--fui-duration-base);transition-timing-function:var(--fui-ease-out);transition-delay:0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-base) var(--fui-ease-out)}@keyframes fui-popover-enter{0%{opacity:0;transform:translateY(-14px)}60%{opacity:1}to{opacity:1;transform:translateY(0)}}.fui-option{position:relative;display:flex;font-size:var(--fui-text-base);align-items:center;margin:0 var(--fui-spacing-2);padding:var(--fui-spacing-3) var(--fui-spacing-4);cursor:pointer;-webkit-user-select:none;user-select:none;outline:none;border-radius:var(--fui-radius-xs);color:var(--fui-text-primary);background-color:transparent;transition-property:background-color,color;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-in-out);transition-delay:0ms}.fui-option:hover:not(.fui-option--disabled){background-color:var(--fui-primary-muted)}.fui-option--disabled{opacity:.5;cursor:not-allowed;color:var(--fui-text-secondary)}.fui-option:focus-visible,.fui-option:focus{background-color:var(--fui-bg-default)}.fui-option--active:not(.fui-option--disabled){background-color:var(--fui-primary-muted)}.fui-option--selected:not(.fui-option--disabled){background-color:var(--fui-bg-subtle)}.fui-option--selected:not(.fui-option--disabled):hover{background-color:color-mix(in srgb,var(--fui-bg-subtle) 100%,var(--fui-primary-muted) 100%)}.fui-option.fui-option--selected.fui-option--active{background-color:color-mix(in srgb,var(--fui-bg-subtle) 100%,var(--fui-primary-muted) 100%)}.fui-option__checkmark{display:flex;align-items:center;justify-content:center;width:1rem;height:1rem;margin-right:.5rem;flex-shrink:0;color:var(--fui-primary-fg);transition-property:transform,opacity;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-emphasized);transition-delay:0ms}.fui-option__checkmark svg{width:100%;height:100%}.fui-option__content{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fui-option--group-header{font-size:var(--fui-text-sm);font-weight:var(--fui-weight-semibold);text-transform:uppercase;letter-spacing:.05em;color:var(--fui-text-secondary);padding:.5rem 1rem;min-height:2rem;cursor:default}.fui-option--group-header:hover{background-color:transparent}@media(prefers-contrast:high){.fui-option--selected{outline:2px solid var(--fui-border-primary);outline-offset:-2px}}\n"] }]
276
+ }] });
231
277
 
232
278
  /**
233
279
  * # fui-select Component
@@ -292,16 +338,23 @@ class FuiSelectComponent {
292
338
  // Static properties
293
339
  static nextId = 0;
294
340
  controlType = 'fui-select';
341
+ // Injected host directives
342
+ _popup = inject(FuiPopupOverlayDirective);
343
+ _formSync = inject(FuiFormControlSyncDirective);
344
+ // Optional parent form-field (null when used standalone)
345
+ _parentFormField = inject(FUI_FORM_FIELD, { optional: true });
295
346
  // Inputs using new signal-based API
296
347
  placeholderInput = input('', { ...(ngDevMode ? { debugName: "placeholderInput" } : /* istanbul ignore next */ {}), alias: 'placeholder' });
297
348
  disabledInput = input(false, { ...(ngDevMode ? { debugName: "disabledInput" } : /* istanbul ignore next */ {}), alias: 'disabled' });
298
- readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
349
+ readonlyInput = input(false, { ...(ngDevMode ? { debugName: "readonlyInput" } : /* istanbul ignore next */ {}), alias: 'readonly' });
299
350
  multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []));
300
- errorStateMatcher = input(null, ...(ngDevMode ? [{ debugName: "errorStateMatcher" }] : /* istanbul ignore next */ []));
301
351
  /**
302
- * Whether to compare option values using object identity or deep equality
352
+ * Comparison function for option values.
353
+ * Defaults to strict equality (`===`). Override when options use object
354
+ * values and you need to match by a property (e.g. comparing by `id`).
303
355
  */
304
356
  compareWith = input((o1, o2) => o1 === o2, ...(ngDevMode ? [{ debugName: "compareWith" }] : /* istanbul ignore next */ []));
357
+ errorStateMatcher = input(null, ...(ngDevMode ? [{ debugName: "errorStateMatcher" }] : /* istanbul ignore next */ []));
305
358
  // Outputs
306
359
  valueChange = output();
307
360
  selectionChange = output();
@@ -310,7 +363,6 @@ class FuiSelectComponent {
310
363
  _value = signal(null, ...(ngDevMode ? [{ debugName: "_value" }] : /* istanbul ignore next */ []));
311
364
  _focused = signal(false, ...(ngDevMode ? [{ debugName: "_focused" }] : /* istanbul ignore next */ []));
312
365
  _disabled = signal(false, ...(ngDevMode ? [{ debugName: "_disabled" }] : /* istanbul ignore next */ []));
313
- _readOnly = signal(false, ...(ngDevMode ? [{ debugName: "_readOnly" }] : /* istanbul ignore next */ []));
314
366
  // FuiFormFieldControl implementation
315
367
  stateChanges = new Subject();
316
368
  _uid = `fui-select-${FuiSelectComponent.nextId++}`;
@@ -319,9 +371,6 @@ class FuiSelectComponent {
319
371
  _errorState = signal(false, ...(ngDevMode ? [{ debugName: "_errorState" }] : /* istanbul ignore next */ []));
320
372
  errorState = this._errorState;
321
373
  // Form control references
322
- _parentForm = inject(NgForm, { optional: true });
323
- _parentFormGroup = inject(FormGroupDirective, { optional: true });
324
- _defaultErrorStateMatcher = inject(DefaultErrorStateMatcher);
325
374
  _ngControlRef = injectNgControl();
326
375
  get ngControl() {
327
376
  return this._ngControlRef.ngControl;
@@ -334,35 +383,24 @@ class FuiSelectComponent {
334
383
  focused = this._focused;
335
384
  _ngControlDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_ngControlDisabled" }] : /* istanbul ignore next */ []));
336
385
  disabled = computed(() => this._disabled() || this.disabledInput() || this._ngControlDisabled(), ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
337
- empty = computed(() => {
338
- const val = this._value();
339
- return val === null || val === undefined || (this.isArray(val) && val.length === 0);
340
- }, ...(ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []));
386
+ readonly = computed(() => this.readonlyInput(), ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
387
+ empty = computed(() => isEmpty(this._value()), ...(ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []));
341
388
  id = this._uid;
342
389
  // ViewChild for trigger and panel
343
390
  trigger;
344
391
  panel;
345
392
  // ContentChildren for options
346
- options = contentChildren(FuiOptionComponent, { ...(ngDevMode ? { debugName: "options" } : /* istanbul ignore next */ {}), descendants: true });
347
- // Panel open/close state
348
- panelOpen = signal(false, ...(ngDevMode ? [{ debugName: "panelOpen" }] : /* istanbul ignore next */ []));
393
+ options = contentChildren(FUI_OPTION, { ...(ngDevMode ? { debugName: "options" } : /* istanbul ignore next */ {}), descendants: true });
394
+ // Panel open/close state — projected from the popup overlay directive
395
+ panelOpen = computed(() => this._popup.panelOpen(), ...(ngDevMode ? [{ debugName: "panelOpen" }] : /* istanbul ignore next */ []));
349
396
  // Active option index for keyboard navigation
350
397
  _activeOptionIndex = signal(-1, ...(ngDevMode ? [{ debugName: "_activeOptionIndex" }] : /* istanbul ignore next */ []));
351
398
  activeOptionIndex = this._activeOptionIndex.asReadonly();
352
399
  // Live announcement for screen readers
353
400
  _liveAnnouncement = signal('', ...(ngDevMode ? [{ debugName: "_liveAnnouncement" }] : /* istanbul ignore next */ []));
354
- // Overlay reference
355
- _overlayRef = null;
356
- _overlaySubscriptions = new Subscription();
357
- // Services
358
- _overlayService = inject(FuiOverlayService);
359
- _elementRef = inject(ElementRef);
360
- _document = inject(DOCUMENT);
361
- _ngZone = inject(NgZone);
362
- _outsideClickSub;
363
401
  // Type-ahead search
364
- _typeaheadBuffer = '';
365
- _typeaheadTimeout = null;
402
+ _typeaheadBuffer = signal('', ...(ngDevMode ? [{ debugName: "_typeaheadBuffer" }] : /* istanbul ignore next */ []));
403
+ _typeaheadResetTimer = null;
366
404
  TYPE_AHEAD_DEBOUNCE = 200;
367
405
  // ControlValueAccessor callbacks
368
406
  _onChange = () => {
@@ -372,26 +410,7 @@ class FuiSelectComponent {
372
410
  // Intentionally empty: will be replaced by Angular forms
373
411
  };
374
412
  // Computed properties
375
- displayValue = computed(() => {
376
- const currentValue = this.value();
377
- const opts = this.options();
378
- if (currentValue === null || currentValue === undefined) {
379
- return '';
380
- }
381
- // Filter out options that don't have a value set yet
382
- const validOpts = opts.filter((opt) => opt.value() !== undefined);
383
- if (this.multiple()) {
384
- if (this.isArray(currentValue)) {
385
- const selectedOptions = validOpts.filter((opt) => currentValue.some((v) => this.compareWith()(v, opt.value())));
386
- return selectedOptions.map((opt) => opt.getLabel()).join(', ');
387
- }
388
- return '';
389
- }
390
- else {
391
- const selectedOption = validOpts.find((opt) => this.compareWith()(opt.value(), currentValue));
392
- return selectedOption ? selectedOption.getLabel() : String(currentValue);
393
- }
394
- }, ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
413
+ displayValue = computed(() => computeMultiDisplayValue(this), ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
395
414
  constructor() {
396
415
  // Set valueAccessor after NgControl is resolved
397
416
  void Promise.resolve().then(() => {
@@ -418,107 +437,84 @@ class FuiSelectComponent {
418
437
  });
419
438
  // Effect to update options selected state when value or options change
420
439
  effect(() => {
421
- const currentValue = this._value();
422
- const opts = this.options();
423
- const isMultiple = this.multiple();
424
- const compareFn = this.compareWith();
425
- opts.forEach((option) => {
426
- const optValue = option.value();
427
- // Skip options that don't have a value set yet
428
- if (optValue === undefined) {
429
- return;
430
- }
431
- let isSelected = false;
432
- if (isMultiple && this.isArray(currentValue)) {
433
- isSelected = currentValue.some((v) => compareFn(v, optValue));
434
- }
435
- else {
436
- isSelected = compareFn(currentValue, optValue);
437
- }
438
- if (isSelected) {
439
- option.select();
440
- }
441
- else {
442
- option.deselect();
443
- }
444
- });
440
+ syncMultiOptions(this);
445
441
  // Notify form-field that state may have changed (for display value updates)
446
442
  this.stateChanges.next();
447
443
  });
448
444
  }
449
- ngDoCheck() {
450
- if (this.ngControl) {
451
- updateErrorState(this.ngControl, this._errorState, this.errorStateMatcher(), this._defaultErrorStateMatcher, this._parentForm, this._parentFormGroup, this.stateChanges);
452
- syncRequiredState(this.ngControl, this._required, this.stateChanges);
453
- syncNgControlDisabled(this.ngControl, this._ngControlDisabled, this.stateChanges);
454
- }
455
- }
456
445
  ngOnDestroy() {
457
446
  this.stateChanges.complete();
458
- this._outsideClickSub?.unsubscribe();
459
- this._disposeOverlay();
460
- if (this._typeaheadTimeout) {
461
- clearTimeout(this._typeaheadTimeout);
447
+ if (this._typeaheadResetTimer) {
448
+ clearTimeout(this._typeaheadResetTimer);
462
449
  }
463
450
  }
464
451
  ngAfterContentInit() {
465
452
  // Set up initial selection based on value
466
453
  this._syncOptionsSelection();
467
454
  }
455
+ ngAfterViewInit() {
456
+ // Wire popup-overlay directive. Both trigger and panel use static:true so refs are
457
+ // resolved here regardless of panel open state (panel is always rendered via [hidden]).
458
+ this._popup.setTrigger(this.trigger ?? null);
459
+ this._popup.setPanel(this.panel ?? null);
460
+ this._popup.panelClass.set(['fui-select-overlay-panel']);
461
+ this._popup.backdropClass.set('fui-select-backdrop');
462
+ // Position the overlay panel 16px below the form-field wrapper (or trigger).
463
+ // This gives visual breathing room between the trigger and the dropdown.
464
+ this._popup.positions.set([
465
+ {
466
+ originX: 'start',
467
+ originY: 'bottom',
468
+ overlayX: 'start',
469
+ overlayY: 'top',
470
+ offsetY: FUI_SELECT_OVERLAY_OFFSET,
471
+ },
472
+ ]);
473
+ // When inside a form-field, use the form-field wrapper as the width reference for
474
+ // the overlay panel so the dropdown spans the full width (including prefix/suffix).
475
+ if (this._parentFormField) {
476
+ this._popup.widthElement.set(this._parentFormField.getConnectedOverlayOrigin());
477
+ }
478
+ // Wire form-control-sync directive signals
479
+ this._formSync.errorState.set(this._errorState);
480
+ this._formSync.errorStateMatcher.set(this.errorStateMatcher());
481
+ this._formSync.required.set(this._required);
482
+ this._formSync.ngControlDisabled.set(this._ngControlDisabled);
483
+ this._formSync.stateChanges.set(this.stateChanges);
484
+ }
468
485
  // ControlValueAccessor implementation
486
+ /** Sets the select value from the form model. Null/undefined coerces to null. */
469
487
  writeValue(value) {
470
488
  this._value.set(value ?? null);
471
- //this._syncOptionsSelection();
472
489
  this.stateChanges.next();
473
490
  }
491
+ /** Registers the callback Angular calls when the value should propagate to the model. */
474
492
  registerOnChange(fn) {
475
493
  this._onChange = fn;
476
494
  }
495
+ /** Registers the callback Angular calls when the control should be marked as touched. */
477
496
  registerOnTouched(fn) {
478
497
  this._onTouched = fn;
479
498
  }
499
+ /** Enables or disables the control programmatically; mirrors the `disabled` form-control state. */
480
500
  setDisabledState(isDisabled) {
481
501
  this._disabled.set(isDisabled);
482
502
  this.stateChanges.next();
483
503
  }
484
504
  // FuiFormFieldControl implementation
505
+ /** Opens the panel when the form-field container is clicked, unless disabled or readonly. */
485
506
  onContainerClick(_event) {
486
507
  if (!this.disabled() && !this.readonly()) {
487
508
  this.toggle();
488
509
  }
489
510
  }
511
+ /** Stores the space-separated list of IDs for the aria-describedby attribute. */
490
512
  setDescribedByIds(ids) {
491
513
  this._ariaDescribedby = ids.length ? ids.join(' ') : null;
492
514
  }
493
- setReadOnly(readOnly) {
494
- this._readOnly.set(readOnly);
495
- }
496
515
  // Sync options selection state with current value
497
516
  _syncOptionsSelection() {
498
- const currentValue = this._value();
499
- const opts = this.options();
500
- const isMultiple = this.multiple();
501
- const compareFn = this.compareWith();
502
- opts.forEach((option) => {
503
- const optValue = option.value();
504
- // Skip options that don't have a value set yet
505
- if (optValue === undefined) {
506
- return;
507
- }
508
- let isSelected = false;
509
- if (isMultiple && this.isArray(currentValue)) {
510
- isSelected = currentValue.some((v) => compareFn(v, optValue));
511
- }
512
- else if (currentValue !== null && currentValue !== undefined) {
513
- isSelected = compareFn(currentValue, optValue);
514
- }
515
- if (isSelected) {
516
- option.select();
517
- }
518
- else {
519
- option.deselect();
520
- }
521
- });
517
+ syncMultiOptions(this);
522
518
  }
523
519
  // Focus/blur event handlers from template
524
520
  _onFocus() {
@@ -530,7 +526,7 @@ class FuiSelectComponent {
530
526
  // Check if focus moved to the overlay panel (e.g. clicking an option).
531
527
  // In that case, don't close — the user is interacting with the dropdown.
532
528
  const relatedTarget = event?.relatedTarget;
533
- const overlayElement = this._overlayRef?.overlayElement;
529
+ const overlayElement = this._popup.overlayRef()?.overlayElement;
534
530
  if (relatedTarget && overlayElement?.contains(relatedTarget)) {
535
531
  return;
536
532
  }
@@ -542,54 +538,48 @@ class FuiSelectComponent {
542
538
  this.stateChanges.next();
543
539
  }
544
540
  // Public methods
541
+ /** Focuses the select trigger element. */
545
542
  focus() {
546
543
  this.trigger?.nativeElement.focus();
547
544
  }
545
+ /** Blurs the select trigger element. */
548
546
  blur() {
549
547
  this.trigger?.nativeElement.blur();
550
548
  }
551
- // Toggle panel open/close
549
+ /** Toggles the select panel. No-op if disabled. */
552
550
  toggle() {
553
551
  if (this.disabled())
554
552
  return;
555
- if (this.panelOpen()) {
553
+ if (this._popup.panelOpen()) {
556
554
  this.close();
557
555
  }
558
556
  else {
559
557
  this.open();
560
558
  }
561
559
  }
562
- // Open the dropdown panel
560
+ /** Opens the select panel. No-op if disabled, readonly, or already open. */
563
561
  open() {
564
- if (this.disabled() || this.readonly() || this.panelOpen())
562
+ if (this.disabled() || this.readonly() || this._popup.panelOpen())
565
563
  return;
566
564
  // Ensure DOM focus is on the trigger so blur/Tab work correctly
567
565
  this.trigger?.nativeElement.focus();
568
- requestAnimationFrame(() => {
569
- this.panelOpen.set(true);
570
- this._focused.set(true);
566
+ this._focused.set(true);
567
+ this._setInitialActiveOption();
568
+ this._popup.open();
569
+ if (this._popup.panelOpen()) {
570
+ this._scrollToActiveOption();
571
571
  this.openedChange.emit(true);
572
- // Set active option to currently selected or first option
573
- this._setInitialActiveOption();
574
- // Create overlay after the view updates
575
- setTimeout(() => {
576
- this._createOverlay();
577
- this._scrollToActiveOption();
578
- this._listenForOutsideClicks();
579
- });
580
- });
572
+ }
581
573
  }
582
- // Close the dropdown panel
574
+ /** Closes the select panel and restores focus to the trigger unless `restoreFocus` is false. No-op if already closed. */
583
575
  close(restoreFocus = true) {
584
- if (!this.panelOpen())
576
+ if (!this._popup.panelOpen())
585
577
  return;
586
- this._outsideClickSub?.unsubscribe();
587
- this.panelOpen.set(false);
588
578
  this._focused.set(false);
589
579
  this._activeOptionIndex.set(-1);
590
- this._disposeOverlay();
591
- this.openedChange.emit(false);
592
580
  this._onTouched();
581
+ this._popup.close();
582
+ this.openedChange.emit(false);
593
583
  // Return focus to trigger only when explicitly requested (e.g. Escape, backdrop click).
594
584
  // When closing via Tab, let the browser move focus naturally.
595
585
  // Focus synchronously to avoid race conditions with Tab key presses.
@@ -599,51 +589,21 @@ class FuiSelectComponent {
599
589
  }
600
590
  // Handle option selection (called by FuiOptionComponent)
601
591
  _onOptionSelected(option) {
602
- const newValue = option.value();
603
- if (this.multiple()) {
604
- const raw = this._value();
605
- const currentValue = this.isArray(raw) ? [...raw] : [];
606
- const compareFn = this.compareWith();
607
- const index = currentValue.findIndex((v) => compareFn(v, newValue));
608
- if (index > -1) {
609
- currentValue.splice(index, 1);
610
- option.deselect();
611
- }
612
- else {
613
- currentValue.push(newValue);
614
- option.select();
615
- }
616
- this._value.set(currentValue);
617
- this._onChange(currentValue);
618
- this.valueChange.emit(currentValue);
619
- this.selectionChange.emit({ source: this, value: currentValue });
620
- // Announce for screen readers
621
- const label = option.getLabel();
622
- const action = index > -1 ? 'deselected' : 'selected';
623
- this._announce(`${label} ${action}`);
592
+ const result = applyOptionSelection(this, option);
593
+ this._value.set(result.newValue);
594
+ this._onChange(result.newValue);
595
+ this.valueChange.emit(result.newValue);
596
+ this.selectionChange.emit({ source: this, value: result.newValue });
597
+ this.stateChanges.next();
598
+ if (result.wasSelected) {
599
+ this._announce(`${option.getLabel()} deselected`);
624
600
  }
625
601
  else {
626
- // Deselect previous option
627
- const opts = this.options();
628
- opts.forEach((opt) => {
629
- if (opt !== option) {
630
- opt.deselect();
631
- }
632
- });
633
- option.select();
634
- this._value.set(newValue);
635
- this._onChange(newValue);
636
- this.valueChange.emit(newValue);
637
- this.selectionChange.emit({ source: this, value: newValue });
638
- // Announce for screen readers
639
602
  this._announce(`${option.getLabel()} selected`);
603
+ }
604
+ if (!this.multiple()) {
640
605
  this.close();
641
606
  }
642
- this.stateChanges.next();
643
- }
644
- // Get display value for trigger
645
- _getDisplayValue() {
646
- return this.displayValue();
647
607
  }
648
608
  // Handle keyboard navigation
649
609
  _handleKeydown(event) {
@@ -686,7 +646,6 @@ class FuiSelectComponent {
686
646
  }
687
647
  // Handle keydown when panel is open
688
648
  _handleOpenKeydown(event) {
689
- const PAGE_SIZE = 5;
690
649
  const key = event.key;
691
650
  const opts = this._getEnabledOptions();
692
651
  const activeIndex = this._activeOptionIndex();
@@ -710,15 +669,13 @@ class FuiSelectComponent {
710
669
  case 'PageDown':
711
670
  event.preventDefault();
712
671
  if (opts.length > 0) {
713
- const newIndex = Math.min(activeIndex + PAGE_SIZE, opts.length - 1);
714
- this._setActiveOptionIndex(newIndex);
672
+ this._setActiveOptionIndex(computePagedActiveOptionIndex(opts.length, activeIndex, 1));
715
673
  }
716
674
  break;
717
675
  case 'PageUp':
718
676
  event.preventDefault();
719
677
  if (opts.length > 0) {
720
- const newIndex = Math.max(activeIndex - PAGE_SIZE, 0);
721
- this._setActiveOptionIndex(newIndex);
678
+ this._setActiveOptionIndex(computePagedActiveOptionIndex(opts.length, activeIndex, -1));
722
679
  }
723
680
  break;
724
681
  case 'Enter':
@@ -730,6 +687,7 @@ class FuiSelectComponent {
730
687
  break;
731
688
  case 'Escape':
732
689
  event.preventDefault();
690
+ event.stopPropagation();
733
691
  this.close();
734
692
  break;
735
693
  case 'Tab':
@@ -750,16 +708,7 @@ class FuiSelectComponent {
750
708
  const opts = this._getEnabledOptions();
751
709
  if (opts.length === 0)
752
710
  return;
753
- const currentIndex = this._activeOptionIndex();
754
- let newIndex = currentIndex + delta;
755
- // Wrap around
756
- if (newIndex < 0) {
757
- newIndex = opts.length - 1;
758
- }
759
- else if (newIndex >= opts.length) {
760
- newIndex = 0;
761
- }
762
- this._setActiveOptionIndex(newIndex);
711
+ this._setActiveOptionIndex(computeNextActiveOptionIndex(opts.length, this._activeOptionIndex(), delta));
763
712
  }
764
713
  // Set the active option index
765
714
  _setActiveOptionIndex(index) {
@@ -774,11 +723,11 @@ class FuiSelectComponent {
774
723
  const activeOption = opts[index];
775
724
  activeOption.setActive();
776
725
  this._activeOptionIndex.set(index);
777
- this._scrollToActiveOption();
726
+ scrollOptionIntoView(activeOption._getHostElement(), this.panel?.nativeElement);
778
727
  // Announce active option for screen readers (when panel is open and using keyboard)
779
728
  if (this.panelOpen()) {
780
729
  const label = activeOption.getLabel();
781
- const selectedState = activeOption._selected() ? ', selected' : '';
730
+ const selectedState = activeOption._selected ? (activeOption._selected() ? ', selected' : '') : '';
782
731
  this._announce(`${label}${selectedState}, ${index + 1} of ${opts.length}`);
783
732
  }
784
733
  }
@@ -789,17 +738,7 @@ class FuiSelectComponent {
789
738
  this._activeOptionIndex.set(-1);
790
739
  return;
791
740
  }
792
- const currentValue = this._value();
793
- const compareFn = this.compareWith();
794
- // Find the first selected option
795
- let selectedIndex = -1;
796
- if (!this.multiple() && currentValue !== null && currentValue !== undefined) {
797
- selectedIndex = opts.findIndex((opt) => compareFn(opt.value(), currentValue));
798
- }
799
- else if (this.multiple() && this.isArray(currentValue) && currentValue.length > 0) {
800
- selectedIndex = opts.findIndex((opt) => currentValue.some((v) => compareFn(v, opt.value())));
801
- }
802
- const initialIndex = selectedIndex >= 0 ? selectedIndex : 0;
741
+ const initialIndex = findInitialActiveOptionIndex(opts, this._value(), this.multiple(), this.compareWith());
803
742
  this._setActiveOptionIndex(initialIndex);
804
743
  }
805
744
  // Scroll to active option
@@ -808,32 +747,18 @@ class FuiSelectComponent {
808
747
  const activeIndex = this._activeOptionIndex();
809
748
  if (activeIndex < 0 || activeIndex >= opts.length)
810
749
  return;
811
- const activeOption = opts[activeIndex];
812
- const element = activeOption._getHostElement();
813
- const panel = this.panel?.nativeElement;
814
- if (element && panel) {
815
- const optionTop = element.offsetTop;
816
- const optionBottom = optionTop + element.offsetHeight;
817
- const panelTop = panel.scrollTop;
818
- const panelBottom = panelTop + panel.clientHeight;
819
- if (optionTop < panelTop) {
820
- panel.scrollTop = optionTop;
821
- }
822
- else if (optionBottom > panelBottom) {
823
- panel.scrollTop = optionBottom - panel.clientHeight;
824
- }
825
- }
750
+ scrollOptionIntoView(opts[activeIndex]._getHostElement(), this.panel?.nativeElement);
826
751
  }
827
752
  // Handle type-ahead search
828
753
  _handleTypeahead(char) {
829
- // Clear timeout and add to buffer
830
- if (this._typeaheadTimeout) {
831
- clearTimeout(this._typeaheadTimeout);
754
+ // Clear timer and append char to buffer
755
+ if (this._typeaheadResetTimer) {
756
+ clearTimeout(this._typeaheadResetTimer);
832
757
  }
833
- this._typeaheadBuffer += char.toLowerCase();
758
+ this._typeaheadBuffer.update((b) => b + char.toLowerCase());
834
759
  // Find matching option
835
760
  const opts = this._getEnabledOptions();
836
- const matchIndex = opts.findIndex((opt) => opt.getLabel().toLowerCase().startsWith(this._typeaheadBuffer));
761
+ const matchIndex = opts.findIndex((opt) => opt.getLabel().toLowerCase().startsWith(this._typeaheadBuffer()));
837
762
  if (matchIndex >= 0) {
838
763
  if (this.panelOpen()) {
839
764
  this._setActiveOptionIndex(matchIndex);
@@ -844,8 +769,8 @@ class FuiSelectComponent {
844
769
  }
845
770
  }
846
771
  // Clear buffer after debounce
847
- this._typeaheadTimeout = setTimeout(() => {
848
- this._typeaheadBuffer = '';
772
+ this._typeaheadResetTimer = setTimeout(() => {
773
+ this._typeaheadBuffer.set('');
849
774
  }, this.TYPE_AHEAD_DEBOUNCE);
850
775
  }
851
776
  // Select first non-disabled option
@@ -864,113 +789,25 @@ class FuiSelectComponent {
864
789
  }
865
790
  // Get all non-disabled options
866
791
  _getEnabledOptions() {
867
- return this.options().filter((opt) => !opt.disabled());
868
- }
869
- // Start listening for outside clicks when panel opens
870
- _listenForOutsideClicks() {
871
- this._outsideClickSub?.unsubscribe();
872
- // Run outside Angular zone to avoid triggering change detection on every document click
873
- this._ngZone.runOutsideAngular(() => {
874
- // Use setTimeout to skip the current click event that opened the panel
875
- setTimeout(() => {
876
- this._outsideClickSub = fromEvent(this._document, 'click')
877
- .pipe(filter(() => this.panelOpen()), filter((event) => {
878
- const target = event.target;
879
- const triggerElement = this.trigger?.nativeElement;
880
- const panelElement = this.panel?.nativeElement;
881
- const overlayElement = this._overlayRef?.overlayElement;
882
- return (!triggerElement?.contains(target) &&
883
- !panelElement?.contains(target) &&
884
- !overlayElement?.contains(target));
885
- }))
886
- .subscribe(() => {
887
- this._ngZone.run(() => {
888
- this.close();
889
- });
890
- });
891
- });
892
- });
893
- }
894
- // Create overlay for panel
895
- _createOverlay() {
896
- if (this._overlayRef || !this.panel || !this.trigger)
897
- return;
898
- const triggerElement = this.trigger.nativeElement;
899
- const triggerWidth = triggerElement.getBoundingClientRect().width;
900
- const positions = [
901
- // Primary: open below
902
- { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4 },
903
- // Fallback: open above if no space below
904
- { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -4 },
905
- ];
906
- const positionStrategy = this._overlayService
907
- .position()
908
- .connectedTo(triggerElement, positions)
909
- .withPush(false)
910
- .withFlexibleDimensions(true)
911
- .withViewportMargin(8);
912
- this._overlayRef = this._overlayService.create({
913
- positionStrategy,
914
- scrollStrategy: this._overlayService.scrollStrategies.reposition(),
915
- hasBackdrop: true,
916
- backdropClass: 'fui-select-backdrop',
917
- backdropClickBehavior: 'close',
918
- panelClass: ['fui-select-overlay-panel'],
919
- width: triggerWidth,
920
- });
921
- // Track overlay subscriptions for proper cleanup
922
- this._overlaySubscriptions.unsubscribe();
923
- this._overlaySubscriptions = new Subscription();
924
- this._overlaySubscriptions.add(this._overlayRef.backdropClick.subscribe(() => {
925
- this.close();
926
- }));
927
- this._overlaySubscriptions.add(this._overlayRef.keydownEvents.subscribe((event) => {
928
- if (event.key === 'Escape') {
929
- this.close();
930
- }
931
- }));
932
- // Attach panel to overlay
933
- const panelElement = this.panel.nativeElement;
934
- this._overlayRef.attach(panelElement);
935
- }
936
- // Dispose overlay
937
- _disposeOverlay() {
938
- this._overlaySubscriptions.unsubscribe();
939
- if (this._overlayRef) {
940
- this._overlayRef.dispose();
941
- this._overlayRef = null;
942
- }
792
+ return getEnabledOptions(this.options());
943
793
  }
944
794
  // Get the active option's id for aria-activedescendant
945
795
  _getActiveDescendant() {
946
- const opts = this._getEnabledOptions();
947
- const activeIndex = this._activeOptionIndex();
948
- if (activeIndex >= 0 && activeIndex < opts.length) {
949
- return opts[activeIndex].id;
950
- }
951
- return null;
796
+ return computeActiveDescendant(this._getEnabledOptions(), this._activeOptionIndex());
952
797
  }
953
- /**
954
- * Announces a message to screen readers via the aria-live region.
955
- * Clears the message after a brief delay to allow repeated announcements.
956
- */
957
798
  _announce(message) {
958
- // Clear first to ensure repeated identical messages are announced
959
- this._liveAnnouncement.set('');
960
- setTimeout(() => {
961
- this._liveAnnouncement.set(message);
962
- }, 50);
799
+ announceMessage(this._liveAnnouncement, message);
963
800
  }
964
801
  // Mat-select compatibility methods
965
802
  get selected() {
966
803
  return this.value;
967
804
  }
968
- // Helper method to check if value is array
805
+ /** @internal Helper to check if a value is an array. Used by tests. */
969
806
  isArray(value) {
970
807
  return Array.isArray(value);
971
808
  }
972
809
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
973
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: FuiSelectComponent, isStandalone: true, selector: "fui-select", inputs: { placeholderInput: { classPropertyName: "placeholderInput", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabledInput: { classPropertyName: "disabledInput", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, errorStateMatcher: { classPropertyName: "errorStateMatcher", publicName: "errorStateMatcher", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", selectionChange: "selectionChange", openedChange: "openedChange" }, host: { properties: { "attr.id": "id", "class.fui-select--open": "panelOpen()", "class.fui-select--disabled": "disabled()", "class.fui-select--multiple": "multiple()", "class.fui-select--readonly": "_readOnly()" }, classAttribute: "fui-select" }, providers: [
810
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: FuiSelectComponent, isStandalone: true, selector: "fui-select", inputs: { placeholderInput: { classPropertyName: "placeholderInput", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabledInput: { classPropertyName: "disabledInput", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonlyInput: { classPropertyName: "readonlyInput", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null }, errorStateMatcher: { classPropertyName: "errorStateMatcher", publicName: "errorStateMatcher", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", selectionChange: "selectionChange", openedChange: "openedChange" }, host: { properties: { "attr.id": "id", "class.fui-select--open": "panelOpen()", "class.fui-select--disabled": "disabled()", "class.fui-select--multiple": "multiple()", "class.fui-select--readonly": "readonly()", "attr.aria-readonly": "readonly() ? \"true\" : null" }, classAttribute: "fui-select" }, providers: [
974
811
  {
975
812
  provide: NG_VALUE_ACCESSOR,
976
813
  useExisting: FuiSelectComponent,
@@ -984,17 +821,25 @@ class FuiSelectComponent {
984
821
  provide: FUI_SELECT,
985
822
  useExisting: FuiSelectComponent,
986
823
  },
987
- ], queries: [{ propertyName: "options", predicate: FuiOptionComponent, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true }, { propertyName: "panel", first: true, predicate: ["panel"], descendants: true }], ngImport: i0, template: "<!-- Select Trigger -->\r\n<div\r\n #trigger\r\n [id]=\"id\"\r\n class=\"fui-select__trigger\"\r\n [attr.tabindex]=\"disabled() ? -1 : 0\"\r\n [attr.role]=\"'combobox'\"\r\n [attr.aria-haspopup]=\"'listbox'\"\r\n [attr.aria-expanded]=\"panelOpen()\"\r\n [attr.aria-disabled]=\"disabled()\"\r\n [attr.aria-invalid]=\"errorState()\"\r\n [attr.aria-describedby]=\"_ariaDescribedby\"\r\n [attr.aria-required]=\"required()\"\r\n [attr.aria-label]=\"empty() ? placeholder() : null\"\r\n aria-autocomplete=\"none\"\r\n [attr.aria-activedescendant]=\"panelOpen() ? _getActiveDescendant() : null\"\r\n [attr.aria-controls]=\"panelOpen() ? id + '-panel' : null\"\r\n (keydown)=\"_handleKeydown($event)\"\r\n (focus)=\"_onFocus()\"\r\n (blur)=\"_onBlur($event)\"\r\n>\r\n <span class=\"fui-select__value\">\r\n @if (empty()) {\r\n <span class=\"fui-select__placeholder\">{{ placeholder() }}</span>\r\n } @else {\r\n <span class=\"fui-select__value-text\">{{ _getDisplayValue() }}</span>\r\n }\r\n </span>\r\n</div>\r\n\r\n<!-- Live region for screen reader announcements -->\r\n<span class=\"fui-sr-only\" aria-live=\"assertive\" aria-atomic=\"true\">{{ _liveAnnouncement() }}</span>\r\n\r\n<!-- Dropdown Panel -->\r\n@if (panelOpen()) {\r\n <div\r\n #panel\r\n [id]=\"id + '-panel'\"\r\n class=\"fui-select__panel\"\r\n role=\"listbox\"\r\n [attr.aria-multiselectable]=\"multiple()\"\r\n [attr.aria-label]=\"placeholder()\"\r\n >\r\n <ng-content></ng-content>\r\n </div>\r\n}\r\n", styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition:opacity var(--fui-duration-fast-02) var(--fui-ease-entrance) 0ms}.fui-motion-fade-out{transition:opacity var(--fui-duration-fast-01) var(--fui-ease-exit) 0ms}.fui-motion-slide-in-bottom{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition:transform,opacity var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-moderate-01) var(--fui-ease-entrance)}@keyframes fui-popover-enter{0%{opacity:0;transform:translateY(-14px)}60%{opacity:1}to{opacity:1;transform:translateY(0)}}.fui-select{display:inline-block;width:100%;position:relative}.fui-select__trigger{width:100%;min-height:100%;display:flex;align-items:center;cursor:pointer;outline:none;-webkit-user-select:none;user-select:none;background:transparent;border:none;font-family:var(--fui-font-family-sans);font-size:var(--fui-font-size-02);font-weight:var(--fui-font-weight-regular);line-height:var(--fui-line-height-02);letter-spacing:var(--fui-letter-spacing-normal);color:var(--fui-text-primary)}.fui-select__trigger:focus{outline:none}.fui-select__trigger:focus-visible{outline:none}.fui-select__trigger[aria-disabled=true]{cursor:not-allowed;opacity:.5}.fui-select__value{flex:1;display:flex;align-items:center;overflow:hidden;min-width:0}.fui-select__arrow{display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-left:var(--fui-spacing-02);color:var(--fui-text-secondary);transition:transform var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-select__arrow svg{width:1rem;height:1rem}.fui-select--open .fui-select__arrow{transform:rotate(180deg)}.fui-select--disabled{pointer-events:none}.fui-select--disabled .fui-select__trigger{cursor:not-allowed;opacity:.5}.fui-select--disabled .fui-select__arrow{color:var(--fui-text-disabled)}.fui-select--readonly{pointer-events:none}.fui-select--readonly .fui-select__trigger{opacity:1;cursor:default;color:var(--fui-text-primary)}.fui-select--readonly .fui-select__trigger[aria-disabled=true]{opacity:1;cursor:default}.fui-select--readonly .fui-select__value-text{color:var(--fui-text-primary)}.fui-select__value-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fui-select__placeholder{color:var(--fui-text-disabled);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fui-select .fui-select__panel{position:fixed;opacity:0;pointer-events:none}.fui-select__panel{--fui-select-panel-max-height: 16rem;--fui-select-panel-border-radius: var(--fui-border-radius-md);--fui-select-panel-shadow: var(--fui-shadow-04);--fui-select-panel-bg: var(--fui-surface-00);width:100%;max-height:var(--fui-select-panel-max-height);overflow-y:auto;overflow-x:hidden;background:var(--fui-select-panel-bg);border:1px solid var(--fui-border-color);border-radius:var(--fui-select-panel-border-radius);box-shadow:var(--fui-select-panel-shadow);padding:var(--fui-spacing-02) 0;transform-origin:top center;animation:fui-popover-enter var(--fui-duration-moderate-01) var(--fui-ease-entrance) both;will-change:transform,opacity}.fui-select__panel::-webkit-scrollbar{width:8px}.fui-select__panel::-webkit-scrollbar-track{background:var(--fui-surface-01);border-radius:var(--fui-border-radius-sm)}.fui-select__panel::-webkit-scrollbar-thumb{background:var(--fui-border-color);border-radius:var(--fui-border-radius-sm)}.fui-select__panel::-webkit-scrollbar-thumb:hover{background:var(--fui-text-secondary)}.fui-select-backdrop{background:transparent}.fui-select-overlay-panel{max-width:32rem}@media(prefers-contrast:high){.fui-select__panel{border-width:2px}}@media(prefers-reduced-motion:reduce){.fui-select__panel{animation:none}}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
824
+ ], queries: [{ propertyName: "options", predicate: FUI_OPTION, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true, static: true }, { propertyName: "panel", first: true, predicate: ["panel"], descendants: true, static: true }], hostDirectives: [{ directive: i1.FuiPopupOverlayDirective, inputs: ["positions", "positions", "panelClass", "panelClass", "backdropClass", "backdropClass", "scrollStrategy", "scrollStrategy", "minWidthFromTrigger", "minWidthFromTrigger"], outputs: ["openedChange", "openedChange", "escapeKey", "escapeKey"] }, { directive: i1.FuiFormControlSyncDirective }], ngImport: i0, template: "<!-- Select Trigger -->\r\n<div\r\n #trigger\r\n [id]=\"id\"\r\n class=\"fui-select__trigger\"\r\n [attr.tabindex]=\"disabled() ? -1 : 0\"\r\n [attr.role]=\"'combobox'\"\r\n [attr.aria-haspopup]=\"'listbox'\"\r\n [attr.aria-expanded]=\"panelOpen()\"\r\n [attr.aria-disabled]=\"disabled()\"\r\n [attr.aria-invalid]=\"errorState()\"\r\n [attr.aria-describedby]=\"_ariaDescribedby\"\r\n [attr.aria-required]=\"required()\"\r\n [attr.aria-readonly]=\"readonly() ? 'true' : null\"\r\n [attr.aria-label]=\"empty() ? placeholder() : null\"\r\n aria-autocomplete=\"none\"\r\n [attr.aria-activedescendant]=\"panelOpen() ? _getActiveDescendant() : null\"\r\n [attr.aria-controls]=\"panelOpen() ? id + '-panel' : null\"\r\n (keydown)=\"_handleKeydown($event)\"\r\n (focus)=\"_onFocus()\"\r\n (blur)=\"_onBlur($event)\"\r\n>\r\n <span class=\"fui-select__value\">\r\n @if (empty()) {\r\n <span class=\"fui-select__placeholder\">{{ placeholder() }}</span>\r\n } @else {\r\n <span class=\"fui-select__value-text\">{{ displayValue() }}</span>\r\n }\r\n </span>\r\n\r\n @if (!readonly() && !disabled()) {\r\n <span class=\"fui-select__arrow\" aria-hidden=\"true\">\r\n <fui-icon name=\"caret-down\" size=\"sm\"></fui-icon>\r\n </span>\r\n }\r\n</div>\r\n\r\n<!-- Live region for screen reader announcements -->\r\n<span class=\"fui-sr-only\" aria-live=\"assertive\" aria-atomic=\"true\">{{ _liveAnnouncement() }}</span>\r\n\r\n<!-- Dropdown Panel (always rendered; hidden when closed so #panel ViewChild is always available) -->\r\n<div\r\n #panel\r\n [hidden]=\"false\"\r\n [id]=\"id + '-panel'\"\r\n class=\"fui-select__panel\"\r\n role=\"listbox\"\r\n [attr.aria-multiselectable]=\"multiple()\"\r\n [attr.aria-label]=\"placeholder()\"\r\n>\r\n <ng-content></ng-content>\r\n</div>\r\n", styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition-property:opacity;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-out);transition-delay:0ms}.fui-motion-fade-out{transition-property:opacity;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-in);transition-delay:0ms}.fui-motion-slide-in-bottom{transition-property:transform;transition-duration:var(--fui-duration-base);transition-timing-function:var(--fui-ease-out);transition-delay:0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition-property:transform;transition-duration:var(--fui-duration-base);transition-timing-function:var(--fui-ease-out);transition-delay:0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition-property:transform,opacity;transition-duration:var(--fui-duration-base);transition-timing-function:var(--fui-ease-out);transition-delay:0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-base) var(--fui-ease-out)}@keyframes fui-popover-enter{0%{opacity:0;transform:translateY(-14px)}60%{opacity:1}to{opacity:1;transform:translateY(0)}}.fui-select{display:inline-block;width:100%;min-width:8rem;position:relative}.fui-select__trigger{width:100%;min-height:100%;display:flex;align-items:center;cursor:pointer;outline:none;-webkit-user-select:none;user-select:none;background:transparent;border:none;font-family:var(--fui-font-sans);font-size:var(--fui-text-base);font-weight:var(--fui-weight-regular);line-height:var(--fui-leading-normal);letter-spacing:var(--fui-tracking-normal);color:var(--fui-text-primary)}.fui-select__trigger:focus{outline:none}.fui-select__trigger:focus-visible{outline:none}.fui-select__trigger[aria-disabled=true]{cursor:not-allowed;opacity:.5}.fui-select__value{flex:1;display:flex;align-items:center;overflow:hidden;min-width:0}.fui-select__arrow{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--fui-icon-xs);height:var(--fui-icon-xs);margin-left:var(--fui-spacing-2);color:var(--fui-text-secondary);pointer-events:none;transition-property:color,transform;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-in-out);transition-delay:0ms}.fui-select--open .fui-select__arrow{color:var(--fui-field-border-focus);transform:rotate(180deg)}.fui-select--disabled{pointer-events:none}.fui-select--disabled .fui-select__trigger{cursor:not-allowed;opacity:.5}.fui-select--disabled .fui-select__arrow{color:var(--fui-text-disabled)}.fui-select--readonly{pointer-events:none}.fui-select--readonly .fui-select__trigger{opacity:1;cursor:default;color:var(--fui-text-primary)}.fui-select--readonly .fui-select__trigger[aria-disabled=true]{opacity:1;cursor:default}.fui-select--readonly .fui-select__value-text{color:var(--fui-text-primary)}.fui-select__value-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fui-select__placeholder{color:var(--fui-text-disabled);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fui-select .fui-select__panel{display:none}.fui-select__panel{--fui-select-panel-max-height: 16rem;--fui-select-panel-border-radius: var(--fui-radius-md);--fui-select-panel-shadow: var(--fui-shadow-lg);--fui-select-panel-bg: var(--fui-bg-default);width:100%;max-height:var(--fui-select-panel-max-height);overflow-y:auto;overflow-x:hidden;background:var(--fui-select-panel-bg);border:var(--fui-border-width-sm) solid var(--fui-border-default);border-radius:var(--fui-select-panel-border-radius);box-shadow:var(--fui-select-panel-shadow);padding:var(--fui-spacing-2) 0;transform-origin:top center;animation:fui-popover-enter var(--fui-duration-base) var(--fui-ease-out) both;will-change:transform,opacity}.fui-select__panel::-webkit-scrollbar{width:8px}.fui-select__panel::-webkit-scrollbar-track{background:var(--fui-bg-default);border-radius:var(--fui-radius-sm)}.fui-select__panel::-webkit-scrollbar-thumb{background:var(--fui-border-default);border-radius:var(--fui-radius-sm)}.fui-select__panel::-webkit-scrollbar-thumb:hover{background:var(--fui-text-secondary)}.fui-select-backdrop{background:transparent}.fui-select-overlay-panel{max-width:32rem}@media(prefers-contrast:high){.fui-select__panel{border-width:2px}}@media(prefers-reduced-motion:reduce){.fui-select__panel{animation:none}}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: FuiIconComponent, selector: "fui-icon", inputs: ["name", "size", "weight", "color", "ariaLabel", "spin", "pulse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
988
825
  }
989
826
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiSelectComponent, decorators: [{
990
827
  type: Component,
991
- args: [{ selector: 'fui-select', standalone: true, imports: [ReactiveFormsModule], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
828
+ args: [{ selector: 'fui-select', standalone: true, imports: [ReactiveFormsModule, FuiIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, hostDirectives: [
829
+ {
830
+ directive: FuiPopupOverlayDirective,
831
+ inputs: ['positions', 'panelClass', 'backdropClass', 'scrollStrategy', 'minWidthFromTrigger'],
832
+ outputs: ['openedChange', 'escapeKey'],
833
+ },
834
+ FuiFormControlSyncDirective,
835
+ ], host: {
992
836
  class: 'fui-select',
993
837
  '[attr.id]': 'id',
994
838
  '[class.fui-select--open]': 'panelOpen()',
995
839
  '[class.fui-select--disabled]': 'disabled()',
996
840
  '[class.fui-select--multiple]': 'multiple()',
997
- '[class.fui-select--readonly]': '_readOnly()',
841
+ '[class.fui-select--readonly]': 'readonly()',
842
+ '[attr.aria-readonly]': 'readonly() ? "true" : null',
998
843
  }, providers: [
999
844
  {
1000
845
  provide: NG_VALUE_ACCESSOR,
@@ -1009,18 +854,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImpor
1009
854
  provide: FUI_SELECT,
1010
855
  useExisting: FuiSelectComponent,
1011
856
  },
1012
- ], template: "<!-- Select Trigger -->\r\n<div\r\n #trigger\r\n [id]=\"id\"\r\n class=\"fui-select__trigger\"\r\n [attr.tabindex]=\"disabled() ? -1 : 0\"\r\n [attr.role]=\"'combobox'\"\r\n [attr.aria-haspopup]=\"'listbox'\"\r\n [attr.aria-expanded]=\"panelOpen()\"\r\n [attr.aria-disabled]=\"disabled()\"\r\n [attr.aria-invalid]=\"errorState()\"\r\n [attr.aria-describedby]=\"_ariaDescribedby\"\r\n [attr.aria-required]=\"required()\"\r\n [attr.aria-label]=\"empty() ? placeholder() : null\"\r\n aria-autocomplete=\"none\"\r\n [attr.aria-activedescendant]=\"panelOpen() ? _getActiveDescendant() : null\"\r\n [attr.aria-controls]=\"panelOpen() ? id + '-panel' : null\"\r\n (keydown)=\"_handleKeydown($event)\"\r\n (focus)=\"_onFocus()\"\r\n (blur)=\"_onBlur($event)\"\r\n>\r\n <span class=\"fui-select__value\">\r\n @if (empty()) {\r\n <span class=\"fui-select__placeholder\">{{ placeholder() }}</span>\r\n } @else {\r\n <span class=\"fui-select__value-text\">{{ _getDisplayValue() }}</span>\r\n }\r\n </span>\r\n</div>\r\n\r\n<!-- Live region for screen reader announcements -->\r\n<span class=\"fui-sr-only\" aria-live=\"assertive\" aria-atomic=\"true\">{{ _liveAnnouncement() }}</span>\r\n\r\n<!-- Dropdown Panel -->\r\n@if (panelOpen()) {\r\n <div\r\n #panel\r\n [id]=\"id + '-panel'\"\r\n class=\"fui-select__panel\"\r\n role=\"listbox\"\r\n [attr.aria-multiselectable]=\"multiple()\"\r\n [attr.aria-label]=\"placeholder()\"\r\n >\r\n <ng-content></ng-content>\r\n </div>\r\n}\r\n", styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition:opacity var(--fui-duration-fast-02) var(--fui-ease-entrance) 0ms}.fui-motion-fade-out{transition:opacity var(--fui-duration-fast-01) var(--fui-ease-exit) 0ms}.fui-motion-slide-in-bottom{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition:transform,opacity var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-moderate-01) var(--fui-ease-entrance)}@keyframes fui-popover-enter{0%{opacity:0;transform:translateY(-14px)}60%{opacity:1}to{opacity:1;transform:translateY(0)}}.fui-select{display:inline-block;width:100%;position:relative}.fui-select__trigger{width:100%;min-height:100%;display:flex;align-items:center;cursor:pointer;outline:none;-webkit-user-select:none;user-select:none;background:transparent;border:none;font-family:var(--fui-font-family-sans);font-size:var(--fui-font-size-02);font-weight:var(--fui-font-weight-regular);line-height:var(--fui-line-height-02);letter-spacing:var(--fui-letter-spacing-normal);color:var(--fui-text-primary)}.fui-select__trigger:focus{outline:none}.fui-select__trigger:focus-visible{outline:none}.fui-select__trigger[aria-disabled=true]{cursor:not-allowed;opacity:.5}.fui-select__value{flex:1;display:flex;align-items:center;overflow:hidden;min-width:0}.fui-select__arrow{display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-left:var(--fui-spacing-02);color:var(--fui-text-secondary);transition:transform var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-select__arrow svg{width:1rem;height:1rem}.fui-select--open .fui-select__arrow{transform:rotate(180deg)}.fui-select--disabled{pointer-events:none}.fui-select--disabled .fui-select__trigger{cursor:not-allowed;opacity:.5}.fui-select--disabled .fui-select__arrow{color:var(--fui-text-disabled)}.fui-select--readonly{pointer-events:none}.fui-select--readonly .fui-select__trigger{opacity:1;cursor:default;color:var(--fui-text-primary)}.fui-select--readonly .fui-select__trigger[aria-disabled=true]{opacity:1;cursor:default}.fui-select--readonly .fui-select__value-text{color:var(--fui-text-primary)}.fui-select__value-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fui-select__placeholder{color:var(--fui-text-disabled);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fui-select .fui-select__panel{position:fixed;opacity:0;pointer-events:none}.fui-select__panel{--fui-select-panel-max-height: 16rem;--fui-select-panel-border-radius: var(--fui-border-radius-md);--fui-select-panel-shadow: var(--fui-shadow-04);--fui-select-panel-bg: var(--fui-surface-00);width:100%;max-height:var(--fui-select-panel-max-height);overflow-y:auto;overflow-x:hidden;background:var(--fui-select-panel-bg);border:1px solid var(--fui-border-color);border-radius:var(--fui-select-panel-border-radius);box-shadow:var(--fui-select-panel-shadow);padding:var(--fui-spacing-02) 0;transform-origin:top center;animation:fui-popover-enter var(--fui-duration-moderate-01) var(--fui-ease-entrance) both;will-change:transform,opacity}.fui-select__panel::-webkit-scrollbar{width:8px}.fui-select__panel::-webkit-scrollbar-track{background:var(--fui-surface-01);border-radius:var(--fui-border-radius-sm)}.fui-select__panel::-webkit-scrollbar-thumb{background:var(--fui-border-color);border-radius:var(--fui-border-radius-sm)}.fui-select__panel::-webkit-scrollbar-thumb:hover{background:var(--fui-text-secondary)}.fui-select-backdrop{background:transparent}.fui-select-overlay-panel{max-width:32rem}@media(prefers-contrast:high){.fui-select__panel{border-width:2px}}@media(prefers-reduced-motion:reduce){.fui-select__panel{animation:none}}\n"] }]
1013
- }], ctorParameters: () => [], propDecorators: { placeholderInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabledInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], errorStateMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorStateMatcher", required: false }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], openedChange: [{ type: i0.Output, args: ["openedChange"] }], trigger: [{
857
+ ], template: "<!-- Select Trigger -->\r\n<div\r\n #trigger\r\n [id]=\"id\"\r\n class=\"fui-select__trigger\"\r\n [attr.tabindex]=\"disabled() ? -1 : 0\"\r\n [attr.role]=\"'combobox'\"\r\n [attr.aria-haspopup]=\"'listbox'\"\r\n [attr.aria-expanded]=\"panelOpen()\"\r\n [attr.aria-disabled]=\"disabled()\"\r\n [attr.aria-invalid]=\"errorState()\"\r\n [attr.aria-describedby]=\"_ariaDescribedby\"\r\n [attr.aria-required]=\"required()\"\r\n [attr.aria-readonly]=\"readonly() ? 'true' : null\"\r\n [attr.aria-label]=\"empty() ? placeholder() : null\"\r\n aria-autocomplete=\"none\"\r\n [attr.aria-activedescendant]=\"panelOpen() ? _getActiveDescendant() : null\"\r\n [attr.aria-controls]=\"panelOpen() ? id + '-panel' : null\"\r\n (keydown)=\"_handleKeydown($event)\"\r\n (focus)=\"_onFocus()\"\r\n (blur)=\"_onBlur($event)\"\r\n>\r\n <span class=\"fui-select__value\">\r\n @if (empty()) {\r\n <span class=\"fui-select__placeholder\">{{ placeholder() }}</span>\r\n } @else {\r\n <span class=\"fui-select__value-text\">{{ displayValue() }}</span>\r\n }\r\n </span>\r\n\r\n @if (!readonly() && !disabled()) {\r\n <span class=\"fui-select__arrow\" aria-hidden=\"true\">\r\n <fui-icon name=\"caret-down\" size=\"sm\"></fui-icon>\r\n </span>\r\n }\r\n</div>\r\n\r\n<!-- Live region for screen reader announcements -->\r\n<span class=\"fui-sr-only\" aria-live=\"assertive\" aria-atomic=\"true\">{{ _liveAnnouncement() }}</span>\r\n\r\n<!-- Dropdown Panel (always rendered; hidden when closed so #panel ViewChild is always available) -->\r\n<div\r\n #panel\r\n [hidden]=\"false\"\r\n [id]=\"id + '-panel'\"\r\n class=\"fui-select__panel\"\r\n role=\"listbox\"\r\n [attr.aria-multiselectable]=\"multiple()\"\r\n [attr.aria-label]=\"placeholder()\"\r\n>\r\n <ng-content></ng-content>\r\n</div>\r\n", styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition-property:opacity;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-out);transition-delay:0ms}.fui-motion-fade-out{transition-property:opacity;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-in);transition-delay:0ms}.fui-motion-slide-in-bottom{transition-property:transform;transition-duration:var(--fui-duration-base);transition-timing-function:var(--fui-ease-out);transition-delay:0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition-property:transform;transition-duration:var(--fui-duration-base);transition-timing-function:var(--fui-ease-out);transition-delay:0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition-property:transform,opacity;transition-duration:var(--fui-duration-base);transition-timing-function:var(--fui-ease-out);transition-delay:0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-base) var(--fui-ease-out)}@keyframes fui-popover-enter{0%{opacity:0;transform:translateY(-14px)}60%{opacity:1}to{opacity:1;transform:translateY(0)}}.fui-select{display:inline-block;width:100%;min-width:8rem;position:relative}.fui-select__trigger{width:100%;min-height:100%;display:flex;align-items:center;cursor:pointer;outline:none;-webkit-user-select:none;user-select:none;background:transparent;border:none;font-family:var(--fui-font-sans);font-size:var(--fui-text-base);font-weight:var(--fui-weight-regular);line-height:var(--fui-leading-normal);letter-spacing:var(--fui-tracking-normal);color:var(--fui-text-primary)}.fui-select__trigger:focus{outline:none}.fui-select__trigger:focus-visible{outline:none}.fui-select__trigger[aria-disabled=true]{cursor:not-allowed;opacity:.5}.fui-select__value{flex:1;display:flex;align-items:center;overflow:hidden;min-width:0}.fui-select__arrow{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--fui-icon-xs);height:var(--fui-icon-xs);margin-left:var(--fui-spacing-2);color:var(--fui-text-secondary);pointer-events:none;transition-property:color,transform;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-in-out);transition-delay:0ms}.fui-select--open .fui-select__arrow{color:var(--fui-field-border-focus);transform:rotate(180deg)}.fui-select--disabled{pointer-events:none}.fui-select--disabled .fui-select__trigger{cursor:not-allowed;opacity:.5}.fui-select--disabled .fui-select__arrow{color:var(--fui-text-disabled)}.fui-select--readonly{pointer-events:none}.fui-select--readonly .fui-select__trigger{opacity:1;cursor:default;color:var(--fui-text-primary)}.fui-select--readonly .fui-select__trigger[aria-disabled=true]{opacity:1;cursor:default}.fui-select--readonly .fui-select__value-text{color:var(--fui-text-primary)}.fui-select__value-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fui-select__placeholder{color:var(--fui-text-disabled);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fui-select .fui-select__panel{display:none}.fui-select__panel{--fui-select-panel-max-height: 16rem;--fui-select-panel-border-radius: var(--fui-radius-md);--fui-select-panel-shadow: var(--fui-shadow-lg);--fui-select-panel-bg: var(--fui-bg-default);width:100%;max-height:var(--fui-select-panel-max-height);overflow-y:auto;overflow-x:hidden;background:var(--fui-select-panel-bg);border:var(--fui-border-width-sm) solid var(--fui-border-default);border-radius:var(--fui-select-panel-border-radius);box-shadow:var(--fui-select-panel-shadow);padding:var(--fui-spacing-2) 0;transform-origin:top center;animation:fui-popover-enter var(--fui-duration-base) var(--fui-ease-out) both;will-change:transform,opacity}.fui-select__panel::-webkit-scrollbar{width:8px}.fui-select__panel::-webkit-scrollbar-track{background:var(--fui-bg-default);border-radius:var(--fui-radius-sm)}.fui-select__panel::-webkit-scrollbar-thumb{background:var(--fui-border-default);border-radius:var(--fui-radius-sm)}.fui-select__panel::-webkit-scrollbar-thumb:hover{background:var(--fui-text-secondary)}.fui-select-backdrop{background:transparent}.fui-select-overlay-panel{max-width:32rem}@media(prefers-contrast:high){.fui-select__panel{border-width:2px}}@media(prefers-reduced-motion:reduce){.fui-select__panel{animation:none}}\n"] }]
858
+ }], ctorParameters: () => [], propDecorators: { placeholderInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabledInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonlyInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], errorStateMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorStateMatcher", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], openedChange: [{ type: i0.Output, args: ["openedChange"] }], trigger: [{
1014
859
  type: ViewChild,
1015
- args: ['trigger', { static: false }]
860
+ args: ['trigger', { static: true }]
1016
861
  }], panel: [{
1017
862
  type: ViewChild,
1018
- args: ['panel', { static: false }]
1019
- }], options: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => FuiOptionComponent), { ...{ descendants: true }, isSignal: true }] }] } });
863
+ args: ['panel', { static: true }]
864
+ }], options: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => FUI_OPTION), { ...{ descendants: true }, isSignal: true }] }] } });
1020
865
 
1021
866
  /**
1022
867
  * Generated bundle index. Do not edit.
1023
868
  */
1024
869
 
1025
- export { FUI_SELECT, FuiOptionComponent, FuiSelectComponent };
870
+ export { FUI_OPTION, FUI_SELECT, FuiOptionBase, FuiOptionComponent, FuiSelectComponent };
1026
871
  //# sourceMappingURL=raintonic-formaui-components-select.mjs.map