@raintonic/formaui 0.4.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/CHANGELOG.md +80 -35
  2. package/README.md +22 -26
  3. package/fesm2022/raintonic-formaui-cdk-drag-drop.mjs +39 -41
  4. package/fesm2022/raintonic-formaui-cdk-drag-drop.mjs.map +1 -1
  5. package/fesm2022/raintonic-formaui-cdk-form-field.mjs +207 -3
  6. package/fesm2022/raintonic-formaui-cdk-form-field.mjs.map +1 -1
  7. package/fesm2022/raintonic-formaui-cdk-overlay.mjs +19 -1
  8. package/fesm2022/raintonic-formaui-cdk-overlay.mjs.map +1 -1
  9. package/fesm2022/raintonic-formaui-cdk-virtual-scroll.mjs +5 -12
  10. package/fesm2022/raintonic-formaui-cdk-virtual-scroll.mjs.map +1 -1
  11. package/fesm2022/raintonic-formaui-components-accordion.mjs +8 -5
  12. package/fesm2022/raintonic-formaui-components-accordion.mjs.map +1 -1
  13. package/fesm2022/raintonic-formaui-components-alert.mjs +16 -2
  14. package/fesm2022/raintonic-formaui-components-alert.mjs.map +1 -1
  15. package/fesm2022/raintonic-formaui-components-autocomplete.mjs +255 -462
  16. package/fesm2022/raintonic-formaui-components-autocomplete.mjs.map +1 -1
  17. package/fesm2022/raintonic-formaui-components-avatar.mjs +34 -59
  18. package/fesm2022/raintonic-formaui-components-avatar.mjs.map +1 -1
  19. package/fesm2022/raintonic-formaui-components-badge.mjs +2 -2
  20. package/fesm2022/raintonic-formaui-components-badge.mjs.map +1 -1
  21. package/fesm2022/raintonic-formaui-components-breadcrumb.mjs +4 -4
  22. package/fesm2022/raintonic-formaui-components-breadcrumb.mjs.map +1 -1
  23. package/fesm2022/raintonic-formaui-components-button-group.mjs +2 -2
  24. package/fesm2022/raintonic-formaui-components-button-group.mjs.map +1 -1
  25. package/fesm2022/raintonic-formaui-components-button.mjs +15 -20
  26. package/fesm2022/raintonic-formaui-components-button.mjs.map +1 -1
  27. package/fesm2022/raintonic-formaui-components-card.mjs +2 -2
  28. package/fesm2022/raintonic-formaui-components-card.mjs.map +1 -1
  29. package/fesm2022/raintonic-formaui-components-checkbox.mjs +2 -2
  30. package/fesm2022/raintonic-formaui-components-checkbox.mjs.map +1 -1
  31. package/fesm2022/raintonic-formaui-components-chip.mjs +97 -0
  32. package/fesm2022/raintonic-formaui-components-chip.mjs.map +1 -0
  33. package/fesm2022/raintonic-formaui-components-data-table.mjs +69 -29
  34. package/fesm2022/raintonic-formaui-components-data-table.mjs.map +1 -1
  35. package/fesm2022/raintonic-formaui-components-date-picker.mjs +223 -144
  36. package/fesm2022/raintonic-formaui-components-date-picker.mjs.map +1 -1
  37. package/fesm2022/raintonic-formaui-components-divider.mjs +2 -2
  38. package/fesm2022/raintonic-formaui-components-divider.mjs.map +1 -1
  39. package/fesm2022/raintonic-formaui-components-drawer.mjs +2 -2
  40. package/fesm2022/raintonic-formaui-components-drawer.mjs.map +1 -1
  41. package/fesm2022/raintonic-formaui-components-dropdown-menu.mjs +888 -0
  42. package/fesm2022/raintonic-formaui-components-dropdown-menu.mjs.map +1 -0
  43. package/fesm2022/raintonic-formaui-components-dual-tier-navigation.mjs +774 -0
  44. package/fesm2022/raintonic-formaui-components-dual-tier-navigation.mjs.map +1 -0
  45. package/fesm2022/raintonic-formaui-components-empty-state.mjs +2 -2
  46. package/fesm2022/raintonic-formaui-components-empty-state.mjs.map +1 -1
  47. package/fesm2022/raintonic-formaui-components-file-upload.mjs +2 -2
  48. package/fesm2022/raintonic-formaui-components-file-upload.mjs.map +1 -1
  49. package/fesm2022/raintonic-formaui-components-form-field.mjs +81 -50
  50. package/fesm2022/raintonic-formaui-components-form-field.mjs.map +1 -1
  51. package/fesm2022/raintonic-formaui-components-icon.mjs +2 -2
  52. package/fesm2022/raintonic-formaui-components-icon.mjs.map +1 -1
  53. package/fesm2022/raintonic-formaui-components-input.mjs +47 -12
  54. package/fesm2022/raintonic-formaui-components-input.mjs.map +1 -1
  55. package/fesm2022/raintonic-formaui-components-list.mjs +4 -4
  56. package/fesm2022/raintonic-formaui-components-list.mjs.map +1 -1
  57. package/fesm2022/raintonic-formaui-components-number-input.mjs +20 -12
  58. package/fesm2022/raintonic-formaui-components-number-input.mjs.map +1 -1
  59. package/fesm2022/raintonic-formaui-components-paginator.mjs +2 -2
  60. package/fesm2022/raintonic-formaui-components-paginator.mjs.map +1 -1
  61. package/fesm2022/raintonic-formaui-components-password-input.mjs +35 -110
  62. package/fesm2022/raintonic-formaui-components-password-input.mjs.map +1 -1
  63. package/fesm2022/raintonic-formaui-components-popover.mjs +3 -2
  64. package/fesm2022/raintonic-formaui-components-popover.mjs.map +1 -1
  65. package/fesm2022/raintonic-formaui-components-progressbar.mjs +3 -2
  66. package/fesm2022/raintonic-formaui-components-progressbar.mjs.map +1 -1
  67. package/fesm2022/raintonic-formaui-components-radio.mjs +5 -6
  68. package/fesm2022/raintonic-formaui-components-radio.mjs.map +1 -1
  69. package/fesm2022/raintonic-formaui-components-select.mjs +257 -412
  70. package/fesm2022/raintonic-formaui-components-select.mjs.map +1 -1
  71. package/fesm2022/raintonic-formaui-components-side-panel.mjs +2 -2
  72. package/fesm2022/raintonic-formaui-components-side-panel.mjs.map +1 -1
  73. package/fesm2022/raintonic-formaui-components-sidebar-nav-menu.mjs +525 -0
  74. package/fesm2022/raintonic-formaui-components-sidebar-nav-menu.mjs.map +1 -0
  75. package/fesm2022/raintonic-formaui-components-skeleton.mjs +2 -2
  76. package/fesm2022/raintonic-formaui-components-skeleton.mjs.map +1 -1
  77. package/fesm2022/raintonic-formaui-components-slider.mjs +2 -2
  78. package/fesm2022/raintonic-formaui-components-slider.mjs.map +1 -1
  79. package/fesm2022/raintonic-formaui-components-spinner.mjs +2 -2
  80. package/fesm2022/raintonic-formaui-components-spinner.mjs.map +1 -1
  81. package/fesm2022/raintonic-formaui-components-stepper.mjs +50 -45
  82. package/fesm2022/raintonic-formaui-components-stepper.mjs.map +1 -1
  83. package/fesm2022/raintonic-formaui-components-strength-meter.mjs +149 -0
  84. package/fesm2022/raintonic-formaui-components-strength-meter.mjs.map +1 -0
  85. package/fesm2022/raintonic-formaui-components-tab.mjs +2 -2
  86. package/fesm2022/raintonic-formaui-components-tab.mjs.map +1 -1
  87. package/fesm2022/raintonic-formaui-components-time-picker.mjs +194 -154
  88. package/fesm2022/raintonic-formaui-components-time-picker.mjs.map +1 -1
  89. package/fesm2022/raintonic-formaui-components-toggle-group.mjs +302 -0
  90. package/fesm2022/raintonic-formaui-components-toggle-group.mjs.map +1 -0
  91. package/fesm2022/raintonic-formaui-components-toggle.mjs +2 -2
  92. package/fesm2022/raintonic-formaui-components-toggle.mjs.map +1 -1
  93. package/fesm2022/raintonic-formaui-components-toolbar.mjs +2 -2
  94. package/fesm2022/raintonic-formaui-components-toolbar.mjs.map +1 -1
  95. package/fesm2022/raintonic-formaui-components-tooltip.mjs +10 -4
  96. package/fesm2022/raintonic-formaui-components-tooltip.mjs.map +1 -1
  97. package/fesm2022/raintonic-formaui-components-topbar.mjs +60 -0
  98. package/fesm2022/raintonic-formaui-components-topbar.mjs.map +1 -0
  99. package/fesm2022/raintonic-formaui-components-tree-select.mjs +59 -69
  100. package/fesm2022/raintonic-formaui-components-tree-select.mjs.map +1 -1
  101. package/fesm2022/raintonic-formaui-components-tree-table.mjs +2 -2
  102. package/fesm2022/raintonic-formaui-components-tree-table.mjs.map +1 -1
  103. package/fesm2022/raintonic-formaui-components-tree.mjs +31 -5
  104. package/fesm2022/raintonic-formaui-components-tree.mjs.map +1 -1
  105. package/fesm2022/raintonic-formaui-core.mjs +279 -1
  106. package/fesm2022/raintonic-formaui-core.mjs.map +1 -1
  107. package/fesm2022/raintonic-formaui-services-breakpoint.mjs +93 -0
  108. package/fesm2022/raintonic-formaui-services-breakpoint.mjs.map +1 -0
  109. package/fesm2022/raintonic-formaui-services-dialog.mjs +314 -16
  110. package/fesm2022/raintonic-formaui-services-dialog.mjs.map +1 -1
  111. package/fesm2022/raintonic-formaui-services-notification.mjs +93 -29
  112. package/fesm2022/raintonic-formaui-services-notification.mjs.map +1 -1
  113. package/fesm2022/raintonic-formaui-services-theme.mjs +46 -196
  114. package/fesm2022/raintonic-formaui-services-theme.mjs.map +1 -1
  115. package/fesm2022/raintonic-formaui.mjs +1 -1
  116. package/fesm2022/raintonic-formaui.mjs.map +1 -1
  117. package/llms-full.txt +2329 -450
  118. package/llms.txt +36 -33
  119. package/package.json +42 -19
  120. package/styles/fonts/Geist-Bold.woff2 +0 -0
  121. package/styles/fonts/Geist-Italic.woff2 +0 -0
  122. package/styles/fonts/Geist-Light.woff2 +0 -0
  123. package/styles/fonts/Geist-Medium.woff2 +0 -0
  124. package/styles/fonts/Geist-Regular.woff2 +0 -0
  125. package/styles/fonts/Geist-SemiBold.woff2 +0 -0
  126. package/styles/fonts/GeistMono-Regular.woff2 +0 -0
  127. package/styles/generated/_tokens.scss +906 -0
  128. package/styles/index.scss +11 -10
  129. package/styles/partials/_brand.scss +46 -0
  130. package/styles/partials/_constants.scss +22 -20
  131. package/styles/partials/_fonts.scss +54 -10
  132. package/styles/partials/_grid.scss +29 -18
  133. package/styles/partials/_mixins.scss +69 -27
  134. package/styles/partials/_motion.scss +28 -33
  135. package/styles/partials/_theme.scss +28 -255
  136. package/styles/partials/_type.scss +117 -0
  137. package/styles/partials/_typography.scss +45 -45
  138. package/styles/partials/_utilities.scss +198 -98
  139. package/styles/partials/components/_button.scss +144 -75
  140. package/styles/partials/components/_dialog.scss +181 -180
  141. package/styles/partials/components/_overlay.scss +87 -87
  142. package/styles/partials/themes/_dark.scss +3 -268
  143. package/styles/partials/themes/_light.scss +4 -268
  144. package/styles/styles.css +7744 -0
  145. package/styles/styles.entry.scss +3 -0
  146. package/styles/utilities.css +4802 -0
  147. package/styles/utilities.entry.scss +3 -0
  148. package/types/raintonic-formaui-cdk-drag-drop.d.ts +0 -1
  149. package/types/raintonic-formaui-cdk-drag-drop.d.ts.map +1 -1
  150. package/types/raintonic-formaui-cdk-form-field.d.ts +118 -2
  151. package/types/raintonic-formaui-cdk-form-field.d.ts.map +1 -1
  152. package/types/raintonic-formaui-cdk-overlay.d.ts +2 -0
  153. package/types/raintonic-formaui-cdk-overlay.d.ts.map +1 -1
  154. package/types/raintonic-formaui-cdk-virtual-scroll.d.ts +0 -1
  155. package/types/raintonic-formaui-cdk-virtual-scroll.d.ts.map +1 -1
  156. package/types/raintonic-formaui-components-accordion.d.ts +1 -1
  157. package/types/raintonic-formaui-components-accordion.d.ts.map +1 -1
  158. package/types/raintonic-formaui-components-alert.d.ts +6 -1
  159. package/types/raintonic-formaui-components-alert.d.ts.map +1 -1
  160. package/types/raintonic-formaui-components-autocomplete.d.ts +73 -116
  161. package/types/raintonic-formaui-components-autocomplete.d.ts.map +1 -1
  162. package/types/raintonic-formaui-components-avatar.d.ts +13 -31
  163. package/types/raintonic-formaui-components-avatar.d.ts.map +1 -1
  164. package/types/raintonic-formaui-components-button.d.ts +4 -10
  165. package/types/raintonic-formaui-components-button.d.ts.map +1 -1
  166. package/types/raintonic-formaui-components-chip.d.ts +43 -0
  167. package/types/raintonic-formaui-components-chip.d.ts.map +1 -0
  168. package/types/raintonic-formaui-components-data-table.d.ts +48 -11
  169. package/types/raintonic-formaui-components-data-table.d.ts.map +1 -1
  170. package/types/raintonic-formaui-components-date-picker.d.ts +59 -23
  171. package/types/raintonic-formaui-components-date-picker.d.ts.map +1 -1
  172. package/types/raintonic-formaui-components-dropdown-menu.d.ts +394 -0
  173. package/types/raintonic-formaui-components-dropdown-menu.d.ts.map +1 -0
  174. package/types/raintonic-formaui-components-dual-tier-navigation.d.ts +87 -0
  175. package/types/raintonic-formaui-components-dual-tier-navigation.d.ts.map +1 -0
  176. package/types/raintonic-formaui-components-form-field.d.ts +51 -21
  177. package/types/raintonic-formaui-components-form-field.d.ts.map +1 -1
  178. package/types/raintonic-formaui-components-input.d.ts +20 -11
  179. package/types/raintonic-formaui-components-input.d.ts.map +1 -1
  180. package/types/raintonic-formaui-components-number-input.d.ts +5 -3
  181. package/types/raintonic-formaui-components-number-input.d.ts.map +1 -1
  182. package/types/raintonic-formaui-components-password-input.d.ts +18 -32
  183. package/types/raintonic-formaui-components-password-input.d.ts.map +1 -1
  184. package/types/raintonic-formaui-components-popover.d.ts.map +1 -1
  185. package/types/raintonic-formaui-components-progressbar.d.ts +1 -1
  186. package/types/raintonic-formaui-components-progressbar.d.ts.map +1 -1
  187. package/types/raintonic-formaui-components-radio.d.ts +1 -2
  188. package/types/raintonic-formaui-components-radio.d.ts.map +1 -1
  189. package/types/raintonic-formaui-components-select.d.ts +107 -76
  190. package/types/raintonic-formaui-components-select.d.ts.map +1 -1
  191. package/types/raintonic-formaui-components-sidebar-nav-menu.d.ts +223 -0
  192. package/types/raintonic-formaui-components-sidebar-nav-menu.d.ts.map +1 -0
  193. package/types/raintonic-formaui-components-stepper.d.ts +4 -2
  194. package/types/raintonic-formaui-components-stepper.d.ts.map +1 -1
  195. package/types/raintonic-formaui-components-strength-meter.d.ts +78 -0
  196. package/types/raintonic-formaui-components-strength-meter.d.ts.map +1 -0
  197. package/types/raintonic-formaui-components-time-picker.d.ts +44 -24
  198. package/types/raintonic-formaui-components-time-picker.d.ts.map +1 -1
  199. package/types/raintonic-formaui-components-toggle-group.d.ts +100 -0
  200. package/types/raintonic-formaui-components-toggle-group.d.ts.map +1 -0
  201. package/types/raintonic-formaui-components-tooltip.d.ts +2 -1
  202. package/types/raintonic-formaui-components-tooltip.d.ts.map +1 -1
  203. package/types/raintonic-formaui-components-topbar.d.ts +48 -0
  204. package/types/raintonic-formaui-components-topbar.d.ts.map +1 -0
  205. package/types/raintonic-formaui-components-tree-select.d.ts +25 -9
  206. package/types/raintonic-formaui-components-tree-select.d.ts.map +1 -1
  207. package/types/raintonic-formaui-components-tree.d.ts +12 -1
  208. package/types/raintonic-formaui-components-tree.d.ts.map +1 -1
  209. package/types/raintonic-formaui-core.d.ts +243 -5
  210. package/types/raintonic-formaui-core.d.ts.map +1 -1
  211. package/types/raintonic-formaui-services-breakpoint.d.ts +44 -0
  212. package/types/raintonic-formaui-services-breakpoint.d.ts.map +1 -0
  213. package/types/raintonic-formaui-services-dialog.d.ts +141 -2
  214. package/types/raintonic-formaui-services-dialog.d.ts.map +1 -1
  215. package/types/raintonic-formaui-services-notification.d.ts +24 -2
  216. package/types/raintonic-formaui-services-notification.d.ts.map +1 -1
  217. package/types/raintonic-formaui-services-theme.d.ts +13 -103
  218. package/types/raintonic-formaui-services-theme.d.ts.map +1 -1
  219. package/types/raintonic-formaui.d.ts +1 -1
  220. package/fesm2022/raintonic-formaui-components-big-menu.mjs +0 -86
  221. package/fesm2022/raintonic-formaui-components-big-menu.mjs.map +0 -1
  222. package/fesm2022/raintonic-formaui-components-menu.mjs +0 -896
  223. package/fesm2022/raintonic-formaui-components-menu.mjs.map +0 -1
  224. package/fesm2022/raintonic-formaui-components-sidebar.mjs +0 -275
  225. package/fesm2022/raintonic-formaui-components-sidebar.mjs.map +0 -1
  226. package/fesm2022/raintonic-formaui-components-tag.mjs +0 -95
  227. package/fesm2022/raintonic-formaui-components-tag.mjs.map +0 -1
  228. package/styles/_fonts-entry.scss +0 -3
  229. package/styles/fonts/inter-tight-latin-italic.woff2 +0 -0
  230. package/styles/fonts/inter-tight-latin.woff2 +0 -0
  231. package/types/raintonic-formaui-components-big-menu.d.ts +0 -73
  232. package/types/raintonic-formaui-components-big-menu.d.ts.map +0 -1
  233. package/types/raintonic-formaui-components-menu.d.ts +0 -403
  234. package/types/raintonic-formaui-components-menu.d.ts.map +0 -1
  235. package/types/raintonic-formaui-components-sidebar.d.ts +0 -185
  236. package/types/raintonic-formaui-components-sidebar.d.ts.map +0 -1
  237. package/types/raintonic-formaui-components-tag.d.ts +0 -43
  238. package/types/raintonic-formaui-components-tag.d.ts.map +0 -1
