@raintonic/formaui 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +145 -0
  3. package/fesm2022/raintonic-formaui-cdk-drag-drop.mjs +806 -0
  4. package/fesm2022/raintonic-formaui-cdk-drag-drop.mjs.map +1 -0
  5. package/fesm2022/raintonic-formaui-cdk-form-field.mjs +86 -0
  6. package/fesm2022/raintonic-formaui-cdk-form-field.mjs.map +1 -0
  7. package/fesm2022/raintonic-formaui-cdk-overlay.mjs +1757 -0
  8. package/fesm2022/raintonic-formaui-cdk-overlay.mjs.map +1 -0
  9. package/fesm2022/raintonic-formaui-cdk-virtual-scroll.mjs +287 -0
  10. package/fesm2022/raintonic-formaui-cdk-virtual-scroll.mjs.map +1 -0
  11. package/fesm2022/raintonic-formaui-components-accordion.mjs +217 -0
  12. package/fesm2022/raintonic-formaui-components-accordion.mjs.map +1 -0
  13. package/fesm2022/raintonic-formaui-components-alert.mjs +161 -0
  14. package/fesm2022/raintonic-formaui-components-alert.mjs.map +1 -0
  15. package/fesm2022/raintonic-formaui-components-autocomplete.mjs +726 -0
  16. package/fesm2022/raintonic-formaui-components-autocomplete.mjs.map +1 -0
  17. package/fesm2022/raintonic-formaui-components-avatar.mjs +92 -0
  18. package/fesm2022/raintonic-formaui-components-avatar.mjs.map +1 -0
  19. package/fesm2022/raintonic-formaui-components-badge.mjs +107 -0
  20. package/fesm2022/raintonic-formaui-components-badge.mjs.map +1 -0
  21. package/fesm2022/raintonic-formaui-components-big-menu.mjs +68 -0
  22. package/fesm2022/raintonic-formaui-components-big-menu.mjs.map +1 -0
  23. package/fesm2022/raintonic-formaui-components-breadcrumb.mjs +55 -0
  24. package/fesm2022/raintonic-formaui-components-breadcrumb.mjs.map +1 -0
  25. package/fesm2022/raintonic-formaui-components-button-group.mjs +103 -0
  26. package/fesm2022/raintonic-formaui-components-button-group.mjs.map +1 -0
  27. package/fesm2022/raintonic-formaui-components-button.mjs +241 -0
  28. package/fesm2022/raintonic-formaui-components-button.mjs.map +1 -0
  29. package/fesm2022/raintonic-formaui-components-card.mjs +270 -0
  30. package/fesm2022/raintonic-formaui-components-card.mjs.map +1 -0
  31. package/fesm2022/raintonic-formaui-components-checkbox.mjs +295 -0
  32. package/fesm2022/raintonic-formaui-components-checkbox.mjs.map +1 -0
  33. package/fesm2022/raintonic-formaui-components-data-table.mjs +631 -0
  34. package/fesm2022/raintonic-formaui-components-data-table.mjs.map +1 -0
  35. package/fesm2022/raintonic-formaui-components-date-picker.mjs +1331 -0
  36. package/fesm2022/raintonic-formaui-components-date-picker.mjs.map +1 -0
  37. package/fesm2022/raintonic-formaui-components-divider.mjs +41 -0
  38. package/fesm2022/raintonic-formaui-components-divider.mjs.map +1 -0
  39. package/fesm2022/raintonic-formaui-components-drawer.mjs +190 -0
  40. package/fesm2022/raintonic-formaui-components-drawer.mjs.map +1 -0
  41. package/fesm2022/raintonic-formaui-components-dynamic-form.mjs +266 -0
  42. package/fesm2022/raintonic-formaui-components-dynamic-form.mjs.map +1 -0
  43. package/fesm2022/raintonic-formaui-components-empty-state.mjs +33 -0
  44. package/fesm2022/raintonic-formaui-components-empty-state.mjs.map +1 -0
  45. package/fesm2022/raintonic-formaui-components-file-upload.mjs +246 -0
  46. package/fesm2022/raintonic-formaui-components-file-upload.mjs.map +1 -0
  47. package/fesm2022/raintonic-formaui-components-form-field.mjs +482 -0
  48. package/fesm2022/raintonic-formaui-components-form-field.mjs.map +1 -0
  49. package/fesm2022/raintonic-formaui-components-icon.mjs +117 -0
  50. package/fesm2022/raintonic-formaui-components-icon.mjs.map +1 -0
  51. package/fesm2022/raintonic-formaui-components-input.mjs +327 -0
  52. package/fesm2022/raintonic-formaui-components-input.mjs.map +1 -0
  53. package/fesm2022/raintonic-formaui-components-list.mjs +149 -0
  54. package/fesm2022/raintonic-formaui-components-list.mjs.map +1 -0
  55. package/fesm2022/raintonic-formaui-components-menu.mjs +896 -0
  56. package/fesm2022/raintonic-formaui-components-menu.mjs.map +1 -0
  57. package/fesm2022/raintonic-formaui-components-number-input.mjs +345 -0
  58. package/fesm2022/raintonic-formaui-components-number-input.mjs.map +1 -0
  59. package/fesm2022/raintonic-formaui-components-paginator.mjs +139 -0
  60. package/fesm2022/raintonic-formaui-components-paginator.mjs.map +1 -0
  61. package/fesm2022/raintonic-formaui-components-password-input.mjs +306 -0
  62. package/fesm2022/raintonic-formaui-components-password-input.mjs.map +1 -0
  63. package/fesm2022/raintonic-formaui-components-popover.mjs +451 -0
  64. package/fesm2022/raintonic-formaui-components-popover.mjs.map +1 -0
  65. package/fesm2022/raintonic-formaui-components-progressbar.mjs +148 -0
  66. package/fesm2022/raintonic-formaui-components-progressbar.mjs.map +1 -0
  67. package/fesm2022/raintonic-formaui-components-radio.mjs +260 -0
  68. package/fesm2022/raintonic-formaui-components-radio.mjs.map +1 -0
  69. package/fesm2022/raintonic-formaui-components-select.mjs +1011 -0
  70. package/fesm2022/raintonic-formaui-components-select.mjs.map +1 -0
  71. package/fesm2022/raintonic-formaui-components-side-panel.mjs +150 -0
  72. package/fesm2022/raintonic-formaui-components-side-panel.mjs.map +1 -0
  73. package/fesm2022/raintonic-formaui-components-sidebar.mjs +257 -0
  74. package/fesm2022/raintonic-formaui-components-sidebar.mjs.map +1 -0
  75. package/fesm2022/raintonic-formaui-components-skeleton.mjs +50 -0
  76. package/fesm2022/raintonic-formaui-components-skeleton.mjs.map +1 -0
  77. package/fesm2022/raintonic-formaui-components-slider.mjs +347 -0
  78. package/fesm2022/raintonic-formaui-components-slider.mjs.map +1 -0
  79. package/fesm2022/raintonic-formaui-components-spinner.mjs +63 -0
  80. package/fesm2022/raintonic-formaui-components-spinner.mjs.map +1 -0
  81. package/fesm2022/raintonic-formaui-components-stepper.mjs +317 -0
  82. package/fesm2022/raintonic-formaui-components-stepper.mjs.map +1 -0
  83. package/fesm2022/raintonic-formaui-components-tab.mjs +197 -0
  84. package/fesm2022/raintonic-formaui-components-tab.mjs.map +1 -0
  85. package/fesm2022/raintonic-formaui-components-tag.mjs +78 -0
  86. package/fesm2022/raintonic-formaui-components-tag.mjs.map +1 -0
  87. package/fesm2022/raintonic-formaui-components-time-picker.mjs +644 -0
  88. package/fesm2022/raintonic-formaui-components-time-picker.mjs.map +1 -0
  89. package/fesm2022/raintonic-formaui-components-toggle.mjs +171 -0
  90. package/fesm2022/raintonic-formaui-components-toggle.mjs.map +1 -0
  91. package/fesm2022/raintonic-formaui-components-toolbar.mjs +140 -0
  92. package/fesm2022/raintonic-formaui-components-toolbar.mjs.map +1 -0
  93. package/fesm2022/raintonic-formaui-components-tooltip.mjs +555 -0
  94. package/fesm2022/raintonic-formaui-components-tooltip.mjs.map +1 -0
  95. package/fesm2022/raintonic-formaui-components-tree-select.mjs +314 -0
  96. package/fesm2022/raintonic-formaui-components-tree-select.mjs.map +1 -0
  97. package/fesm2022/raintonic-formaui-components-tree-table.mjs +103 -0
  98. package/fesm2022/raintonic-formaui-components-tree-table.mjs.map +1 -0
  99. package/fesm2022/raintonic-formaui-components-tree.mjs +430 -0
  100. package/fesm2022/raintonic-formaui-components-tree.mjs.map +1 -0
  101. package/fesm2022/raintonic-formaui-core.mjs +62 -0
  102. package/fesm2022/raintonic-formaui-core.mjs.map +1 -0
  103. package/fesm2022/raintonic-formaui-services-dialog.mjs +798 -0
  104. package/fesm2022/raintonic-formaui-services-dialog.mjs.map +1 -0
  105. package/fesm2022/raintonic-formaui-services-notification.mjs +391 -0
  106. package/fesm2022/raintonic-formaui-services-notification.mjs.map +1 -0
  107. package/fesm2022/raintonic-formaui-services-theme.mjs +248 -0
  108. package/fesm2022/raintonic-formaui-services-theme.mjs.map +1 -0
  109. package/fesm2022/raintonic-formaui-test-utils.mjs +66 -0
  110. package/fesm2022/raintonic-formaui-test-utils.mjs.map +1 -0
  111. package/fesm2022/raintonic-formaui.mjs +15 -0
  112. package/fesm2022/raintonic-formaui.mjs.map +1 -0
  113. package/llms-full.txt +1627 -0
  114. package/llms.txt +60 -0
  115. package/package.json +251 -0
  116. package/styles/_fonts-entry.scss +3 -0
  117. package/styles/fonts/dm-mono-400-latin.woff2 +0 -0
  118. package/styles/fonts/inter-tight-latin-italic.woff2 +0 -0
  119. package/styles/fonts/inter-tight-latin.woff2 +0 -0
  120. package/styles/index.scss +127 -0
  121. package/styles/partials/_constants.scss +29 -0
  122. package/styles/partials/_fonts.scss +36 -0
  123. package/styles/partials/_grid.scss +171 -0
  124. package/styles/partials/_mixins.scss +145 -0
  125. package/styles/partials/_motion.scss +252 -0
  126. package/styles/partials/_theme.scss +275 -0
  127. package/styles/partials/_typography.scss +112 -0
  128. package/styles/partials/_utilities.scss +480 -0
  129. package/styles/partials/themes/_dark.scss +254 -0
  130. package/styles/partials/themes/_light.scss +254 -0
  131. package/types/raintonic-formaui-cdk-drag-drop.d.ts +196 -0
  132. package/types/raintonic-formaui-cdk-drag-drop.d.ts.map +1 -0
  133. package/types/raintonic-formaui-cdk-form-field.d.ts +62 -0
  134. package/types/raintonic-formaui-cdk-form-field.d.ts.map +1 -0
  135. package/types/raintonic-formaui-cdk-overlay.d.ts +843 -0
  136. package/types/raintonic-formaui-cdk-overlay.d.ts.map +1 -0
  137. package/types/raintonic-formaui-cdk-virtual-scroll.d.ts +112 -0
  138. package/types/raintonic-formaui-cdk-virtual-scroll.d.ts.map +1 -0
  139. package/types/raintonic-formaui-components-accordion.d.ts +124 -0
  140. package/types/raintonic-formaui-components-accordion.d.ts.map +1 -0
  141. package/types/raintonic-formaui-components-alert.d.ts +143 -0
  142. package/types/raintonic-formaui-components-alert.d.ts.map +1 -0
  143. package/types/raintonic-formaui-components-autocomplete.d.ts +193 -0
  144. package/types/raintonic-formaui-components-autocomplete.d.ts.map +1 -0
  145. package/types/raintonic-formaui-components-avatar.d.ts +52 -0
  146. package/types/raintonic-formaui-components-avatar.d.ts.map +1 -0
  147. package/types/raintonic-formaui-components-badge.d.ts +47 -0
  148. package/types/raintonic-formaui-components-badge.d.ts.map +1 -0
  149. package/types/raintonic-formaui-components-big-menu.d.ts +62 -0
  150. package/types/raintonic-formaui-components-big-menu.d.ts.map +1 -0
  151. package/types/raintonic-formaui-components-breadcrumb.d.ts +26 -0
  152. package/types/raintonic-formaui-components-breadcrumb.d.ts.map +1 -0
  153. package/types/raintonic-formaui-components-button-group.d.ts +61 -0
  154. package/types/raintonic-formaui-components-button-group.d.ts.map +1 -0
  155. package/types/raintonic-formaui-components-button.d.ts +116 -0
  156. package/types/raintonic-formaui-components-button.d.ts.map +1 -0
  157. package/types/raintonic-formaui-components-card.d.ts +191 -0
  158. package/types/raintonic-formaui-components-card.d.ts.map +1 -0
  159. package/types/raintonic-formaui-components-checkbox.d.ts +132 -0
  160. package/types/raintonic-formaui-components-checkbox.d.ts.map +1 -0
  161. package/types/raintonic-formaui-components-data-table.d.ts +368 -0
  162. package/types/raintonic-formaui-components-data-table.d.ts.map +1 -0
  163. package/types/raintonic-formaui-components-date-picker.d.ts +341 -0
  164. package/types/raintonic-formaui-components-date-picker.d.ts.map +1 -0
  165. package/types/raintonic-formaui-components-divider.d.ts +21 -0
  166. package/types/raintonic-formaui-components-divider.d.ts.map +1 -0
  167. package/types/raintonic-formaui-components-drawer.d.ts +48 -0
  168. package/types/raintonic-formaui-components-drawer.d.ts.map +1 -0
  169. package/types/raintonic-formaui-components-dynamic-form.d.ts +412 -0
  170. package/types/raintonic-formaui-components-dynamic-form.d.ts.map +1 -0
  171. package/types/raintonic-formaui-components-empty-state.d.ts +14 -0
  172. package/types/raintonic-formaui-components-empty-state.d.ts.map +1 -0
  173. package/types/raintonic-formaui-components-file-upload.d.ts +77 -0
  174. package/types/raintonic-formaui-components-file-upload.d.ts.map +1 -0
  175. package/types/raintonic-formaui-components-form-field.d.ts +271 -0
  176. package/types/raintonic-formaui-components-form-field.d.ts.map +1 -0
  177. package/types/raintonic-formaui-components-icon.d.ts +61 -0
  178. package/types/raintonic-formaui-components-icon.d.ts.map +1 -0
  179. package/types/raintonic-formaui-components-input.d.ts +149 -0
  180. package/types/raintonic-formaui-components-input.d.ts.map +1 -0
  181. package/types/raintonic-formaui-components-list.d.ts +48 -0
  182. package/types/raintonic-formaui-components-list.d.ts.map +1 -0
  183. package/types/raintonic-formaui-components-menu.d.ts +403 -0
  184. package/types/raintonic-formaui-components-menu.d.ts.map +1 -0
  185. package/types/raintonic-formaui-components-number-input.d.ts +127 -0
  186. package/types/raintonic-formaui-components-number-input.d.ts.map +1 -0
  187. package/types/raintonic-formaui-components-paginator.d.ts +37 -0
  188. package/types/raintonic-formaui-components-paginator.d.ts.map +1 -0
  189. package/types/raintonic-formaui-components-password-input.d.ts +111 -0
  190. package/types/raintonic-formaui-components-password-input.d.ts.map +1 -0
  191. package/types/raintonic-formaui-components-popover.d.ts +131 -0
  192. package/types/raintonic-formaui-components-popover.d.ts.map +1 -0
  193. package/types/raintonic-formaui-components-progressbar.d.ts +111 -0
  194. package/types/raintonic-formaui-components-progressbar.d.ts.map +1 -0
  195. package/types/raintonic-formaui-components-radio.d.ts +95 -0
  196. package/types/raintonic-formaui-components-radio.d.ts.map +1 -0
  197. package/types/raintonic-formaui-components-select.d.ts +307 -0
  198. package/types/raintonic-formaui-components-select.d.ts.map +1 -0
  199. package/types/raintonic-formaui-components-side-panel.d.ts +51 -0
  200. package/types/raintonic-formaui-components-side-panel.d.ts.map +1 -0
  201. package/types/raintonic-formaui-components-sidebar.d.ts +174 -0
  202. package/types/raintonic-formaui-components-sidebar.d.ts.map +1 -0
  203. package/types/raintonic-formaui-components-skeleton.d.ts +20 -0
  204. package/types/raintonic-formaui-components-skeleton.d.ts.map +1 -0
  205. package/types/raintonic-formaui-components-slider.d.ts +108 -0
  206. package/types/raintonic-formaui-components-slider.d.ts.map +1 -0
  207. package/types/raintonic-formaui-components-spinner.d.ts +42 -0
  208. package/types/raintonic-formaui-components-spinner.d.ts.map +1 -0
  209. package/types/raintonic-formaui-components-stepper.d.ts +126 -0
  210. package/types/raintonic-formaui-components-stepper.d.ts.map +1 -0
  211. package/types/raintonic-formaui-components-tab.d.ts +96 -0
  212. package/types/raintonic-formaui-components-tab.d.ts.map +1 -0
  213. package/types/raintonic-formaui-components-tag.d.ts +34 -0
  214. package/types/raintonic-formaui-components-tag.d.ts.map +1 -0
  215. package/types/raintonic-formaui-components-time-picker.d.ts +172 -0
  216. package/types/raintonic-formaui-components-time-picker.d.ts.map +1 -0
  217. package/types/raintonic-formaui-components-toggle.d.ts +70 -0
  218. package/types/raintonic-formaui-components-toggle.d.ts.map +1 -0
  219. package/types/raintonic-formaui-components-toolbar.d.ts +128 -0
  220. package/types/raintonic-formaui-components-toolbar.d.ts.map +1 -0
  221. package/types/raintonic-formaui-components-tooltip.d.ts +268 -0
  222. package/types/raintonic-formaui-components-tooltip.d.ts.map +1 -0
  223. package/types/raintonic-formaui-components-tree-select.d.ts +80 -0
  224. package/types/raintonic-formaui-components-tree-select.d.ts.map +1 -0
  225. package/types/raintonic-formaui-components-tree-table.d.ts +90 -0
  226. package/types/raintonic-formaui-components-tree-table.d.ts.map +1 -0
  227. package/types/raintonic-formaui-components-tree.d.ts +104 -0
  228. package/types/raintonic-formaui-components-tree.d.ts.map +1 -0
  229. package/types/raintonic-formaui-core.d.ts +115 -0
  230. package/types/raintonic-formaui-core.d.ts.map +1 -0
  231. package/types/raintonic-formaui-services-dialog.d.ts +451 -0
  232. package/types/raintonic-formaui-services-dialog.d.ts.map +1 -0
  233. package/types/raintonic-formaui-services-notification.d.ts +221 -0
  234. package/types/raintonic-formaui-services-notification.d.ts.map +1 -0
  235. package/types/raintonic-formaui-services-theme.d.ts +126 -0
  236. package/types/raintonic-formaui-services-theme.d.ts.map +1 -0
  237. package/types/raintonic-formaui-test-utils.d.ts +24 -0
  238. package/types/raintonic-formaui-test-utils.d.ts.map +1 -0
  239. package/types/raintonic-formaui.d.ts +4 -0
  240. package/types/raintonic-formaui.d.ts.map +1 -0
