@raintonic/formaui 0.4.0 → 0.9.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 (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
@@ -0,0 +1,888 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, booleanAttribute, output, signal, computed, inject, ElementRef, Renderer2, HostListener, Component, DestroyRef, contentChildren, effect, ViewChild, Directive } from '@angular/core';
3
+ import { FuiOverlayService } from '@raintonic/formaui/cdk/overlay';
4
+ import * as i1 from '@raintonic/formaui/cdk/form-field';
5
+ import { FuiPopupOverlayDirective } from '@raintonic/formaui/cdk/form-field';
6
+
7
+ const DROPDOWN_MENU_ITEM_VARIANTS = ['default', 'danger'];
8
+ /**
9
+ * # FuiDropdownMenuItem Component
10
+ *
11
+ * A menu item component designed to be used within fui-dropdown-menu.
12
+ * Provides consistent styling and behavior for menu options.
13
+ *
14
+ * ## Features
15
+ * - Default and danger variants
16
+ * - Full accessibility support (ARIA attributes, keyboard navigation)
17
+ * - Icon support with proper spacing
18
+ * - Disabled state support
19
+ * - Hover and focus states
20
+ * - Keyboard activation (Enter and Space)
21
+ *
22
+ * ## Usage
23
+ *
24
+ * ### Basic Menu Item
25
+ * ```html
26
+ * <fui-dropdown-menu-item>Profile</fui-dropdown-menu-item>
27
+ * ```
28
+ *
29
+ * ### Menu Item with Icon
30
+ * ```html
31
+ * <fui-dropdown-menu-item>
32
+ * <fui-icon name="user" fuiPrefix></fui-icon>
33
+ * Profile
34
+ * </fui-dropdown-menu-item>
35
+ * ```
36
+ *
37
+ * ### Danger Menu Item
38
+ * ```html
39
+ * <fui-dropdown-menu-item variant="danger">
40
+ * <fui-icon name="trash" fuiPrefix></fui-icon>
41
+ * Delete Account
42
+ * </fui-dropdown-menu-item>
43
+ * ```
44
+ *
45
+ * ### Disabled Menu Item
46
+ * ```html
47
+ * <fui-dropdown-menu-item [disabled]="true">
48
+ * Unavailable Option
49
+ * </fui-dropdown-menu-item>
50
+ * ```
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * import { FuiDropdownMenuItemComponent } from '@raintonic/formaui/components/dropdown-menu';
55
+ *
56
+ * @Component({
57
+ * standalone: true,
58
+ * imports: [FuiDropdownMenuItemComponent],
59
+ * templateUrl: './my-component.component.html',
60
+ * styleUrl: './my-component.component.scss'
61
+ * })
62
+ * export class MyComponent {
63
+ * onItemClick(event: Event) {
64
+ * console.log('Menu item clicked:', event);
65
+ * }
66
+ * }
67
+ * ```
68
+ */
69
+ class FuiDropdownMenuItemComponent {
70
+ /**
71
+ * Menu item variant that determines the visual style
72
+ * @default 'default'
73
+ */
74
+ variant = input('default', { ...(ngDevMode ? { debugName: "variant" } : /* istanbul ignore next */ {}), transform: (v) => DROPDOWN_MENU_ITEM_VARIANTS.includes(v) ? v : 'default' });
75
+ /**
76
+ * Whether the menu item is disabled
77
+ * @default false
78
+ */
79
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
80
+ /**
81
+ * Emitted when the menu item is clicked or activated
82
+ */
83
+ selected = output();
84
+ /**
85
+ * Internal tabindex for roving tabindex pattern.
86
+ * Managed by the parent FuiDropdownMenuComponent.
87
+ * @internal
88
+ */
89
+ tabIndex = signal('-1', ...(ngDevMode ? [{ debugName: "tabIndex" }] : /* istanbul ignore next */ []));
90
+ // Computed properties
91
+ computedClasses = computed(() => {
92
+ const classes = ['fui-dropdown-menu-item', `fui-dropdown-menu-item--${this.variant()}`];
93
+ if (this.disabled()) {
94
+ classes.push('fui-dropdown-menu-item--disabled');
95
+ }
96
+ return classes.join(' ');
97
+ }, ...(ngDevMode ? [{ debugName: "computedClasses" }] : /* istanbul ignore next */ []));
98
+ /** @internal */ _elementRef = inject(ElementRef);
99
+ _renderer = inject(Renderer2);
100
+ onClick(event) {
101
+ if (this.disabled()) {
102
+ event.preventDefault();
103
+ event.stopPropagation();
104
+ return;
105
+ }
106
+ this.selected.emit(event);
107
+ }
108
+ onKeydown(event) {
109
+ if (this.disabled()) {
110
+ return;
111
+ }
112
+ switch (event.key) {
113
+ case 'Enter':
114
+ case ' ':
115
+ event.preventDefault();
116
+ // Dispatch a synthetic click so the parent menu's click handler
117
+ // can detect the activation and close the menu.
118
+ this._elementRef.nativeElement.click();
119
+ break;
120
+ }
121
+ }
122
+ /**
123
+ * Focuses the menu item
124
+ */
125
+ focus() {
126
+ this._elementRef.nativeElement.focus();
127
+ }
128
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiDropdownMenuItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
129
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.6", type: FuiDropdownMenuItemComponent, isStandalone: true, selector: "fui-dropdown-menu-item", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selected" }, host: { listeners: { "click": "onClick($event)", "keydown": "onKeydown($event)" }, properties: { "class": "computedClasses()", "attr.role": "\"menuitem\"", "attr.tabindex": "disabled() ? \"-1\" : tabIndex()", "attr.aria-disabled": "disabled() ? \"true\" : null" } }, ngImport: i0, template: "<ng-content></ng-content>\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)}}:host.fui-dropdown-menu-item{transition-property:background-color;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-in-out);transition-delay:0ms;display:flex;align-items:center;gap:var(--fui-spacing-4);width:100%;padding:var(--fui-spacing-4) var(--fui-spacing-6);margin:0;background-color:transparent;border:none;border-radius:var(--fui-radius-sm);font-family:var(--fui-font-sans);letter-spacing:.56px;font-size:var(--fui-text-base);font-weight:var(--fui-weight-regular);line-height:var(--fui-leading-normal);text-align:left;text-decoration:none;color:var(--fui-text-secondary);cursor:pointer;-webkit-user-select:none;user-select:none;outline:none}:host.fui-dropdown-menu-item:focus-visible{background-color:var(--fui-bg-subtle);outline:2px solid var(--fui-border-primary);outline-offset:-2px}:host.fui-dropdown-menu-item:hover:not(.fui-dropdown-menu-item--disabled){background-color:var(--fui-bg-subtle)}:host.fui-dropdown-menu-item:active:not(.fui-dropdown-menu-item--disabled){background-color:var(--fui-bg-muted)}:host.fui-dropdown-menu-item--danger{color:var(--fui-error-100)}:host.fui-dropdown-menu-item--danger:hover:not(.fui-dropdown-menu-item--disabled){background-color:var(--fui-error-10);color:var(--fui-error-70)}:host.fui-dropdown-menu-item--danger:active:not(.fui-dropdown-menu-item--disabled){background-color:var(--fui-error-20);color:var(--fui-error-80)}:host.fui-dropdown-menu-item--danger:focus-visible{background-color:var(--fui-error-10);outline-color:var(--fui-error-60)}:host.fui-dropdown-menu-item--disabled{color:var(--fui-text-secondary);cursor:not-allowed;opacity:var(--fui-state-disabled-opacity)}:host.fui-dropdown-menu-item--disabled:hover,:host.fui-dropdown-menu-item--disabled:active,:host.fui-dropdown-menu-item--disabled:focus{background-color:transparent}:host.fui-dropdown-menu-item .fui-icon{flex-shrink:0;width:var(--fui-icon-xs);height:var(--fui-icon-xs);font-size:var(--fui-icon-md)}:host.fui-dropdown-menu-item .fui-icon[fuiPrefix]{margin-right:var(--fui-spacing-2)}:host.fui-dropdown-menu-item .fui-icon[fuiSuffix]{margin-left:auto;margin-right:0}:host.fui-dropdown-menu-item .fui-dropdown-menu-item__shortcut{margin-left:auto;font-size:var(--fui-text-sm);color:var(--fui-text-secondary);font-family:var(--fui-font-mono)}@media(prefers-contrast:more){:host.fui-dropdown-menu-item:focus-visible{outline-width:3px}:host.fui-dropdown-menu-item--danger:focus-visible{outline-width:3px}}@media(prefers-reduced-motion:reduce){:host.fui-dropdown-menu-item{transition:none}}\n"] });
130
+ }
131
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiDropdownMenuItemComponent, decorators: [{
132
+ type: Component,
133
+ args: [{ selector: 'fui-dropdown-menu-item', standalone: true, imports: [], host: {
134
+ '[class]': 'computedClasses()',
135
+ '[attr.role]': '"menuitem"',
136
+ '[attr.tabindex]': 'disabled() ? "-1" : tabIndex()',
137
+ '[attr.aria-disabled]': 'disabled() ? "true" : null',
138
+ }, template: "<ng-content></ng-content>\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)}}:host.fui-dropdown-menu-item{transition-property:background-color;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-in-out);transition-delay:0ms;display:flex;align-items:center;gap:var(--fui-spacing-4);width:100%;padding:var(--fui-spacing-4) var(--fui-spacing-6);margin:0;background-color:transparent;border:none;border-radius:var(--fui-radius-sm);font-family:var(--fui-font-sans);letter-spacing:.56px;font-size:var(--fui-text-base);font-weight:var(--fui-weight-regular);line-height:var(--fui-leading-normal);text-align:left;text-decoration:none;color:var(--fui-text-secondary);cursor:pointer;-webkit-user-select:none;user-select:none;outline:none}:host.fui-dropdown-menu-item:focus-visible{background-color:var(--fui-bg-subtle);outline:2px solid var(--fui-border-primary);outline-offset:-2px}:host.fui-dropdown-menu-item:hover:not(.fui-dropdown-menu-item--disabled){background-color:var(--fui-bg-subtle)}:host.fui-dropdown-menu-item:active:not(.fui-dropdown-menu-item--disabled){background-color:var(--fui-bg-muted)}:host.fui-dropdown-menu-item--danger{color:var(--fui-error-100)}:host.fui-dropdown-menu-item--danger:hover:not(.fui-dropdown-menu-item--disabled){background-color:var(--fui-error-10);color:var(--fui-error-70)}:host.fui-dropdown-menu-item--danger:active:not(.fui-dropdown-menu-item--disabled){background-color:var(--fui-error-20);color:var(--fui-error-80)}:host.fui-dropdown-menu-item--danger:focus-visible{background-color:var(--fui-error-10);outline-color:var(--fui-error-60)}:host.fui-dropdown-menu-item--disabled{color:var(--fui-text-secondary);cursor:not-allowed;opacity:var(--fui-state-disabled-opacity)}:host.fui-dropdown-menu-item--disabled:hover,:host.fui-dropdown-menu-item--disabled:active,:host.fui-dropdown-menu-item--disabled:focus{background-color:transparent}:host.fui-dropdown-menu-item .fui-icon{flex-shrink:0;width:var(--fui-icon-xs);height:var(--fui-icon-xs);font-size:var(--fui-icon-md)}:host.fui-dropdown-menu-item .fui-icon[fuiPrefix]{margin-right:var(--fui-spacing-2)}:host.fui-dropdown-menu-item .fui-icon[fuiSuffix]{margin-left:auto;margin-right:0}:host.fui-dropdown-menu-item .fui-dropdown-menu-item__shortcut{margin-left:auto;font-size:var(--fui-text-sm);color:var(--fui-text-secondary);font-family:var(--fui-font-mono)}@media(prefers-contrast:more){:host.fui-dropdown-menu-item:focus-visible{outline-width:3px}:host.fui-dropdown-menu-item--danger:focus-visible{outline-width:3px}}@media(prefers-reduced-motion:reduce){:host.fui-dropdown-menu-item{transition:none}}\n"] }]
139
+ }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], selected: [{ type: i0.Output, args: ["selected"] }], onClick: [{
140
+ type: HostListener,
141
+ args: ['click', ['$event']]
142
+ }], onKeydown: [{
143
+ type: HostListener,
144
+ args: ['keydown', ['$event']]
145
+ }] } });
146
+
147
+ const FUI_DROPDOWN_MENU_POSITIONS = [
148
+ 'top-start',
149
+ 'top',
150
+ 'top-end',
151
+ 'bottom-start',
152
+ 'bottom',
153
+ 'bottom-end',
154
+ 'left-start',
155
+ 'left',
156
+ 'left-end',
157
+ 'right-start',
158
+ 'right',
159
+ 'right-end',
160
+ ];
161
+ const FUI_DROPDOWN_MENU_SIZES = ['sm', 'md', 'lg'];
162
+ /**
163
+ * # FuiDropdownMenu Component
164
+ *
165
+ * A dropdown menu component that provides a list of options or actions.
166
+ * Designed to work with external triggers using the fuiDropdownMenuTrigger directive.
167
+ *
168
+ * ## Features
169
+ * - Multiple positioning options relative to trigger
170
+ * - Keyboard navigation (Arrow keys, Enter, Escape)
171
+ * - Click outside to close
172
+ * - Full accessibility support (ARIA attributes, focus management)
173
+ * - Customizable size variants
174
+ * - Auto-positioning with collision detection
175
+ * - Portal attachment to document body to avoid clipping issues
176
+ *
177
+ * ## Usage
178
+ *
179
+ * ### Basic DropdownMenu with External Trigger
180
+ * ```html
181
+ * <button fuiButton fuiDropdownMenuTrigger [fuiDropdownMenuTriggerFor]="menu">
182
+ * Open Menu
183
+ * </button>
184
+ * <fui-dropdown-menu #menu>
185
+ * <fui-dropdown-menu-item>Option 1</fui-dropdown-menu-item>
186
+ * <fui-dropdown-menu-item>Option 2</fui-dropdown-menu-item>
187
+ * </fui-dropdown-menu>
188
+ * ```
189
+ *
190
+ * ### DropdownMenu with Custom Position
191
+ * ```html
192
+ * <button fuiButton fuiDropdownMenuTrigger [fuiDropdownMenuTriggerFor]="menu">
193
+ * Open Menu
194
+ * </button>
195
+ * <fui-dropdown-menu #menu position="top-start" size="lg">
196
+ * <fui-dropdown-menu-item>Profile</fui-dropdown-menu-item>
197
+ * <fui-dropdown-menu-item variant="danger">Logout</fui-dropdown-menu-item>
198
+ * </fui-dropdown-menu>
199
+ * ```
200
+ *
201
+ * ### DropdownMenu without Portal (for special cases)
202
+ * ```html
203
+ * <button fuiButton fuiDropdownMenuTrigger [fuiDropdownMenuTriggerFor]="menu">
204
+ * Open Menu
205
+ * </button>
206
+ * <fui-dropdown-menu #menu [attachToBody]="false">
207
+ * <fui-dropdown-menu-item>Option 1</fui-dropdown-menu-item>
208
+ * <fui-dropdown-menu-item>Option 2</fui-dropdown-menu-item>
209
+ * </fui-dropdown-menu>
210
+ * ```
211
+ *
212
+ * ### DropdownMenu with Data Passed from Trigger
213
+ * ```html
214
+ * <button fuiButton fuiDropdownMenuTrigger
215
+ * [fuiDropdownMenuTriggerFor]="dynamicMenu"
216
+ * [menuTriggerData]="{ user: currentUser, items: menuItems }">
217
+ * Open Menu
218
+ * </button>
219
+ * <fui-dropdown-menu #dynamicMenu>
220
+ * <!-- Access data in component using menu.menuData() -->
221
+ * </fui-dropdown-menu>
222
+ * ```
223
+ *
224
+ * ```typescript
225
+ * @Component({
226
+ * template: `
227
+ * <fui-dropdown-menu #menu>
228
+ * <fui-dropdown-menu-item *ngFor="let item of menu.menuData()?.items">
229
+ * {{ item.label }}
230
+ * </fui-dropdown-menu-item>
231
+ * </fui-dropdown-menu>
232
+ * `
233
+ * })
234
+ * export class MyComponent { }
235
+ * ```
236
+ */
237
+ class FuiDropdownMenuComponent {
238
+ /**
239
+ * Menu position relative to the trigger element
240
+ * @default 'bottom-start'
241
+ */
242
+ position = input('bottom-start', ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
243
+ /**
244
+ * Menu size variant
245
+ * @default 'md'
246
+ */
247
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
248
+ /**
249
+ * Whether the menu should close when clicking outside
250
+ * @default true
251
+ */
252
+ closeOnClickOutside = input(true, { ...(ngDevMode ? { debugName: "closeOnClickOutside" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
253
+ /**
254
+ * Whether the menu is disabled
255
+ * @default false
256
+ */
257
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
258
+ /**
259
+ * Whether to attach the menu panel to the document body to avoid clipping issues
260
+ * @default true
261
+ */
262
+ attachToBody = input(true, { ...(ngDevMode ? { debugName: "attachToBody" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
263
+ /**
264
+ * Emitted when the menu open state changes
265
+ */
266
+ openChange = output();
267
+ /**
268
+ * Emitted when a menu item is selected
269
+ */
270
+ itemSelected = output();
271
+ // Internal state
272
+ _isOpen = signal(false, ...(ngDevMode ? [{ debugName: "_isOpen" }] : /* istanbul ignore next */ []));
273
+ _animationState = signal('void', ...(ngDevMode ? [{ debugName: "_animationState" }] : /* istanbul ignore next */ []));
274
+ _triggerElement = signal(null, ...(ngDevMode ? [{ debugName: "_triggerElement" }] : /* istanbul ignore next */ []));
275
+ _menuData = signal(null, ...(ngDevMode ? [{ debugName: "_menuData" }] : /* istanbul ignore next */ []));
276
+ _previousFocusedElement = null;
277
+ _closeAnimationTimeout = null;
278
+ // Computed properties
279
+ computedClasses = computed(() => {
280
+ const classes = [
281
+ 'fui-dropdown-menu',
282
+ `fui-dropdown-menu--${this.position()}`,
283
+ `fui-dropdown-menu--${this.size()}`,
284
+ ];
285
+ if (this.disabled()) {
286
+ classes.push('fui-dropdown-menu--disabled');
287
+ }
288
+ return classes.join(' ');
289
+ }, ...(ngDevMode ? [{ debugName: "computedClasses" }] : /* istanbul ignore next */ []));
290
+ // Injected dependencies
291
+ _destroyRef = inject(DestroyRef);
292
+ _elementRef = inject(ElementRef);
293
+ // Kept for _isTopmostOverlay() — getActiveOverlays() is not exposed on the directive
294
+ _overlayService = inject(FuiOverlayService);
295
+ _popup = inject(FuiPopupOverlayDirective);
296
+ // View references
297
+ menuPanel;
298
+ // Content children for roving tabindex management
299
+ _menuItems = contentChildren(FuiDropdownMenuItemComponent, { ...(ngDevMode ? { debugName: "_menuItems" } : /* istanbul ignore next */ {}), descendants: true });
300
+ constructor() {
301
+ // Disable the directive's own ESC handler — we handle ESC via document:keydown
302
+ // with _isTopmostOverlay() to support nested menus correctly.
303
+ this._popup.closeOnEscape.set(false);
304
+ // Drive the open/close animation lifecycle via an effect, so the close-animation
305
+ // setTimeout is scheduled in reaction to the open-state signal changing.
306
+ effect(() => {
307
+ if (this._isOpen()) {
308
+ // Panel just became open: initiate overlay creation.
309
+ // _animationState is set to 'enter' INSIDE _openMenu(), deferred until AFTER
310
+ // the overlay portal and position have been applied. This prevents the panel
311
+ // from flashing at the in-place (wrong) position: the panel renders initially
312
+ // with the default 'void' CSS state (opacity:0; translateY(-14px)), then the
313
+ // enter transition fires from the correctly-positioned overlay location.
314
+ this._openMenu();
315
+ }
316
+ else {
317
+ // Panel just became closed: start the leave animation + cleanup timer.
318
+ this._startCloseAnimation();
319
+ }
320
+ });
321
+ // Emit open change events — cleaned up on destroy
322
+ const openChangeEffect = effect(() => {
323
+ this.openChange.emit(this._isOpen());
324
+ }, ...(ngDevMode ? [{ debugName: "openChangeEffect" }] : /* istanbul ignore next */ []));
325
+ this._destroyRef.onDestroy(() => {
326
+ openChangeEffect.destroy();
327
+ });
328
+ }
329
+ /**
330
+ * Whether the menu is currently open
331
+ */
332
+ isOpen() {
333
+ return this._isOpen();
334
+ }
335
+ /**
336
+ * Opens the menu
337
+ */
338
+ open() {
339
+ if (this.disabled())
340
+ return;
341
+ this._isOpen.set(true);
342
+ }
343
+ /**
344
+ * Closes the menu
345
+ */
346
+ close() {
347
+ this._isOpen.set(false);
348
+ }
349
+ /**
350
+ * Toggles the menu open/closed state
351
+ */
352
+ toggle() {
353
+ if (this._isOpen()) {
354
+ this.close();
355
+ }
356
+ else {
357
+ this.open();
358
+ }
359
+ }
360
+ /**
361
+ * Sets the trigger element for positioning (called by trigger directive)
362
+ */
363
+ setTriggerElement(element) {
364
+ this._triggerElement.set(element);
365
+ }
366
+ /**
367
+ * Gets the menu data passed from the trigger
368
+ * Returns a signal containing the data
369
+ */
370
+ menuData() {
371
+ return this._menuData();
372
+ }
373
+ /**
374
+ * Sets the menu data (called by trigger directive)
375
+ * @param data The data to pass to the menu
376
+ */
377
+ setMenuData(data) {
378
+ this._menuData.set(data);
379
+ }
380
+ onDocumentKeydown(event) {
381
+ if (!this._isOpen() || !this._isTopmostOverlay()) {
382
+ return;
383
+ }
384
+ switch (event.key) {
385
+ case 'Escape': {
386
+ // The directive's closeOnEscape is set false; we own the ESC handling here
387
+ // via the document-level listener with _isTopmostOverlay() for nested menu support.
388
+ event.preventDefault();
389
+ event.stopPropagation();
390
+ this.close();
391
+ break;
392
+ }
393
+ case 'ArrowDown':
394
+ event.preventDefault();
395
+ this._focusNextItem();
396
+ break;
397
+ case 'ArrowUp':
398
+ event.preventDefault();
399
+ this._focusPreviousItem();
400
+ break;
401
+ case 'Home':
402
+ event.preventDefault();
403
+ this._focusFirstItem();
404
+ break;
405
+ case 'End':
406
+ event.preventDefault();
407
+ this._focusLastItem();
408
+ break;
409
+ }
410
+ }
411
+ onMenuClick(event) {
412
+ // Check if the click was on a menu item
413
+ const target = event.target;
414
+ const menuItem = target.closest('fui-dropdown-menu-item');
415
+ if (menuItem && this._isOpen()) {
416
+ // Emit the itemSelected event and close the menu
417
+ this.itemSelected.emit(event);
418
+ this.close();
419
+ }
420
+ }
421
+ ngOnDestroy() {
422
+ this._clearCloseTimeout();
423
+ this._restoreFocus();
424
+ // Dispose overlay if still open (directive's ngOnDestroy would handle this,
425
+ // but we call it explicitly to ensure cleanup order is predictable)
426
+ if (this._popup.panelOpen()) {
427
+ this._popup.close();
428
+ }
429
+ }
430
+ _openMenu() {
431
+ // Panel is @if-rendered: it becomes part of the DOM only after _isOpen() = true
432
+ // and a change-detection cycle. We use a double requestAnimationFrame to ensure
433
+ // the panel element is rendered before wiring to the directive.
434
+ requestAnimationFrame(() => {
435
+ if (this._destroyRef.destroyed)
436
+ return;
437
+ requestAnimationFrame(() => {
438
+ if (this._destroyRef.destroyed)
439
+ return;
440
+ const triggerEl = this._triggerElement();
441
+ if (!triggerEl || !this.menuPanel)
442
+ return;
443
+ // Wire trigger and panel into the popup directive
444
+ this._popup.setTrigger({ nativeElement: triggerEl });
445
+ this._popup.setPanel(this.menuPanel);
446
+ // Configure directive options for this open session
447
+ this._popup.panelClass.set(['fui-dropdown-menu-panel', `fui-dropdown-menu-panel--${this.size()}`]);
448
+ this._popup.backdropClass.set('fui-dropdown-menu-backdrop');
449
+ // Disable directive's internal backdrop-close: we handle it via backdropClick output
450
+ // so our leave-animation close() runs instead of the directive's synchronous close().
451
+ this._popup.closeOnBackdrop.set(false);
452
+ // ESC is handled by the document-level handler with _isTopmostOverlay()
453
+ this._popup.closeOnEscape.set(false);
454
+ this._popup.positions.set(this._getPositionsForMenuPosition(this.position()));
455
+ // Open the overlay
456
+ this._popup.open();
457
+ // Defer the enter animation until AFTER the overlay's internal position RAF
458
+ // has run. The overlay-ref schedules a RAF inside attach() to apply position;
459
+ // this RAF is queued AFTER that one (FIFO), so by the time it fires the panel
460
+ // is at the correct trigger position. The panel rendered with 'void' state
461
+ // (opacity:0) and the transition now fires from the correctly-placed location.
462
+ requestAnimationFrame(() => {
463
+ if (this._destroyRef.destroyed)
464
+ return;
465
+ this._animationState.set('enter');
466
+ });
467
+ // Subscribe to backdrop clicks so our animated close() runs (leave animation)
468
+ const backdropSub = this._popup.backdropClick.subscribe(() => {
469
+ backdropSub.unsubscribe();
470
+ if (this.closeOnClickOutside()) {
471
+ this.close();
472
+ }
473
+ });
474
+ // aria and focus
475
+ triggerEl.setAttribute('aria-expanded', 'true');
476
+ this._previousFocusedElement = document.activeElement;
477
+ setTimeout(() => {
478
+ if (this._destroyRef.destroyed)
479
+ return;
480
+ this._focusFirstItem();
481
+ }, 0);
482
+ });
483
+ });
484
+ }
485
+ _closeMenu() {
486
+ const triggerElement = this._triggerElement();
487
+ if (triggerElement) {
488
+ triggerElement.setAttribute('aria-expanded', 'false');
489
+ }
490
+ // Reset roving tabindex on all items
491
+ this._resetRovingTabindex();
492
+ this._popup.close();
493
+ this._restoreFocus();
494
+ }
495
+ _getPositionsForMenuPosition(position) {
496
+ const offset = 4;
497
+ switch (position) {
498
+ case 'bottom-start':
499
+ return [
500
+ { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: offset },
501
+ { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -offset },
502
+ ];
503
+ case 'bottom':
504
+ return [
505
+ { originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top', offsetY: offset },
506
+ { originX: 'center', originY: 'top', overlayX: 'center', overlayY: 'bottom', offsetY: -offset },
507
+ ];
508
+ case 'bottom-end':
509
+ return [
510
+ { originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top', offsetY: offset },
511
+ { originX: 'end', originY: 'top', overlayX: 'end', overlayY: 'bottom', offsetY: -offset },
512
+ ];
513
+ case 'top-start':
514
+ return [
515
+ { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -offset },
516
+ { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: offset },
517
+ ];
518
+ case 'top':
519
+ return [
520
+ { originX: 'center', originY: 'top', overlayX: 'center', overlayY: 'bottom', offsetY: -offset },
521
+ { originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top', offsetY: offset },
522
+ ];
523
+ case 'top-end':
524
+ return [
525
+ { originX: 'end', originY: 'top', overlayX: 'end', overlayY: 'bottom', offsetY: -offset },
526
+ { originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top', offsetY: offset },
527
+ ];
528
+ case 'right-start':
529
+ return [
530
+ { originX: 'end', originY: 'top', overlayX: 'start', overlayY: 'top', offsetX: offset },
531
+ { originX: 'start', originY: 'top', overlayX: 'end', overlayY: 'top', offsetX: -offset },
532
+ ];
533
+ case 'right':
534
+ return [
535
+ { originX: 'end', originY: 'center', overlayX: 'start', overlayY: 'center', offsetX: offset },
536
+ { originX: 'start', originY: 'center', overlayX: 'end', overlayY: 'center', offsetX: -offset },
537
+ ];
538
+ case 'right-end':
539
+ return [
540
+ { originX: 'end', originY: 'bottom', overlayX: 'start', overlayY: 'bottom', offsetX: offset },
541
+ { originX: 'start', originY: 'bottom', overlayX: 'end', overlayY: 'bottom', offsetX: offset },
542
+ ];
543
+ case 'left-start':
544
+ return [
545
+ { originX: 'start', originY: 'top', overlayX: 'end', overlayY: 'top', offsetX: -offset },
546
+ { originX: 'end', originY: 'top', overlayX: 'start', overlayY: 'top', offsetX: offset },
547
+ ];
548
+ case 'left':
549
+ return [
550
+ { originX: 'start', originY: 'center', overlayX: 'end', overlayY: 'center', offsetX: -offset },
551
+ { originX: 'end', originY: 'center', overlayX: 'start', overlayY: 'center', offsetX: offset },
552
+ ];
553
+ case 'left-end':
554
+ return [
555
+ { originX: 'start', originY: 'bottom', overlayX: 'end', overlayY: 'bottom', offsetX: -offset },
556
+ { originX: 'end', originY: 'bottom', overlayX: 'start', overlayY: 'bottom', offsetX: offset },
557
+ ];
558
+ default:
559
+ return [{ originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: offset }];
560
+ }
561
+ }
562
+ _restoreFocus() {
563
+ // Prefer the trigger element for focus restoration
564
+ const triggerElement = this._triggerElement();
565
+ if (triggerElement) {
566
+ triggerElement.focus();
567
+ }
568
+ else if (this._previousFocusedElement) {
569
+ this._previousFocusedElement.focus();
570
+ }
571
+ this._previousFocusedElement = null;
572
+ }
573
+ _getMenuItems() {
574
+ const menuElement = this.menuPanel?.nativeElement;
575
+ if (!menuElement)
576
+ return [];
577
+ return Array.from(menuElement.querySelectorAll('fui-dropdown-menu-item:not([aria-disabled="true"])'));
578
+ }
579
+ /** @internal Called by FuiDropdownMenuTriggerDirective */
580
+ _focusFirstItem() {
581
+ const items = this._getMenuItems();
582
+ if (items.length > 0) {
583
+ this._focusItem(items[0]);
584
+ }
585
+ }
586
+ /** @internal Called by FuiDropdownMenuTriggerDirective */
587
+ _focusLastItem() {
588
+ const items = this._getMenuItems();
589
+ if (items.length > 0) {
590
+ this._focusItem(items[items.length - 1]);
591
+ }
592
+ }
593
+ _focusNextItem() {
594
+ const items = this._getMenuItems();
595
+ if (items.length === 0) {
596
+ return;
597
+ }
598
+ const currentIndex = items.findIndex((item) => item === document.activeElement);
599
+ const nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
600
+ this._focusItem(items[nextIndex]);
601
+ }
602
+ _focusPreviousItem() {
603
+ const items = this._getMenuItems();
604
+ if (items.length === 0) {
605
+ return;
606
+ }
607
+ const currentIndex = items.findIndex((item) => item === document.activeElement);
608
+ const prevIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
609
+ this._focusItem(items[prevIndex]);
610
+ }
611
+ /**
612
+ * Focuses a menu item and updates roving tabindex across all items.
613
+ */
614
+ _focusItem(element) {
615
+ this._updateRovingTabindex(element);
616
+ element.focus();
617
+ }
618
+ /**
619
+ * Resets all items to tabindex="-1" when the menu closes.
620
+ */
621
+ _resetRovingTabindex() {
622
+ const items = this._menuItems();
623
+ for (const item of items) {
624
+ item.tabIndex.set('-1');
625
+ }
626
+ }
627
+ /**
628
+ * Updates roving tabindex: sets tabindex="0" on the target item
629
+ * and tabindex="-1" on all other items.
630
+ */
631
+ _updateRovingTabindex(targetElement) {
632
+ const items = this._menuItems();
633
+ for (const item of items) {
634
+ if (item._elementRef.nativeElement === targetElement) {
635
+ item.tabIndex.set('0');
636
+ }
637
+ else {
638
+ item.tabIndex.set('-1');
639
+ }
640
+ }
641
+ }
642
+ _startCloseAnimation() {
643
+ // Clear any existing timeout
644
+ this._clearCloseTimeout();
645
+ // Trigger leave animation
646
+ this._animationState.set('leave');
647
+ // Wait for animation to complete before closing (matches CSS transition duration).
648
+ this._closeAnimationTimeout = setTimeout(() => {
649
+ this._closeMenu();
650
+ this._animationState.set('void');
651
+ }, 150);
652
+ }
653
+ _clearCloseTimeout() {
654
+ if (this._closeAnimationTimeout !== null) {
655
+ clearTimeout(this._closeAnimationTimeout);
656
+ this._closeAnimationTimeout = null;
657
+ }
658
+ }
659
+ /**
660
+ * Checks if this menu's overlay is the topmost (most recently opened) overlay.
661
+ * This ensures that only the topmost menu responds to keyboard events when
662
+ * multiple menus are open (e.g., nested menus).
663
+ */
664
+ _isTopmostOverlay() {
665
+ const ref = this._popup.overlayRef();
666
+ if (!ref) {
667
+ return false;
668
+ }
669
+ const activeOverlays = this._overlayService.getActiveOverlays();
670
+ // The last overlay in the array is the most recently created (topmost)
671
+ const topmostOverlay = activeOverlays[activeOverlays.length - 1];
672
+ return topmostOverlay === ref;
673
+ }
674
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiDropdownMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
675
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: FuiDropdownMenuComponent, isStandalone: true, selector: "fui-dropdown-menu", inputs: { position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, closeOnClickOutside: { classPropertyName: "closeOnClickOutside", publicName: "closeOnClickOutside", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, attachToBody: { classPropertyName: "attachToBody", publicName: "attachToBody", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { openChange: "openChange", itemSelected: "itemSelected" }, host: { listeners: { "document:keydown": "onDocumentKeydown($event)", "click": "onMenuClick($event)" }, properties: { "class": "computedClasses()", "attr.data-open": "isOpen() ? \"true\" : null" } }, queries: [{ propertyName: "_menuItems", predicate: FuiDropdownMenuItemComponent, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "menuPanel", first: true, predicate: ["menuPanel"], descendants: true }], hostDirectives: [{ directive: i1.FuiPopupOverlayDirective, inputs: ["positions", "positions", "panelClass", "panelClass", "backdropClass", "backdropClass", "scrollStrategy", "scrollStrategy", "minWidthFromTrigger", "minWidthFromTrigger", "closeOnEscape", "closeOnEscape", "closeOnBackdrop", "closeOnBackdrop", "viewportMargin", "viewportMargin"], outputs: ["openedChange", "openedChange", "escapeKey", "escapeKey", "backdropClick", "backdropClick"] }], ngImport: i0, template: "@if (_isOpen() || _animationState() === 'leave') {\r\n <div class=\"fui-dropdown-menu__panel\" #menuPanel role=\"menu\" [attr.data-animation-state]=\"_animationState()\">\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-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)}}::ng-deep .fui-dropdown-menu{position:absolute}::ng-deep .fui-dropdown-menu--disabled{opacity:var(--fui-state-disabled-opacity);pointer-events:none}::ng-deep .fui-dropdown-menu .fui-dropdown-menu__panel{position:fixed;top:0;left:0;opacity:0;pointer-events:none}::ng-deep .fui-dropdown-menu__panel{--fui-dropdown-menu-min-width: 150px;--fui-dropdown-menu-max-width: 500px;--fui-dropdown-menu-border-radius: var(--fui-radius-sm);--fui-dropdown-menu-padding: var(--fui-spacing-2);--fui-dropdown-menu-bg: var(--fui-bg-default);--fui-dropdown-menu-shadow: var(--fui-shadow-lg);min-width:var(--fui-dropdown-menu-min-width);max-width:var(--fui-dropdown-menu-max-width);background-color:var(--fui-dropdown-menu-bg);border-radius:var(--fui-dropdown-menu-border-radius);padding:var(--fui-dropdown-menu-padding);box-shadow:var(--fui-dropdown-menu-shadow);overflow:hidden;transform-origin:top center;will-change:transform,opacity;opacity:0;transform:translateY(-14px);transition:opacity var(--fui-duration-moderate) var(--fui-ease-in),transform var(--fui-duration-moderate) var(--fui-ease-in)}::ng-deep .fui-dropdown-menu__panel:focus{outline:none}::ng-deep .fui-dropdown-menu__panel[data-animation-state=enter]{opacity:1;transform:translateY(0);transition:opacity var(--fui-duration-base) var(--fui-ease-out),transform var(--fui-duration-base) var(--fui-ease-out)}::ng-deep .fui-dropdown-menu__panel[data-animation-state=leave]{opacity:0;transform:translateY(-14px)}::ng-deep .fui-dropdown-menu-panel--sm .fui-dropdown-menu__panel{min-width:8rem;font-size:var(--fui-text-sm)}::ng-deep .fui-dropdown-menu-panel--md .fui-dropdown-menu__panel{min-width:12rem;font-size:var(--fui-text-base)}::ng-deep .fui-dropdown-menu-panel--lg .fui-dropdown-menu__panel{min-width:16rem;font-size:var(--fui-text-md)}@media(prefers-contrast:more){::ng-deep .fui-dropdown-menu__panel{border-width:2px;box-shadow:var(--fui-shadow-lg)}}@media(prefers-reduced-motion:reduce){::ng-deep .fui-dropdown-menu__panel{transition:none}}@media print{::ng-deep .fui-dropdown-menu__panel{display:none!important}}\n"] });
676
+ }
677
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiDropdownMenuComponent, decorators: [{
678
+ type: Component,
679
+ args: [{ selector: 'fui-dropdown-menu', standalone: true, imports: [], host: {
680
+ '[class]': 'computedClasses()',
681
+ '[attr.data-open]': 'isOpen() ? "true" : null',
682
+ }, hostDirectives: [
683
+ {
684
+ directive: FuiPopupOverlayDirective,
685
+ inputs: [
686
+ 'positions',
687
+ 'panelClass',
688
+ 'backdropClass',
689
+ 'scrollStrategy',
690
+ 'minWidthFromTrigger',
691
+ 'closeOnEscape',
692
+ 'closeOnBackdrop',
693
+ 'viewportMargin',
694
+ ],
695
+ outputs: ['openedChange', 'escapeKey', 'backdropClick'],
696
+ },
697
+ ], template: "@if (_isOpen() || _animationState() === 'leave') {\r\n <div class=\"fui-dropdown-menu__panel\" #menuPanel role=\"menu\" [attr.data-animation-state]=\"_animationState()\">\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-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)}}::ng-deep .fui-dropdown-menu{position:absolute}::ng-deep .fui-dropdown-menu--disabled{opacity:var(--fui-state-disabled-opacity);pointer-events:none}::ng-deep .fui-dropdown-menu .fui-dropdown-menu__panel{position:fixed;top:0;left:0;opacity:0;pointer-events:none}::ng-deep .fui-dropdown-menu__panel{--fui-dropdown-menu-min-width: 150px;--fui-dropdown-menu-max-width: 500px;--fui-dropdown-menu-border-radius: var(--fui-radius-sm);--fui-dropdown-menu-padding: var(--fui-spacing-2);--fui-dropdown-menu-bg: var(--fui-bg-default);--fui-dropdown-menu-shadow: var(--fui-shadow-lg);min-width:var(--fui-dropdown-menu-min-width);max-width:var(--fui-dropdown-menu-max-width);background-color:var(--fui-dropdown-menu-bg);border-radius:var(--fui-dropdown-menu-border-radius);padding:var(--fui-dropdown-menu-padding);box-shadow:var(--fui-dropdown-menu-shadow);overflow:hidden;transform-origin:top center;will-change:transform,opacity;opacity:0;transform:translateY(-14px);transition:opacity var(--fui-duration-moderate) var(--fui-ease-in),transform var(--fui-duration-moderate) var(--fui-ease-in)}::ng-deep .fui-dropdown-menu__panel:focus{outline:none}::ng-deep .fui-dropdown-menu__panel[data-animation-state=enter]{opacity:1;transform:translateY(0);transition:opacity var(--fui-duration-base) var(--fui-ease-out),transform var(--fui-duration-base) var(--fui-ease-out)}::ng-deep .fui-dropdown-menu__panel[data-animation-state=leave]{opacity:0;transform:translateY(-14px)}::ng-deep .fui-dropdown-menu-panel--sm .fui-dropdown-menu__panel{min-width:8rem;font-size:var(--fui-text-sm)}::ng-deep .fui-dropdown-menu-panel--md .fui-dropdown-menu__panel{min-width:12rem;font-size:var(--fui-text-base)}::ng-deep .fui-dropdown-menu-panel--lg .fui-dropdown-menu__panel{min-width:16rem;font-size:var(--fui-text-md)}@media(prefers-contrast:more){::ng-deep .fui-dropdown-menu__panel{border-width:2px;box-shadow:var(--fui-shadow-lg)}}@media(prefers-reduced-motion:reduce){::ng-deep .fui-dropdown-menu__panel{transition:none}}@media print{::ng-deep .fui-dropdown-menu__panel{display:none!important}}\n"] }]
698
+ }], ctorParameters: () => [], propDecorators: { position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], closeOnClickOutside: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnClickOutside", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], attachToBody: [{ type: i0.Input, args: [{ isSignal: true, alias: "attachToBody", required: false }] }], openChange: [{ type: i0.Output, args: ["openChange"] }], itemSelected: [{ type: i0.Output, args: ["itemSelected"] }], menuPanel: [{
699
+ type: ViewChild,
700
+ args: ['menuPanel', { static: false }]
701
+ }], _menuItems: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => FuiDropdownMenuItemComponent), { ...{ descendants: true }, isSignal: true }] }], onDocumentKeydown: [{
702
+ type: HostListener,
703
+ args: ['document:keydown', ['$event']]
704
+ }], onMenuClick: [{
705
+ type: HostListener,
706
+ args: ['click', ['$event']]
707
+ }] } });
708
+
709
+ /**
710
+ * # fuiDropdownMenuTrigger Directive
711
+ *
712
+ * A directive that marks an element as a dropdown menu trigger, similar to Angular Material's matMenuTriggerFor.
713
+ * This directive should be used in conjunction with FuiDropdownMenuComponent.
714
+ *
715
+ * ## Usage
716
+ *
717
+ * ### Basic Usage
718
+ * ```html
719
+ * <button fuiDropdownMenuTrigger [fuiDropdownMenuTriggerFor]="menu">Open Menu</button>
720
+ * <fui-dropdown-menu #menu>
721
+ * <fui-dropdown-menu-item>Option 1</fui-dropdown-menu-item>
722
+ * <fui-dropdown-menu-item>Option 2</fui-dropdown-menu-item>
723
+ * </fui-dropdown-menu>
724
+ * ```
725
+ *
726
+ * ### With Menu Reference
727
+ * ```html
728
+ * <button fuiDropdownMenuTrigger [fuiDropdownMenuTriggerFor]="userMenu">
729
+ * <fui-icon name="user"></fui-icon>
730
+ * User Menu
731
+ * </button>
732
+ *
733
+ * <fui-dropdown-menu #userMenu position="bottom-end">
734
+ * <fui-dropdown-menu-item>Profile</fui-dropdown-menu-item>
735
+ * <fui-dropdown-menu-item>Settings</fui-dropdown-menu-item>
736
+ * <fui-dropdown-menu-item variant="danger">Logout</fui-dropdown-menu-item>
737
+ * </fui-dropdown-menu>
738
+ * ```
739
+ *
740
+ * ### Passing Data to Menu
741
+ * ```html
742
+ * <button fuiDropdownMenuTrigger
743
+ * [fuiDropdownMenuTriggerFor]="dynamicMenu"
744
+ * [menuTriggerData]="{ user: currentUser, role: 'admin' }">
745
+ * Open Menu
746
+ * </button>
747
+ *
748
+ * <fui-dropdown-menu #dynamicMenu>
749
+ * <!-- Access menu data in your component via menu.menuData() -->
750
+ * </fui-dropdown-menu>
751
+ * ```
752
+ *
753
+ * @example
754
+ * ```typescript
755
+ * import { FuiDropdownMenuTriggerDirective, FuiDropdownMenuComponent, FuiDropdownMenuItemComponent } from '@raintonic/formaui/components/dropdown-menu';
756
+ *
757
+ * @Component({
758
+ * standalone: true,
759
+ * imports: [FuiDropdownMenuTriggerDirective, FuiDropdownMenuComponent, FuiDropdownMenuItemComponent],
760
+ * template: `
761
+ * <button fuiDropdownMenuTrigger [fuiDropdownMenuTriggerFor]="menu">Open Menu</button>
762
+ * <fui-dropdown-menu #menu>
763
+ * <fui-dropdown-menu-item>Option 1</fui-dropdown-menu-item>
764
+ * <fui-dropdown-menu-item>Option 2</fui-dropdown-menu-item>
765
+ * </fui-dropdown-menu>
766
+ * `
767
+ * })
768
+ * export class MyComponent { }
769
+ * ```
770
+ */
771
+ class FuiDropdownMenuTriggerDirective {
772
+ _elementRef = inject((ElementRef));
773
+ /** The menu instance that this trigger should open */
774
+ fuiDropdownMenuTriggerFor = input(...(ngDevMode ? [undefined, { debugName: "fuiDropdownMenuTriggerFor" }] : /* istanbul ignore next */ []));
775
+ /**
776
+ * Data to be passed to the menu.
777
+ * Can be accessed in the menu component or menu items.
778
+ * Similar to Angular Material's matMenuTriggerData.
779
+ */
780
+ menuTriggerData = input(...(ngDevMode ? [undefined, { debugName: "menuTriggerData" }] : /* istanbul ignore next */ []));
781
+ /** The menu instance that this trigger is associated with */
782
+ menu = null;
783
+ constructor() {
784
+ // Set up the menu reference when fuiDropdownMenuTriggerFor changes
785
+ effect(() => {
786
+ const menuRef = this.fuiDropdownMenuTriggerFor();
787
+ if (menuRef) {
788
+ this.menu = menuRef;
789
+ // Set the trigger element on the menu for positioning
790
+ menuRef.setTriggerElement(this._elementRef.nativeElement);
791
+ }
792
+ });
793
+ }
794
+ ngAfterViewInit() {
795
+ // Ensure the menu reference is set after view initialization
796
+ const menuRef = this.fuiDropdownMenuTriggerFor();
797
+ if (menuRef) {
798
+ this.menu = menuRef;
799
+ menuRef.setTriggerElement(this._elementRef.nativeElement);
800
+ }
801
+ }
802
+ onClick(event) {
803
+ if (this.menu) {
804
+ event.preventDefault();
805
+ // Update trigger element to ensure correct positioning when multiple triggers exist
806
+ this.menu.setTriggerElement(this._elementRef.nativeElement);
807
+ // Pass data to menu before opening/toggling
808
+ const data = this.menuTriggerData();
809
+ if (data !== undefined && this.menu.setMenuData) {
810
+ this.menu.setMenuData(data);
811
+ }
812
+ this.menu.toggle();
813
+ }
814
+ }
815
+ onKeydown(event) {
816
+ const menu = this.menu;
817
+ if (!menu)
818
+ return;
819
+ // Update trigger element and pass data to menu before opening
820
+ const prepareMenu = () => {
821
+ // Update trigger element to ensure correct positioning when multiple triggers exist
822
+ menu.setTriggerElement(this._elementRef.nativeElement);
823
+ // Pass data to menu
824
+ const data = this.menuTriggerData();
825
+ if (data !== undefined) {
826
+ menu.setMenuData(data);
827
+ }
828
+ };
829
+ switch (event.key) {
830
+ case 'Enter':
831
+ case ' ':
832
+ event.preventDefault();
833
+ prepareMenu();
834
+ menu.toggle();
835
+ break;
836
+ case 'ArrowDown':
837
+ event.preventDefault();
838
+ prepareMenu();
839
+ menu.open();
840
+ // Focus first item after menu opens
841
+ setTimeout(() => {
842
+ menu._focusFirstItem();
843
+ }, 0);
844
+ break;
845
+ case 'ArrowUp':
846
+ event.preventDefault();
847
+ prepareMenu();
848
+ menu.open();
849
+ // Focus last item after menu opens
850
+ setTimeout(() => {
851
+ menu._focusLastItem();
852
+ }, 0);
853
+ break;
854
+ }
855
+ }
856
+ /** Gets the trigger element */
857
+ getElement() {
858
+ return this._elementRef.nativeElement;
859
+ }
860
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiDropdownMenuTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
861
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.6", type: FuiDropdownMenuTriggerDirective, isStandalone: true, selector: "[fuiDropdownMenuTrigger]", inputs: { fuiDropdownMenuTriggerFor: { classPropertyName: "fuiDropdownMenuTriggerFor", publicName: "fuiDropdownMenuTriggerFor", isSignal: true, isRequired: false, transformFunction: null }, menuTriggerData: { classPropertyName: "menuTriggerData", publicName: "menuTriggerData", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "onClick($event)", "keydown": "onKeydown($event)" }, properties: { "attr.aria-haspopup": "\"true\"", "attr.aria-expanded": "menu?.isOpen() ? \"true\" : \"false\"" } }, ngImport: i0 });
862
+ }
863
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiDropdownMenuTriggerDirective, decorators: [{
864
+ type: Directive,
865
+ args: [{
866
+ selector: '[fuiDropdownMenuTrigger]',
867
+ standalone: true,
868
+ host: {
869
+ '[attr.aria-haspopup]': '"true"',
870
+ '[attr.aria-expanded]': 'menu?.isOpen() ? "true" : "false"',
871
+ },
872
+ }]
873
+ }], ctorParameters: () => [], propDecorators: { fuiDropdownMenuTriggerFor: [{ type: i0.Input, args: [{ isSignal: true, alias: "fuiDropdownMenuTriggerFor", required: false }] }], menuTriggerData: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuTriggerData", required: false }] }], onClick: [{
874
+ type: HostListener,
875
+ args: ['click', ['$event']]
876
+ }], onKeydown: [{
877
+ type: HostListener,
878
+ args: ['keydown', ['$event']]
879
+ }] } });
880
+
881
+ // Public API for dropdown-menu components
882
+
883
+ /**
884
+ * Generated bundle index. Do not edit.
885
+ */
886
+
887
+ export { DROPDOWN_MENU_ITEM_VARIANTS, FUI_DROPDOWN_MENU_POSITIONS, FUI_DROPDOWN_MENU_SIZES, FuiDropdownMenuComponent, FuiDropdownMenuItemComponent, FuiDropdownMenuTriggerDirective };
888
+ //# sourceMappingURL=raintonic-formaui-components-dropdown-menu.mjs.map