@@ -0,0 +1,93 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, DestroyRef, PLATFORM_ID, signal, computed, Injectable } from '@angular/core';
3
+ import { DOCUMENT, isPlatformBrowser } from '@angular/common';
4
+
5
+ /**
6
+ * Soglie in px (mobile-first min-width).
7
+ *
8
+ * Sync con:
9
+ * - tokens/core/layout.json (bp.{xs..2xl})
10
+ * - lib/styles/partials/_constants.scss ($fui-bp-{xs..2xl})
11
+ *
12
+ * Drift detection: vedi consistency test in breakpoint.service.spec.ts.
13
+ */
14
+ const FUI_BREAKPOINTS = {
15
+ xs: 480,
16
+ sm: 640,
17
+ md: 768,
18
+ lg: 1024,
19
+ xl: 1280,
20
+ '2xl': 1536,
21
+ };
22
+ /**
23
+ * FuiBreakpointService — accesso signal-based ai breakpoint del PRD.
24
+ *
25
+ * - `matches(bp)`: Signal<boolean> true se viewport ≥ soglia del bp (semantica
26
+ * `@media (min-width: …)`).
27
+ * - SSR-safe: prima dell'attach al browser tutti i signal sono `false`.
28
+ */
29
+ class FuiBreakpointService {
30
+ _destroyRef = inject(DestroyRef);
31
+ _document = inject(DOCUMENT);
32
+ _platformId = inject(PLATFORM_ID);
33
+ _matches = {
34
+ xs: signal(false),
35
+ sm: signal(false),
36
+ md: signal(false),
37
+ lg: signal(false),
38
+ xl: signal(false),
39
+ '2xl': signal(false),
40
+ };
41
+ // Ordine mobile-first dal più grande al più piccolo per il computed `active`.
42
+ static BP_ORDER_DESC = ['2xl', 'xl', 'lg', 'md', 'sm', 'xs'];
43
+ /**
44
+ * Bp più grande matched mobile-first. `null` se viewport < `xs` (480px) o in SSR.
45
+ */
46
+ active = computed(() => {
47
+ for (const bp of FuiBreakpointService.BP_ORDER_DESC) {
48
+ if (this._matches[bp]()) {
49
+ return bp;
50
+ }
51
+ }
52
+ return null;
53
+ }, ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
54
+ constructor() {
55
+ if (!isPlatformBrowser(this._platformId)) {
56
+ return;
57
+ }
58
+ const win = this._document.defaultView;
59
+ if (!win) {
60
+ return;
61
+ }
62
+ for (const bp of Object.keys(FUI_BREAKPOINTS)) {
63
+ const mq = win.matchMedia(`(min-width: ${FUI_BREAKPOINTS[bp].toString()}px)`);
64
+ this._matches[bp].set(mq.matches);
65
+ const handler = (e) => {
66
+ this._matches[bp].set(e.matches);
67
+ };
68
+ mq.addEventListener('change', handler);
69
+ this._destroyRef.onDestroy(() => {
70
+ mq.removeEventListener('change', handler);
71
+ });
72
+ }
73
+ }
74
+ /**
75
+ * Signal true se viewport ≥ soglia del bp. Stesso Signal per chiamate ripetute.
76
+ */
77
+ matches(bp) {
78
+ return this._matches[bp].asReadonly();
79
+ }
80
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiBreakpointService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
81
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiBreakpointService, providedIn: 'root' });
82
+ }
83
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiBreakpointService, decorators: [{
84
+ type: Injectable,
85
+ args: [{ providedIn: 'root' }]
86
+ }], ctorParameters: () => [] });
87
+
88
+ /**
89
+ * Generated bundle index. Do not edit.
90
+ */
91
+
92
+ export { FUI_BREAKPOINTS, FuiBreakpointService };
93
+ //# sourceMappingURL=raintonic-formaui-services-breakpoint.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"raintonic-formaui-services-breakpoint.mjs","sources":["../../../lib/services/breakpoint/breakpoint.service.ts","../../../lib/services/breakpoint/raintonic-formaui-services-breakpoint.ts"],"sourcesContent":["import { computed, DestroyRef, inject, Injectable, PLATFORM_ID, signal, Signal, WritableSignal } from '@angular/core';\r\nimport { DOCUMENT, isPlatformBrowser } from '@angular/common';\r\n\r\n/** I 6 breakpoint nominali (mobile-first min-width). */\r\nexport type FuiBreakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';\r\n\r\n/**\r\n * Soglie in px (mobile-first min-width).\r\n *\r\n * Sync con:\r\n * - tokens/core/layout.json (bp.{xs..2xl})\r\n * - lib/styles/partials/_constants.scss ($fui-bp-{xs..2xl})\r\n *\r\n * Drift detection: vedi consistency test in breakpoint.service.spec.ts.\r\n */\r\nexport const FUI_BREAKPOINTS: Readonly<Record<FuiBreakpoint, number>> = {\r\n xs: 480,\r\n sm: 640,\r\n md: 768,\r\n lg: 1024,\r\n xl: 1280,\r\n '2xl': 1536,\r\n} as const;\r\n\r\n/**\r\n * FuiBreakpointService — accesso signal-based ai breakpoint del PRD.\r\n *\r\n * - `matches(bp)`: Signal<boolean> true se viewport ≥ soglia del bp (semantica\r\n * `@media (min-width: …)`).\r\n * - SSR-safe: prima dell'attach al browser tutti i signal sono `false`.\r\n */\r\n@Injectable({ providedIn: 'root' })\r\nexport class FuiBreakpointService {\r\n private readonly _destroyRef = inject(DestroyRef);\r\n private readonly _document = inject(DOCUMENT);\r\n private readonly _platformId = inject(PLATFORM_ID);\r\n\r\n private readonly _matches: Record<FuiBreakpoint, WritableSignal<boolean>> = {\r\n xs: signal(false),\r\n sm: signal(false),\r\n md: signal(false),\r\n lg: signal(false),\r\n xl: signal(false),\r\n '2xl': signal(false),\r\n };\r\n\r\n // Ordine mobile-first dal più grande al più piccolo per il computed `active`.\r\n private static readonly BP_ORDER_DESC: readonly FuiBreakpoint[] = ['2xl', 'xl', 'lg', 'md', 'sm', 'xs'];\r\n\r\n /**\r\n * Bp più grande matched mobile-first. `null` se viewport < `xs` (480px) o in SSR.\r\n */\r\n readonly active: Signal<FuiBreakpoint | null> = computed(() => {\r\n for (const bp of FuiBreakpointService.BP_ORDER_DESC) {\r\n if (this._matches[bp]()) {\r\n return bp;\r\n }\r\n }\r\n return null;\r\n });\r\n\r\n constructor() {\r\n if (!isPlatformBrowser(this._platformId)) {\r\n return;\r\n }\r\n const win = this._document.defaultView;\r\n if (!win) {\r\n return;\r\n }\r\n for (const bp of Object.keys(FUI_BREAKPOINTS) as FuiBreakpoint[]) {\r\n const mq = win.matchMedia(`(min-width: ${FUI_BREAKPOINTS[bp].toString()}px)`);\r\n this._matches[bp].set(mq.matches);\r\n const handler = (e: MediaQueryListEvent): void => {\r\n this._matches[bp].set(e.matches);\r\n };\r\n mq.addEventListener('change', handler);\r\n this._destroyRef.onDestroy(() => {\r\n mq.removeEventListener('change', handler);\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Signal true se viewport ≥ soglia del bp. Stesso Signal per chiamate ripetute.\r\n */\r\n matches(bp: FuiBreakpoint): Signal<boolean> {\r\n return this._matches[bp].asReadonly();\r\n }\r\n}\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAMA;;;;;;;;AAQG;AACI,MAAM,eAAe,GAA4C;AACtE,IAAA,EAAE,EAAE,GAAG;AACP,IAAA,EAAE,EAAE,GAAG;AACP,IAAA,EAAE,EAAE,GAAG;AACP,IAAA,EAAE,EAAE,IAAI;AACR,IAAA,EAAE,EAAE,IAAI;AACR,IAAA,KAAK,EAAE,IAAI;;AAGb;;;;;;AAMG;MAEU,oBAAoB,CAAA;AACd,IAAA,WAAW,GAAG,MAAM,CAAC,UAAU,CAAC;AAChC,IAAA,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC;AAC5B,IAAA,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC;AAEjC,IAAA,QAAQ,GAAmD;AAC1E,QAAA,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC;AACjB,QAAA,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC;AACjB,QAAA,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC;AACjB,QAAA,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC;AACjB,QAAA,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC;AACjB,QAAA,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC;KACrB;;AAGO,IAAA,OAAgB,aAAa,GAA6B,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC;AAEvG;;AAEG;AACM,IAAA,MAAM,GAAiC,QAAQ,CAAC,MAAK;AAC5D,QAAA,KAAK,MAAM,EAAE,IAAI,oBAAoB,CAAC,aAAa,EAAE;YACnD,IAAI,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE;AACvB,gBAAA,OAAO,EAAE;YACX;QACF;AACA,QAAA,OAAO,IAAI;AACb,IAAA,CAAC,6EAAC;AAEF,IAAA,WAAA,GAAA;QACE,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE;YACxC;QACF;AACA,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW;QACtC,IAAI,CAAC,GAAG,EAAE;YACR;QACF;QACA,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,IAAI,CAAC,eAAe,CAAoB,EAAE;AAChE,YAAA,MAAM,EAAE,GAAG,GAAG,CAAC,UAAU,CAAC,CAAA,YAAA,EAAe,eAAe,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAA,GAAA,CAAK,CAAC;AAC7E,YAAA,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,CAAC;AACjC,YAAA,MAAM,OAAO,GAAG,CAAC,CAAsB,KAAU;AAC/C,gBAAA,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC;AAClC,YAAA,CAAC;AACD,YAAA,EAAE,CAAC,gBAAgB,CAAC,QAAQ,EAAE,OAAO,CAAC;AACtC,YAAA,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,MAAK;AAC9B,gBAAA,EAAE,CAAC,mBAAmB,CAAC,QAAQ,EAAE,OAAO,CAAC;AAC3C,YAAA,CAAC,CAAC;QACJ;IACF;AAEA;;AAEG;AACH,IAAA,OAAO,CAAC,EAAiB,EAAA;QACvB,OAAO,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,UAAU,EAAE;IACvC;uGAvDW,oBAAoB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;AAApB,IAAA,OAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,oBAAoB,cADP,MAAM,EAAA,CAAA;;2FACnB,oBAAoB,EAAA,UAAA,EAAA,CAAA;kBADhC,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE;;;AC/BlC;;AAEG;;;;"}
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, ElementRef, signal, computed, HostListener, ViewChild, ChangeDetectionStrategy, Component, ViewEncapsulation, EnvironmentInjector, Injector, ApplicationRef, TemplateRef, createComponent, Injectable } from '@angular/core';
2
+ import { InjectionToken, inject, ElementRef, signal, computed, HostListener, ViewChild, ChangeDetectionStrategy, Component, Renderer2, output, ViewEncapsulation, EnvironmentInjector, Injector, ApplicationRef, TemplateRef, createComponent, Injectable } from '@angular/core';
3
3
  import { Subject, defer } from 'rxjs';