@@ -0,0 +1,1011 @@
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';
10
+
11
+ /**
12
+ * Injection token used to provide the parent select to options.
13
+ */
14
+ const FUI_SELECT = new InjectionToken('FUI_SELECT');
15
+
16
+ /**
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
29
+ *
30
+ * ## Usage
31
+ *
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
+ * ```
40
+ *
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
+ * ```
54
+ */
55
+ class FuiOptionComponent {
56
+ static nextId = 0;
57
+ /**
58
+ * The value of the option
59
+ */
60
+ value = input(undefined, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
61
+ /**
62
+ * Whether the option is disabled
63
+ * @default false
64
+ */
65
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
66
+ /**
67
+ * Event emitted when the option is selected
68
+ */
69
+ selectionChange = output();
70
+ // Internal state
71
+ _selected = signal(false, ...(ngDevMode ? [{ debugName: "_selected" }] : /* istanbul ignore next */ []));
72
+ _active = signal(false, ...(ngDevMode ? [{ debugName: "_active" }] : /* istanbul ignore next */ []));
73
+ stateChanges = new Subject();
74
+ // Element reference
75
+ _element = inject(ElementRef);
76
+ // Parent select (optional - may not exist if used standalone)
77
+ _parentSelect = inject(FUI_SELECT, { optional: true });
78
+ // 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
+ }
86
+ ngOnDestroy() {
87
+ this.stateChanges.complete();
88
+ }
89
+ // View value (text content)
90
+ get viewValue() {
91
+ return (this._element.nativeElement.textContent || '').trim();
92
+ }
93
+ _handleClick(event) {
94
+ if (!this.disabled()) {
95
+ event.preventDefault();
96
+ event.stopPropagation();
97
+ // Notify parent select
98
+ if (this._parentSelect) {
99
+ this._parentSelect._onOptionSelected(this);
100
+ }
101
+ else {
102
+ // Standalone usage - emit event
103
+ this._emitSelectionChangeEvent();
104
+ }
105
+ }
106
+ }
107
+ _handleMouseEnter() {
108
+ if (!this.disabled()) {
109
+ this._active.set(true);
110
+ }
111
+ }
112
+ _handleMouseLeave() {
113
+ this._active.set(false);
114
+ }
115
+ /**
116
+ * Selects the option
117
+ */
118
+ select() {
119
+ if (!this._selected()) {
120
+ this._selected.set(true);
121
+ this._updateCheckmarkVisibility();
122
+ this.stateChanges.next();
123
+ }
124
+ }
125
+ /**
126
+ * Deselects the option
127
+ */
128
+ deselect() {
129
+ if (this._selected()) {
130
+ this._selected.set(false);
131
+ this._updateCheckmarkVisibility();
132
+ this.stateChanges.next();
133
+ }
134
+ }
135
+ /**
136
+ * Sets the option as active (keyboard navigation)
137
+ */
138
+ setActive() {
139
+ if (!this._active()) {
140
+ this._active.set(true);
141
+ this.stateChanges.next();
142
+ }
143
+ }
144
+ /**
145
+ * Sets the option as inactive
146
+ */
147
+ setInactive() {
148
+ if (this._active()) {
149
+ this._active.set(false);
150
+ this.stateChanges.next();
151
+ }
152
+ }
153
+ /**
154
+ * Sets focus onto this option
155
+ */
156
+ focus() {
157
+ const element = this._element.nativeElement;
158
+ if (typeof element.focus === 'function') {
159
+ element.focus();
160
+ }
161
+ }
162
+ /**
163
+ * Gets the label to be used when displaying the option
164
+ */
165
+ getLabel() {
166
+ return this.viewValue;
167
+ }
168
+ /**
169
+ * Gets the host element
170
+ */
171
+ _getHostElement() {
172
+ return this._element.nativeElement;
173
+ }
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
+ /** Emits the selection change event */
180
+ _emitSelectionChangeEvent() {
181
+ this.selectionChange.emit({ source: this, value: this.value() });
182
+ }
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
+ }
192
+ <span class="fui-option__content">
193
+ <ng-content></ng-content>
194
+ </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)}.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 });
196
+ }
197
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiOptionComponent, decorators: [{
198
+ 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
+ }
207
+ <span class="fui-option__content">
208
+ <ng-content></ng-content>
209
+ </span>
210
+ `, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
211
+ class: 'fui-option',
212
+ '[class.fui-option--selected]': '_selected()',
213
+ '[class.fui-option--disabled]': 'disabled()',
214
+ '[class.fui-option--active]': '_active()',
215
+ role: 'option',
216
+ '[attr.id]': 'id',
217
+ '[attr.aria-selected]': '_selected()',
218
+ '[attr.aria-disabled]': 'disabled()',
219
+ '[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)}.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
+ }] } });
231
+
232
+ /**
233
+ * # fui-select Component
234
+ *
235
+ * A select component designed to work seamlessly with fui-form-field.
236
+ * Similar to Angular Material's mat-select integration with mat-form-field.
237
+ * Provides full Reactive Forms support with validation and error handling.
238
+ *
239
+ * ## Features
240
+ * - Works inside fui-form-field like mat-select
241
+ * - Full Reactive Forms integration (ControlValueAccessor)
242
+ * - Multiple selection support
243
+ * - Options via projected content (fui-option)
244
+ * - Disabled and readonly states
245
+ * - Full accessibility support
246
+ * - Full keyboard navigation (Arrow keys, Enter, Space, Escape, Home, End)
247
+ * - Type-ahead search functionality
248
+ *
249
+ * ## Usage
250
+ *
251
+ * ### Basic Select with Form Field
252
+ * ```html
253
+ * <fui-form-field>
254
+ * <label>Country</label>
255
+ * <fui-select placeholder="Select a country">
256
+ * <fui-option value="us">United States</fui-option>
257
+ * <fui-option value="ca">Canada</fui-option>
258
+ * <fui-option value="mx">Mexico</fui-option>
259
+ * </fui-select>
260
+ * </fui-form-field>
261
+ * ```
262
+ *
263
+ * ### With Reactive Forms and Validation
264
+ * ```html
265
+ * <form [formGroup]="form">
266
+ * <fui-form-field>
267
+ * <label>Country</label>
268
+ * <fui-select formControlName="country" placeholder="Select a country">
269
+ * <fui-option value="us">United States</fui-option>
270
+ * <fui-option value="ca">Canada</fui-option>
271
+ * </fui-select>
272
+ * <fui-error *ngIf="form.get('country')?.hasError('required')">
273
+ * Country is required
274
+ * </fui-error>
275
+ * </fui-form-field>
276
+ * </form>
277
+ * ```
278
+ *
279
+ * ### Multiple Selection
280
+ * ```html
281
+ * <fui-form-field>
282
+ * <label>Skills</label>
283
+ * <fui-select formControlName="skills" [multiple]="true">
284
+ * <fui-option value="js">JavaScript</fui-option>
285
+ * <fui-option value="ts">TypeScript</fui-option>
286
+ * <fui-option value="py">Python</fui-option>
287
+ * </fui-select>
288
+ * </fui-form-field>
289
+ * ```
290
+ */
291
+ class FuiSelectComponent {
292
+ // Static properties
293
+ static nextId = 0;
294
+ controlType = 'fui-select';
295
+ // Inputs using new signal-based API
296
+ placeholderInput = input('', { ...(ngDevMode ? { debugName: "placeholderInput" } : /* istanbul ignore next */ {}), alias: 'placeholder' });
297
+ disabledInput = input(false, { ...(ngDevMode ? { debugName: "disabledInput" } : /* istanbul ignore next */ {}), alias: 'disabled' });
298
+ readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
299
+ multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []));
300
+ errorStateMatcher = input(null, ...(ngDevMode ? [{ debugName: "errorStateMatcher" }] : /* istanbul ignore next */ []));
301
+ /**
302
+ * Whether to compare option values using object identity or deep equality
303
+ */
304
+ compareWith = input((o1, o2) => o1 === o2, ...(ngDevMode ? [{ debugName: "compareWith" }] : /* istanbul ignore next */ []));
305
+ // Outputs
306
+ valueChange = output();
307
+ selectionChange = output();
308
+ openedChange = output();
309
+ // Internal state signals
310
+ _value = signal(null, ...(ngDevMode ? [{ debugName: "_value" }] : /* istanbul ignore next */ []));
311
+ _focused = signal(false, ...(ngDevMode ? [{ debugName: "_focused" }] : /* istanbul ignore next */ []));
312
+ _disabled = signal(false, ...(ngDevMode ? [{ debugName: "_disabled" }] : /* istanbul ignore next */ []));
313
+ _readOnly = signal(false, ...(ngDevMode ? [{ debugName: "_readOnly" }] : /* istanbul ignore next */ []));
314
+ // FuiFormFieldControl implementation
315
+ stateChanges = new Subject();
316
+ _uid = `fui-select-${FuiSelectComponent.nextId++}`;
317
+ _ariaDescribedby = null;
318
+ // Error state
319
+ _errorState = signal(false, ...(ngDevMode ? [{ debugName: "_errorState" }] : /* istanbul ignore next */ []));
320
+ errorState = this._errorState;
321
+ // Form control references
322
+ _parentForm = inject(NgForm, { optional: true });
323
+ _parentFormGroup = inject(FormGroupDirective, { optional: true });
324
+ _defaultErrorStateMatcher = inject(DefaultErrorStateMatcher);
325
+ _ngControlRef = injectNgControl();
326
+ get ngControl() {
327
+ return this._ngControlRef.ngControl;
328
+ }
329
+ // Interface implementation
330
+ placeholder = computed(() => this.placeholderInput(), ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
331
+ _required = signal(false, ...(ngDevMode ? [{ debugName: "_required" }] : /* istanbul ignore next */ []));
332
+ required = this._required;
333
+ value = this._value;
334
+ focused = this._focused;
335
+ _ngControlDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_ngControlDisabled" }] : /* istanbul ignore next */ []));
336
+ 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 */ []));
341
+ id = this._uid;
342
+ // ViewChild for trigger and panel
343
+ trigger;
344
+ panel;
345
+ // 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 */ []));
349
+ // Active option index for keyboard navigation
350
+ _activeOptionIndex = signal(-1, ...(ngDevMode ? [{ debugName: "_activeOptionIndex" }] : /* istanbul ignore next */ []));
351
+ activeOptionIndex = this._activeOptionIndex.asReadonly();
352
+ // Live announcement for screen readers
353
+ _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
+ // Type-ahead search
364
+ _typeaheadBuffer = '';
365
+ _typeaheadTimeout = null;
366
+ TYPE_AHEAD_DEBOUNCE = 200;
367
+ // ControlValueAccessor callbacks
368
+ _onChange = () => {
369
+ // Intentionally empty: will be replaced by Angular forms
370
+ };
371
+ _onTouched = () => {
372
+ // Intentionally empty: will be replaced by Angular forms
373
+ };
374
+ // 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 */ []));
395
+ constructor() {
396
+ // Set valueAccessor after NgControl is resolved
397
+ void Promise.resolve().then(() => {
398
+ if (this._ngControlRef.ngControl) {
399
+ this._ngControlRef.ngControl.valueAccessor = this;
400
+ }
401
+ });
402
+ // Effect to emit state changes
403
+ effect(() => {
404
+ // Track all reactive inputs and internal signals
405
+ this.placeholderInput();
406
+ this.readonly();
407
+ this.disabledInput();
408
+ this.multiple();
409
+ this.errorStateMatcher();
410
+ this._focused();
411
+ this._disabled();
412
+ this._value();
413
+ this._ngControlDisabled();
414
+ this._required();
415
+ this._errorState();
416
+ // Emit state change
417
+ this.stateChanges.next();
418
+ });
419
+ // Effect to update options selected state when value or options change
420
+ 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
+ });
445
+ // Notify form-field that state may have changed (for display value updates)
446
+ this.stateChanges.next();
447
+ });
448
+ }
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
+ ngOnDestroy() {
457
+ this.stateChanges.complete();
458
+ this._outsideClickSub?.unsubscribe();
459
+ this._disposeOverlay();
460
+ if (this._typeaheadTimeout) {
461
+ clearTimeout(this._typeaheadTimeout);
462
+ }
463
+ }
464
+ ngAfterContentInit() {
465
+ // Set up initial selection based on value
466
+ this._syncOptionsSelection();
467
+ }
468
+ // ControlValueAccessor implementation
469
+ writeValue(value) {
470
+ this._value.set(value ?? null);
471
+ //this._syncOptionsSelection();
472
+ this.stateChanges.next();
473
+ }
474
+ registerOnChange(fn) {
475
+ this._onChange = fn;
476
+ }
477
+ registerOnTouched(fn) {
478
+ this._onTouched = fn;
479
+ }
480
+ setDisabledState(isDisabled) {
481
+ this._disabled.set(isDisabled);
482
+ this.stateChanges.next();
483
+ }
484
+ // FuiFormFieldControl implementation
485
+ onContainerClick(_event) {
486
+ if (!this.disabled() && !this.readonly()) {
487
+ this.toggle();
488
+ }
489
+ }
490
+ setDescribedByIds(ids) {
491
+ this._ariaDescribedby = ids.length ? ids.join(' ') : null;
492
+ }
493
+ setReadOnly(readOnly) {
494
+ this._readOnly.set(readOnly);
495
+ }
496
+ // Sync options selection state with current value
497
+ _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
+ });
522
+ }
523
+ // Focus/blur event handlers from template
524
+ _onFocus() {
525
+ this._focused.set(true);
526
+ this.stateChanges.next();
527
+ }
528
+ _onBlur(event) {
529
+ if (this.panelOpen()) {
530
+ // Check if focus moved to the overlay panel (e.g. clicking an option).
531
+ // In that case, don't close — the user is interacting with the dropdown.
532
+ const relatedTarget = event?.relatedTarget;
533
+ const overlayElement = this._overlayRef?.overlayElement;
534
+ if (relatedTarget && overlayElement?.contains(relatedTarget)) {
535
+ return;
536
+ }
537
+ // Focus left the select entirely (e.g. Tab) — close without restoring focus
538
+ this.close(false);
539
+ }
540
+ this._focused.set(false);
541
+ this._onTouched();
542
+ this.stateChanges.next();
543
+ }
544
+ // Public methods
545
+ focus() {
546
+ this.trigger?.nativeElement.focus();
547
+ }
548
+ blur() {
549
+ this.trigger?.nativeElement.blur();
550
+ }
551
+ // Toggle panel open/close
552
+ toggle() {
553
+ if (this.disabled())
554
+ return;
555
+ if (this.panelOpen()) {
556
+ this.close();
557
+ }
558
+ else {
559
+ this.open();
560
+ }
561
+ }
562
+ // Open the dropdown panel
563
+ open() {
564
+ if (this.disabled() || this.readonly() || this.panelOpen())
565
+ return;
566
+ // Ensure DOM focus is on the trigger so blur/Tab work correctly
567
+ this.trigger?.nativeElement.focus();
568
+ requestAnimationFrame(() => {
569
+ this.panelOpen.set(true);
570
+ this._focused.set(true);
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
+ });
581
+ }
582
+ // Close the dropdown panel
583
+ close(restoreFocus = true) {
584
+ if (!this.panelOpen())
585
+ return;
586
+ this._outsideClickSub?.unsubscribe();
587
+ this.panelOpen.set(false);
588
+ this._focused.set(false);
589
+ this._activeOptionIndex.set(-1);
590
+ this._disposeOverlay();
591
+ this.openedChange.emit(false);
592
+ this._onTouched();
593
+ // Return focus to trigger only when explicitly requested (e.g. Escape, backdrop click).
594
+ // When closing via Tab, let the browser move focus naturally.
595
+ // Focus synchronously to avoid race conditions with Tab key presses.
596
+ if (restoreFocus) {
597
+ this.trigger?.nativeElement.focus();
598
+ }
599
+ }
600
+ // Handle option selection (called by FuiOptionComponent)
601
+ _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}`);
624
+ }
625
+ 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
+ this._announce(`${option.getLabel()} selected`);
640
+ this.close();
641
+ }
642
+ this.stateChanges.next();
643
+ }
644
+ // Get display value for trigger
645
+ _getDisplayValue() {
646
+ return this.displayValue();
647
+ }
648
+ // Handle keyboard navigation
649
+ _handleKeydown(event) {
650
+ if (this.disabled())
651
+ return;
652
+ const isOpen = this.panelOpen();
653
+ if (!isOpen) {
654
+ this._handleClosedKeydown(event);
655
+ }
656
+ else {
657
+ this._handleOpenKeydown(event);
658
+ }
659
+ }
660
+ // Handle keydown when panel is closed
661
+ _handleClosedKeydown(event) {
662
+ const key = event.key;
663
+ switch (key) {
664
+ case 'Enter':
665
+ case ' ':
666
+ case 'ArrowDown':
667
+ case 'ArrowUp':
668
+ event.preventDefault();
669
+ this.open();
670
+ break;
671
+ case 'Home':
672
+ event.preventDefault();
673
+ this._selectFirstOption();
674
+ break;
675
+ case 'End':
676
+ event.preventDefault();
677
+ this._selectLastOption();
678
+ break;
679
+ default:
680
+ // Type-ahead when closed
681
+ if (key.length === 1 && !event.ctrlKey && !event.metaKey) {
682
+ this._handleTypeahead(key);
683
+ }
684
+ break;
685
+ }
686
+ }
687
+ // Handle keydown when panel is open
688
+ _handleOpenKeydown(event) {
689
+ const key = event.key;
690
+ const opts = this._getEnabledOptions();
691
+ const activeIndex = this._activeOptionIndex();
692
+ switch (key) {
693
+ case 'ArrowDown':
694
+ event.preventDefault();
695
+ this._setNextActiveOption(1);
696
+ break;
697
+ case 'ArrowUp':
698
+ event.preventDefault();
699
+ this._setNextActiveOption(-1);
700
+ break;
701
+ case 'Home':
702
+ event.preventDefault();
703
+ this._setActiveOptionIndex(0);
704
+ break;
705
+ case 'End':
706
+ event.preventDefault();
707
+ this._setActiveOptionIndex(opts.length - 1);
708
+ break;
709
+ case 'Enter':
710
+ case ' ':
711
+ event.preventDefault();
712
+ if (activeIndex >= 0 && activeIndex < opts.length) {
713
+ this._onOptionSelected(opts[activeIndex]);
714
+ }
715
+ break;
716
+ case 'Escape':
717
+ event.preventDefault();
718
+ this.close();
719
+ break;
720
+ case 'Tab':
721
+ // Don't preventDefault — let browser handle Tab naturally.
722
+ // _onBlur will close the panel when focus leaves the trigger.
723
+ break;
724
+ default:
725
+ // Type-ahead when open
726
+ if (key.length === 1 && !event.ctrlKey && !event.metaKey) {
727
+ event.preventDefault();
728
+ this._handleTypeahead(key);
729
+ }
730
+ break;
731
+ }
732
+ }
733
+ // Set the next active option based on delta
734
+ _setNextActiveOption(delta) {
735
+ const opts = this._getEnabledOptions();
736
+ if (opts.length === 0)
737
+ return;
738
+ const currentIndex = this._activeOptionIndex();
739
+ let newIndex = currentIndex + delta;
740
+ // Wrap around
741
+ if (newIndex < 0) {
742
+ newIndex = opts.length - 1;
743
+ }
744
+ else if (newIndex >= opts.length) {
745
+ newIndex = 0;
746
+ }
747
+ this._setActiveOptionIndex(newIndex);
748
+ }
749
+ // Set the active option index
750
+ _setActiveOptionIndex(index) {
751
+ const opts = this._getEnabledOptions();
752
+ if (index < 0 || index >= opts.length)
753
+ return;
754
+ // Update active state on options
755
+ const allOpts = this.options();
756
+ allOpts.forEach((opt) => {
757
+ opt.setInactive();
758
+ });
759
+ const activeOption = opts[index];
760
+ activeOption.setActive();
761
+ this._activeOptionIndex.set(index);
762
+ this._scrollToActiveOption();
763
+ // Announce active option for screen readers (when panel is open and using keyboard)
764
+ if (this.panelOpen()) {
765
+ const label = activeOption.getLabel();
766
+ const selectedState = activeOption._selected() ? ', selected' : '';
767
+ this._announce(`${label}${selectedState}, ${index + 1} of ${opts.length}`);
768
+ }
769
+ }
770
+ // Set initial active option when opening
771
+ _setInitialActiveOption() {
772
+ const opts = this._getEnabledOptions();
773
+ if (opts.length === 0) {
774
+ this._activeOptionIndex.set(-1);
775
+ return;
776
+ }
777
+ const currentValue = this._value();
778
+ const compareFn = this.compareWith();
779
+ // Find the first selected option
780
+ let selectedIndex = -1;
781
+ if (!this.multiple() && currentValue !== null && currentValue !== undefined) {
782
+ selectedIndex = opts.findIndex((opt) => compareFn(opt.value(), currentValue));
783
+ }
784
+ else if (this.multiple() && this.isArray(currentValue) && currentValue.length > 0) {
785
+ selectedIndex = opts.findIndex((opt) => currentValue.some((v) => compareFn(v, opt.value())));
786
+ }
787
+ const initialIndex = selectedIndex >= 0 ? selectedIndex : 0;
788
+ this._setActiveOptionIndex(initialIndex);
789
+ }
790
+ // Scroll to active option
791
+ _scrollToActiveOption() {
792
+ const opts = this._getEnabledOptions();
793
+ const activeIndex = this._activeOptionIndex();
794
+ if (activeIndex < 0 || activeIndex >= opts.length)
795
+ return;
796
+ const activeOption = opts[activeIndex];
797
+ const element = activeOption._getHostElement();
798
+ const panel = this.panel?.nativeElement;
799
+ if (element && panel) {
800
+ const optionTop = element.offsetTop;
801
+ const optionBottom = optionTop + element.offsetHeight;
802
+ const panelTop = panel.scrollTop;
803
+ const panelBottom = panelTop + panel.clientHeight;
804
+ if (optionTop < panelTop) {
805
+ panel.scrollTop = optionTop;
806
+ }
807
+ else if (optionBottom > panelBottom) {
808
+ panel.scrollTop = optionBottom - panel.clientHeight;
809
+ }
810
+ }
811
+ }
812
+ // Handle type-ahead search
813
+ _handleTypeahead(char) {
814
+ // Clear timeout and add to buffer
815
+ if (this._typeaheadTimeout) {
816
+ clearTimeout(this._typeaheadTimeout);
817
+ }
818
+ this._typeaheadBuffer += char.toLowerCase();
819
+ // Find matching option
820
+ const opts = this._getEnabledOptions();
821
+ const matchIndex = opts.findIndex((opt) => opt.getLabel().toLowerCase().startsWith(this._typeaheadBuffer));
822
+ if (matchIndex >= 0) {
823
+ if (this.panelOpen()) {
824
+ this._setActiveOptionIndex(matchIndex);
825
+ }
826
+ else {
827
+ // Select the option when closed
828
+ this._onOptionSelected(opts[matchIndex]);
829
+ }
830
+ }
831
+ // Clear buffer after debounce
832
+ this._typeaheadTimeout = setTimeout(() => {
833
+ this._typeaheadBuffer = '';
834
+ }, this.TYPE_AHEAD_DEBOUNCE);
835
+ }
836
+ // Select first non-disabled option
837
+ _selectFirstOption() {
838
+ const opts = this._getEnabledOptions();
839
+ if (opts.length > 0) {
840
+ this._onOptionSelected(opts[0]);
841
+ }
842
+ }
843
+ // Select last non-disabled option
844
+ _selectLastOption() {
845
+ const opts = this._getEnabledOptions();
846
+ if (opts.length > 0) {
847
+ this._onOptionSelected(opts[opts.length - 1]);
848
+ }
849
+ }
850
+ // Get all non-disabled options
851
+ _getEnabledOptions() {
852
+ return this.options().filter((opt) => !opt.disabled());
853
+ }
854
+ // Start listening for outside clicks when panel opens
855
+ _listenForOutsideClicks() {
856
+ this._outsideClickSub?.unsubscribe();
857
+ // Run outside Angular zone to avoid triggering change detection on every document click
858
+ this._ngZone.runOutsideAngular(() => {
859
+ // Use setTimeout to skip the current click event that opened the panel
860
+ setTimeout(() => {
861
+ this._outsideClickSub = fromEvent(this._document, 'click')
862
+ .pipe(filter(() => this.panelOpen()), filter((event) => {
863
+ const target = event.target;
864
+ const triggerElement = this.trigger?.nativeElement;
865
+ const panelElement = this.panel?.nativeElement;
866
+ const overlayElement = this._overlayRef?.overlayElement;
867
+ return (!triggerElement?.contains(target) &&
868
+ !panelElement?.contains(target) &&
869
+ !overlayElement?.contains(target));
870
+ }))
871
+ .subscribe(() => {
872
+ this._ngZone.run(() => {
873
+ this.close();
874
+ });
875
+ });
876
+ });
877
+ });
878
+ }
879
+ // Create overlay for panel
880
+ _createOverlay() {
881
+ if (this._overlayRef || !this.panel || !this.trigger)
882
+ return;
883
+ const triggerElement = this.trigger.nativeElement;
884
+ const triggerWidth = triggerElement.getBoundingClientRect().width;
885
+ const positions = [
886
+ // Primary: open below
887
+ { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4 },
888
+ // Fallback: open above if no space below
889
+ { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -4 },
890
+ ];
891
+ const positionStrategy = this._overlayService
892
+ .position()
893
+ .connectedTo(triggerElement, positions)
894
+ .withPush(false)
895
+ .withFlexibleDimensions(true)
896
+ .withViewportMargin(8);
897
+ this._overlayRef = this._overlayService.create({
898
+ positionStrategy,
899
+ scrollStrategy: this._overlayService.scrollStrategies.reposition(),
900
+ hasBackdrop: true,
901
+ backdropClass: 'fui-select-backdrop',
902
+ backdropClickBehavior: 'close',
903
+ panelClass: ['fui-select-overlay-panel'],
904
+ width: triggerWidth,
905
+ });
906
+ // Track overlay subscriptions for proper cleanup
907
+ this._overlaySubscriptions.unsubscribe();
908
+ this._overlaySubscriptions = new Subscription();
909
+ this._overlaySubscriptions.add(this._overlayRef.backdropClick.subscribe(() => {
910
+ this.close();
911
+ }));
912
+ this._overlaySubscriptions.add(this._overlayRef.keydownEvents.subscribe((event) => {
913
+ if (event.key === 'Escape') {
914
+ this.close();
915
+ }
916
+ }));
917
+ // Attach panel to overlay
918
+ const panelElement = this.panel.nativeElement;
919
+ this._overlayRef.attach(panelElement);
920
+ }
921
+ // Dispose overlay
922
+ _disposeOverlay() {
923
+ this._overlaySubscriptions.unsubscribe();
924
+ if (this._overlayRef) {
925
+ this._overlayRef.dispose();
926
+ this._overlayRef = null;
927
+ }
928
+ }
929
+ // Get the active option's id for aria-activedescendant
930
+ _getActiveDescendant() {
931
+ const opts = this._getEnabledOptions();
932
+ const activeIndex = this._activeOptionIndex();
933
+ if (activeIndex >= 0 && activeIndex < opts.length) {
934
+ return opts[activeIndex].id;
935
+ }
936
+ return null;
937
+ }
938
+ /**
939
+ * Announces a message to screen readers via the aria-live region.
940
+ * Clears the message after a brief delay to allow repeated announcements.
941
+ */
942
+ _announce(message) {
943
+ // Clear first to ensure repeated identical messages are announced
944
+ this._liveAnnouncement.set('');
945
+ setTimeout(() => {
946
+ this._liveAnnouncement.set(message);
947
+ }, 50);
948
+ }
949
+ // Mat-select compatibility methods
950
+ get selected() {
951
+ return this.value;
952
+ }
953
+ // Helper method to check if value is array
954
+ isArray(value) {
955
+ return Array.isArray(value);
956
+ }
957
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
958
+ 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: [
959
+ {
960
+ provide: NG_VALUE_ACCESSOR,
961
+ useExisting: FuiSelectComponent,
962
+ multi: true,
963
+ },
964
+ {
965
+ provide: FUI_FORM_FIELD_CONTROL,
966
+ useExisting: FuiSelectComponent,
967
+ },
968
+ {
969
+ provide: FUI_SELECT,
970
+ useExisting: FuiSelectComponent,
971
+ },
972
+ ], 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 -->\n<div\n #trigger\n [id]=\"id\"\n class=\"fui-select__trigger\"\n [attr.tabindex]=\"disabled() ? -1 : 0\"\n [attr.role]=\"'combobox'\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"panelOpen()\"\n [attr.aria-disabled]=\"disabled()\"\n [attr.aria-invalid]=\"errorState()\"\n [attr.aria-describedby]=\"_ariaDescribedby\"\n [attr.aria-required]=\"required()\"\n [attr.aria-label]=\"empty() ? placeholder() : null\"\n aria-autocomplete=\"none\"\n [attr.aria-activedescendant]=\"panelOpen() ? _getActiveDescendant() : null\"\n [attr.aria-controls]=\"panelOpen() ? id + '-panel' : null\"\n (keydown)=\"_handleKeydown($event)\"\n (focus)=\"_onFocus()\"\n (blur)=\"_onBlur($event)\"\n>\n <span class=\"fui-select__value\">\n @if (empty()) {\n <span class=\"fui-select__placeholder\">{{ placeholder() }}</span>\n } @else {\n <span class=\"fui-select__value-text\">{{ _getDisplayValue() }}</span>\n }\n </span>\n</div>\n\n<!-- Live region for screen reader announcements -->\n<span class=\"fui-sr-only\" aria-live=\"assertive\" aria-atomic=\"true\">{{ _liveAnnouncement() }}</span>\n\n<!-- Dropdown Panel -->\n@if (panelOpen()) {\n <div\n #panel\n [id]=\"id + '-panel'\"\n class=\"fui-select__panel\"\n role=\"listbox\"\n [attr.aria-multiselectable]=\"multiple()\"\n [attr.aria-label]=\"placeholder()\"\n >\n <ng-content></ng-content>\n </div>\n}\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)}.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-03);--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;transition:opacity,transform var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.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;z-index:var(--fui-z-popover, 1060)}@media(prefers-contrast:high){.fui-select__panel{border-width:2px}}@media(prefers-reduced-motion:reduce){.fui-select__panel{transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
973
+ }
974
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiSelectComponent, decorators: [{
975
+ type: Component,
976
+ args: [{ selector: 'fui-select', standalone: true, imports: [ReactiveFormsModule], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
977
+ class: 'fui-select',
978
+ '[attr.id]': 'id',
979
+ '[class.fui-select--open]': 'panelOpen()',
980
+ '[class.fui-select--disabled]': 'disabled()',
981
+ '[class.fui-select--multiple]': 'multiple()',
982
+ '[class.fui-select--readonly]': '_readOnly()',
983
+ }, providers: [
984
+ {
985
+ provide: NG_VALUE_ACCESSOR,
986
+ useExisting: FuiSelectComponent,
987
+ multi: true,
988
+ },
989
+ {
990
+ provide: FUI_FORM_FIELD_CONTROL,
991
+ useExisting: FuiSelectComponent,
992
+ },
993
+ {
994
+ provide: FUI_SELECT,
995
+ useExisting: FuiSelectComponent,
996
+ },
997
+ ], template: "<!-- Select Trigger -->\n<div\n #trigger\n [id]=\"id\"\n class=\"fui-select__trigger\"\n [attr.tabindex]=\"disabled() ? -1 : 0\"\n [attr.role]=\"'combobox'\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"panelOpen()\"\n [attr.aria-disabled]=\"disabled()\"\n [attr.aria-invalid]=\"errorState()\"\n [attr.aria-describedby]=\"_ariaDescribedby\"\n [attr.aria-required]=\"required()\"\n [attr.aria-label]=\"empty() ? placeholder() : null\"\n aria-autocomplete=\"none\"\n [attr.aria-activedescendant]=\"panelOpen() ? _getActiveDescendant() : null\"\n [attr.aria-controls]=\"panelOpen() ? id + '-panel' : null\"\n (keydown)=\"_handleKeydown($event)\"\n (focus)=\"_onFocus()\"\n (blur)=\"_onBlur($event)\"\n>\n <span class=\"fui-select__value\">\n @if (empty()) {\n <span class=\"fui-select__placeholder\">{{ placeholder() }}</span>\n } @else {\n <span class=\"fui-select__value-text\">{{ _getDisplayValue() }}</span>\n }\n </span>\n</div>\n\n<!-- Live region for screen reader announcements -->\n<span class=\"fui-sr-only\" aria-live=\"assertive\" aria-atomic=\"true\">{{ _liveAnnouncement() }}</span>\n\n<!-- Dropdown Panel -->\n@if (panelOpen()) {\n <div\n #panel\n [id]=\"id + '-panel'\"\n class=\"fui-select__panel\"\n role=\"listbox\"\n [attr.aria-multiselectable]=\"multiple()\"\n [attr.aria-label]=\"placeholder()\"\n >\n <ng-content></ng-content>\n </div>\n}\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)}.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-03);--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;transition:opacity,transform var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.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;z-index:var(--fui-z-popover, 1060)}@media(prefers-contrast:high){.fui-select__panel{border-width:2px}}@media(prefers-reduced-motion:reduce){.fui-select__panel{transition:none}}\n"] }]
998
+ }], 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: [{
999
+ type: ViewChild,
1000
+ args: ['trigger', { static: false }]
1001
+ }], panel: [{
1002
+ type: ViewChild,
1003
+ args: ['panel', { static: false }]
1004
+ }], options: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => FuiOptionComponent), { ...{ descendants: true }, isSignal: true }] }] } });
1005
+
1006
+ /**
1007
+ * Generated bundle index. Do not edit.
1008
+ */
1009
+
1010
+ export { FUI_SELECT, FuiOptionComponent, FuiSelectComponent };
1011
+ //# sourceMappingURL=raintonic-formaui-components-select.mjs.map