@raintonic/formaui 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +145 -0
  3. package/fesm2022/raintonic-formaui-cdk-drag-drop.mjs +806 -0
  4. package/fesm2022/raintonic-formaui-cdk-drag-drop.mjs.map +1 -0
  5. package/fesm2022/raintonic-formaui-cdk-form-field.mjs +86 -0
  6. package/fesm2022/raintonic-formaui-cdk-form-field.mjs.map +1 -0
  7. package/fesm2022/raintonic-formaui-cdk-overlay.mjs +1757 -0
  8. package/fesm2022/raintonic-formaui-cdk-overlay.mjs.map +1 -0
  9. package/fesm2022/raintonic-formaui-cdk-virtual-scroll.mjs +287 -0
  10. package/fesm2022/raintonic-formaui-cdk-virtual-scroll.mjs.map +1 -0
  11. package/fesm2022/raintonic-formaui-components-accordion.mjs +217 -0
  12. package/fesm2022/raintonic-formaui-components-accordion.mjs.map +1 -0
  13. package/fesm2022/raintonic-formaui-components-alert.mjs +161 -0
  14. package/fesm2022/raintonic-formaui-components-alert.mjs.map +1 -0
  15. package/fesm2022/raintonic-formaui-components-autocomplete.mjs +726 -0
  16. package/fesm2022/raintonic-formaui-components-autocomplete.mjs.map +1 -0
  17. package/fesm2022/raintonic-formaui-components-avatar.mjs +92 -0
  18. package/fesm2022/raintonic-formaui-components-avatar.mjs.map +1 -0
  19. package/fesm2022/raintonic-formaui-components-badge.mjs +107 -0
  20. package/fesm2022/raintonic-formaui-components-badge.mjs.map +1 -0
  21. package/fesm2022/raintonic-formaui-components-big-menu.mjs +68 -0
  22. package/fesm2022/raintonic-formaui-components-big-menu.mjs.map +1 -0
  23. package/fesm2022/raintonic-formaui-components-breadcrumb.mjs +55 -0
  24. package/fesm2022/raintonic-formaui-components-breadcrumb.mjs.map +1 -0
  25. package/fesm2022/raintonic-formaui-components-button-group.mjs +103 -0
  26. package/fesm2022/raintonic-formaui-components-button-group.mjs.map +1 -0
  27. package/fesm2022/raintonic-formaui-components-button.mjs +241 -0
  28. package/fesm2022/raintonic-formaui-components-button.mjs.map +1 -0
  29. package/fesm2022/raintonic-formaui-components-card.mjs +270 -0
  30. package/fesm2022/raintonic-formaui-components-card.mjs.map +1 -0
  31. package/fesm2022/raintonic-formaui-components-checkbox.mjs +295 -0
  32. package/fesm2022/raintonic-formaui-components-checkbox.mjs.map +1 -0
  33. package/fesm2022/raintonic-formaui-components-data-table.mjs +631 -0
  34. package/fesm2022/raintonic-formaui-components-data-table.mjs.map +1 -0
  35. package/fesm2022/raintonic-formaui-components-date-picker.mjs +1331 -0
  36. package/fesm2022/raintonic-formaui-components-date-picker.mjs.map +1 -0
  37. package/fesm2022/raintonic-formaui-components-divider.mjs +41 -0
  38. package/fesm2022/raintonic-formaui-components-divider.mjs.map +1 -0
  39. package/fesm2022/raintonic-formaui-components-drawer.mjs +190 -0
  40. package/fesm2022/raintonic-formaui-components-drawer.mjs.map +1 -0
  41. package/fesm2022/raintonic-formaui-components-dynamic-form.mjs +266 -0
  42. package/fesm2022/raintonic-formaui-components-dynamic-form.mjs.map +1 -0
  43. package/fesm2022/raintonic-formaui-components-empty-state.mjs +33 -0
  44. package/fesm2022/raintonic-formaui-components-empty-state.mjs.map +1 -0
  45. package/fesm2022/raintonic-formaui-components-file-upload.mjs +246 -0
  46. package/fesm2022/raintonic-formaui-components-file-upload.mjs.map +1 -0
  47. package/fesm2022/raintonic-formaui-components-form-field.mjs +482 -0
  48. package/fesm2022/raintonic-formaui-components-form-field.mjs.map +1 -0
  49. package/fesm2022/raintonic-formaui-components-icon.mjs +117 -0
  50. package/fesm2022/raintonic-formaui-components-icon.mjs.map +1 -0
  51. package/fesm2022/raintonic-formaui-components-input.mjs +327 -0
  52. package/fesm2022/raintonic-formaui-components-input.mjs.map +1 -0
  53. package/fesm2022/raintonic-formaui-components-list.mjs +149 -0
  54. package/fesm2022/raintonic-formaui-components-list.mjs.map +1 -0
  55. package/fesm2022/raintonic-formaui-components-menu.mjs +896 -0
  56. package/fesm2022/raintonic-formaui-components-menu.mjs.map +1 -0
  57. package/fesm2022/raintonic-formaui-components-number-input.mjs +345 -0
  58. package/fesm2022/raintonic-formaui-components-number-input.mjs.map +1 -0
  59. package/fesm2022/raintonic-formaui-components-paginator.mjs +139 -0
  60. package/fesm2022/raintonic-formaui-components-paginator.mjs.map +1 -0
  61. package/fesm2022/raintonic-formaui-components-password-input.mjs +306 -0
  62. package/fesm2022/raintonic-formaui-components-password-input.mjs.map +1 -0
  63. package/fesm2022/raintonic-formaui-components-popover.mjs +451 -0
  64. package/fesm2022/raintonic-formaui-components-popover.mjs.map +1 -0
  65. package/fesm2022/raintonic-formaui-components-progressbar.mjs +148 -0
  66. package/fesm2022/raintonic-formaui-components-progressbar.mjs.map +1 -0
  67. package/fesm2022/raintonic-formaui-components-radio.mjs +260 -0
  68. package/fesm2022/raintonic-formaui-components-radio.mjs.map +1 -0
  69. package/fesm2022/raintonic-formaui-components-select.mjs +1011 -0
  70. package/fesm2022/raintonic-formaui-components-select.mjs.map +1 -0
  71. package/fesm2022/raintonic-formaui-components-side-panel.mjs +150 -0
  72. package/fesm2022/raintonic-formaui-components-side-panel.mjs.map +1 -0
  73. package/fesm2022/raintonic-formaui-components-sidebar.mjs +257 -0
  74. package/fesm2022/raintonic-formaui-components-sidebar.mjs.map +1 -0
  75. package/fesm2022/raintonic-formaui-components-skeleton.mjs +50 -0
  76. package/fesm2022/raintonic-formaui-components-skeleton.mjs.map +1 -0
  77. package/fesm2022/raintonic-formaui-components-slider.mjs +347 -0
  78. package/fesm2022/raintonic-formaui-components-slider.mjs.map +1 -0
  79. package/fesm2022/raintonic-formaui-components-spinner.mjs +63 -0
  80. package/fesm2022/raintonic-formaui-components-spinner.mjs.map +1 -0
  81. package/fesm2022/raintonic-formaui-components-stepper.mjs +317 -0
  82. package/fesm2022/raintonic-formaui-components-stepper.mjs.map +1 -0
  83. package/fesm2022/raintonic-formaui-components-tab.mjs +197 -0
  84. package/fesm2022/raintonic-formaui-components-tab.mjs.map +1 -0
  85. package/fesm2022/raintonic-formaui-components-tag.mjs +78 -0
  86. package/fesm2022/raintonic-formaui-components-tag.mjs.map +1 -0
  87. package/fesm2022/raintonic-formaui-components-time-picker.mjs +644 -0
  88. package/fesm2022/raintonic-formaui-components-time-picker.mjs.map +1 -0
  89. package/fesm2022/raintonic-formaui-components-toggle.mjs +171 -0
  90. package/fesm2022/raintonic-formaui-components-toggle.mjs.map +1 -0
  91. package/fesm2022/raintonic-formaui-components-toolbar.mjs +140 -0
  92. package/fesm2022/raintonic-formaui-components-toolbar.mjs.map +1 -0
  93. package/fesm2022/raintonic-formaui-components-tooltip.mjs +555 -0
  94. package/fesm2022/raintonic-formaui-components-tooltip.mjs.map +1 -0
  95. package/fesm2022/raintonic-formaui-components-tree-select.mjs +314 -0
  96. package/fesm2022/raintonic-formaui-components-tree-select.mjs.map +1 -0
  97. package/fesm2022/raintonic-formaui-components-tree-table.mjs +103 -0
  98. package/fesm2022/raintonic-formaui-components-tree-table.mjs.map +1 -0
  99. package/fesm2022/raintonic-formaui-components-tree.mjs +430 -0
  100. package/fesm2022/raintonic-formaui-components-tree.mjs.map +1 -0
  101. package/fesm2022/raintonic-formaui-core.mjs +62 -0
  102. package/fesm2022/raintonic-formaui-core.mjs.map +1 -0
  103. package/fesm2022/raintonic-formaui-services-dialog.mjs +798 -0
  104. package/fesm2022/raintonic-formaui-services-dialog.mjs.map +1 -0
  105. package/fesm2022/raintonic-formaui-services-notification.mjs +391 -0
  106. package/fesm2022/raintonic-formaui-services-notification.mjs.map +1 -0
  107. package/fesm2022/raintonic-formaui-services-theme.mjs +248 -0
  108. package/fesm2022/raintonic-formaui-services-theme.mjs.map +1 -0
  109. package/fesm2022/raintonic-formaui-test-utils.mjs +66 -0
  110. package/fesm2022/raintonic-formaui-test-utils.mjs.map +1 -0
  111. package/fesm2022/raintonic-formaui.mjs +15 -0
  112. package/fesm2022/raintonic-formaui.mjs.map +1 -0
  113. package/llms-full.txt +1627 -0
  114. package/llms.txt +60 -0
  115. package/package.json +251 -0
  116. package/styles/_fonts-entry.scss +3 -0
  117. package/styles/fonts/dm-mono-400-latin.woff2 +0 -0
  118. package/styles/fonts/inter-tight-latin-italic.woff2 +0 -0
  119. package/styles/fonts/inter-tight-latin.woff2 +0 -0
  120. package/styles/index.scss +127 -0
  121. package/styles/partials/_constants.scss +29 -0
  122. package/styles/partials/_fonts.scss +36 -0
  123. package/styles/partials/_grid.scss +171 -0
  124. package/styles/partials/_mixins.scss +145 -0
  125. package/styles/partials/_motion.scss +252 -0
  126. package/styles/partials/_theme.scss +275 -0
  127. package/styles/partials/_typography.scss +112 -0
  128. package/styles/partials/_utilities.scss +480 -0
  129. package/styles/partials/themes/_dark.scss +254 -0
  130. package/styles/partials/themes/_light.scss +254 -0
  131. package/types/raintonic-formaui-cdk-drag-drop.d.ts +196 -0
  132. package/types/raintonic-formaui-cdk-drag-drop.d.ts.map +1 -0
  133. package/types/raintonic-formaui-cdk-form-field.d.ts +62 -0
  134. package/types/raintonic-formaui-cdk-form-field.d.ts.map +1 -0
  135. package/types/raintonic-formaui-cdk-overlay.d.ts +843 -0
  136. package/types/raintonic-formaui-cdk-overlay.d.ts.map +1 -0
  137. package/types/raintonic-formaui-cdk-virtual-scroll.d.ts +112 -0
  138. package/types/raintonic-formaui-cdk-virtual-scroll.d.ts.map +1 -0
  139. package/types/raintonic-formaui-components-accordion.d.ts +124 -0
  140. package/types/raintonic-formaui-components-accordion.d.ts.map +1 -0
  141. package/types/raintonic-formaui-components-alert.d.ts +143 -0
  142. package/types/raintonic-formaui-components-alert.d.ts.map +1 -0
  143. package/types/raintonic-formaui-components-autocomplete.d.ts +193 -0
  144. package/types/raintonic-formaui-components-autocomplete.d.ts.map +1 -0
  145. package/types/raintonic-formaui-components-avatar.d.ts +52 -0
  146. package/types/raintonic-formaui-components-avatar.d.ts.map +1 -0
  147. package/types/raintonic-formaui-components-badge.d.ts +47 -0
  148. package/types/raintonic-formaui-components-badge.d.ts.map +1 -0
  149. package/types/raintonic-formaui-components-big-menu.d.ts +62 -0
  150. package/types/raintonic-formaui-components-big-menu.d.ts.map +1 -0
  151. package/types/raintonic-formaui-components-breadcrumb.d.ts +26 -0
  152. package/types/raintonic-formaui-components-breadcrumb.d.ts.map +1 -0
  153. package/types/raintonic-formaui-components-button-group.d.ts +61 -0
  154. package/types/raintonic-formaui-components-button-group.d.ts.map +1 -0
  155. package/types/raintonic-formaui-components-button.d.ts +116 -0
  156. package/types/raintonic-formaui-components-button.d.ts.map +1 -0
  157. package/types/raintonic-formaui-components-card.d.ts +191 -0
  158. package/types/raintonic-formaui-components-card.d.ts.map +1 -0
  159. package/types/raintonic-formaui-components-checkbox.d.ts +132 -0
  160. package/types/raintonic-formaui-components-checkbox.d.ts.map +1 -0
  161. package/types/raintonic-formaui-components-data-table.d.ts +368 -0
  162. package/types/raintonic-formaui-components-data-table.d.ts.map +1 -0
  163. package/types/raintonic-formaui-components-date-picker.d.ts +341 -0
  164. package/types/raintonic-formaui-components-date-picker.d.ts.map +1 -0
  165. package/types/raintonic-formaui-components-divider.d.ts +21 -0
  166. package/types/raintonic-formaui-components-divider.d.ts.map +1 -0
  167. package/types/raintonic-formaui-components-drawer.d.ts +48 -0
  168. package/types/raintonic-formaui-components-drawer.d.ts.map +1 -0
  169. package/types/raintonic-formaui-components-dynamic-form.d.ts +412 -0
  170. package/types/raintonic-formaui-components-dynamic-form.d.ts.map +1 -0
  171. package/types/raintonic-formaui-components-empty-state.d.ts +14 -0
  172. package/types/raintonic-formaui-components-empty-state.d.ts.map +1 -0
  173. package/types/raintonic-formaui-components-file-upload.d.ts +77 -0
  174. package/types/raintonic-formaui-components-file-upload.d.ts.map +1 -0
  175. package/types/raintonic-formaui-components-form-field.d.ts +271 -0
  176. package/types/raintonic-formaui-components-form-field.d.ts.map +1 -0
  177. package/types/raintonic-formaui-components-icon.d.ts +61 -0
  178. package/types/raintonic-formaui-components-icon.d.ts.map +1 -0
  179. package/types/raintonic-formaui-components-input.d.ts +149 -0
  180. package/types/raintonic-formaui-components-input.d.ts.map +1 -0
  181. package/types/raintonic-formaui-components-list.d.ts +48 -0
  182. package/types/raintonic-formaui-components-list.d.ts.map +1 -0
  183. package/types/raintonic-formaui-components-menu.d.ts +403 -0
  184. package/types/raintonic-formaui-components-menu.d.ts.map +1 -0
  185. package/types/raintonic-formaui-components-number-input.d.ts +127 -0
  186. package/types/raintonic-formaui-components-number-input.d.ts.map +1 -0
  187. package/types/raintonic-formaui-components-paginator.d.ts +37 -0
  188. package/types/raintonic-formaui-components-paginator.d.ts.map +1 -0
  189. package/types/raintonic-formaui-components-password-input.d.ts +111 -0
  190. package/types/raintonic-formaui-components-password-input.d.ts.map +1 -0
  191. package/types/raintonic-formaui-components-popover.d.ts +131 -0
  192. package/types/raintonic-formaui-components-popover.d.ts.map +1 -0
  193. package/types/raintonic-formaui-components-progressbar.d.ts +111 -0
  194. package/types/raintonic-formaui-components-progressbar.d.ts.map +1 -0
  195. package/types/raintonic-formaui-components-radio.d.ts +95 -0
  196. package/types/raintonic-formaui-components-radio.d.ts.map +1 -0
  197. package/types/raintonic-formaui-components-select.d.ts +307 -0
  198. package/types/raintonic-formaui-components-select.d.ts.map +1 -0
  199. package/types/raintonic-formaui-components-side-panel.d.ts +51 -0
  200. package/types/raintonic-formaui-components-side-panel.d.ts.map +1 -0
  201. package/types/raintonic-formaui-components-sidebar.d.ts +174 -0
  202. package/types/raintonic-formaui-components-sidebar.d.ts.map +1 -0
  203. package/types/raintonic-formaui-components-skeleton.d.ts +20 -0
  204. package/types/raintonic-formaui-components-skeleton.d.ts.map +1 -0
  205. package/types/raintonic-formaui-components-slider.d.ts +108 -0
  206. package/types/raintonic-formaui-components-slider.d.ts.map +1 -0
  207. package/types/raintonic-formaui-components-spinner.d.ts +42 -0
  208. package/types/raintonic-formaui-components-spinner.d.ts.map +1 -0
  209. package/types/raintonic-formaui-components-stepper.d.ts +126 -0
  210. package/types/raintonic-formaui-components-stepper.d.ts.map +1 -0
  211. package/types/raintonic-formaui-components-tab.d.ts +96 -0
  212. package/types/raintonic-formaui-components-tab.d.ts.map +1 -0
  213. package/types/raintonic-formaui-components-tag.d.ts +34 -0
  214. package/types/raintonic-formaui-components-tag.d.ts.map +1 -0
  215. package/types/raintonic-formaui-components-time-picker.d.ts +172 -0
  216. package/types/raintonic-formaui-components-time-picker.d.ts.map +1 -0
  217. package/types/raintonic-formaui-components-toggle.d.ts +70 -0
  218. package/types/raintonic-formaui-components-toggle.d.ts.map +1 -0
  219. package/types/raintonic-formaui-components-toolbar.d.ts +128 -0
  220. package/types/raintonic-formaui-components-toolbar.d.ts.map +1 -0
  221. package/types/raintonic-formaui-components-tooltip.d.ts +268 -0
  222. package/types/raintonic-formaui-components-tooltip.d.ts.map +1 -0
  223. package/types/raintonic-formaui-components-tree-select.d.ts +80 -0
  224. package/types/raintonic-formaui-components-tree-select.d.ts.map +1 -0
  225. package/types/raintonic-formaui-components-tree-table.d.ts +90 -0
  226. package/types/raintonic-formaui-components-tree-table.d.ts.map +1 -0
  227. package/types/raintonic-formaui-components-tree.d.ts +104 -0
  228. package/types/raintonic-formaui-components-tree.d.ts.map +1 -0
  229. package/types/raintonic-formaui-core.d.ts +115 -0
  230. package/types/raintonic-formaui-core.d.ts.map +1 -0
  231. package/types/raintonic-formaui-services-dialog.d.ts +451 -0
  232. package/types/raintonic-formaui-services-dialog.d.ts.map +1 -0
  233. package/types/raintonic-formaui-services-notification.d.ts +221 -0
  234. package/types/raintonic-formaui-services-notification.d.ts.map +1 -0
  235. package/types/raintonic-formaui-services-theme.d.ts +126 -0
  236. package/types/raintonic-formaui-services-theme.d.ts.map +1 -0
  237. package/types/raintonic-formaui-test-utils.d.ts +24 -0
  238. package/types/raintonic-formaui-test-utils.d.ts.map +1 -0
  239. package/types/raintonic-formaui.d.ts +4 -0
  240. package/types/raintonic-formaui.d.ts.map +1 -0