4
4
  import { filter, startWith, map } from 'rxjs/operators';
5
5
  import { FuiOverlayService } from '@raintonic/formaui/cdk/overlay';
@@ -15,6 +15,13 @@ const FUI_DIALOG_DATA = new InjectionToken('FuiDialogData');
15
15
  * Injection token for the dialog scroll strategy
16
16
  */
17
17
  const FUI_DIALOG_DEFAULT_OPTIONS = new InjectionToken('FuiDialogDefaultOptions');
18
+ /** Size presets mapped to CSS values */
19
+ const DRAWER_SIZE_VALUES = {
20
+ sm: '320px',
21
+ md: '480px',
22
+ lg: '640px',
23
+ full: '100%',
24
+ };
18
25
 
19
26
  /**
20
27
  * # FuiDialogRefImpl
@@ -417,7 +424,7 @@ class FuiDialogContainerComponent {
417
424
  <ng-content></ng-content>
418
425
  </div>
419
426
  </div>
420
- `, isInline: true, styles: [":host{display:block;outline:0}.fui-dialog-container{display:flex;flex-direction:column;box-sizing:border-box;overflow:auto;outline:0;max-height:inherit;border:1px solid var(--fui-border-color);border-radius:var(--fui-border-radius-md);background:var(--fui-surface-00);box-shadow:var(--fui-dialog-box-shadow, var(--fui-shadow-05))}.fui-dialog-content{display:contents}.fui-dialog-enter{animation:fui-dialog-enter var(--fui-duration-moderate-02) var(--fui-ease-entrance)}.fui-dialog-exit{animation:fui-dialog-exit var(--fui-duration-moderate-01) var(--fui-ease-exit)}@keyframes fui-dialog-enter{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@keyframes fui-dialog-exit{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.95)}}@media(prefers-reduced-motion:reduce){.fui-dialog-enter,.fui-dialog-exit{animation:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
427
+ `, isInline: true, styles: [":host{display:block;outline:0}.fui-dialog-container{display:flex;flex-direction:column;box-sizing:border-box;overflow:auto;outline:0;max-height:inherit;border:1px solid var(--fui-border-default);border-radius:var(--fui-radius-md);background:var(--fui-bg-default);box-shadow:var(--fui-dialog-box-shadow, var(--fui-shadow-xl))}.fui-dialog-content{display:contents}.fui-dialog-enter{animation:fui-dialog-enter var(--fui-duration-moderate) var(--fui-ease-out)}.fui-dialog-exit{animation:fui-dialog-exit var(--fui-duration-base) var(--fui-ease-in)}@keyframes fui-dialog-enter{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@keyframes fui-dialog-exit{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.95)}}@media(prefers-reduced-motion:reduce){.fui-dialog-enter,.fui-dialog-exit{animation:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
421
428
  }
422
429
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiDialogContainerComponent, decorators: [{
423
430
  type: Component,
@@ -440,7 +447,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImpor
440
447
  </div>
441
448
  `, changeDetection: ChangeDetectionStrategy.OnPush, host: {
442
449
  class: 'fui-dialog-container-host',
443
- }, styles: [":host{display:block;outline:0}.fui-dialog-container{display:flex;flex-direction:column;box-sizing:border-box;overflow:auto;outline:0;max-height:inherit;border:1px solid var(--fui-border-color);border-radius:var(--fui-border-radius-md);background:var(--fui-surface-00);box-shadow:var(--fui-dialog-box-shadow, var(--fui-shadow-05))}.fui-dialog-content{display:contents}.fui-dialog-enter{animation:fui-dialog-enter var(--fui-duration-moderate-02) var(--fui-ease-entrance)}.fui-dialog-exit{animation:fui-dialog-exit var(--fui-duration-moderate-01) var(--fui-ease-exit)}@keyframes fui-dialog-enter{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@keyframes fui-dialog-exit{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.95)}}@media(prefers-reduced-motion:reduce){.fui-dialog-enter,.fui-dialog-exit{animation:none}}\n"] }]
450
+ }, styles: [":host{display:block;outline:0}.fui-dialog-container{display:flex;flex-direction:column;box-sizing:border-box;overflow:auto;outline:0;max-height:inherit;border:1px solid var(--fui-border-default);border-radius:var(--fui-radius-md);background:var(--fui-bg-default);box-shadow:var(--fui-dialog-box-shadow, var(--fui-shadow-xl))}.fui-dialog-content{display:contents}.fui-dialog-enter{animation:fui-dialog-enter var(--fui-duration-moderate) var(--fui-ease-out)}.fui-dialog-exit{animation:fui-dialog-exit var(--fui-duration-base) var(--fui-ease-in)}@keyframes fui-dialog-enter{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@keyframes fui-dialog-exit{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.95)}}@media(prefers-reduced-motion:reduce){.fui-dialog-enter,.fui-dialog-exit{animation:none}}\n"] }]
444
451
  }], propDecorators: { _dialogContainer: [{
445
452
  type: ViewChild,
446
453
  args: ['dialogContainer', { static: true }]
@@ -449,6 +456,146 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImpor
449
456
  args: ['keydown', ['$event']]
450
457
  }] } });
451
458
 
459
+ /**
460
+ * # FuiDialogDrawerContainerComponent
461
+ *
462
+ * Internal container component used by `FuiDialogService.openAsDrawer()`. Renders
463
+ * the drawer chrome (header, content slot, optional footer) with a position-based
464
+ * slide animation.
465
+ *
466
+ * Unlike the standalone `FuiDrawerComponent` (which manages its own backdrop and
467
+ * fixed positioning), this component delegates backdrop and positioning to the CDK
468
+ * overlay. The container's host element carries position classes and toggles the
469
+ * open class to trigger CSS transitions on the child panel — mirroring the original
470
+ * drawer's animation model.
471
+ */
472
+ class FuiDialogDrawerContainerComponent {
473
+ _document = inject(DOCUMENT);
474
+ _renderer = inject(Renderer2);
475
+ _elementRef = inject((ElementRef));
476
+ _drawerPanel;
477
+ /** Which edge the drawer slides from */
478
+ position = 'right';
479
+ title = null;
480
+ /** Whether to show the close button in the header */
481
+ showCloseButton = true;
482
+ /** ARIA label */
483
+ ariaLabel = null;
484
+ /** ARIA labelled-by element ID */
485
+ ariaLabelledBy = null;
486
+ /** Emitted when the close button is clicked */
487
+ closed = output();
488
+ /** Element that had focus before the drawer opened (for restoration) */
489
+ _previouslyFocusedElement = null;
490
+ ngAfterViewInit() {
491
+ setTimeout(() => {
492
+ this._renderer.addClass(this._elementRef.nativeElement, 'fui-drawer-service--open');
493
+ });
494
+ this._trapFocus();
495
+ }
496
+ ngOnDestroy() {
497
+ this._restoreFocus();
498
+ }
499
+ /**
500
+ * Returns the host element (for internal use by DialogService when projecting content).
501
+ */
502
+ _getHostElement() {
503
+ return this._elementRef.nativeElement;
504
+ }
505
+ /**
506
+ * Appends a DOM element to the drawer's content area (for internal use).
507
+ */
508
+ _appendToContent(element) {
509
+ const contentArea = this._elementRef.nativeElement.querySelector('.fui-drawer-service-content');
510
+ if (contentArea) {
511
+ this._renderer.appendChild(contentArea, element);
512
+ }
513
+ }
514
+ /**
515
+ * Handles keydown events for focus trap cycling within the drawer.
516
+ * Tab loops from last to first focusable element, Shift+Tab from first to last.
517
+ */
518
+ _onKeydown(event) {
519
+ if (event.key === 'Tab') {
520
+ const focusableElements = this._getFocusableElements();
521
+ if (focusableElements.length === 0) {
522
+ event.preventDefault();
523
+ return;
524
+ }
525
+ const firstFocusable = focusableElements[0];
526
+ const lastFocusable = focusableElements[focusableElements.length - 1];
527
+ const activeElement = this._document.activeElement;
528
+ if (event.shiftKey) {
529
+ if (activeElement === firstFocusable) {
530
+ event.preventDefault();
531
+ lastFocusable.focus();
532
+ }
533
+ }
534
+ else {
535
+ if (activeElement === lastFocusable) {
536
+ event.preventDefault();
537
+ firstFocusable.focus();
538
+ }
539
+ }
540
+ }
541
+ }
542
+ /**
543
+ * Focuses the first tabbable element in the drawer panel.
544
+ */
545
+ _trapFocus() {
546
+ this._previouslyFocusedElement = this._document.activeElement;
547
+ const focusable = this._getFocusableElements();
548
+ if (focusable.length > 0) {
549
+ focusable[0].focus();
550
+ }
551
+ else {
552
+ this._drawerPanel.nativeElement.focus();
553
+ }
554
+ }
555
+ /**
556
+ * Restores focus to the element that was focused before the drawer opened.
557
+ */
558
+ _restoreFocus() {
559
+ if (this._previouslyFocusedElement && typeof this._previouslyFocusedElement.focus === 'function') {
560
+ this._previouslyFocusedElement.focus();
561
+ }
562
+ this._previouslyFocusedElement = null;
563
+ }
564
+ /**
565
+ * Returns all focusable elements currently visible inside the drawer.
566
+ */
567
+ _getFocusableElements() {
568
+ const selectors = [
569
+ 'a[href]',
570
+ 'button:not([disabled])',
571
+ 'textarea:not([disabled])',
572
+ 'input:not([disabled])',
573
+ 'select:not([disabled])',
574
+ '[tabindex]:not([tabindex="-1"])',
575
+ '[contenteditable="true"]',
576
+ ].join(',');
577
+ return Array.from(this._drawerPanel.nativeElement.querySelectorAll(selectors)).filter((el) => el.getClientRects().length > 0);
578
+ }
579
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiDialogDrawerContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
580
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: FuiDialogDrawerContainerComponent, isStandalone: true, selector: "fui-dialog-drawer-container", outputs: { closed: "closed" }, host: { listeners: { "keydown": "_onKeydown($event)" }, properties: { "class.fui-drawer-service--left": "position === \"left\"", "class.fui-drawer-service--right": "position === \"right\"", "class.fui-drawer-service--top": "position === \"top\"", "class.fui-drawer-service--bottom": "position === \"bottom\"" }, classAttribute: "fui-drawer-service" }, viewQueries: [{ propertyName: "_drawerPanel", first: true, predicate: ["drawerPanel"], descendants: true, static: true }], ngImport: i0, template: "<div\r\n #drawerPanel\r\n class=\"fui-drawer-service-panel\"\r\n [attr.role]=\"'dialog'\"\r\n [attr.aria-modal]=\"true\"\r\n [attr.aria-label]=\"ariaLabel || title || null\"\r\n [attr.aria-labelledby]=\"ariaLabelledBy\"\r\n tabindex=\"-1\"\r\n>\r\n @if (title || showCloseButton) {\r\n <div class=\"fui-drawer-service-header\">\r\n @if (title) {\r\n <h2 class=\"fui-drawer-service-title\">{{ title }}</h2>\r\n }\r\n @if (showCloseButton) {\r\n <button\r\n type=\"button\"\r\n class=\"fui-drawer-service-close\"\r\n (click)=\"closed.emit()\"\r\n [attr.aria-label]=\"'Close drawer'\"\r\n >\r\n <fui-icon name=\"x\" size=\"sm\"></fui-icon>\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"fui-drawer-service-content\">\r\n <ng-content></ng-content>\r\n </div>\r\n\r\n <div class=\"fui-drawer-service-footer\">\r\n <ng-content select=\"[drawerFooter]\"></ng-content>\r\n </div>\r\n</div>\r\n", styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition-property:opacity;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-out);transition-delay:0ms}.fui-motion-fade-out{transition-property:opacity;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-in);transition-delay:0ms}.fui-motion-slide-in-bottom{transition-property:transform;transition-duration:var(--fui-duration-base);transition-timing-function:var(--fui-ease-out);transition-delay:0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition-property:transform;transition-duration:var(--fui-duration-base);transition-timing-function:var(--fui-ease-out);transition-delay:0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition-property:transform,opacity;transition-duration:var(--fui-duration-base);transition-timing-function:var(--fui-ease-out);transition-delay:0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-base) var(--fui-ease-out)}@keyframes fui-popover-enter{0%{opacity:0;transform:translateY(-14px)}60%{opacity:1}to{opacity:1;transform:translateY(0)}}.fui-drawer-service{display:block;width:100%;height:100%;outline:0;overflow:hidden}.fui-drawer-service-panel{display:flex;flex-direction:column;width:100%;height:100%;background-color:var(--fui-bg-default);transition-property:transform;transition-duration:var(--fui-duration-base);transition-timing-function:var(--fui-ease-out);transition-delay:0ms}.fui-drawer-service--right .fui-drawer-service-panel{border-left:1px solid var(--fui-border-default);box-shadow:-4px 0 24px #0000001a;transform:translate(100%)}.fui-drawer-service--left .fui-drawer-service-panel{border-right:1px solid var(--fui-border-default);box-shadow:4px 0 24px #0000001a;transform:translate(-100%)}.fui-drawer-service--top .fui-drawer-service-panel{border-bottom:1px solid var(--fui-border-default);box-shadow:0 4px 24px #0000001a;transform:translateY(-100%)}.fui-drawer-service--bottom .fui-drawer-service-panel{border-top:1px solid var(--fui-border-default);box-shadow:0 -4px 24px #0000001a;transform:translateY(100%)}.fui-drawer-service--open .fui-drawer-service-panel{transform:translate(0)}.fui-drawer-service-header{display:flex;align-items:center;justify-content:space-between;padding:var(--fui-spacing-6) var(--fui-spacing-7);border-bottom:1px solid var(--fui-border-default);flex-shrink:0}.fui-drawer-service-title{font-size:var(--fui-text-md);font-weight:var(--fui-weight-semibold);color:var(--fui-text-primary);margin:0}.fui-drawer-service-close{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;border:none;background:transparent;border-radius:var(--fui-radius-sm);color:var(--fui-text-secondary);cursor:pointer;transition:background-color var(--fui-duration-base) var(--fui-ease-in-out),color var(--fui-duration-base) var(--fui-ease-in-out)}.fui-drawer-service-close:hover{background-color:var(--fui-bg-subtle);color:var(--fui-text-primary)}.fui-drawer-service-close:focus-visible{outline:2px solid var(--fui-primary-10)}.fui-drawer-service-content{flex:1;overflow-y:auto;padding:var(--fui-spacing-7)}.fui-drawer-service-footer{display:flex;align-items:center;justify-content:flex-end;gap:var(--fui-spacing-4);padding:var(--fui-spacing-6) var(--fui-spacing-7);border-top:1px solid var(--fui-border-default);flex-shrink:0}.fui-drawer-service-footer:empty{display:none}@media(prefers-reduced-motion:reduce){.fui-drawer-service-panel{transition:none!important}.fui-drawer-service--right .fui-drawer-service-panel,.fui-drawer-service--left .fui-drawer-service-panel,.fui-drawer-service--top .fui-drawer-service-panel,.fui-drawer-service--bottom .fui-drawer-service-panel{transform:translate(0)}.fui-drawer-service-close{transition:none}}\n"], dependencies: [{ kind: "component", type: FuiIconComponent, selector: "fui-icon", inputs: ["name", "size", "weight", "color", "ariaLabel", "spin", "pulse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
581
+ }
582
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiDialogDrawerContainerComponent, decorators: [{
583
+ type: Component,
584
+ args: [{ selector: 'fui-dialog-drawer-container', standalone: true, imports: [FuiIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
585
+ class: 'fui-drawer-service',
586
+ '[class.fui-drawer-service--left]': 'position === "left"',
587
+ '[class.fui-drawer-service--right]': 'position === "right"',
588
+ '[class.fui-drawer-service--top]': 'position === "top"',
589
+ '[class.fui-drawer-service--bottom]': 'position === "bottom"',
590
+ }, template: "<div\r\n #drawerPanel\r\n class=\"fui-drawer-service-panel\"\r\n [attr.role]=\"'dialog'\"\r\n [attr.aria-modal]=\"true\"\r\n [attr.aria-label]=\"ariaLabel || title || null\"\r\n [attr.aria-labelledby]=\"ariaLabelledBy\"\r\n tabindex=\"-1\"\r\n>\r\n @if (title || showCloseButton) {\r\n <div class=\"fui-drawer-service-header\">\r\n @if (title) {\r\n <h2 class=\"fui-drawer-service-title\">{{ title }}</h2>\r\n }\r\n @if (showCloseButton) {\r\n <button\r\n type=\"button\"\r\n class=\"fui-drawer-service-close\"\r\n (click)=\"closed.emit()\"\r\n [attr.aria-label]=\"'Close drawer'\"\r\n >\r\n <fui-icon name=\"x\" size=\"sm\"></fui-icon>\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"fui-drawer-service-content\">\r\n <ng-content></ng-content>\r\n </div>\r\n\r\n <div class=\"fui-drawer-service-footer\">\r\n <ng-content select=\"[drawerFooter]\"></ng-content>\r\n </div>\r\n</div>\r\n", styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition-property:opacity;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-out);transition-delay:0ms}.fui-motion-fade-out{transition-property:opacity;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-in);transition-delay:0ms}.fui-motion-slide-in-bottom{transition-property:transform;transition-duration:var(--fui-duration-base);transition-timing-function:var(--fui-ease-out);transition-delay:0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition-property:transform;transition-duration:var(--fui-duration-base);transition-timing-function:var(--fui-ease-out);transition-delay:0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition-property:transform,opacity;transition-duration:var(--fui-duration-base);transition-timing-function:var(--fui-ease-out);transition-delay:0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-base) var(--fui-ease-out)}@keyframes fui-popover-enter{0%{opacity:0;transform:translateY(-14px)}60%{opacity:1}to{opacity:1;transform:translateY(0)}}.fui-drawer-service{display:block;width:100%;height:100%;outline:0;overflow:hidden}.fui-drawer-service-panel{display:flex;flex-direction:column;width:100%;height:100%;background-color:var(--fui-bg-default);transition-property:transform;transition-duration:var(--fui-duration-base);transition-timing-function:var(--fui-ease-out);transition-delay:0ms}.fui-drawer-service--right .fui-drawer-service-panel{border-left:1px solid var(--fui-border-default);box-shadow:-4px 0 24px #0000001a;transform:translate(100%)}.fui-drawer-service--left .fui-drawer-service-panel{border-right:1px solid var(--fui-border-default);box-shadow:4px 0 24px #0000001a;transform:translate(-100%)}.fui-drawer-service--top .fui-drawer-service-panel{border-bottom:1px solid var(--fui-border-default);box-shadow:0 4px 24px #0000001a;transform:translateY(-100%)}.fui-drawer-service--bottom .fui-drawer-service-panel{border-top:1px solid var(--fui-border-default);box-shadow:0 -4px 24px #0000001a;transform:translateY(100%)}.fui-drawer-service--open .fui-drawer-service-panel{transform:translate(0)}.fui-drawer-service-header{display:flex;align-items:center;justify-content:space-between;padding:var(--fui-spacing-6) var(--fui-spacing-7);border-bottom:1px solid var(--fui-border-default);flex-shrink:0}.fui-drawer-service-title{font-size:var(--fui-text-md);font-weight:var(--fui-weight-semibold);color:var(--fui-text-primary);margin:0}.fui-drawer-service-close{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;border:none;background:transparent;border-radius:var(--fui-radius-sm);color:var(--fui-text-secondary);cursor:pointer;transition:background-color var(--fui-duration-base) var(--fui-ease-in-out),color var(--fui-duration-base) var(--fui-ease-in-out)}.fui-drawer-service-close:hover{background-color:var(--fui-bg-subtle);color:var(--fui-text-primary)}.fui-drawer-service-close:focus-visible{outline:2px solid var(--fui-primary-10)}.fui-drawer-service-content{flex:1;overflow-y:auto;padding:var(--fui-spacing-7)}.fui-drawer-service-footer{display:flex;align-items:center;justify-content:flex-end;gap:var(--fui-spacing-4);padding:var(--fui-spacing-6) var(--fui-spacing-7);border-top:1px solid var(--fui-border-default);flex-shrink:0}.fui-drawer-service-footer:empty{display:none}@media(prefers-reduced-motion:reduce){.fui-drawer-service-panel{transition:none!important}.fui-drawer-service--right .fui-drawer-service-panel,.fui-drawer-service--left .fui-drawer-service-panel,.fui-drawer-service--top .fui-drawer-service-panel,.fui-drawer-service--bottom .fui-drawer-service-panel{transform:translate(0)}.fui-drawer-service-close{transition:none}}\n"] }]
591
+ }], propDecorators: { _drawerPanel: [{
592
+ type: ViewChild,
593
+ args: ['drawerPanel', { static: true }]
594
+ }], closed: [{ type: i0.Output, args: ["closed"] }], _onKeydown: [{
595
+ type: HostListener,
596
+ args: ['keydown', ['$event']]
597
+ }] } });
598
+
452
599
  class FuiConfirmDialogComponent {
453
600
  data = inject(FUI_DIALOG_DATA);
454
601
  dialogRef = inject(FuiDialogRef);
@@ -484,11 +631,11 @@ class FuiConfirmDialogComponent {
484
631
  this.dialogRef.close(false);
485
632
  }
486
633
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiConfirmDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
487
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.6", type: FuiConfirmDialogComponent, isStandalone: true, selector: "fui-confirm-dialog", host: { classAttribute: "fui-confirm-dialog" }, ngImport: i0, template: "<div class=\"fui-confirm-dialog__header\">\r\n <fui-icon class=\"fui-confirm-dialog__icon fui-confirm-dialog__icon--{{ variant }}\" [name]=\"icon()\" size=\"lg\" />\r\n <h2 class=\"fui-confirm-dialog__title\" id=\"confirm-dialog-title\">{{ title }}</h2>\r\n</div>\r\n\r\n<div class=\"fui-confirm-dialog__body\" id=\"confirm-dialog-description\">\r\n <p class=\"fui-confirm-dialog__message\">{{ message }}</p>\r\n</div>\r\n\r\n<div class=\"fui-confirm-dialog__actions\">\r\n <button fuiButton variant=\"tertiary\" (click)=\"onCancel()\">{{ cancelText }}</button>\r\n <button fuiButton [variant]=\"confirmButtonVariant()\" (click)=\"onConfirm()\">{{ confirmText }}</button>\r\n</div>\r\n", styles: [".fui-confirm-dialog{display:flex;flex-direction:column;padding:var(--fui-padding-24, 1.5rem)}.fui-confirm-dialog__header{display:flex;align-items:center;gap:var(--fui-gap-12, .75rem);margin-bottom:var(--fui-gap-16, 1rem)}.fui-confirm-dialog__icon{flex-shrink:0}.fui-confirm-dialog__icon--info{color:var(--fui-info)}.fui-confirm-dialog__icon--warning{color:var(--fui-warning)}.fui-confirm-dialog__icon--danger{color:var(--fui-danger)}.fui-confirm-dialog__title{font-family:var(--fui-font-family-sans);font-size:var(--fui-font-size-04);font-weight:var(--fui-font-weight-semibold, 600);color:var(--fui-text-primary);margin:0}.fui-confirm-dialog__body{margin-bottom:var(--fui-padding-24, 1.5rem)}.fui-confirm-dialog__message{font-size:var(--fui-font-size-02);color:var(--fui-text-secondary);line-height:1.5;margin:0}.fui-confirm-dialog__actions{display:flex;justify-content:flex-end;gap:var(--fui-gap-8, .5rem)}\n"], dependencies: [{ kind: "component", type: FuiIconComponent, selector: "fui-icon", inputs: ["name", "size", "weight", "color", "ariaLabel", "spin", "pulse"] }, { kind: "directive", type: FuiButtonDirective, selector: "button[fuiButton], a[fuiButton]", inputs: ["variant", "size", "disabled", "fullWidth", "loading", "iconOnly", "aria-label", "type"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
634
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.6", type: FuiConfirmDialogComponent, isStandalone: true, selector: "fui-confirm-dialog", host: { classAttribute: "fui-confirm-dialog" }, ngImport: i0, template: "<div class=\"fui-confirm-dialog__header\">\r\n <fui-icon class=\"fui-confirm-dialog__icon fui-confirm-dialog__icon--{{ variant }}\" [name]=\"icon()\" size=\"lg\" />\r\n <h2 class=\"fui-confirm-dialog__title\" id=\"confirm-dialog-title\">{{ title }}</h2>\r\n</div>\r\n\r\n<div class=\"fui-confirm-dialog__body\" id=\"confirm-dialog-description\">\r\n <p class=\"fui-confirm-dialog__message\">{{ message }}</p>\r\n</div>\r\n\r\n<div class=\"fui-confirm-dialog__actions\">\r\n <button fuiButton variant=\"tertiary\" (click)=\"onCancel()\">{{ cancelText }}</button>\r\n <button fuiButton [variant]=\"confirmButtonVariant()\" (click)=\"onConfirm()\">{{ confirmText }}</button>\r\n</div>\r\n", styles: [".fui-confirm-dialog{display:flex;flex-direction:column;padding:var(--fui-spacing-9, 1.5rem)}.fui-confirm-dialog__header{display:flex;align-items:center;gap:var(--fui-spacing-6, .75rem);margin-bottom:var(--fui-spacing-7, 1rem)}.fui-confirm-dialog__icon{flex-shrink:0}.fui-confirm-dialog__icon--info{color:var(--fui-text-info)}.fui-confirm-dialog__icon--warning{color:var(--fui-text-warning)}.fui-confirm-dialog__icon--danger{color:var(--fui-text-error)}.fui-confirm-dialog__title{font-family:var(--fui-font-sans);font-size:var(--fui-text-lg);font-weight:var(--fui-weight-semibold, 600);color:var(--fui-text-primary);margin:0}.fui-confirm-dialog__body{margin-bottom:var(--fui-spacing-9, 1.5rem)}.fui-confirm-dialog__message{font-size:var(--fui-text-base);color:var(--fui-text-secondary);line-height:1.5;margin:0}.fui-confirm-dialog__actions{display:flex;justify-content:flex-end;gap:var(--fui-spacing-4, .5rem)}\n"], dependencies: [{ kind: "component", type: FuiIconComponent, selector: "fui-icon", inputs: ["name", "size", "weight", "color", "ariaLabel", "spin", "pulse"] }, { kind: "directive", type: FuiButtonDirective, selector: "button[fuiButton], a[fuiButton]", inputs: ["variant", "size", "disabled", "fullWidth", "loading", "iconOnly", "aria-label", "type"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
488
635
  }
489
636
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiConfirmDialogComponent, decorators: [{
490
637
  type: Component,
491
- args: [{ selector: 'fui-confirm-dialog', standalone: true, imports: [FuiIconComponent, FuiButtonDirective], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: { class: 'fui-confirm-dialog' }, template: "<div class=\"fui-confirm-dialog__header\">\r\n <fui-icon class=\"fui-confirm-dialog__icon fui-confirm-dialog__icon--{{ variant }}\" [name]=\"icon()\" size=\"lg\" />\r\n <h2 class=\"fui-confirm-dialog__title\" id=\"confirm-dialog-title\">{{ title }}</h2>\r\n</div>\r\n\r\n<div class=\"fui-confirm-dialog__body\" id=\"confirm-dialog-description\">\r\n <p class=\"fui-confirm-dialog__message\">{{ message }}</p>\r\n</div>\r\n\r\n<div class=\"fui-confirm-dialog__actions\">\r\n <button fuiButton variant=\"tertiary\" (click)=\"onCancel()\">{{ cancelText }}</button>\r\n <button fuiButton [variant]=\"confirmButtonVariant()\" (click)=\"onConfirm()\">{{ confirmText }}</button>\r\n</div>\r\n", styles: [".fui-confirm-dialog{display:flex;flex-direction:column;padding:var(--fui-padding-24, 1.5rem)}.fui-confirm-dialog__header{display:flex;align-items:center;gap:var(--fui-gap-12, .75rem);margin-bottom:var(--fui-gap-16, 1rem)}.fui-confirm-dialog__icon{flex-shrink:0}.fui-confirm-dialog__icon--info{color:var(--fui-info)}.fui-confirm-dialog__icon--warning{color:var(--fui-warning)}.fui-confirm-dialog__icon--danger{color:var(--fui-danger)}.fui-confirm-dialog__title{font-family:var(--fui-font-family-sans);font-size:var(--fui-font-size-04);font-weight:var(--fui-font-weight-semibold, 600);color:var(--fui-text-primary);margin:0}.fui-confirm-dialog__body{margin-bottom:var(--fui-padding-24, 1.5rem)}.fui-confirm-dialog__message{font-size:var(--fui-font-size-02);color:var(--fui-text-secondary);line-height:1.5;margin:0}.fui-confirm-dialog__actions{display:flex;justify-content:flex-end;gap:var(--fui-gap-8, .5rem)}\n"] }]
638
+ args: [{ selector: 'fui-confirm-dialog', standalone: true, imports: [FuiIconComponent, FuiButtonDirective], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: { class: 'fui-confirm-dialog' }, template: "<div class=\"fui-confirm-dialog__header\">\r\n <fui-icon class=\"fui-confirm-dialog__icon fui-confirm-dialog__icon--{{ variant }}\" [name]=\"icon()\" size=\"lg\" />\r\n <h2 class=\"fui-confirm-dialog__title\" id=\"confirm-dialog-title\">{{ title }}</h2>\r\n</div>\r\n\r\n<div class=\"fui-confirm-dialog__body\" id=\"confirm-dialog-description\">\r\n <p class=\"fui-confirm-dialog__message\">{{ message }}</p>\r\n</div>\r\n\r\n<div class=\"fui-confirm-dialog__actions\">\r\n <button fuiButton variant=\"tertiary\" (click)=\"onCancel()\">{{ cancelText }}</button>\r\n <button fuiButton [variant]=\"confirmButtonVariant()\" (click)=\"onConfirm()\">{{ confirmText }}</button>\r\n</div>\r\n", styles: [".fui-confirm-dialog{display:flex;flex-direction:column;padding:var(--fui-spacing-9, 1.5rem)}.fui-confirm-dialog__header{display:flex;align-items:center;gap:var(--fui-spacing-6, .75rem);margin-bottom:var(--fui-spacing-7, 1rem)}.fui-confirm-dialog__icon{flex-shrink:0}.fui-confirm-dialog__icon--info{color:var(--fui-text-info)}.fui-confirm-dialog__icon--warning{color:var(--fui-text-warning)}.fui-confirm-dialog__icon--danger{color:var(--fui-text-error)}.fui-confirm-dialog__title{font-family:var(--fui-font-sans);font-size:var(--fui-text-lg);font-weight:var(--fui-weight-semibold, 600);color:var(--fui-text-primary);margin:0}.fui-confirm-dialog__body{margin-bottom:var(--fui-spacing-9, 1.5rem)}.fui-confirm-dialog__message{font-size:var(--fui-text-base);color:var(--fui-text-secondary);line-height:1.5;margin:0}.fui-confirm-dialog__actions{display:flex;justify-content:flex-end;gap:var(--fui-spacing-4, .5rem)}\n"] }]
492
639
  }] });
493
640
 
494
641
  /**
@@ -692,7 +839,20 @@ class FuiDialogService {
692
839
  return new FuiDialogRef(overlayRef, config, dialogId);
693
840
  }
694
841
  _attachComponentContent(container, component, dialogRef, config) {
695
- // Create custom injector with dialog data and ref
842
+ return this._createAndAttachContentComponent(component, dialogRef, config, (el) => {
843
+ const hostElement = container._getHostElement();
844
+ const containerElement = hostElement.querySelector('.fui-dialog-container');
845
+ if (containerElement) {
846
+ containerElement.appendChild(el);
847
+ }
848
+ });
849
+ }
850
+ /**
851
+ * Shared helper: creates a component with dialog/drawer data and ref providers,
852
+ * attaches it to ApplicationRef for change detection, runs initial CD, and
853
+ * delegates DOM insertion to the provided callback.
854
+ */
855
+ _createAndAttachContentComponent(component, dialogRef, config, appendTo) {
696
856
  const providers = [
697
857
  { provide: FUI_DIALOG_DATA, useValue: config.data },
698
858
  { provide: FuiDialogRef, useValue: dialogRef },
@@ -701,21 +861,14 @@ class FuiDialogService {
701
861
  parent: config.injector ?? this._injector,
702
862
  providers,
703
863
  });
704
- // Create the component
705
864
  const componentRef = createComponent(component, {
706
865
  environmentInjector: this._environmentInjector,
707
866
  elementInjector: injector,
708
867
  });
709
868
  // Attach to ApplicationRef to connect it to Angular's change detection tree
710
- // This is crucial for form controls and event bindings to work properly
711
869
  this._appRef.attachView(componentRef.hostView);
712
- // Append to container
713
- const hostElement = container._getHostElement();
714
- const containerElement = hostElement.querySelector('.fui-dialog-container');
715
- if (containerElement) {
716
- containerElement.appendChild(componentRef.location.nativeElement);
717
- }
718
- // Trigger change detection
870
+ // Delegate DOM insertion to caller
871
+ appendTo(componentRef.location.nativeElement);
719
872
  componentRef.changeDetectorRef.detectChanges();
720
873
  return componentRef;
721
874
  }
@@ -750,6 +903,54 @@ class FuiDialogService {
750
903
  _getAfterAllClosed() {
751
904
  return this._afterAllClosedSubject.asObservable();
752
905
  }
906
+ /**
907
+ * Opens a component or template as a drawer that slides in from the specified
908
+ * viewport edge. Built on the same overlay infrastructure as `open()`, but
909
+ * anchored to the viewport edge with a slide animation instead of a centered modal.
910
+ *
911
+ * @param componentOrTemplateRef Component type or TemplateRef to display
912
+ * @param config Configuration options for the drawer (position, size, etc.)
913
+ * @returns Reference to the opened drawer (same lifecycle API as a dialog)
914
+ *
915
+ * ## Usage
916
+ * ```typescript
917
+ * const drawerRef = this.dialog.openAsDrawer(MyPanelComponent, {
918
+ * position: 'right',
919
+ * size: 'md',
920
+ * title: 'Settings'
921
+ * });
922
+ * drawerRef.afterClosed().subscribe(result => console.log(result));
923
+ * ```
924
+ */
925
+ openAsDrawer(componentOrTemplateRef, config) {
926
+ const mergedConfig = this._applyDrawerConfigDefaults(config);
927
+ const overlayRef = this._createDrawerOverlay(mergedConfig);
928
+ const drawerContainer = this._attachDrawerContainer(overlayRef, mergedConfig);
929
+ const dialogRef = this._createDialogRef(overlayRef, drawerContainer, mergedConfig);
930
+ if (componentOrTemplateRef instanceof TemplateRef) {
931
+ this._attachTemplateContent(drawerContainer, componentOrTemplateRef, dialogRef);
932
+ }
933
+ else {
934
+ const componentRef = this._attachDrawerComponentContent(drawerContainer, componentOrTemplateRef, dialogRef, mergedConfig);
935
+ dialogRef.componentInstance = componentRef.instance;
936
+ }
937
+ // Listen for drawer close button click
938
+ const subscription = drawerContainer.closed.subscribe(() => {
939
+ dialogRef.close();
940
+ subscription.unsubscribe();
941
+ });
942
+ // Track open dialogs
943
+ this._openDialogs.push(dialogRef);
944
+ dialogRef.afterClosed.subscribe(() => {
945
+ this._removeOpenDialog(dialogRef);
946
+ });
947
+ // Notify opened
948
+ requestAnimationFrame(() => {
949
+ dialogRef._notifyOpened();
950
+ this._afterOpenedSubject.next(dialogRef);
951
+ });
952
+ return dialogRef;
953
+ }
753
954
  /**
754
955
  * Opens a confirmation dialog and returns an Observable<boolean>.
755
956
  * Resolves to `true` when confirmed, `false` when cancelled, ESC, or backdrop click.
@@ -766,6 +967,103 @@ class FuiDialogService {
766
967
  });
767
968
  return dialogRef.afterClosed.pipe(map((result) => result === true));
768
969
  }
970
+ // ── Drawer-specific helpers ──
971
+ _createDrawerOverlay(config) {
972
+ const overlayConfig = this._getDrawerOverlayConfig(config);
973
+ return this._overlayService.create(overlayConfig);
974
+ }
975
+ _getDrawerOverlayConfig(config) {
976
+ const position = config.position ?? 'right';
977
+ const sizeValue = this._resolveDrawerSize(config.size);
978
+ const isHorizontal = position === 'left' || position === 'right';
979
+ const positionStrategy = this._overlayService.position().global();
980
+ // Anchor to viewport edge
981
+ switch (position) {
982
+ case 'left':
983
+ positionStrategy.left('0').top('0');
984
+ break;
985
+ case 'right':
986
+ positionStrategy.right('0').top('0');
987
+ break;
988
+ case 'top':
989
+ positionStrategy.top('0').left('0');
990
+ break;
991
+ case 'bottom':
992
+ positionStrategy.bottom('0').left('0');
993
+ break;
994
+ }
995
+ // Set the drawer dimension (width for left/right, height for top/bottom)
996
+ if (isHorizontal) {
997
+ positionStrategy.width(sizeValue);
998
+ positionStrategy.height('100%');
999
+ }
1000
+ else {
1001
+ positionStrategy.height(sizeValue);
1002
+ positionStrategy.width('100%');
1003
+ }
1004
+ return {
1005
+ positionStrategy,
1006
+ scrollStrategy: this._overlayService.scrollStrategies.block(),
1007
+ hasBackdrop: config.hasBackdrop ?? true,
1008
+ backdropClass: this._getDrawerBackdropClass(config),
1009
+ backdropClickBehavior: config.disableClose ? 'ignore' : 'close',
1010
+ panelClass: this._getDrawerPanelClass(config),
1011
+ direction: config.direction,
1012
+ };
1013
+ }
1014
+ _attachDrawerContainer(overlayRef, config) {
1015
+ const containerRef = createComponent(FuiDialogDrawerContainerComponent, {
1016
+ environmentInjector: this._environmentInjector,
1017
+ });
1018
+ const instance = containerRef.instance;
1019
+ instance.position = config.position ?? 'right';
1020
+ instance.title = config.title ?? null;
1021
+ instance.showCloseButton = config.showCloseButton ?? true;
1022
+ instance.ariaLabel = config.ariaLabel ?? null;
1023
+ instance.ariaLabelledBy = config.ariaLabelledBy ?? null;
1024
+ containerRef.changeDetectorRef.detectChanges();
1025
+ overlayRef.attach(containerRef);
1026
+ return instance;
1027
+ }
1028
+ _attachDrawerComponentContent(container, component, dialogRef, config) {
1029
+ return this._createAndAttachContentComponent(component, dialogRef, config, (el) => { container._appendToContent(el); });
1030
+ }
1031
+ _resolveDrawerSize(size) {
1032
+ if (!size) {
1033
+ return DRAWER_SIZE_VALUES.md;
1034
+ }
1035
+ return DRAWER_SIZE_VALUES[size] ?? size;
1036
+ }
1037
+ _getDrawerBackdropClass(config) {
1038
+ const baseClass = 'fui-drawer-service-backdrop';
1039
+ const customClasses = config.backdropClass
1040
+ ? Array.isArray(config.backdropClass)
1041
+ ? config.backdropClass
1042
+ : [config.backdropClass]
1043
+ : [];
1044
+ return [baseClass, ...customClasses];
1045
+ }
1046
+ _getDrawerPanelClass(config) {
1047
+ const baseClass = 'fui-drawer-service-panel-container';
1048
+ const customClasses = config.panelClass
1049
+ ? Array.isArray(config.panelClass)
1050
+ ? config.panelClass
1051
+ : [config.panelClass]
1052
+ : [];
1053
+ return [baseClass, ...customClasses];
1054
+ }
1055
+ _applyDrawerConfigDefaults(config) {
1056
+ return {
1057
+ position: 'right',
1058
+ size: 'md',
1059
+ showCloseButton: true,
1060
+ hasBackdrop: true,
1061
+ disableClose: false,
1062
+ restoreFocus: true,
1063
+ ...config,
1064
+ };
1065
+ }
1066
+ // ── End drawer-specific helpers ──
769
1067
  _applyConfigDefaults(config) {
770
1068
  return {
771
1069
  role: 'dialog',
@@ -794,5 +1092,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImpor
794
1092
  * Generated bundle index. Do not edit.
795
1093
  */
796
1094
 
797
- export { FUI_DIALOG_DATA, FUI_DIALOG_DEFAULT_OPTIONS, FuiConfirmDialogComponent, FuiDialogContainerComponent, FuiDialogRef, FuiDialogService };
1095
+ export { DRAWER_SIZE_VALUES, FUI_DIALOG_DATA, FUI_DIALOG_DEFAULT_OPTIONS, FuiConfirmDialogComponent, FuiDialogContainerComponent, FuiDialogDrawerContainerComponent, FuiDialogRef, FuiDialogService };
798
1096
  //# sourceMappingURL=raintonic-formaui-services-dialog.mjs.map