@@ -0,0 +1,1331 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, output, signal, computed, inject, ElementRef, ViewEncapsulation, ChangeDetectionStrategy, Component, NgZone, effect, ViewChild } from '@angular/core';
3
+ import { NgForm, FormGroupDirective, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
4
+ import { DOCUMENT } from '@angular/common';
5
+ import { Subject, Subscription, fromEvent } from 'rxjs';
6
+ import { filter } from 'rxjs/operators';
7
+ import { injectNgControl, updateErrorState, syncRequiredState, syncNgControlDisabled } from '@raintonic/formaui/cdk/form-field';
8
+ import { DefaultErrorStateMatcher, FUI_FORM_FIELD_CONTROL } from '@raintonic/formaui/core';
9
+ import { FuiOverlayService } from '@raintonic/formaui/cdk/overlay';
10
+ import { FuiIconComponent } from '@raintonic/formaui/components/icon';
11
+
12
+ /**
13
+ * Normalize a date to noon to avoid DST edge cases.
14
+ */
15
+ function normalizeDate(date) {
16
+ const d = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 12, 0, 0, 0);
17
+ return d;
18
+ }
19
+ /**
20
+ * Check if two dates are the same day (ignoring time).
21
+ */
22
+ function isSameDay(a, b) {
23
+ if (!a || !b)
24
+ return false;
25
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
26
+ }
27
+ /**
28
+ * Check if a date is within [min, max] bounds (day-level).
29
+ */
30
+ function isDateInRange(date, min, max) {
31
+ const d = normalizeDate(date).getTime();
32
+ if (min && normalizeDate(min).getTime() > d)
33
+ return false;
34
+ if (max && normalizeDate(max).getTime() < d)
35
+ return false;
36
+ return true;
37
+ }
38
+ /**
39
+ * Get number of days in a given month.
40
+ */
41
+ function getDaysInMonth(year, month) {
42
+ return new Date(year, month + 1, 0).getDate();
43
+ }
44
+ /**
45
+ * Get localized weekday labels.
46
+ * @param firstDayOfWeek 0=Sunday, 1=Monday, etc.
47
+ * @param format 'short' | 'narrow'
48
+ */
49
+ function getWeekdayLabels(firstDayOfWeek = 1, format = 'narrow') {
50
+ const formatter = new Intl.DateTimeFormat(undefined, { weekday: format });
51
+ const labels = [];
52
+ // Jan 4, 2024 is a Thursday — use a known date to map
53
+ // Instead, iterate from a known Sunday (Jan 7, 2024)
54
+ const sunday = new Date(2024, 0, 7); // Known Sunday
55
+ for (let i = 0; i < 7; i++) {
56
+ const dayIndex = (firstDayOfWeek + i) % 7;
57
+ const date = new Date(sunday);
58
+ date.setDate(sunday.getDate() + dayIndex);
59
+ labels.push(formatter.format(date));
60
+ }
61
+ return labels;
62
+ }
63
+ /**
64
+ * Get localized month label.
65
+ */
66
+ function getMonthLabel(year, month, format = 'long') {
67
+ const formatter = new Intl.DateTimeFormat(undefined, { month: format, year: 'numeric' });
68
+ return formatter.format(new Date(year, month, 1));
69
+ }
70
+ /**
71
+ * Get short month name (for month view grid).
72
+ */
73
+ function getShortMonthName(month) {
74
+ const formatter = new Intl.DateTimeFormat(undefined, { month: 'short' });
75
+ return formatter.format(new Date(2024, month, 1));
76
+ }
77
+ /**
78
+ * Build a 6x7 calendar grid for a given month.
79
+ * @returns 6 rows of 7 Date objects each.
80
+ */
81
+ function buildCalendarGrid(year, month, firstDayOfWeek = 1) {
82
+ const firstOfMonth = new Date(year, month, 1);
83
+ const dayOfWeek = firstOfMonth.getDay();
84
+ // How many days to show from the previous month
85
+ const offset = (dayOfWeek - firstDayOfWeek + 7) % 7;
86
+ const startDate = new Date(year, month, 1 - offset);
87
+ const weeks = [];
88
+ const current = new Date(startDate);
89
+ for (let week = 0; week < 6; week++) {
90
+ const row = [];
91
+ for (let day = 0; day < 7; day++) {
92
+ row.push(normalizeDate(new Date(current)));
93
+ current.setDate(current.getDate() + 1);
94
+ }
95
+ weeks.push(row);
96
+ }
97
+ return weeks;
98
+ }
99
+ // --- Format / Parse ---
100
+ function getSeparator(format) {
101
+ // Find first non-letter character
102
+ const match = /[^dMyy]/.exec(format);
103
+ return match ? match[0] : '/';
104
+ }
105
+ /**
106
+ * Format a Date to a string using the given format pattern.
107
+ * Supported tokens: dd, d, MM, M, yyyy, yy
108
+ */
109
+ function formatDate(date, format) {
110
+ if (!date || isNaN(date.getTime()))
111
+ return '';
112
+ const day = date.getDate();
113
+ const month = date.getMonth() + 1;
114
+ const year = date.getFullYear();
115
+ let result = format;
116
+ // Replace in order of longest token first to avoid partial matches
117
+ result = result.replace('dd', String(day).padStart(2, '0'));
118
+ result = result.replace(/\bd\b/, String(day));
119
+ result = result.replace('MM', String(month).padStart(2, '0'));
120
+ result = result.replace(/\bM\b/, String(month));
121
+ result = result.replace('yyyy', String(year).padStart(4, '0'));
122
+ result = result.replace('yy', String(year % 100).padStart(2, '0'));
123
+ return result;
124
+ }
125
+ /**
126
+ * Parse a date string to a Date using the given format.
127
+ * Returns null on failure.
128
+ */
129
+ function parseDate(text, format) {
130
+ if (!text?.trim())
131
+ return null;
132
+ const sep = getSeparator(format);
133
+ const parts = text.split(sep);
134
+ // Build a map of token → position in split parts
135
+ let day = -1;
136
+ let month = -1;
137
+ let year = -1;
138
+ // Build expected parts from format
139
+ const formatParts = format.split(sep);
140
+ if (parts.length !== formatParts.length)
141
+ return null;
142
+ for (let i = 0; i < formatParts.length; i++) {
143
+ const fp = formatParts[i];
144
+ const val = parseInt(parts[i], 10);
145
+ if (isNaN(val))
146
+ return null;
147
+ if (fp === 'dd' || fp === 'd') {
148
+ day = val;
149
+ }
150
+ else if (fp === 'MM' || fp === 'M') {
151
+ month = val;
152
+ }
153
+ else if (fp === 'yyyy') {
154
+ year = val;
155
+ }
156
+ else if (fp === 'yy') {
157
+ year = val < 50 ? 2000 + val : 1900 + val;
158
+ }
159
+ }
160
+ if (day < 1 || day > 31 || month < 1 || month > 12 || year < 0)
161
+ return null;
162
+ // Validate day for the given month
163
+ const daysInMonth = getDaysInMonth(year, month - 1);
164
+ if (day > daysInMonth)
165
+ return null;
166
+ return normalizeDate(new Date(year, month - 1, day));
167
+ }
168
+ /**
169
+ * Apply date mask to raw input as user types.
170
+ * Returns the masked value and corrected cursor position.
171
+ */
172
+ function applyDateMask(rawInput, format) {
173
+ const sep = getSeparator(format);
174
+ const digits = rawInput.replace(/\D/g, '');
175
+ // Build a pattern of digit counts from format tokens
176
+ // e.g., 'dd/MM/yyyy' → [2, 2, 4] with separators at positions 2, 5
177
+ const formatParts = format.split(sep);
178
+ const digitCounts = formatParts.map((p) => p.length);
179
+ const maxDigits = digitCounts.reduce((a, b) => a + b, 0);
180
+ const capped = digits.substring(0, maxDigits);
181
+ let masked = '';
182
+ let digitIndex = 0;
183
+ for (let partIdx = 0; partIdx < digitCounts.length; partIdx++) {
184
+ const count = digitCounts[partIdx];
185
+ const partDigits = capped.substring(digitIndex, digitIndex + count);
186
+ if (partDigits.length === 0)
187
+ break;
188
+ if (partIdx > 0) {
189
+ masked += sep;
190
+ }
191
+ masked += partDigits;
192
+ digitIndex += partDigits.length;
193
+ // Auto-insert separator when part is complete and there are more digits
194
+ if (partDigits.length === count && digitIndex < capped.length && partIdx < digitCounts.length - 1) {
195
+ // separator will be added in next iteration
196
+ }
197
+ }
198
+ return { masked, cursor: masked.length };
199
+ }
200
+ /**
201
+ * Check if date a is before date b (day-level).
202
+ */
203
+ function isBefore(a, b) {
204
+ return normalizeDate(a).getTime() < normalizeDate(b).getTime();
205
+ }
206
+ /**
207
+ * Check if date a is after date b (day-level).
208
+ */
209
+ function isAfter(a, b) {
210
+ return normalizeDate(a).getTime() > normalizeDate(b).getTime();
211
+ }
212
+ /**
213
+ * Check if date is between start and end (inclusive, day-level).
214
+ */
215
+ function isBetween(date, start, end) {
216
+ if (!start || !end)
217
+ return false;
218
+ const d = normalizeDate(date).getTime();
219
+ const s = normalizeDate(start).getTime();
220
+ const e = normalizeDate(end).getTime();
221
+ const min = Math.min(s, e);
222
+ const max = Math.max(s, e);
223
+ return d >= min && d <= max;
224
+ }
225
+
226
+ /**
227
+ * @component FuiCalendarComponent
228
+ * @selector fui-calendar
229
+ * @description Internal calendar grid used by `fui-date-picker`. Renders day, month,
230
+ * and year views with full keyboard navigation (Arrow keys, Home, End, PageUp/Down,
231
+ * Enter, Escape). Supports single-date selection and range highlighting with hover preview.
232
+ *
233
+ * @input selected - Currently selected date for single mode (Date | null)
234
+ * @input rangeStart - Start date of a range selection (Date | null)
235
+ * @input rangeEnd - End date of a range selection (Date | null)
236
+ * @input range - Whether range mode is active (default: false)
237
+ * @input min - Minimum selectable date (Date | null)
238
+ * @input max - Maximum selectable date (Date | null)
239
+ * @input dateFilter - Custom filter function to disable specific dates (DateFilterFn | null)
240
+ * @input startAt - Initial date to display when the calendar opens (Date | null)
241
+ * @input firstDayOfWeek - First day of the week: 0 = Sunday, 1 = Monday (default: 1)
242
+ * @input hoveredDate - Currently hovered date for range preview (Date | null)
243
+ *
244
+ * @output dateSelected - Emits the Date when a day cell is clicked or confirmed via keyboard
245
+ * @output dateHovered - Emits the hovered Date or null on mouse enter/leave
246
+ * @output monthChanged - Emits the new view Date when navigating months/years
247
+ *
248
+ * @example
249
+ * <fui-calendar
250
+ * [selected]="selectedDate"
251
+ * [min]="minDate"
252
+ * [max]="maxDate"
253
+ * (dateSelected)="onDateSelected($event)">
254
+ * </fui-calendar>
255
+ */
256
+ class FuiCalendarComponent {
257
+ // Inputs
258
+ selected = input(null, ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
259
+ rangeStart = input(null, ...(ngDevMode ? [{ debugName: "rangeStart" }] : /* istanbul ignore next */ []));
260
+ rangeEnd = input(null, ...(ngDevMode ? [{ debugName: "rangeEnd" }] : /* istanbul ignore next */ []));
261
+ range = input(false, ...(ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []));
262
+ min = input(null, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
263
+ max = input(null, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
264
+ dateFilter = input(null, ...(ngDevMode ? [{ debugName: "dateFilter" }] : /* istanbul ignore next */ []));
265
+ startAt = input(null, ...(ngDevMode ? [{ debugName: "startAt" }] : /* istanbul ignore next */ []));
266
+ firstDayOfWeek = input(1, ...(ngDevMode ? [{ debugName: "firstDayOfWeek" }] : /* istanbul ignore next */ []));
267
+ hoveredDate = input(null, ...(ngDevMode ? [{ debugName: "hoveredDate" }] : /* istanbul ignore next */ []));
268
+ // Outputs
269
+ dateSelected = output();
270
+ dateHovered = output();
271
+ monthChanged = output();
272
+ // Internal state
273
+ currentView = signal('days', ...(ngDevMode ? [{ debugName: "currentView" }] : /* istanbul ignore next */ []));
274
+ viewDate = signal(normalizeDate(new Date()), ...(ngDevMode ? [{ debugName: "viewDate" }] : /* istanbul ignore next */ []));
275
+ activeDate = signal(normalizeDate(new Date()), ...(ngDevMode ? [{ debugName: "activeDate" }] : /* istanbul ignore next */ []));
276
+ // Computed: weekday header labels
277
+ weekDayHeaders = computed(() => getWeekdayLabels(this.firstDayOfWeek(), 'narrow'), ...(ngDevMode ? [{ debugName: "weekDayHeaders" }] : /* istanbul ignore next */ []));
278
+ // Computed: month/year labels for header
279
+ monthYearLabel = computed(() => {
280
+ const d = this.viewDate();
281
+ const formatter = new Intl.DateTimeFormat(undefined, { month: 'long', year: 'numeric' });
282
+ return formatter.format(d);
283
+ }, ...(ngDevMode ? [{ debugName: "monthYearLabel" }] : /* istanbul ignore next */ []));
284
+ yearLabel = computed(() => {
285
+ return String(this.viewDate().getFullYear());
286
+ }, ...(ngDevMode ? [{ debugName: "yearLabel" }] : /* istanbul ignore next */ []));
287
+ decadeLabel = computed(() => {
288
+ const year = this.viewDate().getFullYear();
289
+ const start = year - (year % 12);
290
+ return `${start} \u2013 ${start + 11}`;
291
+ }, ...(ngDevMode ? [{ debugName: "decadeLabel" }] : /* istanbul ignore next */ []));
292
+ // Accessible navigation button labels
293
+ prevButtonLabel = computed(() => {
294
+ switch (this.currentView()) {
295
+ case 'days':
296
+ return 'Previous month';
297
+ case 'months':
298
+ return 'Previous year';
299
+ case 'years':
300
+ return 'Previous decade';
301
+ }
302
+ }, ...(ngDevMode ? [{ debugName: "prevButtonLabel" }] : /* istanbul ignore next */ []));
303
+ nextButtonLabel = computed(() => {
304
+ switch (this.currentView()) {
305
+ case 'days':
306
+ return 'Next month';
307
+ case 'months':
308
+ return 'Next year';
309
+ case 'years':
310
+ return 'Next decade';
311
+ }
312
+ }, ...(ngDevMode ? [{ debugName: "nextButtonLabel" }] : /* istanbul ignore next */ []));
313
+ headerButtonLabel = computed(() => {
314
+ switch (this.currentView()) {
315
+ case 'days':
316
+ return 'Switch to month view';
317
+ case 'months':
318
+ return 'Switch to year view';
319
+ case 'years':
320
+ return this.decadeLabel();
321
+ }
322
+ }, ...(ngDevMode ? [{ debugName: "headerButtonLabel" }] : /* istanbul ignore next */ []));
323
+ // Computed: 6x7 calendar day grid
324
+ weeks = computed(() => {
325
+ const vd = this.viewDate();
326
+ const grid = buildCalendarGrid(vd.getFullYear(), vd.getMonth(), this.firstDayOfWeek());
327
+ const today = normalizeDate(new Date());
328
+ const sel = this.selected();
329
+ const rStart = this.rangeStart();
330
+ const rEnd = this.rangeEnd();
331
+ const hovered = this.hoveredDate();
332
+ const isRange = this.range();
333
+ const minDate = this.min();
334
+ const maxDate = this.max();
335
+ const filter = this.dateFilter();
336
+ return grid.map((week) => week.map((date) => {
337
+ const isCurrentMonth = date.getMonth() === vd.getMonth();
338
+ const isDisabled = !isDateInRange(date, minDate, maxDate) || (filter ? !filter(date) : false);
339
+ const isSelected = !isRange ? isSameDay(date, sel) : isSameDay(date, rStart) || isSameDay(date, rEnd);
340
+ const isRangeStart = isRange && isSameDay(date, rStart);
341
+ const isRangeEnd = isRange && rEnd ? isSameDay(date, rEnd) : false;
342
+ const isInRange = isRange && rStart && rEnd ? isBetween(date, rStart, rEnd) && !isRangeStart && !isRangeEnd : false;
343
+ const isRangePreview = isRange && rStart && !rEnd && hovered
344
+ ? isBetween(date, rStart, hovered) && !isSameDay(date, rStart) && !isSameDay(date, hovered)
345
+ : false;
346
+ return {
347
+ date,
348
+ day: date.getDate(),
349
+ isCurrentMonth,
350
+ isToday: isSameDay(date, today),
351
+ isSelected,
352
+ isDisabled,
353
+ isRangeStart,
354
+ isRangeEnd,
355
+ isInRange,
356
+ isRangePreview,
357
+ };
358
+ }));
359
+ }, ...(ngDevMode ? [{ debugName: "weeks" }] : /* istanbul ignore next */ []));
360
+ // Computed: 3x4 months grid
361
+ monthsGrid = computed(() => {
362
+ const vd = this.viewDate();
363
+ const currentYear = vd.getFullYear();
364
+ const now = new Date();
365
+ const sel = this.selected();
366
+ const rStart = this.rangeStart();
367
+ const minDate = this.min();
368
+ const maxDate = this.max();
369
+ const months = [];
370
+ for (let m = 0; m < 12; m++) {
371
+ const isDisabled = this._isMonthDisabled(currentYear, m, minDate, maxDate);
372
+ const isSelected = sel ? sel.getFullYear() === currentYear && sel.getMonth() === m : false;
373
+ months.push({
374
+ month: m,
375
+ label: getShortMonthName(m),
376
+ isCurrentMonth: now.getFullYear() === currentYear && now.getMonth() === m,
377
+ isSelected: isSelected || (rStart ? rStart.getFullYear() === currentYear && rStart.getMonth() === m : false),
378
+ isDisabled,
379
+ });
380
+ }
381
+ // Arrange in 4 rows of 3
382
+ const grid = [];
383
+ for (let i = 0; i < 12; i += 3) {
384
+ grid.push(months.slice(i, i + 3));
385
+ }
386
+ return grid;
387
+ }, ...(ngDevMode ? [{ debugName: "monthsGrid" }] : /* istanbul ignore next */ []));
388
+ // Computed: 3x4 years grid
389
+ yearsGrid = computed(() => {
390
+ const vd = this.viewDate();
391
+ const baseYear = vd.getFullYear();
392
+ const startYear = baseYear - (baseYear % 12);
393
+ const now = new Date();
394
+ const sel = this.selected();
395
+ const rStart = this.rangeStart();
396
+ const minDate = this.min();
397
+ const maxDate = this.max();
398
+ const years = [];
399
+ for (let i = 0; i < 12; i++) {
400
+ const year = startYear + i;
401
+ const isDisabled = this._isYearDisabled(year, minDate, maxDate);
402
+ const isSelected = sel ? sel.getFullYear() === year : false;
403
+ years.push({
404
+ year,
405
+ isCurrentYear: now.getFullYear() === year,
406
+ isSelected: isSelected || (rStart ? rStart.getFullYear() === year : false),
407
+ isDisabled,
408
+ });
409
+ }
410
+ const grid = [];
411
+ for (let i = 0; i < 12; i += 3) {
412
+ grid.push(years.slice(i, i + 3));
413
+ }
414
+ return grid;
415
+ }, ...(ngDevMode ? [{ debugName: "yearsGrid" }] : /* istanbul ignore next */ []));
416
+ _elementRef = inject(ElementRef);
417
+ constructor() {
418
+ // Initialize viewDate from startAt or selected or now
419
+ const initial = this.startAt() ?? this.selected() ?? new Date();
420
+ this.viewDate.set(normalizeDate(initial));
421
+ this.activeDate.set(normalizeDate(initial));
422
+ }
423
+ // --- Header actions ---
424
+ onHeaderLabelClick() {
425
+ const view = this.currentView();
426
+ if (view === 'days') {
427
+ this.currentView.set('months');
428
+ }
429
+ else if (view === 'months') {
430
+ this.currentView.set('years');
431
+ }
432
+ }
433
+ onPrev() {
434
+ const view = this.currentView();
435
+ const vd = this.viewDate();
436
+ if (view === 'days') {
437
+ this.viewDate.set(new Date(vd.getFullYear(), vd.getMonth() - 1, 1, 12));
438
+ }
439
+ else if (view === 'months') {
440
+ this.viewDate.set(new Date(vd.getFullYear() - 1, vd.getMonth(), 1, 12));
441
+ }
442
+ else {
443
+ this.viewDate.set(new Date(vd.getFullYear() - 12, vd.getMonth(), 1, 12));
444
+ }
445
+ this.monthChanged.emit(this.viewDate());
446
+ }
447
+ onNext() {
448
+ const view = this.currentView();
449
+ const vd = this.viewDate();
450
+ if (view === 'days') {
451
+ this.viewDate.set(new Date(vd.getFullYear(), vd.getMonth() + 1, 1, 12));
452
+ }
453
+ else if (view === 'months') {
454
+ this.viewDate.set(new Date(vd.getFullYear() + 1, vd.getMonth(), 1, 12));
455
+ }
456
+ else {
457
+ this.viewDate.set(new Date(vd.getFullYear() + 12, vd.getMonth(), 1, 12));
458
+ }
459
+ this.monthChanged.emit(this.viewDate());
460
+ }
461
+ // --- Day view actions ---
462
+ onDayClick(day) {
463
+ if (day.isDisabled)
464
+ return;
465
+ this.dateSelected.emit(day.date);
466
+ }
467
+ onDayMouseEnter(day) {
468
+ if (day.isDisabled)
469
+ return;
470
+ this.dateHovered.emit(day.date);
471
+ }
472
+ onDayMouseLeave() {
473
+ this.dateHovered.emit(null);
474
+ }
475
+ // --- Month/year view actions ---
476
+ onMonthClick(month) {
477
+ if (month.isDisabled)
478
+ return;
479
+ const vd = this.viewDate();
480
+ this.viewDate.set(new Date(vd.getFullYear(), month.month, 1, 12));
481
+ this.currentView.set('days');
482
+ }
483
+ onYearClick(year) {
484
+ if (year.isDisabled)
485
+ return;
486
+ const vd = this.viewDate();
487
+ this.viewDate.set(new Date(year.year, vd.getMonth(), 1, 12));
488
+ this.currentView.set('months');
489
+ }
490
+ // --- Keyboard navigation ---
491
+ onKeydown(event) {
492
+ const view = this.currentView();
493
+ if (view === 'days') {
494
+ this._handleDayKeydown(event);
495
+ }
496
+ else if (view === 'months') {
497
+ this._handleMonthKeydown(event);
498
+ }
499
+ else {
500
+ this._handleYearKeydown(event);
501
+ }
502
+ }
503
+ _handleDayKeydown(event) {
504
+ const active = this.activeDate();
505
+ let newDate = null;
506
+ switch (event.key) {
507
+ case 'ArrowLeft':
508
+ newDate = new Date(active.getFullYear(), active.getMonth(), active.getDate() - 1, 12);
509
+ break;
510
+ case 'ArrowRight':
511
+ newDate = new Date(active.getFullYear(), active.getMonth(), active.getDate() + 1, 12);
512
+ break;
513
+ case 'ArrowUp':
514
+ newDate = new Date(active.getFullYear(), active.getMonth(), active.getDate() - 7, 12);
515
+ break;
516
+ case 'ArrowDown':
517
+ newDate = new Date(active.getFullYear(), active.getMonth(), active.getDate() + 7, 12);
518
+ break;
519
+ case 'Home':
520
+ newDate = new Date(active.getFullYear(), active.getMonth(), 1, 12);
521
+ break;
522
+ case 'End':
523
+ newDate = new Date(active.getFullYear(), active.getMonth(), getDaysInMonth(active.getFullYear(), active.getMonth()), 12);
524
+ break;
525
+ case 'PageUp':
526
+ if (event.shiftKey) {
527
+ newDate = new Date(active.getFullYear() - 1, active.getMonth(), active.getDate(), 12);
528
+ }
529
+ else {
530
+ newDate = new Date(active.getFullYear(), active.getMonth() - 1, active.getDate(), 12);
531
+ }
532
+ break;
533
+ case 'PageDown':
534
+ if (event.shiftKey) {
535
+ newDate = new Date(active.getFullYear() + 1, active.getMonth(), active.getDate(), 12);
536
+ }
537
+ else {
538
+ newDate = new Date(active.getFullYear(), active.getMonth() + 1, active.getDate(), 12);
539
+ }
540
+ break;
541
+ case 'Enter':
542
+ case ' ':
543
+ event.preventDefault();
544
+ if (isDateInRange(active, this.min(), this.max())) {
545
+ const filter = this.dateFilter();
546
+ if (!filter || filter(active)) {
547
+ this.dateSelected.emit(active);
548
+ }
549
+ }
550
+ return;
551
+ case 'Escape':
552
+ // Let parent handle
553
+ return;
554
+ default:
555
+ return;
556
+ }
557
+ if (newDate) {
558
+ event.preventDefault();
559
+ this.activeDate.set(normalizeDate(newDate));
560
+ // If the new active date is in a different month, update viewDate
561
+ if (newDate.getMonth() !== this.viewDate().getMonth() ||
562
+ newDate.getFullYear() !== this.viewDate().getFullYear()) {
563
+ this.viewDate.set(new Date(newDate.getFullYear(), newDate.getMonth(), 1, 12));
564
+ this.monthChanged.emit(this.viewDate());
565
+ }
566
+ }
567
+ }
568
+ _handleMonthKeydown(event) {
569
+ // Simple arrow key handling for month grid (3 columns)
570
+ const vd = this.viewDate();
571
+ let month = vd.getMonth();
572
+ switch (event.key) {
573
+ case 'ArrowLeft':
574
+ month--;
575
+ break;
576
+ case 'ArrowRight':
577
+ month++;
578
+ break;
579
+ case 'ArrowUp':
580
+ month -= 3;
581
+ break;
582
+ case 'ArrowDown':
583
+ month += 3;
584
+ break;
585
+ case 'Enter':
586
+ case ' ':
587
+ event.preventDefault();
588
+ this.viewDate.set(new Date(vd.getFullYear(), month, 1, 12));
589
+ this.currentView.set('days');
590
+ return;
591
+ case 'Escape':
592
+ this.currentView.set('days');
593
+ return;
594
+ default:
595
+ return;
596
+ }
597
+ event.preventDefault();
598
+ if (month < 0) {
599
+ this.viewDate.set(new Date(vd.getFullYear() - 1, 11, 1, 12));
600
+ }
601
+ else if (month > 11) {
602
+ this.viewDate.set(new Date(vd.getFullYear() + 1, 0, 1, 12));
603
+ }
604
+ else {
605
+ this.viewDate.set(new Date(vd.getFullYear(), month, 1, 12));
606
+ }
607
+ }
608
+ _handleYearKeydown(event) {
609
+ const vd = this.viewDate();
610
+ let year = vd.getFullYear();
611
+ switch (event.key) {
612
+ case 'ArrowLeft':
613
+ year--;
614
+ break;
615
+ case 'ArrowRight':
616
+ year++;
617
+ break;
618
+ case 'ArrowUp':
619
+ year -= 3;
620
+ break;
621
+ case 'ArrowDown':
622
+ year += 3;
623
+ break;
624
+ case 'Enter':
625
+ case ' ':
626
+ event.preventDefault();
627
+ this.viewDate.set(new Date(year, vd.getMonth(), 1, 12));
628
+ this.currentView.set('months');
629
+ return;
630
+ case 'Escape':
631
+ this.currentView.set('months');
632
+ return;
633
+ default:
634
+ return;
635
+ }
636
+ event.preventDefault();
637
+ this.viewDate.set(new Date(year, vd.getMonth(), 1, 12));
638
+ }
639
+ // Track by functions
640
+ trackByWeek(_index, _week) {
641
+ return _index;
642
+ }
643
+ trackByDay(_index, day) {
644
+ return day.date.getTime();
645
+ }
646
+ trackByMonth(_index, month) {
647
+ return month.month;
648
+ }
649
+ trackByYear(_index, year) {
650
+ return year.year;
651
+ }
652
+ trackByRow(_index) {
653
+ return _index;
654
+ }
655
+ // Helper: is entire month disabled?
656
+ _isMonthDisabled(year, month, min, max) {
657
+ if (min) {
658
+ const lastOfMonth = new Date(year, month + 1, 0);
659
+ if (isBefore(lastOfMonth, min) && !isSameDay(lastOfMonth, min))
660
+ return true;
661
+ }
662
+ if (max) {
663
+ const firstOfMonth = new Date(year, month, 1);
664
+ if (isAfter(firstOfMonth, max) && !isSameDay(firstOfMonth, max))
665
+ return true;
666
+ }
667
+ return false;
668
+ }
669
+ // Helper: is entire year disabled?
670
+ _isYearDisabled(year, min, max) {
671
+ if (min && year < min.getFullYear())
672
+ return true;
673
+ if (max && year > max.getFullYear())
674
+ return true;
675
+ return false;
676
+ }
677
+ // Public: focus the calendar for keyboard nav
678
+ focus() {
679
+ this._elementRef.nativeElement.focus();
680
+ }
681
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiCalendarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
682
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: FuiCalendarComponent, isStandalone: true, selector: "fui-calendar", inputs: { selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, rangeStart: { classPropertyName: "rangeStart", publicName: "rangeStart", isSignal: true, isRequired: false, transformFunction: null }, rangeEnd: { classPropertyName: "rangeEnd", publicName: "rangeEnd", isSignal: true, isRequired: false, transformFunction: null }, range: { classPropertyName: "range", publicName: "range", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, dateFilter: { classPropertyName: "dateFilter", publicName: "dateFilter", isSignal: true, isRequired: false, transformFunction: null }, startAt: { classPropertyName: "startAt", publicName: "startAt", isSignal: true, isRequired: false, transformFunction: null }, firstDayOfWeek: { classPropertyName: "firstDayOfWeek", publicName: "firstDayOfWeek", isSignal: true, isRequired: false, transformFunction: null }, hoveredDate: { classPropertyName: "hoveredDate", publicName: "hoveredDate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dateSelected: "dateSelected", dateHovered: "dateHovered", monthChanged: "monthChanged" }, host: { attributes: { "tabindex": "0", "role": "application", "aria-label": "Calendar" }, classAttribute: "fui-calendar" }, ngImport: i0, template: "<!-- Calendar Header -->\n<div class=\"fui-calendar__header\">\n <button type=\"button\" class=\"fui-calendar__nav-btn\" (click)=\"onPrev()\" [attr.aria-label]=\"prevButtonLabel()\">\n <fui-icon name=\"caret-left\" size=\"sm\" />\n </button>\n\n <button\n type=\"button\"\n class=\"fui-calendar__header-label\"\n (click)=\"onHeaderLabelClick()\"\n [attr.aria-label]=\"headerButtonLabel()\"\n >\n @switch (currentView()) {\n @case ('days') {\n {{ monthYearLabel() }}\n }\n @case ('months') {\n {{ yearLabel() }}\n }\n @case ('years') {\n {{ decadeLabel() }}\n }\n }\n </button>\n\n <button type=\"button\" class=\"fui-calendar__nav-btn\" (click)=\"onNext()\" [attr.aria-label]=\"nextButtonLabel()\">\n <fui-icon name=\"caret-right\" size=\"sm\" />\n </button>\n</div>\n\n<!-- Day View -->\n@if (currentView() === 'days') {\n <table class=\"fui-calendar__grid\" role=\"grid\" [attr.aria-label]=\"monthYearLabel()\" (keydown)=\"onKeydown($event)\">\n <thead>\n <tr>\n @for (header of weekDayHeaders(); track header) {\n <th class=\"fui-calendar__weekday\" scope=\"col\">{{ header }}</th>\n }\n </tr>\n </thead>\n <tbody>\n @for (week of weeks(); track trackByWeek($index, week)) {\n <tr>\n @for (day of week; track trackByDay($index, day)) {\n <td\n class=\"fui-calendar__cell\"\n [class.--today]=\"day.isToday\"\n [class.--selected]=\"day.isSelected\"\n [class.--outside]=\"!day.isCurrentMonth\"\n [class.--disabled]=\"day.isDisabled\"\n [class.--range-start]=\"day.isRangeStart\"\n [class.--range-end]=\"day.isRangeEnd\"\n [class.--in-range]=\"day.isInRange\"\n [class.--range-preview]=\"day.isRangePreview\"\n role=\"gridcell\"\n [attr.aria-selected]=\"day.isSelected\"\n [attr.aria-disabled]=\"day.isDisabled\"\n [attr.aria-current]=\"day.isToday ? 'date' : null\"\n [attr.tabindex]=\"day.isCurrentMonth && !day.isDisabled ? 0 : -1\"\n (click)=\"onDayClick(day)\"\n (mouseenter)=\"onDayMouseEnter(day)\"\n (mouseleave)=\"onDayMouseLeave()\"\n >\n <span class=\"fui-calendar__day\">{{ day.day }}</span>\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n}\n\n<!-- Month View -->\n@if (currentView() === 'months') {\n <div class=\"fui-calendar__months\" role=\"grid\" [attr.aria-label]=\"yearLabel()\" (keydown)=\"onKeydown($event)\">\n @for (row of monthsGrid(); track trackByRow($index)) {\n <div class=\"fui-calendar__months-row\" role=\"row\">\n @for (month of row; track trackByMonth($index, month)) {\n <button\n type=\"button\"\n class=\"fui-calendar__month-cell\"\n [class.--current]=\"month.isCurrentMonth\"\n [class.--selected]=\"month.isSelected\"\n [class.--disabled]=\"month.isDisabled\"\n role=\"gridcell\"\n [attr.aria-selected]=\"month.isSelected\"\n [attr.aria-disabled]=\"month.isDisabled\"\n [disabled]=\"month.isDisabled\"\n (click)=\"onMonthClick(month)\"\n >\n {{ month.label }}\n </button>\n }\n </div>\n }\n </div>\n}\n\n<!-- Year View -->\n@if (currentView() === 'years') {\n <div class=\"fui-calendar__years\" role=\"grid\" [attr.aria-label]=\"decadeLabel()\" (keydown)=\"onKeydown($event)\">\n @for (row of yearsGrid(); track trackByRow($index)) {\n <div class=\"fui-calendar__years-row\" role=\"row\">\n @for (year of row; track trackByYear($index, year)) {\n <button\n type=\"button\"\n class=\"fui-calendar__year-cell\"\n [class.--current]=\"year.isCurrentYear\"\n [class.--selected]=\"year.isSelected\"\n [class.--disabled]=\"year.isDisabled\"\n role=\"gridcell\"\n [attr.aria-selected]=\"year.isSelected\"\n [attr.aria-disabled]=\"year.isDisabled\"\n [disabled]=\"year.isDisabled\"\n (click)=\"onYearClick(year)\"\n >\n {{ year.year }}\n </button>\n }\n </div>\n }\n </div>\n}\n\n<!-- Live region for screen readers -->\n<div class=\"fui-calendar__announcer\" aria-live=\"polite\" aria-atomic=\"true\">\n @switch (currentView()) {\n @case ('days') {\n {{ monthYearLabel() }}\n }\n @case ('months') {\n {{ yearLabel() }}\n }\n @case ('years') {\n {{ decadeLabel() }}\n }\n }\n</div>\n", styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition:opacity var(--fui-duration-fast-02) var(--fui-ease-entrance) 0ms}.fui-motion-fade-out{transition:opacity var(--fui-duration-fast-01) var(--fui-ease-exit) 0ms}.fui-motion-slide-in-bottom{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition:transform,opacity var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-moderate-01) var(--fui-ease-entrance)}.fui-calendar{display:block;width:280px;-webkit-user-select:none;user-select:none}.fui-calendar__header{display:flex;align-items:center;justify-content:space-between;padding:var(--fui-spacing-02) var(--fui-spacing-03)}.fui-calendar__nav-btn{background:none;border:none;padding:0;margin:0;font:inherit;color:inherit;cursor:pointer;outline:none}.fui-calendar__nav-btn:focus-visible{outline:2px solid var(--fui-primary);outline-offset:2px}.fui-calendar__nav-btn{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:var(--fui-border-radius-sm);color:var(--fui-text-secondary);transition:background-color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-calendar__nav-btn:hover{background-color:var(--fui-surface-02);color:var(--fui-text-primary)}.fui-calendar__nav-btn:focus-visible{outline:2px solid var(--fui-primary);outline-offset:0}.fui-calendar__header-label{background:none;border:none;padding:0;margin:0;font:inherit;color:inherit;cursor:pointer;outline:none}.fui-calendar__header-label:focus-visible{outline:2px solid var(--fui-primary);outline-offset:2px}.fui-calendar__header-label{font-size:var(--fui-font-size-02);font-weight:var(--fui-font-weight-semibold);color:var(--fui-text-primary);padding:var(--fui-spacing-01) var(--fui-spacing-02);border-radius:var(--fui-border-radius-sm);transition:background-color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-calendar__header-label:hover{background-color:var(--fui-surface-02)}.fui-calendar__header-label:focus-visible{outline:2px solid var(--fui-primary);outline-offset:0}.fui-calendar__grid{width:100%;border-collapse:collapse;border-spacing:0;table-layout:fixed;padding:0 var(--fui-spacing-02)}.fui-calendar__weekday{font-size:var(--fui-font-size-01);font-weight:var(--fui-font-weight-semibold);color:var(--fui-text-secondary);text-align:center;padding-bottom:var(--fui-spacing-01);height:2rem;vertical-align:middle}.fui-calendar__cell{text-align:center;padding:1px;cursor:pointer;position:relative;outline:none}.fui-calendar__cell:focus-visible .fui-calendar__day{outline:2px solid var(--fui-primary);outline-offset:0}.fui-calendar__cell.--disabled{cursor:not-allowed}.fui-calendar__cell.--disabled .fui-calendar__day{color:var(--fui-text-disabled);cursor:not-allowed}.fui-calendar__cell.--disabled .fui-calendar__day:hover{background-color:transparent}.fui-calendar__cell.--outside .fui-calendar__day{color:var(--fui-text-disabled);opacity:.4}.fui-calendar__cell.--today .fui-calendar__day{position:relative;font-weight:var(--fui-font-weight-semibold)}.fui-calendar__cell.--today .fui-calendar__day:after{content:\"\";position:absolute;bottom:2px;left:50%;transform:translate(-50%);width:4px;height:4px;border-radius:50%;background-color:var(--fui-primary)}.fui-calendar__cell.--selected .fui-calendar__day{background-color:var(--fui-primary);color:var(--fui-text-on-primary);font-weight:var(--fui-font-weight-semibold)}.fui-calendar__cell.--selected .fui-calendar__day:after{display:none}.fui-calendar__cell.--range-start .fui-calendar__day,.fui-calendar__cell.--range-end .fui-calendar__day{background-color:var(--fui-primary);color:var(--fui-text-on-primary);font-weight:var(--fui-font-weight-semibold)}.fui-calendar__cell.--range-start{background:linear-gradient(to right,transparent 50%,var(--fui-primary-10) 50%)}.fui-calendar__cell.--range-end{background:linear-gradient(to left,transparent 50%,var(--fui-primary-10) 50%)}.fui-calendar__cell.--range-start.--range-end{background:transparent}.fui-calendar__cell.--in-range{background-color:var(--fui-primary-10)}.fui-calendar__cell.--in-range .fui-calendar__day{color:var(--fui-primary);font-weight:var(--fui-font-weight-medium)}.fui-calendar__cell.--range-preview{background-color:var(--fui-primary-10);opacity:.5}.fui-calendar__cell.--range-preview .fui-calendar__day{color:var(--fui-primary)}.fui-calendar__day{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:50%;font-size:var(--fui-font-size-01);color:var(--fui-text-primary);margin:0 auto;transition:background-color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-calendar__day:hover{background-color:var(--fui-surface-02)}.fui-calendar__months,.fui-calendar__years{padding:var(--fui-spacing-03)}.fui-calendar__months-row,.fui-calendar__years-row{display:flex;justify-content:space-around;margin-bottom:var(--fui-spacing-02)}.fui-calendar__months-row:last-child,.fui-calendar__years-row:last-child{margin-bottom:0}.fui-calendar__month-cell,.fui-calendar__year-cell{background:none;border:none;padding:0;margin:0;font:inherit;color:inherit;cursor:pointer;outline:none}.fui-calendar__month-cell:focus-visible,.fui-calendar__year-cell:focus-visible{outline:2px solid var(--fui-primary);outline-offset:2px}.fui-calendar__month-cell,.fui-calendar__year-cell{display:flex;align-items:center;justify-content:center;width:5rem;height:2.5rem;border-radius:var(--fui-border-radius-md);font-size:var(--fui-font-size-01);color:var(--fui-text-primary);transition:background-color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-calendar__month-cell:hover:not(:disabled),.fui-calendar__year-cell:hover:not(:disabled){background-color:var(--fui-surface-02)}.fui-calendar__month-cell:focus-visible,.fui-calendar__year-cell:focus-visible{outline:2px solid var(--fui-primary);outline-offset:0}.fui-calendar__month-cell.--current,.fui-calendar__year-cell.--current{font-weight:var(--fui-font-weight-semibold);color:var(--fui-primary)}.fui-calendar__month-cell.--selected,.fui-calendar__year-cell.--selected{background-color:var(--fui-primary);color:var(--fui-text-on-primary);font-weight:var(--fui-font-weight-semibold)}.fui-calendar__month-cell.--disabled,.fui-calendar__year-cell.--disabled{color:var(--fui-text-disabled);cursor:not-allowed}.fui-calendar__announcer{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}@media(prefers-contrast:high){.fui-calendar__cell.--selected .fui-calendar__day,.fui-calendar__cell.--range-start .fui-calendar__day,.fui-calendar__cell.--range-end .fui-calendar__day{outline:2px solid}}@media(prefers-reduced-motion:reduce){.fui-calendar__day,.fui-calendar__nav-btn,.fui-calendar__header-label,.fui-calendar__month-cell,.fui-calendar__year-cell{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 });
683
+ }
684
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiCalendarComponent, decorators: [{
685
+ type: Component,
686
+ args: [{ selector: 'fui-calendar', standalone: true, imports: [FuiIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
687
+ class: 'fui-calendar',
688
+ tabindex: '0',
689
+ role: 'application',
690
+ 'aria-label': 'Calendar',
691
+ }, template: "<!-- Calendar Header -->\n<div class=\"fui-calendar__header\">\n <button type=\"button\" class=\"fui-calendar__nav-btn\" (click)=\"onPrev()\" [attr.aria-label]=\"prevButtonLabel()\">\n <fui-icon name=\"caret-left\" size=\"sm\" />\n </button>\n\n <button\n type=\"button\"\n class=\"fui-calendar__header-label\"\n (click)=\"onHeaderLabelClick()\"\n [attr.aria-label]=\"headerButtonLabel()\"\n >\n @switch (currentView()) {\n @case ('days') {\n {{ monthYearLabel() }}\n }\n @case ('months') {\n {{ yearLabel() }}\n }\n @case ('years') {\n {{ decadeLabel() }}\n }\n }\n </button>\n\n <button type=\"button\" class=\"fui-calendar__nav-btn\" (click)=\"onNext()\" [attr.aria-label]=\"nextButtonLabel()\">\n <fui-icon name=\"caret-right\" size=\"sm\" />\n </button>\n</div>\n\n<!-- Day View -->\n@if (currentView() === 'days') {\n <table class=\"fui-calendar__grid\" role=\"grid\" [attr.aria-label]=\"monthYearLabel()\" (keydown)=\"onKeydown($event)\">\n <thead>\n <tr>\n @for (header of weekDayHeaders(); track header) {\n <th class=\"fui-calendar__weekday\" scope=\"col\">{{ header }}</th>\n }\n </tr>\n </thead>\n <tbody>\n @for (week of weeks(); track trackByWeek($index, week)) {\n <tr>\n @for (day of week; track trackByDay($index, day)) {\n <td\n class=\"fui-calendar__cell\"\n [class.--today]=\"day.isToday\"\n [class.--selected]=\"day.isSelected\"\n [class.--outside]=\"!day.isCurrentMonth\"\n [class.--disabled]=\"day.isDisabled\"\n [class.--range-start]=\"day.isRangeStart\"\n [class.--range-end]=\"day.isRangeEnd\"\n [class.--in-range]=\"day.isInRange\"\n [class.--range-preview]=\"day.isRangePreview\"\n role=\"gridcell\"\n [attr.aria-selected]=\"day.isSelected\"\n [attr.aria-disabled]=\"day.isDisabled\"\n [attr.aria-current]=\"day.isToday ? 'date' : null\"\n [attr.tabindex]=\"day.isCurrentMonth && !day.isDisabled ? 0 : -1\"\n (click)=\"onDayClick(day)\"\n (mouseenter)=\"onDayMouseEnter(day)\"\n (mouseleave)=\"onDayMouseLeave()\"\n >\n <span class=\"fui-calendar__day\">{{ day.day }}</span>\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n}\n\n<!-- Month View -->\n@if (currentView() === 'months') {\n <div class=\"fui-calendar__months\" role=\"grid\" [attr.aria-label]=\"yearLabel()\" (keydown)=\"onKeydown($event)\">\n @for (row of monthsGrid(); track trackByRow($index)) {\n <div class=\"fui-calendar__months-row\" role=\"row\">\n @for (month of row; track trackByMonth($index, month)) {\n <button\n type=\"button\"\n class=\"fui-calendar__month-cell\"\n [class.--current]=\"month.isCurrentMonth\"\n [class.--selected]=\"month.isSelected\"\n [class.--disabled]=\"month.isDisabled\"\n role=\"gridcell\"\n [attr.aria-selected]=\"month.isSelected\"\n [attr.aria-disabled]=\"month.isDisabled\"\n [disabled]=\"month.isDisabled\"\n (click)=\"onMonthClick(month)\"\n >\n {{ month.label }}\n </button>\n }\n </div>\n }\n </div>\n}\n\n<!-- Year View -->\n@if (currentView() === 'years') {\n <div class=\"fui-calendar__years\" role=\"grid\" [attr.aria-label]=\"decadeLabel()\" (keydown)=\"onKeydown($event)\">\n @for (row of yearsGrid(); track trackByRow($index)) {\n <div class=\"fui-calendar__years-row\" role=\"row\">\n @for (year of row; track trackByYear($index, year)) {\n <button\n type=\"button\"\n class=\"fui-calendar__year-cell\"\n [class.--current]=\"year.isCurrentYear\"\n [class.--selected]=\"year.isSelected\"\n [class.--disabled]=\"year.isDisabled\"\n role=\"gridcell\"\n [attr.aria-selected]=\"year.isSelected\"\n [attr.aria-disabled]=\"year.isDisabled\"\n [disabled]=\"year.isDisabled\"\n (click)=\"onYearClick(year)\"\n >\n {{ year.year }}\n </button>\n }\n </div>\n }\n </div>\n}\n\n<!-- Live region for screen readers -->\n<div class=\"fui-calendar__announcer\" aria-live=\"polite\" aria-atomic=\"true\">\n @switch (currentView()) {\n @case ('days') {\n {{ monthYearLabel() }}\n }\n @case ('months') {\n {{ yearLabel() }}\n }\n @case ('years') {\n {{ decadeLabel() }}\n }\n }\n</div>\n", styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition:opacity var(--fui-duration-fast-02) var(--fui-ease-entrance) 0ms}.fui-motion-fade-out{transition:opacity var(--fui-duration-fast-01) var(--fui-ease-exit) 0ms}.fui-motion-slide-in-bottom{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition:transform,opacity var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-moderate-01) var(--fui-ease-entrance)}.fui-calendar{display:block;width:280px;-webkit-user-select:none;user-select:none}.fui-calendar__header{display:flex;align-items:center;justify-content:space-between;padding:var(--fui-spacing-02) var(--fui-spacing-03)}.fui-calendar__nav-btn{background:none;border:none;padding:0;margin:0;font:inherit;color:inherit;cursor:pointer;outline:none}.fui-calendar__nav-btn:focus-visible{outline:2px solid var(--fui-primary);outline-offset:2px}.fui-calendar__nav-btn{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:var(--fui-border-radius-sm);color:var(--fui-text-secondary);transition:background-color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-calendar__nav-btn:hover{background-color:var(--fui-surface-02);color:var(--fui-text-primary)}.fui-calendar__nav-btn:focus-visible{outline:2px solid var(--fui-primary);outline-offset:0}.fui-calendar__header-label{background:none;border:none;padding:0;margin:0;font:inherit;color:inherit;cursor:pointer;outline:none}.fui-calendar__header-label:focus-visible{outline:2px solid var(--fui-primary);outline-offset:2px}.fui-calendar__header-label{font-size:var(--fui-font-size-02);font-weight:var(--fui-font-weight-semibold);color:var(--fui-text-primary);padding:var(--fui-spacing-01) var(--fui-spacing-02);border-radius:var(--fui-border-radius-sm);transition:background-color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-calendar__header-label:hover{background-color:var(--fui-surface-02)}.fui-calendar__header-label:focus-visible{outline:2px solid var(--fui-primary);outline-offset:0}.fui-calendar__grid{width:100%;border-collapse:collapse;border-spacing:0;table-layout:fixed;padding:0 var(--fui-spacing-02)}.fui-calendar__weekday{font-size:var(--fui-font-size-01);font-weight:var(--fui-font-weight-semibold);color:var(--fui-text-secondary);text-align:center;padding-bottom:var(--fui-spacing-01);height:2rem;vertical-align:middle}.fui-calendar__cell{text-align:center;padding:1px;cursor:pointer;position:relative;outline:none}.fui-calendar__cell:focus-visible .fui-calendar__day{outline:2px solid var(--fui-primary);outline-offset:0}.fui-calendar__cell.--disabled{cursor:not-allowed}.fui-calendar__cell.--disabled .fui-calendar__day{color:var(--fui-text-disabled);cursor:not-allowed}.fui-calendar__cell.--disabled .fui-calendar__day:hover{background-color:transparent}.fui-calendar__cell.--outside .fui-calendar__day{color:var(--fui-text-disabled);opacity:.4}.fui-calendar__cell.--today .fui-calendar__day{position:relative;font-weight:var(--fui-font-weight-semibold)}.fui-calendar__cell.--today .fui-calendar__day:after{content:\"\";position:absolute;bottom:2px;left:50%;transform:translate(-50%);width:4px;height:4px;border-radius:50%;background-color:var(--fui-primary)}.fui-calendar__cell.--selected .fui-calendar__day{background-color:var(--fui-primary);color:var(--fui-text-on-primary);font-weight:var(--fui-font-weight-semibold)}.fui-calendar__cell.--selected .fui-calendar__day:after{display:none}.fui-calendar__cell.--range-start .fui-calendar__day,.fui-calendar__cell.--range-end .fui-calendar__day{background-color:var(--fui-primary);color:var(--fui-text-on-primary);font-weight:var(--fui-font-weight-semibold)}.fui-calendar__cell.--range-start{background:linear-gradient(to right,transparent 50%,var(--fui-primary-10) 50%)}.fui-calendar__cell.--range-end{background:linear-gradient(to left,transparent 50%,var(--fui-primary-10) 50%)}.fui-calendar__cell.--range-start.--range-end{background:transparent}.fui-calendar__cell.--in-range{background-color:var(--fui-primary-10)}.fui-calendar__cell.--in-range .fui-calendar__day{color:var(--fui-primary);font-weight:var(--fui-font-weight-medium)}.fui-calendar__cell.--range-preview{background-color:var(--fui-primary-10);opacity:.5}.fui-calendar__cell.--range-preview .fui-calendar__day{color:var(--fui-primary)}.fui-calendar__day{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:50%;font-size:var(--fui-font-size-01);color:var(--fui-text-primary);margin:0 auto;transition:background-color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-calendar__day:hover{background-color:var(--fui-surface-02)}.fui-calendar__months,.fui-calendar__years{padding:var(--fui-spacing-03)}.fui-calendar__months-row,.fui-calendar__years-row{display:flex;justify-content:space-around;margin-bottom:var(--fui-spacing-02)}.fui-calendar__months-row:last-child,.fui-calendar__years-row:last-child{margin-bottom:0}.fui-calendar__month-cell,.fui-calendar__year-cell{background:none;border:none;padding:0;margin:0;font:inherit;color:inherit;cursor:pointer;outline:none}.fui-calendar__month-cell:focus-visible,.fui-calendar__year-cell:focus-visible{outline:2px solid var(--fui-primary);outline-offset:2px}.fui-calendar__month-cell,.fui-calendar__year-cell{display:flex;align-items:center;justify-content:center;width:5rem;height:2.5rem;border-radius:var(--fui-border-radius-md);font-size:var(--fui-font-size-01);color:var(--fui-text-primary);transition:background-color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-calendar__month-cell:hover:not(:disabled),.fui-calendar__year-cell:hover:not(:disabled){background-color:var(--fui-surface-02)}.fui-calendar__month-cell:focus-visible,.fui-calendar__year-cell:focus-visible{outline:2px solid var(--fui-primary);outline-offset:0}.fui-calendar__month-cell.--current,.fui-calendar__year-cell.--current{font-weight:var(--fui-font-weight-semibold);color:var(--fui-primary)}.fui-calendar__month-cell.--selected,.fui-calendar__year-cell.--selected{background-color:var(--fui-primary);color:var(--fui-text-on-primary);font-weight:var(--fui-font-weight-semibold)}.fui-calendar__month-cell.--disabled,.fui-calendar__year-cell.--disabled{color:var(--fui-text-disabled);cursor:not-allowed}.fui-calendar__announcer{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}@media(prefers-contrast:high){.fui-calendar__cell.--selected .fui-calendar__day,.fui-calendar__cell.--range-start .fui-calendar__day,.fui-calendar__cell.--range-end .fui-calendar__day{outline:2px solid}}@media(prefers-reduced-motion:reduce){.fui-calendar__day,.fui-calendar__nav-btn,.fui-calendar__header-label,.fui-calendar__month-cell,.fui-calendar__year-cell{transition:none}}\n"] }]
692
+ }], ctorParameters: () => [], propDecorators: { selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], rangeStart: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangeStart", required: false }] }], rangeEnd: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangeEnd", required: false }] }], range: [{ type: i0.Input, args: [{ isSignal: true, alias: "range", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], dateFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFilter", required: false }] }], startAt: [{ type: i0.Input, args: [{ isSignal: true, alias: "startAt", required: false }] }], firstDayOfWeek: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstDayOfWeek", required: false }] }], hoveredDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "hoveredDate", required: false }] }], dateSelected: [{ type: i0.Output, args: ["dateSelected"] }], dateHovered: [{ type: i0.Output, args: ["dateHovered"] }], monthChanged: [{ type: i0.Output, args: ["monthChanged"] }] } });
693
+
694
+ const DEFAULT_DATE_FORMAT = 'dd/MM/yyyy';
695
+ const RANGE_SEPARATOR = ' \u2014 ';
696
+
697
+ /**
698
+ * @component FuiDatePickerComponent
699
+ * @selector fui-date-picker
700
+ * @description A date picker form control with inline text input, calendar overlay panel,
701
+ * and optional range selection. Integrates with `fui-form-field` via `FuiFormFieldControl`
702
+ * and supports Reactive Forms (ControlValueAccessor) plus built-in Validator for min/max/filter
703
+ * constraints. Uses the overlay service for proper dropdown positioning.
704
+ *
705
+ * @input placeholder - Placeholder text for the date input (alias: 'placeholder')
706
+ * @input disabled - Whether the picker is disabled (alias: 'disabled')
707
+ * @input readonly - Whether the picker is read-only
708
+ * @input range - Enables date-range mode with start/end inputs (default: false)
709
+ * @input format - Display format string (default: 'DD/MM/YYYY')
710
+ * @input min - Minimum selectable date (Date | null)
711
+ * @input max - Maximum selectable date (Date | null)
712
+ * @input dateFilter - Custom filter function to disable specific dates (DateFilterFn | null)
713
+ * @input startAt - Initial calendar view date when opened (Date | null)
714
+ * @input firstDayOfWeek - First day of the week: 0 = Sunday, 1 = Monday (default: 1)
715
+ * @input rangeSeparator - Separator string between range start/end display (default: RANGE_SEPARATOR)
716
+ * @input errorStateMatcher - Custom error state matcher (ErrorStateMatcher | null)
717
+ *
718
+ * @output valueChange - Emits the new model value on change (Date | DateRange | null)
719
+ * @output dateChange - Emits the new model value on date selection (Date | DateRange | null)
720
+ * @output openedChange - Emits boolean when the calendar panel opens or closes
721
+ *
722
+ * @cssvar --fui-date-picker-font-size - Font size of the input text
723
+ * @cssvar --fui-date-picker-panel-border-radius - Border radius of the calendar panel
724
+ * @cssvar --fui-date-picker-panel-shadow - Box shadow of the calendar panel
725
+ * @cssvar --fui-date-picker-panel-bg - Background color of the calendar panel
726
+ * @cssvar --fui-date-picker-panel-border-color - Border color of the calendar panel
727
+ * @cssvar --fui-date-picker-toggle-size - Size of the calendar toggle icon button
728
+ *
729
+ * @example
730
+ * <fui-form-field>
731
+ * <fui-label>Birth date</fui-label>
732
+ * <fui-date-picker formControlName="birthDate" placeholder="DD/MM/YYYY"></fui-date-picker>
733
+ * </fui-form-field>
734
+ *
735
+ * @example
736
+ * <fui-form-field>
737
+ * <fui-label>Stay period</fui-label>
738
+ * <fui-date-picker [range]="true" formControlName="period"></fui-date-picker>
739
+ * </fui-form-field>
740
+ */
741
+ class FuiDatePickerComponent {
742
+ static nextId = 0;
743
+ controlType = 'fui-date-picker';
744
+ // Inputs
745
+ placeholderInput = input('', { ...(ngDevMode ? { debugName: "placeholderInput" } : /* istanbul ignore next */ {}), alias: 'placeholder' });
746
+ disabledInput = input(false, { ...(ngDevMode ? { debugName: "disabledInput" } : /* istanbul ignore next */ {}), alias: 'disabled' });
747
+ readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
748
+ range = input(false, ...(ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []));
749
+ format = input(DEFAULT_DATE_FORMAT, ...(ngDevMode ? [{ debugName: "format" }] : /* istanbul ignore next */ []));
750
+ min = input(null, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
751
+ max = input(null, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
752
+ dateFilter = input(null, ...(ngDevMode ? [{ debugName: "dateFilter" }] : /* istanbul ignore next */ []));
753
+ startAt = input(null, ...(ngDevMode ? [{ debugName: "startAt" }] : /* istanbul ignore next */ []));
754
+ firstDayOfWeek = input(1, ...(ngDevMode ? [{ debugName: "firstDayOfWeek" }] : /* istanbul ignore next */ []));
755
+ rangeSeparator = input(RANGE_SEPARATOR, ...(ngDevMode ? [{ debugName: "rangeSeparator" }] : /* istanbul ignore next */ []));
756
+ errorStateMatcher = input(null, ...(ngDevMode ? [{ debugName: "errorStateMatcher" }] : /* istanbul ignore next */ []));
757
+ // Outputs
758
+ valueChange = output();
759
+ dateChange = output();
760
+ openedChange = output();
761
+ // Internal state
762
+ _value = signal(null, ...(ngDevMode ? [{ debugName: "_value" }] : /* istanbul ignore next */ []));
763
+ _focused = signal(false, ...(ngDevMode ? [{ debugName: "_focused" }] : /* istanbul ignore next */ []));
764
+ _disabled = signal(false, ...(ngDevMode ? [{ debugName: "_disabled" }] : /* istanbul ignore next */ []));
765
+ _ngControlDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_ngControlDisabled" }] : /* istanbul ignore next */ []));
766
+ _required = signal(false, ...(ngDevMode ? [{ debugName: "_required" }] : /* istanbul ignore next */ []));
767
+ _errorState = signal(false, ...(ngDevMode ? [{ debugName: "_errorState" }] : /* istanbul ignore next */ []));
768
+ _readOnly = signal(false, ...(ngDevMode ? [{ debugName: "_readOnly" }] : /* istanbul ignore next */ []));
769
+ panelOpen = signal(false, ...(ngDevMode ? [{ debugName: "panelOpen" }] : /* istanbul ignore next */ []));
770
+ // Range hover
771
+ hoveredDate = signal(null, ...(ngDevMode ? [{ debugName: "hoveredDate" }] : /* istanbul ignore next */ []));
772
+ // FuiFormFieldControl
773
+ stateChanges = new Subject();
774
+ _uid = `fui-date-picker-${FuiDatePickerComponent.nextId++}`;
775
+ _ariaDescribedby = null;
776
+ // Form references
777
+ _parentForm = inject(NgForm, { optional: true });
778
+ _parentFormGroup = inject(FormGroupDirective, { optional: true });
779
+ _defaultErrorStateMatcher = inject(DefaultErrorStateMatcher);
780
+ _ngControlRef = injectNgControl();
781
+ get ngControl() {
782
+ return this._ngControlRef.ngControl;
783
+ }
784
+ // Interface signals
785
+ placeholder = computed(() => this.placeholderInput(), ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
786
+ required = this._required;
787
+ value = this._value;
788
+ focused = this._focused;
789
+ disabled = computed(() => this._disabled() || this.disabledInput() || this._ngControlDisabled(), ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
790
+ errorState = this._errorState;
791
+ id = this._uid;
792
+ empty = computed(() => {
793
+ const val = this._value();
794
+ if (val === null)
795
+ return true;
796
+ if (this.range() && this._isDateRange(val)) {
797
+ return val.start === null && val.end === null;
798
+ }
799
+ return false;
800
+ }, ...(ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []));
801
+ // Computed: selected date for single mode calendar input
802
+ selectedDate = computed(() => {
803
+ const val = this._value();
804
+ if (val instanceof Date)
805
+ return val;
806
+ return null;
807
+ }, ...(ngDevMode ? [{ debugName: "selectedDate" }] : /* istanbul ignore next */ []));
808
+ rangeStartDate = computed(() => {
809
+ const val = this._value();
810
+ if (this._isDateRange(val))
811
+ return val.start;
812
+ return null;
813
+ }, ...(ngDevMode ? [{ debugName: "rangeStartDate" }] : /* istanbul ignore next */ []));
814
+ rangeEndDate = computed(() => {
815
+ const val = this._value();
816
+ if (this._isDateRange(val))
817
+ return val.end;
818
+ return null;
819
+ }, ...(ngDevMode ? [{ debugName: "rangeEndDate" }] : /* istanbul ignore next */ []));
820
+ // ViewChildren
821
+ triggerEl;
822
+ calendarPanel;
823
+ startInputEl;
824
+ endInputEl;
825
+ calendarComponent;
826
+ // Overlay
827
+ _overlayRef = null;
828
+ _overlaySubscriptions = new Subscription();
829
+ _overlayService = inject(FuiOverlayService);
830
+ _elementRef = inject(ElementRef);
831
+ _document = inject(DOCUMENT);
832
+ _ngZone = inject(NgZone);
833
+ _outsideClickSub;
834
+ // CVA callbacks
835
+ _onChange = () => {
836
+ /* noop */
837
+ };
838
+ _onTouched = () => {
839
+ /* noop */
840
+ };
841
+ _onValidatorChange = () => {
842
+ /* noop */
843
+ };
844
+ // Input text tracking
845
+ startInputText = signal('', ...(ngDevMode ? [{ debugName: "startInputText" }] : /* istanbul ignore next */ []));
846
+ endInputText = signal('', ...(ngDevMode ? [{ debugName: "endInputText" }] : /* istanbul ignore next */ []));
847
+ // Display value for form-field readOnly mode (duck-typed like select/autocomplete)
848
+ displayValue = computed(() => {
849
+ if (this.range()) {
850
+ const start = this.startInputText();
851
+ const end = this.endInputText();
852
+ if (!start && !end)
853
+ return '';
854
+ return `${start || '—'} ${this.rangeSeparator()} ${end || '—'}`;
855
+ }
856
+ return this.startInputText() || '';
857
+ }, ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
858
+ constructor() {
859
+ void Promise.resolve().then(() => {
860
+ if (this._ngControlRef.ngControl) {
861
+ this._ngControlRef.ngControl.valueAccessor = this;
862
+ }
863
+ });
864
+ // Effect to emit state changes
865
+ effect(() => {
866
+ this.placeholderInput();
867
+ this.readonly();
868
+ this.disabledInput();
869
+ this.range();
870
+ this.format();
871
+ this.errorStateMatcher();
872
+ this._focused();
873
+ this._disabled();
874
+ this._value();
875
+ this._ngControlDisabled();
876
+ this._required();
877
+ this._errorState();
878
+ this.stateChanges.next();
879
+ });
880
+ // Re-trigger validation when min/max/dateFilter change
881
+ effect(() => {
882
+ this.min();
883
+ this.max();
884
+ this.dateFilter();
885
+ this._onValidatorChange();
886
+ });
887
+ // Sync display text when value changes
888
+ effect(() => {
889
+ const val = this._value();
890
+ const fmt = this.format();
891
+ if (this.range() && this._isDateRange(val)) {
892
+ this.startInputText.set(formatDate(val.start, fmt));
893
+ this.endInputText.set(formatDate(val.end, fmt));
894
+ }
895
+ else if (val instanceof Date) {
896
+ this.startInputText.set(formatDate(val, fmt));
897
+ }
898
+ else {
899
+ this.startInputText.set('');
900
+ this.endInputText.set('');
901
+ }
902
+ });
903
+ }
904
+ ngDoCheck() {
905
+ if (this.ngControl) {
906
+ updateErrorState(this.ngControl, this._errorState, this.errorStateMatcher(), this._defaultErrorStateMatcher, this._parentForm, this._parentFormGroup, this.stateChanges);
907
+ syncRequiredState(this.ngControl, this._required, this.stateChanges);
908
+ syncNgControlDisabled(this.ngControl, this._ngControlDisabled, this.stateChanges);
909
+ }
910
+ }
911
+ ngOnDestroy() {
912
+ this.stateChanges.complete();
913
+ this._outsideClickSub?.unsubscribe();
914
+ this._disposeOverlay();
915
+ }
916
+ // --- CVA ---
917
+ writeValue(value) {
918
+ if (typeof value === 'string') {
919
+ const parsed = parseDate(value, this.format());
920
+ this._value.set(parsed);
921
+ }
922
+ else if (value instanceof Date) {
923
+ this._value.set(normalizeDate(value));
924
+ }
925
+ else if (value && typeof value === 'object' && ('start' in value || 'end' in value)) {
926
+ const range = value;
927
+ this._value.set({
928
+ start: range.start ? normalizeDate(range.start) : null,
929
+ end: range.end ? normalizeDate(range.end) : null,
930
+ });
931
+ }
932
+ else {
933
+ this._value.set(null);
934
+ }
935
+ this.stateChanges.next();
936
+ }
937
+ registerOnChange(fn) {
938
+ this._onChange = fn;
939
+ }
940
+ registerOnTouched(fn) {
941
+ this._onTouched = fn;
942
+ }
943
+ setDisabledState(isDisabled) {
944
+ this._disabled.set(isDisabled);
945
+ this.stateChanges.next();
946
+ }
947
+ // --- Validator ---
948
+ validate(_control) {
949
+ const value = this._value();
950
+ if (value === null)
951
+ return null;
952
+ const minDate = this.min();
953
+ const maxDate = this.max();
954
+ const filter = this.dateFilter();
955
+ if (value instanceof Date) {
956
+ return this._validateSingleDate(value, minDate, maxDate, filter);
957
+ }
958
+ if (this._isDateRange(value)) {
959
+ const errors = {};
960
+ if (value.start) {
961
+ const startErrors = this._validateSingleDate(value.start, minDate, maxDate, filter);
962
+ if (startErrors)
963
+ Object.assign(errors, startErrors);
964
+ }
965
+ if (value.end) {
966
+ const endErrors = this._validateSingleDate(value.end, minDate, maxDate, filter);
967
+ if (endErrors)
968
+ Object.assign(errors, endErrors);
969
+ }
970
+ return Object.keys(errors).length > 0 ? errors : null;
971
+ }
972
+ return null;
973
+ }
974
+ registerOnValidatorChange(fn) {
975
+ this._onValidatorChange = fn;
976
+ }
977
+ _validateSingleDate(date, min, max, filter) {
978
+ if (min && isBefore(date, min)) {
979
+ return { dateMin: { min, actual: date } };
980
+ }
981
+ if (max && isAfter(date, max)) {
982
+ return { dateMax: { max, actual: date } };
983
+ }
984
+ if (filter && !filter(date)) {
985
+ return { dateFilter: { actual: date } };
986
+ }
987
+ return null;
988
+ }
989
+ // --- FuiFormFieldControl ---
990
+ onContainerClick(_event) {
991
+ if (!this.disabled()) {
992
+ this.startInputEl?.nativeElement.focus();
993
+ if (!this.panelOpen()) {
994
+ this.open();
995
+ }
996
+ }
997
+ }
998
+ setDescribedByIds(ids) {
999
+ this._ariaDescribedby = ids.length ? ids.join(' ') : null;
1000
+ }
1001
+ setReadOnly(readOnly) {
1002
+ this._readOnly.set(readOnly);
1003
+ }
1004
+ // --- Panel open/close ---
1005
+ open() {
1006
+ if (this.disabled() || this.readonly() || this.panelOpen())
1007
+ return;
1008
+ requestAnimationFrame(() => {
1009
+ this.panelOpen.set(true);
1010
+ this._focused.set(true);
1011
+ this.openedChange.emit(true);
1012
+ setTimeout(() => {
1013
+ this._createOverlay();
1014
+ this._listenForOutsideClicks();
1015
+ this.calendarComponent?.focus();
1016
+ });
1017
+ });
1018
+ }
1019
+ close() {
1020
+ if (!this.panelOpen())
1021
+ return;
1022
+ this._outsideClickSub?.unsubscribe();
1023
+ this.panelOpen.set(false);
1024
+ this._focused.set(false);
1025
+ this.hoveredDate.set(null);
1026
+ this._disposeOverlay();
1027
+ this.openedChange.emit(false);
1028
+ this._onTouched();
1029
+ setTimeout(() => {
1030
+ this.startInputEl?.nativeElement.focus();
1031
+ }, 0);
1032
+ }
1033
+ toggle() {
1034
+ if (this.panelOpen()) {
1035
+ this.close();
1036
+ }
1037
+ else {
1038
+ this.open();
1039
+ }
1040
+ }
1041
+ // --- Trigger click ---
1042
+ onTriggerClick() {
1043
+ if (!this.disabled() && !this.readonly()) {
1044
+ this.toggle();
1045
+ }
1046
+ }
1047
+ onToggleClick(event) {
1048
+ event.stopPropagation();
1049
+ if (!this.disabled() && !this.readonly()) {
1050
+ this.toggle();
1051
+ }
1052
+ }
1053
+ // --- Input masking (start input) ---
1054
+ onInput(event) {
1055
+ const input = event.target;
1056
+ const { masked, cursor } = applyDateMask(input.value, this.format());
1057
+ input.value = masked;
1058
+ input.setSelectionRange(cursor, cursor);
1059
+ this.startInputText.set(masked);
1060
+ }
1061
+ onBlur() {
1062
+ const text = this.startInputText();
1063
+ if (!text) {
1064
+ if (!this.range()) {
1065
+ this._setValue(null);
1066
+ }
1067
+ return;
1068
+ }
1069
+ const parsed = parseDate(text, this.format());
1070
+ if (parsed) {
1071
+ if (this.range()) {
1072
+ const current = this._value();
1073
+ const rangeVal = this._isDateRange(current) ? current : { start: null, end: null };
1074
+ this._setValue({ ...rangeVal, start: parsed });
1075
+ }
1076
+ else {
1077
+ this._setValue(parsed);
1078
+ }
1079
+ }
1080
+ else {
1081
+ // Invalid input — revert to formatted value
1082
+ const val = this._value();
1083
+ if (this.range() && this._isDateRange(val)) {
1084
+ this.startInputText.set(formatDate(val.start, this.format()));
1085
+ }
1086
+ else if (val instanceof Date) {
1087
+ this.startInputText.set(formatDate(val, this.format()));
1088
+ }
1089
+ else {
1090
+ this.startInputText.set('');
1091
+ }
1092
+ }
1093
+ if (!this.panelOpen()) {
1094
+ this._onTouched();
1095
+ }
1096
+ }
1097
+ onKeydown(event) {
1098
+ if (event.key === 'Escape' && this.panelOpen()) {
1099
+ event.preventDefault();
1100
+ this.close();
1101
+ }
1102
+ else if ((event.key === 'ArrowDown' || event.key === 'Enter') && !this.panelOpen()) {
1103
+ event.preventDefault();
1104
+ this.open();
1105
+ }
1106
+ }
1107
+ // --- End input (range mode) ---
1108
+ onEndInput(event) {
1109
+ const input = event.target;
1110
+ const { masked, cursor } = applyDateMask(input.value, this.format());
1111
+ input.value = masked;
1112
+ input.setSelectionRange(cursor, cursor);
1113
+ this.endInputText.set(masked);
1114
+ }
1115
+ onEndBlur() {
1116
+ const text = this.endInputText();
1117
+ if (!text)
1118
+ return;
1119
+ const parsed = parseDate(text, this.format());
1120
+ if (parsed) {
1121
+ const current = this._value();
1122
+ const rangeVal = this._isDateRange(current) ? current : { start: null, end: null };
1123
+ this._setValue({ ...rangeVal, end: parsed });
1124
+ }
1125
+ else {
1126
+ const val = this._value();
1127
+ if (this._isDateRange(val)) {
1128
+ this.endInputText.set(formatDate(val.end, this.format()));
1129
+ }
1130
+ else {
1131
+ this.endInputText.set('');
1132
+ }
1133
+ }
1134
+ if (!this.panelOpen()) {
1135
+ this._onTouched();
1136
+ }
1137
+ }
1138
+ onEndKeydown(event) {
1139
+ if (event.key === 'Escape' && this.panelOpen()) {
1140
+ event.preventDefault();
1141
+ this.close();
1142
+ }
1143
+ }
1144
+ onInputFocus() {
1145
+ this._focused.set(true);
1146
+ }
1147
+ // --- Calendar events ---
1148
+ onDateSelected(date) {
1149
+ if (this.range()) {
1150
+ this._handleRangeSelection(date);
1151
+ }
1152
+ else {
1153
+ this._setValue(date);
1154
+ this.close();
1155
+ }
1156
+ }
1157
+ onDateHovered(date) {
1158
+ this.hoveredDate.set(date);
1159
+ }
1160
+ // Start listening for outside clicks when panel opens
1161
+ _listenForOutsideClicks() {
1162
+ this._outsideClickSub?.unsubscribe();
1163
+ this._ngZone.runOutsideAngular(() => {
1164
+ setTimeout(() => {
1165
+ this._outsideClickSub = fromEvent(this._document, 'click')
1166
+ .pipe(filter(() => this.panelOpen()), filter((event) => {
1167
+ const target = event.target;
1168
+ const hostElement = this._elementRef.nativeElement;
1169
+ const overlayElement = this._overlayRef?.overlayElement;
1170
+ return !hostElement.contains(target) && !overlayElement?.contains(target);
1171
+ }))
1172
+ .subscribe(() => {
1173
+ this._ngZone.run(() => {
1174
+ this.close();
1175
+ });
1176
+ });
1177
+ });
1178
+ });
1179
+ }
1180
+ // --- Private helpers ---
1181
+ _handleRangeSelection(date) {
1182
+ const current = this._value();
1183
+ const rangeVal = this._isDateRange(current) ? current : { start: null, end: null };
1184
+ if (!rangeVal.start || rangeVal.end) {
1185
+ // First click or resetting
1186
+ this._setValue({ start: date, end: null });
1187
+ this.hoveredDate.set(null);
1188
+ }
1189
+ else {
1190
+ // Second click — auto-swap if needed
1191
+ let start = rangeVal.start;
1192
+ let end = date;
1193
+ if (normalizeDate(end).getTime() < normalizeDate(start).getTime()) {
1194
+ [start, end] = [end, start];
1195
+ }
1196
+ this._setValue({ start, end });
1197
+ this.close();
1198
+ }
1199
+ }
1200
+ _setValue(value) {
1201
+ this._value.set(value);
1202
+ this._onChange(value);
1203
+ this.valueChange.emit(value);
1204
+ this.dateChange.emit(value);
1205
+ this.stateChanges.next();
1206
+ }
1207
+ _createOverlay() {
1208
+ if (this._overlayRef || !this.calendarPanel || !this.triggerEl)
1209
+ return;
1210
+ const triggerElement = this.triggerEl.nativeElement;
1211
+ const triggerWidth = triggerElement.getBoundingClientRect().width;
1212
+ const positions = [
1213
+ { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4 },
1214
+ { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -4 },
1215
+ ];
1216
+ const positionStrategy = this._overlayService
1217
+ .position()
1218
+ .connectedTo(triggerElement, positions)
1219
+ .withPush(true)
1220
+ .withViewportMargin(8);
1221
+ this._overlayRef = this._overlayService.create({
1222
+ positionStrategy,
1223
+ scrollStrategy: this._overlayService.scrollStrategies.reposition(),
1224
+ hasBackdrop: true,
1225
+ backdropClass: 'fui-date-picker-backdrop',
1226
+ backdropClickBehavior: 'close',
1227
+ panelClass: ['fui-date-picker-overlay-panel'],
1228
+ minWidth: Math.max(triggerWidth, 280),
1229
+ });
1230
+ // Track overlay subscriptions for proper cleanup
1231
+ this._overlaySubscriptions.unsubscribe();
1232
+ this._overlaySubscriptions = new Subscription();
1233
+ this._overlaySubscriptions.add(this._overlayRef.backdropClick.subscribe(() => {
1234
+ this.close();
1235
+ }));
1236
+ this._overlaySubscriptions.add(this._overlayRef.keydownEvents.subscribe((event) => {
1237
+ if (event.key === 'Escape') {
1238
+ this.close();
1239
+ }
1240
+ }));
1241
+ const panelElement = this.calendarPanel.nativeElement;
1242
+ this._overlayRef.attach(panelElement);
1243
+ }
1244
+ _disposeOverlay() {
1245
+ this._overlaySubscriptions.unsubscribe();
1246
+ if (this._overlayRef) {
1247
+ this._overlayRef.dispose();
1248
+ this._overlayRef = null;
1249
+ }
1250
+ }
1251
+ _isDateRange(val) {
1252
+ return val !== null && !(val instanceof Date) && typeof val === 'object' && 'start' in val;
1253
+ }
1254
+ // Public: get the computed placeholder for the start input
1255
+ getStartPlaceholder() {
1256
+ const p = this.placeholderInput();
1257
+ if (p)
1258
+ return p;
1259
+ return this.format().toLowerCase();
1260
+ }
1261
+ getEndPlaceholder() {
1262
+ return this.format().toLowerCase();
1263
+ }
1264
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiDatePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1265
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: FuiDatePickerComponent, isStandalone: true, selector: "fui-date-picker", inputs: { placeholderInput: { classPropertyName: "placeholderInput", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabledInput: { classPropertyName: "disabledInput", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, range: { classPropertyName: "range", publicName: "range", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, dateFilter: { classPropertyName: "dateFilter", publicName: "dateFilter", isSignal: true, isRequired: false, transformFunction: null }, startAt: { classPropertyName: "startAt", publicName: "startAt", isSignal: true, isRequired: false, transformFunction: null }, firstDayOfWeek: { classPropertyName: "firstDayOfWeek", publicName: "firstDayOfWeek", isSignal: true, isRequired: false, transformFunction: null }, rangeSeparator: { classPropertyName: "rangeSeparator", publicName: "rangeSeparator", isSignal: true, isRequired: false, transformFunction: null }, errorStateMatcher: { classPropertyName: "errorStateMatcher", publicName: "errorStateMatcher", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", dateChange: "dateChange", openedChange: "openedChange" }, host: { properties: { "attr.id": "id", "class.fui-date-picker--open": "panelOpen()", "class.fui-date-picker--disabled": "disabled()", "class.fui-date-picker--range": "range()", "class.fui-date-picker--focused": "focused()", "class.fui-date-picker--error": "errorState()", "class.fui-date-picker--readonly": "_readOnly()" }, classAttribute: "fui-date-picker" }, providers: [
1266
+ {
1267
+ provide: NG_VALUE_ACCESSOR,
1268
+ useExisting: FuiDatePickerComponent,
1269
+ multi: true,
1270
+ },
1271
+ {
1272
+ provide: NG_VALIDATORS,
1273
+ useExisting: FuiDatePickerComponent,
1274
+ multi: true,
1275
+ },
1276
+ {
1277
+ provide: FUI_FORM_FIELD_CONTROL,
1278
+ useExisting: FuiDatePickerComponent,
1279
+ },
1280
+ ], viewQueries: [{ propertyName: "triggerEl", first: true, predicate: ["triggerEl"], descendants: true }, { propertyName: "calendarPanel", first: true, predicate: ["calendarPanel"], descendants: true }, { propertyName: "startInputEl", first: true, predicate: ["startInput"], descendants: true }, { propertyName: "endInputEl", first: true, predicate: ["endInput"], descendants: true }, { propertyName: "calendarComponent", first: true, predicate: ["calendar"], descendants: true }], ngImport: i0, template: "<!-- Trigger area -->\n<div #triggerEl class=\"fui-date-picker__trigger\" (click)=\"onTriggerClick()\">\n <!-- Start input -->\n <input\n #startInput\n class=\"fui-date-picker__input\"\n [id]=\"id\"\n [value]=\"startInputText()\"\n [placeholder]=\"getStartPlaceholder()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [attr.aria-describedby]=\"_ariaDescribedby\"\n [attr.aria-required]=\"required()\"\n [attr.aria-invalid]=\"errorState()\"\n [attr.aria-expanded]=\"panelOpen()\"\n aria-haspopup=\"dialog\"\n aria-autocomplete=\"none\"\n role=\"combobox\"\n inputmode=\"numeric\"\n autocomplete=\"off\"\n (input)=\"onInput($event)\"\n (blur)=\"onBlur()\"\n (focus)=\"onInputFocus()\"\n (keydown)=\"onKeydown($event)\"\n (click)=\"$event.stopPropagation()\"\n />\n\n <!-- Range separator + end input -->\n @if (range()) {\n <span class=\"fui-date-picker__range-separator\" aria-hidden=\"true\">{{ rangeSeparator() }}</span>\n <input\n #endInput\n class=\"fui-date-picker__input fui-date-picker__input--end\"\n [value]=\"endInputText()\"\n [placeholder]=\"getEndPlaceholder()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [attr.aria-label]=\"'End date'\"\n [attr.aria-invalid]=\"errorState()\"\n inputmode=\"numeric\"\n autocomplete=\"off\"\n (input)=\"onEndInput($event)\"\n (blur)=\"onEndBlur()\"\n (focus)=\"onInputFocus()\"\n (keydown)=\"onEndKeydown($event)\"\n (click)=\"$event.stopPropagation()\"\n />\n }\n\n <!-- Calendar toggle button \u2014 hidden in readOnly mode -->\n @if (!_readOnly()) {\n <button\n type=\"button\"\n class=\"fui-date-picker__toggle\"\n tabindex=\"-1\"\n [attr.aria-expanded]=\"panelOpen()\"\n [disabled]=\"disabled()\"\n aria-haspopup=\"dialog\"\n aria-label=\"Open calendar\"\n (click)=\"onToggleClick($event)\"\n >\n <fui-icon name=\"calendar-blank\" size=\"sm\" />\n </button>\n }\n</div>\n\n<!-- Calendar panel -->\n@if (panelOpen()) {\n <div #calendarPanel class=\"fui-date-picker__panel\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Calendar\">\n <fui-calendar\n #calendar\n [selected]=\"selectedDate()\"\n [rangeStart]=\"rangeStartDate()\"\n [rangeEnd]=\"rangeEndDate()\"\n [range]=\"range()\"\n [min]=\"min()\"\n [max]=\"max()\"\n [dateFilter]=\"dateFilter()\"\n [startAt]=\"startAt()\"\n [firstDayOfWeek]=\"firstDayOfWeek()\"\n [hoveredDate]=\"hoveredDate()\"\n (dateSelected)=\"onDateSelected($event)\"\n (dateHovered)=\"onDateHovered($event)\"\n />\n </div>\n}\n", styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition:opacity var(--fui-duration-fast-02) var(--fui-ease-entrance) 0ms}.fui-motion-fade-out{transition:opacity var(--fui-duration-fast-01) var(--fui-ease-exit) 0ms}.fui-motion-slide-in-bottom{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition:transform,opacity var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-moderate-01) var(--fui-ease-entrance)}.fui-date-picker{--fui-date-picker-font-size: var(--fui-font-size-02);--fui-date-picker-panel-border-radius: var(--fui-border-radius-md);--fui-date-picker-panel-shadow: var(--fui-shadow-03);--fui-date-picker-panel-bg: var(--fui-surface-00);--fui-date-picker-panel-border-color: var(--fui-border-color);--fui-date-picker-toggle-size: 1.5rem;display:inline-block;width:100%;position:relative}.fui-date-picker__trigger{width:100%;min-height:100%;display:flex;align-items:center;gap:var(--fui-spacing-01);cursor:pointer;background:transparent;border:none}.fui-date-picker__input{flex:1;min-width:0;border:none;outline:none;background:transparent;font-family:var(--fui-font-family-sans);font-size:var(--fui-font-size-02);font-weight:var(--fui-font-weight-regular);line-height:var(--fui-line-height-02);letter-spacing:var(--fui-letter-spacing-normal);color:var(--fui-text-primary);padding:0;cursor:text}.fui-date-picker__input::placeholder{color:var(--fui-text-disabled)}.fui-date-picker__input:disabled{cursor:not-allowed;opacity:.5}.fui-date-picker__range-separator{flex-shrink:0;color:var(--fui-text-secondary);font-size:var(--fui-font-size-02);-webkit-user-select:none;user-select:none}.fui-date-picker__toggle{background:none;border:none;padding:0;margin:0;font:inherit;color:inherit;cursor:pointer;outline:none}.fui-date-picker__toggle:focus-visible{outline:2px solid var(--fui-primary);outline-offset:2px}.fui-date-picker__toggle{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:1.5rem;height:1.5rem;color:var(--fui-text-secondary);border-radius:var(--fui-border-radius-sm);transition:color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-date-picker__toggle:hover:not(:disabled){color:var(--fui-text-primary)}.fui-date-picker__toggle:disabled{color:var(--fui-text-disabled);cursor:not-allowed}.fui-date-picker__panel{background:var(--fui-date-picker-panel-bg);border:1px solid var(--fui-date-picker-panel-border-color);border-radius:var(--fui-date-picker-panel-border-radius);box-shadow:var(--fui-date-picker-panel-shadow);padding:var(--fui-spacing-02);transition:opacity,transform var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-date-picker--disabled{pointer-events:none}.fui-date-picker--disabled .fui-date-picker__trigger{cursor:not-allowed;opacity:.5}.fui-date-picker--readonly{pointer-events:none}.fui-date-picker--readonly .fui-date-picker__trigger{cursor:default;opacity:1}.fui-date-picker--readonly .fui-date-picker__input{color:var(--fui-text-primary)!important;-webkit-text-fill-color:var(--fui-text-primary)!important;cursor:default;opacity:1}.fui-date-picker--readonly .fui-date-picker__input:disabled{opacity:1;cursor:default}.fui-date-picker-backdrop{background:transparent}.fui-date-picker-overlay-panel{z-index:var(--fui-z-popover, 1060)}@media(prefers-contrast:high){.fui-date-picker__panel{border-width:2px}}@media(prefers-reduced-motion:reduce){.fui-date-picker__panel{transition:none}}\n"], dependencies: [{ kind: "component", type: FuiIconComponent, selector: "fui-icon", inputs: ["name", "size", "weight", "color", "ariaLabel", "spin", "pulse"] }, { kind: "component", type: FuiCalendarComponent, selector: "fui-calendar", inputs: ["selected", "rangeStart", "rangeEnd", "range", "min", "max", "dateFilter", "startAt", "firstDayOfWeek", "hoveredDate"], outputs: ["dateSelected", "dateHovered", "monthChanged"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1281
+ }
1282
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiDatePickerComponent, decorators: [{
1283
+ type: Component,
1284
+ args: [{ selector: 'fui-date-picker', standalone: true, imports: [FuiIconComponent, FuiCalendarComponent], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
1285
+ class: 'fui-date-picker',
1286
+ '[attr.id]': 'id',
1287
+ '[class.fui-date-picker--open]': 'panelOpen()',
1288
+ '[class.fui-date-picker--disabled]': 'disabled()',
1289
+ '[class.fui-date-picker--range]': 'range()',
1290
+ '[class.fui-date-picker--focused]': 'focused()',
1291
+ '[class.fui-date-picker--error]': 'errorState()',
1292
+ '[class.fui-date-picker--readonly]': '_readOnly()',
1293
+ }, providers: [
1294
+ {
1295
+ provide: NG_VALUE_ACCESSOR,
1296
+ useExisting: FuiDatePickerComponent,
1297
+ multi: true,
1298
+ },
1299
+ {
1300
+ provide: NG_VALIDATORS,
1301
+ useExisting: FuiDatePickerComponent,
1302
+ multi: true,
1303
+ },
1304
+ {
1305
+ provide: FUI_FORM_FIELD_CONTROL,
1306
+ useExisting: FuiDatePickerComponent,
1307
+ },
1308
+ ], template: "<!-- Trigger area -->\n<div #triggerEl class=\"fui-date-picker__trigger\" (click)=\"onTriggerClick()\">\n <!-- Start input -->\n <input\n #startInput\n class=\"fui-date-picker__input\"\n [id]=\"id\"\n [value]=\"startInputText()\"\n [placeholder]=\"getStartPlaceholder()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [attr.aria-describedby]=\"_ariaDescribedby\"\n [attr.aria-required]=\"required()\"\n [attr.aria-invalid]=\"errorState()\"\n [attr.aria-expanded]=\"panelOpen()\"\n aria-haspopup=\"dialog\"\n aria-autocomplete=\"none\"\n role=\"combobox\"\n inputmode=\"numeric\"\n autocomplete=\"off\"\n (input)=\"onInput($event)\"\n (blur)=\"onBlur()\"\n (focus)=\"onInputFocus()\"\n (keydown)=\"onKeydown($event)\"\n (click)=\"$event.stopPropagation()\"\n />\n\n <!-- Range separator + end input -->\n @if (range()) {\n <span class=\"fui-date-picker__range-separator\" aria-hidden=\"true\">{{ rangeSeparator() }}</span>\n <input\n #endInput\n class=\"fui-date-picker__input fui-date-picker__input--end\"\n [value]=\"endInputText()\"\n [placeholder]=\"getEndPlaceholder()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [attr.aria-label]=\"'End date'\"\n [attr.aria-invalid]=\"errorState()\"\n inputmode=\"numeric\"\n autocomplete=\"off\"\n (input)=\"onEndInput($event)\"\n (blur)=\"onEndBlur()\"\n (focus)=\"onInputFocus()\"\n (keydown)=\"onEndKeydown($event)\"\n (click)=\"$event.stopPropagation()\"\n />\n }\n\n <!-- Calendar toggle button \u2014 hidden in readOnly mode -->\n @if (!_readOnly()) {\n <button\n type=\"button\"\n class=\"fui-date-picker__toggle\"\n tabindex=\"-1\"\n [attr.aria-expanded]=\"panelOpen()\"\n [disabled]=\"disabled()\"\n aria-haspopup=\"dialog\"\n aria-label=\"Open calendar\"\n (click)=\"onToggleClick($event)\"\n >\n <fui-icon name=\"calendar-blank\" size=\"sm\" />\n </button>\n }\n</div>\n\n<!-- Calendar panel -->\n@if (panelOpen()) {\n <div #calendarPanel class=\"fui-date-picker__panel\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Calendar\">\n <fui-calendar\n #calendar\n [selected]=\"selectedDate()\"\n [rangeStart]=\"rangeStartDate()\"\n [rangeEnd]=\"rangeEndDate()\"\n [range]=\"range()\"\n [min]=\"min()\"\n [max]=\"max()\"\n [dateFilter]=\"dateFilter()\"\n [startAt]=\"startAt()\"\n [firstDayOfWeek]=\"firstDayOfWeek()\"\n [hoveredDate]=\"hoveredDate()\"\n (dateSelected)=\"onDateSelected($event)\"\n (dateHovered)=\"onDateHovered($event)\"\n />\n </div>\n}\n", styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition:opacity var(--fui-duration-fast-02) var(--fui-ease-entrance) 0ms}.fui-motion-fade-out{transition:opacity var(--fui-duration-fast-01) var(--fui-ease-exit) 0ms}.fui-motion-slide-in-bottom{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition:transform,opacity var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-moderate-01) var(--fui-ease-entrance)}.fui-date-picker{--fui-date-picker-font-size: var(--fui-font-size-02);--fui-date-picker-panel-border-radius: var(--fui-border-radius-md);--fui-date-picker-panel-shadow: var(--fui-shadow-03);--fui-date-picker-panel-bg: var(--fui-surface-00);--fui-date-picker-panel-border-color: var(--fui-border-color);--fui-date-picker-toggle-size: 1.5rem;display:inline-block;width:100%;position:relative}.fui-date-picker__trigger{width:100%;min-height:100%;display:flex;align-items:center;gap:var(--fui-spacing-01);cursor:pointer;background:transparent;border:none}.fui-date-picker__input{flex:1;min-width:0;border:none;outline:none;background:transparent;font-family:var(--fui-font-family-sans);font-size:var(--fui-font-size-02);font-weight:var(--fui-font-weight-regular);line-height:var(--fui-line-height-02);letter-spacing:var(--fui-letter-spacing-normal);color:var(--fui-text-primary);padding:0;cursor:text}.fui-date-picker__input::placeholder{color:var(--fui-text-disabled)}.fui-date-picker__input:disabled{cursor:not-allowed;opacity:.5}.fui-date-picker__range-separator{flex-shrink:0;color:var(--fui-text-secondary);font-size:var(--fui-font-size-02);-webkit-user-select:none;user-select:none}.fui-date-picker__toggle{background:none;border:none;padding:0;margin:0;font:inherit;color:inherit;cursor:pointer;outline:none}.fui-date-picker__toggle:focus-visible{outline:2px solid var(--fui-primary);outline-offset:2px}.fui-date-picker__toggle{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:1.5rem;height:1.5rem;color:var(--fui-text-secondary);border-radius:var(--fui-border-radius-sm);transition:color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-date-picker__toggle:hover:not(:disabled){color:var(--fui-text-primary)}.fui-date-picker__toggle:disabled{color:var(--fui-text-disabled);cursor:not-allowed}.fui-date-picker__panel{background:var(--fui-date-picker-panel-bg);border:1px solid var(--fui-date-picker-panel-border-color);border-radius:var(--fui-date-picker-panel-border-radius);box-shadow:var(--fui-date-picker-panel-shadow);padding:var(--fui-spacing-02);transition:opacity,transform var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-date-picker--disabled{pointer-events:none}.fui-date-picker--disabled .fui-date-picker__trigger{cursor:not-allowed;opacity:.5}.fui-date-picker--readonly{pointer-events:none}.fui-date-picker--readonly .fui-date-picker__trigger{cursor:default;opacity:1}.fui-date-picker--readonly .fui-date-picker__input{color:var(--fui-text-primary)!important;-webkit-text-fill-color:var(--fui-text-primary)!important;cursor:default;opacity:1}.fui-date-picker--readonly .fui-date-picker__input:disabled{opacity:1;cursor:default}.fui-date-picker-backdrop{background:transparent}.fui-date-picker-overlay-panel{z-index:var(--fui-z-popover, 1060)}@media(prefers-contrast:high){.fui-date-picker__panel{border-width:2px}}@media(prefers-reduced-motion:reduce){.fui-date-picker__panel{transition:none}}\n"] }]
1309
+ }], ctorParameters: () => [], propDecorators: { placeholderInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabledInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], range: [{ type: i0.Input, args: [{ isSignal: true, alias: "range", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], dateFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFilter", required: false }] }], startAt: [{ type: i0.Input, args: [{ isSignal: true, alias: "startAt", required: false }] }], firstDayOfWeek: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstDayOfWeek", required: false }] }], rangeSeparator: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangeSeparator", required: false }] }], errorStateMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorStateMatcher", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], dateChange: [{ type: i0.Output, args: ["dateChange"] }], openedChange: [{ type: i0.Output, args: ["openedChange"] }], triggerEl: [{
1310
+ type: ViewChild,
1311
+ args: ['triggerEl', { static: false }]
1312
+ }], calendarPanel: [{
1313
+ type: ViewChild,
1314
+ args: ['calendarPanel', { static: false }]
1315
+ }], startInputEl: [{
1316
+ type: ViewChild,
1317
+ args: ['startInput', { static: false }]
1318
+ }], endInputEl: [{
1319
+ type: ViewChild,
1320
+ args: ['endInput', { static: false }]
1321
+ }], calendarComponent: [{
1322
+ type: ViewChild,
1323
+ args: ['calendar', { static: false }]
1324
+ }] } });
1325
+
1326
+ /**
1327
+ * Generated bundle index. Do not edit.
1328
+ */
1329
+
1330
+ export { DEFAULT_DATE_FORMAT, FuiCalendarComponent, FuiDatePickerComponent, RANGE_SEPARATOR, applyDateMask, buildCalendarGrid, formatDate, getDaysInMonth, getMonthLabel, getShortMonthName, getWeekdayLabels, isAfter, isBefore, isBetween, isDateInRange, isSameDay, normalizeDate, parseDate };
1331
+ //# sourceMappingURL=raintonic-formaui-components-date-picker.mjs.map