@caribeng/ui 22.1.0 → 22.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 (253) hide show
  1. package/README.md +52 -12
  2. package/fesm2022/caribeng-ui-accordion.mjs +2 -2
  3. package/fesm2022/caribeng-ui-anchor.mjs +169 -0
  4. package/fesm2022/caribeng-ui-autocomplete.mjs +10 -8
  5. package/fesm2022/caribeng-ui-avatargroup.mjs +9 -3
  6. package/fesm2022/caribeng-ui-blockui.mjs +12 -4
  7. package/fesm2022/caribeng-ui-breadcrumb.mjs +5 -2
  8. package/fesm2022/caribeng-ui-button.mjs +22 -2
  9. package/fesm2022/caribeng-ui-caribe-alert.mjs +5 -2
  10. package/fesm2022/caribeng-ui-caribe-animation-lottie.mjs +6 -3
  11. package/fesm2022/caribeng-ui-caribe-button-group.mjs +6 -3
  12. package/fesm2022/caribeng-ui-caribe-calendar.mjs +11 -6
  13. package/fesm2022/caribeng-ui-caribe-card-content.mjs +6 -3
  14. package/fesm2022/caribeng-ui-caribe-card-move.mjs +29 -7
  15. package/fesm2022/caribeng-ui-caribe-card-select.mjs +56 -14
  16. package/fesm2022/caribeng-ui-caribe-circle-loading.mjs +10 -7
  17. package/fesm2022/caribeng-ui-caribe-coachmark.mjs +42 -19
  18. package/fesm2022/caribeng-ui-caribe-data-table.mjs +15 -9
  19. package/fesm2022/caribeng-ui-caribe-dots-indicator.mjs +38 -5
  20. package/fesm2022/caribeng-ui-caribe-expansion-panel.mjs +16 -5
  21. package/fesm2022/caribeng-ui-caribe-fab-menu.mjs +56 -7
  22. package/fesm2022/caribeng-ui-caribe-file-download.mjs +68 -27
  23. package/fesm2022/caribeng-ui-caribe-file-select.mjs +22 -17
  24. package/fesm2022/caribeng-ui-caribe-float-menu.mjs +6 -3
  25. package/fesm2022/caribeng-ui-caribe-footer.mjs +8 -4
  26. package/fesm2022/caribeng-ui-caribe-header.mjs +6 -3
  27. package/fesm2022/caribeng-ui-caribe-icon-button.mjs +6 -3
  28. package/fesm2022/caribeng-ui-caribe-illustration.mjs +6 -3
  29. package/fesm2022/caribeng-ui-caribe-inline-alert.mjs +5 -2
  30. package/fesm2022/caribeng-ui-caribe-input-search.mjs +14 -10
  31. package/fesm2022/caribeng-ui-caribe-input-select.mjs +12 -10
  32. package/fesm2022/caribeng-ui-caribe-input-token.mjs +61 -11
  33. package/fesm2022/caribeng-ui-caribe-link.mjs +7 -2
  34. package/fesm2022/caribeng-ui-caribe-loader.mjs +8 -5
  35. package/fesm2022/caribeng-ui-caribe-modal.mjs +32 -16
  36. package/fesm2022/caribeng-ui-caribe-off-canvas.mjs +26 -12
  37. package/fesm2022/caribeng-ui-caribe-onboarding.mjs +44 -19
  38. package/fesm2022/caribeng-ui-caribe-pictogram.mjs +6 -3
  39. package/fesm2022/caribeng-ui-caribe-pop-over.mjs +34 -10
  40. package/fesm2022/caribeng-ui-caribe-preloader.mjs +9 -6
  41. package/fesm2022/caribeng-ui-caribe-progress-bar.mjs +5 -2
  42. package/fesm2022/caribeng-ui-caribe-shortcut.mjs +8 -3
  43. package/fesm2022/caribeng-ui-caribe-step-indicator.mjs +12 -5
  44. package/fesm2022/caribeng-ui-caribe-stroke.mjs +6 -3
  45. package/fesm2022/caribeng-ui-caribe-switch.mjs +5 -3
  46. package/fesm2022/caribeng-ui-caribe-tag-button.mjs +1 -1
  47. package/fesm2022/caribeng-ui-caribe-tracker.mjs +5 -2
  48. package/fesm2022/caribeng-ui-caribe-transaction-status.mjs +1 -1
  49. package/fesm2022/caribeng-ui-caribe-voucher.mjs +11 -8
  50. package/fesm2022/caribeng-ui-carousel.mjs +49 -14
  51. package/fesm2022/caribeng-ui-cascadeselect.mjs +22 -8
  52. package/fesm2022/caribeng-ui-chart.mjs +2598 -303
  53. package/fesm2022/caribeng-ui-chip.mjs +9 -4
  54. package/fesm2022/caribeng-ui-chips.mjs +9 -5
  55. package/fesm2022/caribeng-ui-colorpicker.mjs +9 -7
  56. package/fesm2022/caribeng-ui-confirmdialog.mjs +74 -9
  57. package/fesm2022/caribeng-ui-confirmpopup.mjs +134 -9
  58. package/fesm2022/caribeng-ui-contextmenu.mjs +6 -3
  59. package/fesm2022/caribeng-ui-dataview.mjs +211 -6
  60. package/fesm2022/caribeng-ui-datepicker.mjs +1630 -393
  61. package/fesm2022/caribeng-ui-descriptions.mjs +124 -0
  62. package/fesm2022/caribeng-ui-dialog.mjs +10 -7
  63. package/fesm2022/caribeng-ui-dock.mjs +6 -3
  64. package/fesm2022/caribeng-ui-drawer.mjs +10 -7
  65. package/fesm2022/caribeng-ui-dynamicdialog.mjs +210 -6
  66. package/fesm2022/caribeng-ui-editor.mjs +4392 -243
  67. package/fesm2022/caribeng-ui-empty.mjs +80 -0
  68. package/fesm2022/caribeng-ui-file-card.mjs +2 -2
  69. package/fesm2022/caribeng-ui-fileupload.mjs +96 -19
  70. package/fesm2022/caribeng-ui-floatlabel.mjs +4 -2
  71. package/fesm2022/caribeng-ui-folder-card.mjs +10 -3
  72. package/fesm2022/caribeng-ui-galleria.mjs +76 -45
  73. package/fesm2022/caribeng-ui-image.mjs +52 -29
  74. package/fesm2022/caribeng-ui-inplace.mjs +22 -6
  75. package/fesm2022/caribeng-ui-input-number.mjs +9 -7
  76. package/fesm2022/caribeng-ui-inputgroup.mjs +2 -2
  77. package/fesm2022/caribeng-ui-inputmask.mjs +14 -11
  78. package/fesm2022/caribeng-ui-inputotp.mjs +12 -6
  79. package/fesm2022/caribeng-ui-keyfilter.mjs +72 -0
  80. package/fesm2022/caribeng-ui-listbox.mjs +13 -11
  81. package/fesm2022/caribeng-ui-megamenu.mjs +222 -30
  82. package/fesm2022/caribeng-ui-menu.mjs +11 -5
  83. package/fesm2022/caribeng-ui-menubar.mjs +10 -5
  84. package/fesm2022/caribeng-ui-message.mjs +7 -4
  85. package/fesm2022/caribeng-ui-metergroup.mjs +29 -11
  86. package/fesm2022/caribeng-ui-multiselect.mjs +399 -97
  87. package/fesm2022/caribeng-ui-orderlist.mjs +230 -11
  88. package/fesm2022/caribeng-ui-organizationchart.mjs +12 -7
  89. package/fesm2022/caribeng-ui-paginator.mjs +18 -15
  90. package/fesm2022/caribeng-ui-panel.mjs +5 -2
  91. package/fesm2022/caribeng-ui-panelmenu.mjs +6 -3
  92. package/fesm2022/caribeng-ui-password.mjs +8 -6
  93. package/fesm2022/caribeng-ui-pdfviewer.mjs +1553 -0
  94. package/fesm2022/caribeng-ui-picklist.mjs +251 -14
  95. package/fesm2022/caribeng-ui-pricing.mjs +17 -14
  96. package/fesm2022/caribeng-ui-rating.mjs +7 -3
  97. package/fesm2022/caribeng-ui-repeater.mjs +223 -0
  98. package/fesm2022/caribeng-ui-result.mjs +91 -0
  99. package/fesm2022/caribeng-ui-scrollpanel.mjs +6 -3
  100. package/fesm2022/caribeng-ui-scrolltop.mjs +6 -3
  101. package/fesm2022/caribeng-ui-select.mjs +379 -87
  102. package/fesm2022/caribeng-ui-selectbutton.mjs +2 -2
  103. package/fesm2022/caribeng-ui-slider.mjs +171 -75
  104. package/fesm2022/caribeng-ui-speeddial.mjs +7 -4
  105. package/fesm2022/caribeng-ui-spinner.mjs +5 -2
  106. package/fesm2022/caribeng-ui-splitbutton.mjs +163 -0
  107. package/fesm2022/caribeng-ui-splitter.mjs +5 -2
  108. package/fesm2022/caribeng-ui-statistic.mjs +183 -0
  109. package/fesm2022/caribeng-ui-stepper.mjs +8 -5
  110. package/fesm2022/caribeng-ui-steps.mjs +6 -3
  111. package/fesm2022/caribeng-ui-table.mjs +708 -82
  112. package/fesm2022/caribeng-ui-tabmenu.mjs +6 -3
  113. package/fesm2022/caribeng-ui-tag.mjs +2 -2
  114. package/fesm2022/caribeng-ui-terminal.mjs +45 -9
  115. package/fesm2022/caribeng-ui-themetoggle.mjs +82 -0
  116. package/fesm2022/caribeng-ui-tieredmenu.mjs +9 -6
  117. package/fesm2022/caribeng-ui-timeline.mjs +175 -6
  118. package/fesm2022/caribeng-ui-toast.mjs +7 -4
  119. package/fesm2022/caribeng-ui-togglebutton.mjs +8 -6
  120. package/fesm2022/caribeng-ui-toolbar.mjs +6 -3
  121. package/fesm2022/caribeng-ui-tree.mjs +302 -6
  122. package/fesm2022/caribeng-ui-treeselect.mjs +26 -10
  123. package/fesm2022/caribeng-ui-treetable.mjs +269 -6
  124. package/fesm2022/caribeng-ui-virtualscroller.mjs +123 -10
  125. package/fesm2022/caribeng-ui.mjs +829 -6
  126. package/package.json +70 -11
  127. package/schematics/collection.json +10 -0
  128. package/schematics/ng-add/index.js +125 -0
  129. package/schematics/ng-add/schema.js +2 -0
  130. package/schematics/ng-add/schema.json +66 -0
  131. package/styles/caribeng.css +380 -0
  132. package/styles/scss/_mixins.scss +46 -0
  133. package/styles/scss/_overlay.scss +15 -0
  134. package/styles/scss/_tokens.scss +267 -0
  135. package/styles/scss/_utilities.scss +73 -0
  136. package/styles/scss/_variables.scss +33 -0
  137. package/styles/scss/index.scss +16 -0
  138. package/types/caribeng-ui-anchor.d.ts +50 -0
  139. package/types/caribeng-ui-autocomplete.d.ts +4 -2
  140. package/types/caribeng-ui-avatargroup.d.ts +3 -1
  141. package/types/caribeng-ui-blockui.d.ts +9 -4
  142. package/types/caribeng-ui-breadcrumb.d.ts +3 -0
  143. package/types/caribeng-ui-button.d.ts +9 -1
  144. package/types/caribeng-ui-caribe-alert.d.ts +3 -0
  145. package/types/caribeng-ui-caribe-animation-lottie.d.ts +4 -1
  146. package/types/caribeng-ui-caribe-button-group.d.ts +4 -1
  147. package/types/caribeng-ui-caribe-calendar.d.ts +5 -1
  148. package/types/caribeng-ui-caribe-card-content.d.ts +4 -1
  149. package/types/caribeng-ui-caribe-card-move.d.ts +13 -13
  150. package/types/caribeng-ui-caribe-card-select.d.ts +10 -1
  151. package/types/caribeng-ui-caribe-circle-loading.d.ts +4 -1
  152. package/types/caribeng-ui-caribe-coachmark.d.ts +6 -0
  153. package/types/caribeng-ui-caribe-data-table.d.ts +7 -2
  154. package/types/caribeng-ui-caribe-dots-indicator.d.ts +9 -0
  155. package/types/caribeng-ui-caribe-expansion-panel.d.ts +7 -10
  156. package/types/caribeng-ui-caribe-fab-menu.d.ts +10 -1
  157. package/types/caribeng-ui-caribe-file-download.d.ts +8 -10
  158. package/types/caribeng-ui-caribe-file-select.d.ts +7 -1
  159. package/types/caribeng-ui-caribe-float-menu.d.ts +4 -1
  160. package/types/caribeng-ui-caribe-header.d.ts +3 -0
  161. package/types/caribeng-ui-caribe-icon-button.d.ts +4 -1
  162. package/types/caribeng-ui-caribe-illustration.d.ts +4 -1
  163. package/types/caribeng-ui-caribe-inline-alert.d.ts +3 -0
  164. package/types/caribeng-ui-caribe-input-search.d.ts +4 -2
  165. package/types/caribeng-ui-caribe-input-select.d.ts +4 -2
  166. package/types/caribeng-ui-caribe-input-token.d.ts +16 -1
  167. package/types/caribeng-ui-caribe-link.d.ts +3 -0
  168. package/types/caribeng-ui-caribe-loader.d.ts +4 -1
  169. package/types/caribeng-ui-caribe-modal.d.ts +7 -10
  170. package/types/caribeng-ui-caribe-off-canvas.d.ts +6 -1
  171. package/types/caribeng-ui-caribe-onboarding.d.ts +6 -0
  172. package/types/caribeng-ui-caribe-pictogram.d.ts +4 -1
  173. package/types/caribeng-ui-caribe-pop-over.d.ts +7 -10
  174. package/types/caribeng-ui-caribe-preloader.d.ts +4 -1
  175. package/types/caribeng-ui-caribe-progress-bar.d.ts +3 -0
  176. package/types/caribeng-ui-caribe-shortcut.d.ts +3 -1
  177. package/types/caribeng-ui-caribe-step-indicator.d.ts +6 -0
  178. package/types/caribeng-ui-caribe-stroke.d.ts +4 -1
  179. package/types/caribeng-ui-caribe-switch.d.ts +3 -1
  180. package/types/caribeng-ui-caribe-tracker.d.ts +3 -0
  181. package/types/caribeng-ui-caribe-voucher.d.ts +5 -2
  182. package/types/caribeng-ui-carousel.d.ts +10 -1
  183. package/types/caribeng-ui-cascadeselect.d.ts +7 -2
  184. package/types/caribeng-ui-chart.d.ts +469 -44
  185. package/types/caribeng-ui-chip.d.ts +6 -0
  186. package/types/caribeng-ui-chips.d.ts +6 -2
  187. package/types/caribeng-ui-colorpicker.d.ts +3 -1
  188. package/types/caribeng-ui-confirmdialog.d.ts +25 -6
  189. package/types/caribeng-ui-confirmpopup.d.ts +37 -5
  190. package/types/caribeng-ui-contextmenu.d.ts +4 -1
  191. package/types/caribeng-ui-dataview.d.ts +63 -5
  192. package/types/caribeng-ui-datepicker.d.ts +186 -21
  193. package/types/caribeng-ui-descriptions.d.ts +63 -0
  194. package/types/caribeng-ui-dialog.d.ts +4 -1
  195. package/types/caribeng-ui-dock.d.ts +4 -1
  196. package/types/caribeng-ui-drawer.d.ts +4 -1
  197. package/types/caribeng-ui-dynamicdialog.d.ts +93 -3
  198. package/types/caribeng-ui-editor.d.ts +486 -13
  199. package/types/caribeng-ui-empty.d.ts +34 -0
  200. package/types/caribeng-ui-file-card.d.ts +1 -1
  201. package/types/caribeng-ui-fileupload.d.ts +27 -4
  202. package/types/caribeng-ui-folder-card.d.ts +4 -1
  203. package/types/caribeng-ui-galleria.d.ts +9 -0
  204. package/types/caribeng-ui-image.d.ts +10 -0
  205. package/types/caribeng-ui-inplace.d.ts +8 -0
  206. package/types/caribeng-ui-input-number.d.ts +3 -1
  207. package/types/caribeng-ui-inputmask.d.ts +5 -0
  208. package/types/caribeng-ui-inputotp.d.ts +7 -2
  209. package/types/caribeng-ui-keyfilter.d.ts +30 -0
  210. package/types/caribeng-ui-listbox.d.ts +5 -3
  211. package/types/caribeng-ui-megamenu.d.ts +26 -2
  212. package/types/caribeng-ui-menu.d.ts +7 -2
  213. package/types/caribeng-ui-menubar.d.ts +5 -1
  214. package/types/caribeng-ui-message.d.ts +3 -0
  215. package/types/caribeng-ui-metergroup.d.ts +9 -1
  216. package/types/caribeng-ui-multiselect.d.ts +71 -10
  217. package/types/caribeng-ui-orderlist.d.ts +58 -6
  218. package/types/caribeng-ui-organizationchart.d.ts +7 -1
  219. package/types/caribeng-ui-paginator.d.ts +3 -0
  220. package/types/caribeng-ui-panel.d.ts +3 -0
  221. package/types/caribeng-ui-panelmenu.d.ts +4 -1
  222. package/types/caribeng-ui-password.d.ts +4 -2
  223. package/types/caribeng-ui-pdfviewer.d.ts +258 -0
  224. package/types/caribeng-ui-picklist.d.ts +67 -7
  225. package/types/caribeng-ui-pricing.d.ts +8 -5
  226. package/types/caribeng-ui-rating.d.ts +6 -2
  227. package/types/caribeng-ui-repeater.d.ts +79 -0
  228. package/types/caribeng-ui-result.d.ts +35 -0
  229. package/types/caribeng-ui-scrollpanel.d.ts +4 -1
  230. package/types/caribeng-ui-scrolltop.d.ts +4 -1
  231. package/types/caribeng-ui-select.d.ts +73 -9
  232. package/types/caribeng-ui-slider.d.ts +63 -10
  233. package/types/caribeng-ui-speeddial.d.ts +5 -2
  234. package/types/caribeng-ui-spinner.d.ts +3 -0
  235. package/types/caribeng-ui-splitbutton.d.ts +49 -0
  236. package/types/caribeng-ui-splitter.d.ts +3 -0
  237. package/types/caribeng-ui-statistic.d.ts +65 -0
  238. package/types/caribeng-ui-stepper.d.ts +3 -0
  239. package/types/caribeng-ui-steps.d.ts +4 -1
  240. package/types/caribeng-ui-table.d.ts +184 -16
  241. package/types/caribeng-ui-tabmenu.d.ts +4 -1
  242. package/types/caribeng-ui-terminal.d.ts +10 -0
  243. package/types/caribeng-ui-themetoggle.d.ts +32 -0
  244. package/types/caribeng-ui-tieredmenu.d.ts +5 -2
  245. package/types/caribeng-ui-timeline.d.ts +63 -4
  246. package/types/caribeng-ui-toast.d.ts +3 -0
  247. package/types/caribeng-ui-togglebutton.d.ts +5 -3
  248. package/types/caribeng-ui-toolbar.d.ts +7 -4
  249. package/types/caribeng-ui-tree.d.ts +76 -4
  250. package/types/caribeng-ui-treeselect.d.ts +10 -2
  251. package/types/caribeng-ui-treetable.d.ts +68 -4
  252. package/types/caribeng-ui-virtualscroller.d.ts +45 -5
  253. package/types/caribeng-ui.d.ts +501 -12
@@ -1,28 +1,406 @@
1
1
  import * as i0 from '@angular/core';
2
- import { model, input, signal, viewChild, computed, effect, forwardRef, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
2
+ import { model, input, inject, Injector, signal, viewChild, computed, effect, afterNextRender, forwardRef, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
3
3
  import { NG_VALUE_ACCESSOR } from '@angular/forms';
4
4
  import * as i1 from '@angular/cdk/overlay';
5
5
  import { OverlayModule } from '@angular/cdk/overlay';
6
- import { cbFieldState } from '@caribeng/ui';
6
+ import { injectCbTranslation, cbFieldState, cbFormat } from '@caribeng/ui';
7
7
  import { CbIcon } from '@caribeng/ui/icon';
8
8
 
9
+ /**
10
+ * Formato de fechas con la sintaxis de PrimeNG / jQuery UI:
11
+ *
12
+ * - `d` / `dd`: día del mes (sin cero / dos dígitos)
13
+ * - `o` / `oo`: día del año (sin ceros / tres dígitos)
14
+ * - `D` / `DD`: nombre del día corto / largo
15
+ * - `m` / `mm`: mes (sin cero / dos dígitos)
16
+ * - `M` / `MM`: nombre del mes corto / largo
17
+ * - `y` / `yy`: año (dos / cuatro dígitos)
18
+ * - `@`: marca de tiempo Unix (ms desde 01/01/1970)
19
+ * - `!`: ticks de Windows (100 ns desde 01/01/0001)
20
+ * - `'...'`: texto literal; `''`: comilla simple; cualquier otro carácter, literal.
21
+ *
22
+ * Los nombres de días y meses salen de `Intl` en el `locale` indicado.
23
+ */
24
+ const namesCache = new Map();
25
+ /** Nombres de días y meses del `locale` (sin puntos finales: "ene" y no "ene."). */
26
+ function dateNames(locale) {
27
+ const key = locale ?? '';
28
+ const cached = namesCache.get(key);
29
+ if (cached)
30
+ return cached;
31
+ const clean = (s) => s.replace(/\.$/, '');
32
+ const day = (style) => {
33
+ const f = new Intl.DateTimeFormat(locale, { weekday: style });
34
+ // 2023-01-01 fue domingo.
35
+ return Array.from({ length: 7 }, (_, i) => clean(f.format(new Date(2023, 0, 1 + i))));
36
+ };
37
+ const month = (style) => {
38
+ const f = new Intl.DateTimeFormat(locale, { month: style });
39
+ return Array.from({ length: 12 }, (_, i) => clean(f.format(new Date(2023, i, 15))));
40
+ };
41
+ const names = { dayShort: day('short'), dayLong: day('long'), monthShort: month('short'), monthLong: month('long') };
42
+ namesCache.set(key, names);
43
+ return names;
44
+ }
45
+ const TICKS_TO_EPOCH = 621_355_968_000_000_000;
46
+ /** Divide un formato en símbolos y textos literales. */
47
+ function tokenize(format) {
48
+ const tokens = [];
49
+ let literal = '';
50
+ const flush = () => {
51
+ if (literal)
52
+ tokens.push({ kind: 'literal', text: literal });
53
+ literal = '';
54
+ };
55
+ for (let i = 0; i < format.length; i++) {
56
+ const c = format[i];
57
+ if (c === "'") {
58
+ if (format[i + 1] === "'") {
59
+ literal += "'";
60
+ i++;
61
+ continue;
62
+ }
63
+ // Texto entre comillas; dentro, '' es una comilla.
64
+ let j = i + 1;
65
+ for (; j < format.length; j++) {
66
+ if (format[j] !== "'") {
67
+ literal += format[j];
68
+ }
69
+ else if (format[j + 1] === "'") {
70
+ literal += "'";
71
+ j++;
72
+ }
73
+ else {
74
+ break;
75
+ }
76
+ }
77
+ i = j;
78
+ continue;
79
+ }
80
+ if ('doDmMy'.includes(c)) {
81
+ flush();
82
+ const double = format[i + 1] === c;
83
+ tokens.push({ kind: (double ? c + c : c) });
84
+ if (double)
85
+ i++;
86
+ continue;
87
+ }
88
+ if (c === '@' || c === '!') {
89
+ flush();
90
+ tokens.push({ kind: c });
91
+ continue;
92
+ }
93
+ literal += c;
94
+ }
95
+ flush();
96
+ return tokens;
97
+ }
98
+ function dayOfYear(d) {
99
+ const start = Date.UTC(d.getFullYear(), 0, 1);
100
+ return Math.round((Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()) - start) / 86_400_000) + 1;
101
+ }
102
+ const pad = (n, size) => String(n).padStart(size, '0');
103
+ /** Escribe una fecha con el formato indicado. */
104
+ function formatDate(date, format, names) {
105
+ return tokenize(format)
106
+ .map((t) => {
107
+ switch (t.kind) {
108
+ case 'literal':
109
+ return t.text;
110
+ case 'd':
111
+ return String(date.getDate());
112
+ case 'dd':
113
+ return pad(date.getDate(), 2);
114
+ case 'o':
115
+ return String(dayOfYear(date));
116
+ case 'oo':
117
+ return pad(dayOfYear(date), 3);
118
+ case 'D':
119
+ return names.dayShort[date.getDay()];
120
+ case 'DD':
121
+ return names.dayLong[date.getDay()];
122
+ case 'm':
123
+ return String(date.getMonth() + 1);
124
+ case 'mm':
125
+ return pad(date.getMonth() + 1, 2);
126
+ case 'M':
127
+ return names.monthShort[date.getMonth()];
128
+ case 'MM':
129
+ return names.monthLong[date.getMonth()];
130
+ case 'y':
131
+ return pad(date.getFullYear() % 100, 2);
132
+ case 'yy':
133
+ return String(date.getFullYear());
134
+ case '@':
135
+ return String(date.getTime());
136
+ case '!':
137
+ return String(date.getTime() * 10_000 + TICKS_TO_EPOCH);
138
+ }
139
+ })
140
+ .join('');
141
+ }
142
+ /**
143
+ * Lee una fecha escrita con el formato indicado. Devuelve `null` si el texto no
144
+ * encaja o la fecha no existe (31 de febrero). Los años de dos dígitos se
145
+ * interpretan como 19xx o 20xx según `shortYearCutoff` años por delante de hoy.
146
+ */
147
+ function parseDate(text, format, names, shortYearCutoff = 10) {
148
+ const value = text.trim();
149
+ if (!value)
150
+ return null;
151
+ let pos = 0;
152
+ let year = -1;
153
+ let month = -1;
154
+ let day = -1;
155
+ let doy = -1;
156
+ let literalDate = null;
157
+ const digits = (min, max) => {
158
+ const match = new RegExp(`^\\d{${min},${max}}`).exec(value.slice(pos));
159
+ if (!match)
160
+ return null;
161
+ pos += match[0].length;
162
+ return Number(match[0]);
163
+ };
164
+ const name = (list) => {
165
+ // El nombre más largo primero, para que "mar" no gane a "marzo".
166
+ const rest = value.slice(pos).toLocaleLowerCase();
167
+ const ordered = list.map((n, i) => ({ n: n.toLocaleLowerCase(), i })).sort((a, b) => b.n.length - a.n.length);
168
+ const hit = ordered.find((o) => o.n && rest.startsWith(o.n));
169
+ if (!hit)
170
+ return null;
171
+ pos += hit.n.length;
172
+ return hit.i;
173
+ };
174
+ for (const t of tokenize(format)) {
175
+ let n;
176
+ switch (t.kind) {
177
+ case 'literal':
178
+ if (value.slice(pos, pos + t.text.length).toLocaleLowerCase() !== t.text.toLocaleLowerCase())
179
+ return null;
180
+ pos += t.text.length;
181
+ continue;
182
+ case 'd':
183
+ case 'dd':
184
+ n = digits(t.kind === 'dd' ? 2 : 1, 2);
185
+ if (n === null)
186
+ return null;
187
+ day = n;
188
+ continue;
189
+ case 'o':
190
+ case 'oo':
191
+ n = digits(t.kind === 'oo' ? 3 : 1, 3);
192
+ if (n === null)
193
+ return null;
194
+ doy = n;
195
+ continue;
196
+ case 'D':
197
+ case 'DD':
198
+ if (name(t.kind === 'DD' ? names.dayLong : names.dayShort) === null)
199
+ return null;
200
+ continue;
201
+ case 'm':
202
+ case 'mm':
203
+ n = digits(t.kind === 'mm' ? 2 : 1, 2);
204
+ if (n === null)
205
+ return null;
206
+ month = n;
207
+ continue;
208
+ case 'M':
209
+ case 'MM':
210
+ n = name(t.kind === 'MM' ? names.monthLong : names.monthShort);
211
+ if (n === null)
212
+ return null;
213
+ month = n + 1;
214
+ continue;
215
+ case 'y':
216
+ case 'yy':
217
+ n = digits(t.kind === 'yy' ? 4 : 2, t.kind === 'yy' ? 4 : 2);
218
+ if (n === null)
219
+ return null;
220
+ if (t.kind === 'y') {
221
+ const now = new Date().getFullYear();
222
+ const cutoff = (now % 100) + shortYearCutoff;
223
+ n += n <= cutoff ? now - (now % 100) : now - (now % 100) - 100;
224
+ }
225
+ year = n;
226
+ continue;
227
+ case '@':
228
+ n = digits(1, 16);
229
+ if (n === null)
230
+ return null;
231
+ literalDate = new Date(n);
232
+ continue;
233
+ case '!':
234
+ n = digits(1, 20);
235
+ if (n === null)
236
+ return null;
237
+ literalDate = new Date((n - TICKS_TO_EPOCH) / 10_000);
238
+ continue;
239
+ }
240
+ }
241
+ if (pos !== value.length)
242
+ return null;
243
+ if (literalDate)
244
+ return Number.isNaN(literalDate.getTime()) ? null : literalDate;
245
+ if (year === -1)
246
+ year = new Date().getFullYear();
247
+ if (doy > -1) {
248
+ const d = new Date(year, 0, doy);
249
+ if (d.getFullYear() !== year)
250
+ return null;
251
+ return d;
252
+ }
253
+ if (month === -1)
254
+ month = 1;
255
+ if (day === -1)
256
+ day = 1;
257
+ const date = new Date(year, month - 1, day);
258
+ // Rechaza fechas que JavaScript "corrige" (31/02 → 03/03).
259
+ if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day)
260
+ return null;
261
+ return date;
262
+ }
263
+ /**
264
+ * Formato numérico del `locale` en la sintaxis de arriba (p. ej. `dd/mm/yy` en
265
+ * es-CO, `mm/dd/yy` en en-US), para escribir fechas cuando no se indica uno.
266
+ */
267
+ function localeDateFormat(locale, view) {
268
+ if (view === 'year')
269
+ return 'yy';
270
+ const parts = new Intl.DateTimeFormat(locale, {
271
+ day: view === 'date' ? '2-digit' : undefined,
272
+ month: '2-digit',
273
+ year: 'numeric',
274
+ }).formatToParts(new Date(2023, 10, 22));
275
+ return parts
276
+ .map((p) => {
277
+ if (p.type === 'day')
278
+ return 'dd';
279
+ if (p.type === 'month')
280
+ return 'mm';
281
+ if (p.type === 'year')
282
+ return 'yy';
283
+ // Letras sueltas en un literal (p. ej. "г." en ruso) se escapan.
284
+ return /[a-zA-Z@!']/.test(p.value) ? `'${p.value.replace(/'/g, "''")}'` : p.value;
285
+ })
286
+ .join('');
287
+ }
288
+
9
289
  let nextUid = 0;
10
290
  /**
11
- * Selector de fecha de CaribeNG. Campo de solo lectura que abre un calendario
12
- * sobre CDK Overlay, con navegación por teclado y rango min/max. Integrado con
13
- * Angular Forms vía `ControlValueAccessor`. El modelo es `Date | null`.
291
+ * Selector de fecha de CaribeNG. Abre un calendario sobre CDK Overlay, con
292
+ * navegación por teclado (patrón WAI-ARIA *date picker dialog*), rango
293
+ * min/max, selección de rangos, hora y vistas de mes y año. Integrado con
294
+ * Angular Forms vía `ControlValueAccessor`.
295
+ *
296
+ * - `selectionMode="single"` (por defecto): `[(value)]` es `Date | null`.
297
+ * - `selectionMode="range"`: `[(range)]` es `[inicio, fin] | null`; muestra dos meses (`numberOfMonths`).
298
+ * - `view="month"` / `view="year"`: se elige un mes (día 1) o un año (1 de enero).
299
+ * - `showTime`: añade horas y minutos (`hourFormat` 24 o 12), también en rangos.
300
+ * - `dateFormat`: formato con la sintaxis de PrimeNG (`dd/mm/yy`, `d 'de' MM`…); `editable` permite escribir la fecha.
14
301
  *
15
302
  * @example
16
303
  * ```html
17
- * <cb-datepicker formControlName="fecha" locale="es-CO"
18
- * [minDate]="hoy" placeholder="Elige una fecha" />
304
+ * <cb-datepicker formControlName="fecha" locale="es-CO" [minDate]="hoy" />
305
+ * <cb-datepicker selectionMode="range" [(range)]="viaje" />
306
+ * <cb-datepicker showTime hourFormat="12" [(value)]="cita" />
307
+ * <cb-datepicker view="month" dateFormat="mm/yy" [(value)]="vencimiento" />
308
+ * <cb-datepicker editable dateFormat="dd/mm/yy" [(value)]="nacimiento" />
19
309
  * ```
20
310
  */
21
311
  class CbDatepicker {
312
+ formatOne(date) {
313
+ const pattern = this.pattern();
314
+ let text;
315
+ if (pattern) {
316
+ text = formatDate(date, pattern, this.names());
317
+ }
318
+ else {
319
+ const options = this.view() === 'year' ? { year: 'numeric' } : this.view() === 'month' ? { month: 'long', year: 'numeric' } : { dateStyle: 'medium' };
320
+ text = new Intl.DateTimeFormat(this.locale(), options).format(date);
321
+ }
322
+ return this.withTime() ? `${text} ${this.timeText(date)}` : text;
323
+ }
324
+ timeText(date) {
325
+ const h = date.getHours();
326
+ const m = this.pad(date.getMinutes());
327
+ return this.hourFormat() === '12' ? `${this.pad(h % 12 || 12)}:${m} ${h < 12 ? 'AM' : 'PM'}` : `${this.pad(h)}:${m}`;
328
+ }
329
+ // --- Vista de días -------------------------------------------------------------------
330
+ monthTitle(date) {
331
+ const label = new Intl.DateTimeFormat(this.locale(), { month: 'long', year: 'numeric' }).format(date);
332
+ return label.charAt(0).toUpperCase() + label.slice(1);
333
+ }
334
+ /** Inicio y fin del rango (o del rango en curso hasta el puntero), ordenados y en la unidad `unit`. */
335
+ rangeBounds(unit) {
336
+ const range = this.activeRange();
337
+ if (!range)
338
+ return [null, null, false];
339
+ const start = startOf(range[0], unit);
340
+ // Mientras se elige el fin, el rango llega hasta el elemento bajo el puntero.
341
+ const endSource = range[1] ?? (this.choosingEnd() ? this.hoverDate() : null);
342
+ const end = endSource ? startOf(endSource, unit) : null;
343
+ const [from, to] = start && end && end < start ? [end, start] : [start, end];
344
+ return [from, to, !!range[1]];
345
+ }
346
+ presetLabel(key) {
347
+ const t = this.t();
348
+ const labels = {
349
+ today: t.today,
350
+ yesterday: t.presetYesterday,
351
+ last7Days: t.presetLast7Days,
352
+ last30Days: t.presetLast30Days,
353
+ thisMonth: t.presetThisMonth,
354
+ lastMonth: t.presetLastMonth,
355
+ thisYear: t.presetThisYear,
356
+ lastYear: t.presetLastYear,
357
+ };
358
+ return labels[key];
359
+ }
360
+ /** Ajusta el atajo al modo (fecha o rango), a la unidad de `view` y, con hora, al día completo. */
361
+ presetValue(raw) {
362
+ const unit = this.view();
363
+ if (!this.isRange())
364
+ return startOf(Array.isArray(raw) ? raw[0] : raw, unit);
365
+ const [a, b] = Array.isArray(raw) ? [raw[0], raw[1] ?? raw[0]] : [raw, raw];
366
+ const start = startOf(a, unit);
367
+ const end = startOf(b, unit);
368
+ if (this.withTime())
369
+ end.setHours(23, 59);
370
+ return [start, end];
371
+ }
372
+ /** Aplica un atajo y cierra (con hora, el panel sigue abierto para ajustarla). */
373
+ applyPreset(value) {
374
+ this.commit(Array.isArray(value) ? [value[0], value[1]] : new Date(value));
375
+ const anchor = Array.isArray(value) ? value[0] : value;
376
+ this.viewDate.set(startOfMonth(anchor));
377
+ this.activeDate.set(startOf(anchor, this.view()));
378
+ this.panelView.set(this.view());
379
+ if (!this.withTime())
380
+ this.close();
381
+ }
382
+ dateOf(target) {
383
+ if (target === 'single')
384
+ return this.single();
385
+ const range = this.activeRange();
386
+ return (target === 'start' ? range?.[0] : range?.[1]) ?? null;
387
+ }
388
+ /** Hora y minutos de la fecha (o 00:00). */
389
+ timeOf(target) {
390
+ const d = this.dateOf(target);
391
+ return { hours: d?.getHours() ?? 0, minutes: d?.getMinutes() ?? 0 };
392
+ }
393
+ displayHour(target) {
394
+ const h = this.timeOf(target).hours;
395
+ return this.pad(this.hourFormat() === '12' ? h % 12 || 12 : h);
396
+ }
22
397
  constructor() {
23
- /** Fecha seleccionada (two-way opcional fuera de Angular Forms). */
398
+ /** Fecha seleccionada en modo `single` (two-way opcional fuera de Angular Forms). */
24
399
  this.value = model(null, /* @ts-ignore */
25
400
  ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
401
+ /** Rango seleccionado en modo `range` (two-way opcional fuera de Angular Forms). */
402
+ this.range = model(null, /* @ts-ignore */
403
+ ...(ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []));
26
404
  this.label = input('', /* @ts-ignore */
27
405
  ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
28
406
  this.hint = input('', /* @ts-ignore */
@@ -35,10 +413,11 @@ class CbDatepicker {
35
413
  ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
36
414
  this.invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: toBoolean });
37
415
  this.required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: toBoolean });
38
- /** Indicador de obligatoriedad de este campo; si se omite usa el global de `provideCaribeNG()`. */
416
+ /** Cómo se marca un campo obligatorio u opcional; por defecto el global de `provideCaribeNG`. */
39
417
  this.requiredIndicator = input(undefined, /* @ts-ignore */
40
418
  ...(ngDevMode ? [{ debugName: "requiredIndicator" }] : /* istanbul ignore next */ []));
41
- /** Estado de validación: inputs manuales + estado de Angular Forms. */
419
+ this.t = injectCbTranslation();
420
+ this.injector = inject(Injector);
42
421
  this.fieldState = cbFieldState({
43
422
  invalid: () => this.invalid(),
44
423
  required: () => this.required(),
@@ -50,24 +429,69 @@ class CbDatepicker {
50
429
  /** Locale BCP 47 para nombres y formato (por defecto el del navegador). */
51
430
  this.locale = input(undefined, /* @ts-ignore */
52
431
  ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
53
- /** Primer día de la semana: 0 = domingo (por defecto), 1 = lunes. */
432
+ /** Primer día de la semana: 0 = domingo, 1 = lunes. */
54
433
  this.firstDayOfWeek = input(0, /* @ts-ignore */
55
434
  ...(ngDevMode ? [{ debugName: "firstDayOfWeek" }] : /* istanbul ignore next */ []));
56
- /** Fecha mínima seleccionable. */
435
+ /** Fecha mínima elegible (incluida). */
57
436
  this.minDate = input(null, /* @ts-ignore */
58
437
  ...(ngDevMode ? [{ debugName: "minDate" }] : /* istanbul ignore next */ []));
59
- /** Fecha máxima seleccionable. */
438
+ /** Fecha máxima elegible (incluida). */
60
439
  this.maxDate = input(null, /* @ts-ignore */
61
440
  ...(ngDevMode ? [{ debugName: "maxDate" }] : /* istanbul ignore next */ []));
441
+ /** `single` (una fecha) o `range` (inicio y fin). */
442
+ this.selectionMode = input('single', /* @ts-ignore */
443
+ ...(ngDevMode ? [{ debugName: "selectionMode" }] : /* istanbul ignore next */ []));
444
+ /** Qué se elige: un día (`date`), un mes (`month`, día 1) o un año (`year`, 1 de enero). */
445
+ this.view = input('date', /* @ts-ignore */
446
+ ...(ngDevMode ? [{ debugName: "view" }] : /* istanbul ignore next */ []));
447
+ /** Meses visibles a la vez (vista de días). Por defecto 2 en rangos y 1 en fecha simple. */
448
+ this.numberOfMonths = input(undefined, /* @ts-ignore */
449
+ ...(ngDevMode ? [{ debugName: "numberOfMonths" }] : /* istanbul ignore next */ []));
450
+ /** Añade horas y minutos (en rangos, del inicio y del fin). Solo con `view="date"`. */
451
+ this.showTime = input(false, { ...(ngDevMode ? { debugName: "showTime" } : /* istanbul ignore next */ {}), transform: toBoolean });
452
+ /** Formato de la hora: 24 h o 12 h (AM/PM). */
453
+ this.hourFormat = input('24', /* @ts-ignore */
454
+ ...(ngDevMode ? [{ debugName: "hourFormat" }] : /* istanbul ignore next */ []));
455
+ /** Paso de los minutos (p. ej. 15). */
456
+ this.stepMinute = input(1, /* @ts-ignore */
457
+ ...(ngDevMode ? [{ debugName: "stepMinute" }] : /* istanbul ignore next */ []));
458
+ /**
459
+ * Formato de la fecha con la sintaxis de PrimeNG: `d`, `dd`, `o`, `oo`, `D`, `DD`,
460
+ * `m`, `mm`, `M`, `MM`, `y`, `yy`, `@`, `!` y `'texto'`. Sin él, el campo usa el
461
+ * formato largo del `locale` (o el numérico si es `editable`).
462
+ */
463
+ this.dateFormat = input(undefined, /* @ts-ignore */
464
+ ...(ngDevMode ? [{ debugName: "dateFormat" }] : /* istanbul ignore next */ []));
465
+ /** Permite escribir la fecha en el campo con `dateFormat` (Enter o al salir del campo la aplica). */
466
+ this.editable = input(false, { ...(ngDevMode ? { debugName: "editable" } : /* istanbul ignore next */ {}), transform: toBoolean });
467
+ /**
468
+ * Atajos junto al calendario: claves predefinidas (`'last7Days'`, `'thisMonth'`…)
469
+ * u objetos `{ label, value }`. En fecha simple, un atajo de rango elige su inicio.
470
+ */
471
+ this.presets = input([], /* @ts-ignore */
472
+ ...(ngDevMode ? [{ debugName: "presets" }] : /* istanbul ignore next */ []));
62
473
  this.open = signal(false, /* @ts-ignore */
63
474
  ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
64
- /** Primer día del mes mostrado. */
475
+ /** Día (o mes, o año) bajo el puntero: previsualiza el rango mientras se elige el final. */
476
+ this.hoverDate = signal(null, /* @ts-ignore */
477
+ ...(ngDevMode ? [{ debugName: "hoverDate" }] : /* istanbul ignore next */ []));
478
+ /** Primer mes mostrado (vista de días) o fecha de referencia del año / la década. */
65
479
  this.viewDate = signal(startOfMonth(new Date()), /* @ts-ignore */
66
480
  ...(ngDevMode ? [{ debugName: "viewDate" }] : /* istanbul ignore next */ []));
67
- /** Día con foco dentro del calendario. */
481
+ /** Día, mes o año con foco dentro del calendario. */
68
482
  this.activeDate = signal(stripTime(new Date()), /* @ts-ignore */
69
483
  ...(ngDevMode ? [{ debugName: "activeDate" }] : /* istanbul ignore next */ []));
70
- this.triggerRef = viewChild.required('trigger');
484
+ /** Vista del panel: puede pasar a meses o años para navegar aunque se elija un día. */
485
+ this.panelView = signal('date', /* @ts-ignore */
486
+ ...(ngDevMode ? [{ debugName: "panelView" }] : /* istanbul ignore next */ []));
487
+ /** Texto escrito en el campo (`editable`) que aún no se ha aplicado. */
488
+ this.draft = signal(null, /* @ts-ignore */
489
+ ...(ngDevMode ? [{ debugName: "draft" }] : /* istanbul ignore next */ []));
490
+ /** El texto escrito no es una fecha válida (o elegible). */
491
+ this.typedInvalid = signal(false, /* @ts-ignore */
492
+ ...(ngDevMode ? [{ debugName: "typedInvalid" }] : /* istanbul ignore next */ []));
493
+ /** Día del mes que se conserva al navegar por meses y años para volver a los días. */
494
+ this.navDay = 1;
71
495
  this.fieldRef = viewChild.required('field');
72
496
  this.panelRef = viewChild('panel', /* @ts-ignore */
73
497
  ...(ngDevMode ? [{ debugName: "panelRef" }] : /* istanbul ignore next */ []));
@@ -75,12 +499,21 @@ class CbDatepicker {
75
499
  ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
76
500
  this.isDisabled = computed(() => this.disabled() || this.cvaDisabled(), /* @ts-ignore */
77
501
  ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
502
+ this.isRange = computed(() => this.selectionMode() === 'range', /* @ts-ignore */
503
+ ...(ngDevMode ? [{ debugName: "isRange" }] : /* istanbul ignore next */ []));
504
+ /** La hora solo tiene sentido al elegir días. */
505
+ this.withTime = computed(() => this.showTime() && this.view() === 'date', /* @ts-ignore */
506
+ ...(ngDevMode ? [{ debugName: "withTime" }] : /* istanbul ignore next */ []));
507
+ this.monthCount = computed(() => clamp(this.numberOfMonths() ?? (this.isRange() ? 2 : 1), 1, 4), /* @ts-ignore */
508
+ ...(ngDevMode ? [{ debugName: "monthCount" }] : /* istanbul ignore next */ []));
509
+ this.fieldInvalid = computed(() => this.fieldState.invalid() || this.typedInvalid(), /* @ts-ignore */
510
+ ...(ngDevMode ? [{ debugName: "fieldInvalid" }] : /* istanbul ignore next */ []));
78
511
  this.errorId = computed(() => `${this.inputId()}-error`, /* @ts-ignore */
79
512
  ...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
80
513
  this.hintId = computed(() => `${this.inputId()}-hint`, /* @ts-ignore */
81
514
  ...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
82
515
  this.describedBy = computed(() => {
83
- if (this.fieldState.invalid() && this.error())
516
+ if (this.typedInvalid() || (this.fieldState.invalid() && this.error()))
84
517
  return this.errorId();
85
518
  if (this.hint())
86
519
  return this.hintId();
@@ -90,22 +523,49 @@ class CbDatepicker {
90
523
  this.positions = [
91
524
  { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4 },
92
525
  { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -4 },
526
+ { originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top', offsetY: 4 },
93
527
  ];
94
- this.display = computed(() => {
95
- const v = this.value();
96
- if (!v)
528
+ /** La fecha (modo simple) o `null`. */
529
+ this.single = computed(() => (this.isRange() ? null : this.value()), /* @ts-ignore */
530
+ ...(ngDevMode ? [{ debugName: "single" }] : /* istanbul ignore next */ []));
531
+ /** El rango (modo rango) o `null`. */
532
+ this.activeRange = computed(() => (this.isRange() ? this.range() : null), /* @ts-ignore */
533
+ ...(ngDevMode ? [{ debugName: "activeRange" }] : /* istanbul ignore next */ []));
534
+ /** Hay fecha inicial y falta la final. */
535
+ this.choosingEnd = computed(() => !!this.activeRange() && this.activeRange()[1] === null, /* @ts-ignore */
536
+ ...(ngDevMode ? [{ debugName: "choosingEnd" }] : /* istanbul ignore next */ []));
537
+ /** Hay algo elegido (para mostrar "Limpiar"). */
538
+ this.hasValue = computed(() => !!this.single() || !!this.activeRange(), /* @ts-ignore */
539
+ ...(ngDevMode ? [{ debugName: "hasValue" }] : /* istanbul ignore next */ []));
540
+ // --- Formato ------------------------------------------------------------------------
541
+ this.names = computed(() => dateNames(this.locale()), /* @ts-ignore */
542
+ ...(ngDevMode ? [{ debugName: "names" }] : /* istanbul ignore next */ []));
543
+ /** Formato con el que se escribe y se lee el campo (si hay `dateFormat` o es editable). */
544
+ this.pattern = computed(() => this.dateFormat() ?? (this.editable() ? localeDateFormat(this.locale(), this.view()) : null), /* @ts-ignore */
545
+ ...(ngDevMode ? [{ debugName: "pattern" }] : /* istanbul ignore next */ []));
546
+ /** Ayuda del formato para el placeholder y el error (`dd/mm/yyyy`, con la hora si la hay). */
547
+ this.formatHint = computed(() => {
548
+ const pattern = this.pattern();
549
+ if (!pattern)
97
550
  return '';
98
- return new Intl.DateTimeFormat(this.locale(), { dateStyle: 'medium' }).format(v);
551
+ const date = pattern.replace(/yy/g, 'yyyy').replace(/(^|[^y])y(?!y)/g, '$1yy').replace(/'/g, '');
552
+ return this.withTime() ? `${date} ${this.hourFormat() === '12' ? 'hh:mm AM' : 'HH:mm'}` : date;
99
553
  }, /* @ts-ignore */
100
- ...(ngDevMode ? [{ debugName: "display" }] : /* istanbul ignore next */ []));
101
- this.monthYearLabel = computed(() => {
102
- const label = new Intl.DateTimeFormat(this.locale(), {
103
- month: 'long',
104
- year: 'numeric',
105
- }).format(this.viewDate());
106
- return label.charAt(0).toUpperCase() + label.slice(1);
554
+ ...(ngDevMode ? [{ debugName: "formatHint" }] : /* istanbul ignore next */ []));
555
+ this.invalidMessage = computed(() => cbFormat(this.t().invalidDate, this.formatHint()), /* @ts-ignore */
556
+ ...(ngDevMode ? [{ debugName: "invalidMessage" }] : /* istanbul ignore next */ []));
557
+ this.display = computed(() => {
558
+ const range = this.activeRange();
559
+ if (range)
560
+ return `${this.formatOne(range[0])} – ${range[1] ? this.formatOne(range[1]) : ''}`.trim();
561
+ const single = this.single();
562
+ return single ? this.formatOne(single) : '';
107
563
  }, /* @ts-ignore */
108
- ...(ngDevMode ? [{ debugName: "monthYearLabel" }] : /* istanbul ignore next */ []));
564
+ ...(ngDevMode ? [{ debugName: "display" }] : /* istanbul ignore next */ []));
565
+ this.fieldText = computed(() => this.draft() ?? this.display(), /* @ts-ignore */
566
+ ...(ngDevMode ? [{ debugName: "fieldText" }] : /* istanbul ignore next */ []));
567
+ this.panelLabel = computed(() => (this.panelView() === 'date' ? this.monthTitle(this.viewDate()) : this.unitTitle()), /* @ts-ignore */
568
+ ...(ngDevMode ? [{ debugName: "panelLabel" }] : /* istanbul ignore next */ []));
109
569
  this.weekdays = computed(() => {
110
570
  const fmt = new Intl.DateTimeFormat(this.locale(), { weekday: 'short' });
111
571
  const long = new Intl.DateTimeFormat(this.locale(), { weekday: 'long' });
@@ -120,55 +580,146 @@ class CbDatepicker {
120
580
  });
121
581
  }, /* @ts-ignore */
122
582
  ...(ngDevMode ? [{ debugName: "weekdays" }] : /* istanbul ignore next */ []));
123
- this.weeks = computed(() => {
124
- const view = this.viewDate();
125
- const monthStart = startOfMonth(view);
126
- const first = this.firstDayOfWeek();
127
- const offset = (monthStart.getDay() - first + 7) % 7;
128
- const gridStart = new Date(monthStart);
129
- gridStart.setDate(monthStart.getDate() - offset);
583
+ this.months = computed(() => {
584
+ const count = this.monthCount();
585
+ const fullDate = new Intl.DateTimeFormat(this.locale(), { dateStyle: 'full' });
130
586
  const today = stripTime(new Date());
131
- const selected = this.value() ? stripTime(this.value()) : null;
587
+ const single = this.single() ? stripTime(this.single()) : null;
588
+ const [from, to, complete] = this.rangeBounds('date');
132
589
  const active = this.activeDate();
133
- const month = view.getMonth();
134
- const weeks = [];
135
- const cursor = new Date(gridStart);
136
- for (let w = 0; w < 6; w++) {
137
- const row = [];
138
- for (let d = 0; d < 7; d++) {
139
- const date = new Date(cursor);
140
- row.push({
141
- date,
142
- day: date.getDate(),
143
- otherMonth: date.getMonth() !== month,
144
- today: isSameDay(date, today),
145
- selected: selected != null && isSameDay(date, selected),
146
- disabled: this.isDisabledDate(date),
147
- active: isSameDay(date, active),
148
- });
149
- cursor.setDate(cursor.getDate() + 1);
590
+ const first = this.firstDayOfWeek();
591
+ return Array.from({ length: count }, (_, i) => {
592
+ const monthStart = addMonths(this.viewDate(), i);
593
+ const offset = (monthStart.getDay() - first + 7) % 7;
594
+ const cursor = new Date(monthStart);
595
+ cursor.setDate(monthStart.getDate() - offset);
596
+ const month = monthStart.getMonth();
597
+ const weeks = [];
598
+ for (let w = 0; w < 6; w++) {
599
+ const row = [];
600
+ for (let d = 0; d < 7; d++) {
601
+ const date = new Date(cursor);
602
+ const otherMonth = date.getMonth() !== month;
603
+ const hidden = count > 1 && otherMonth;
604
+ const isStart = !!from && isSameDay(date, from);
605
+ const isEnd = !!to && isSameDay(date, to);
606
+ row.push({
607
+ date,
608
+ day: date.getDate(),
609
+ label: fullDate.format(date),
610
+ otherMonth,
611
+ hidden,
612
+ today: isSameDay(date, today),
613
+ selected: (single != null && isSameDay(date, single)) || isStart || (isEnd && complete),
614
+ rangeStart: isStart && !!to,
615
+ rangeEnd: isEnd && !!from,
616
+ inRange: !!from && !!to && date > from && date < to,
617
+ disabled: this.isDisabledIn(date, 'date'),
618
+ // Con varios meses, el foco va al día dentro de su mes (no a la copia de otro mes).
619
+ active: !hidden && isSameDay(date, active) && (count === 1 || !otherMonth),
620
+ });
621
+ cursor.setDate(cursor.getDate() + 1);
622
+ }
623
+ weeks.push(row);
150
624
  }
151
- weeks.push(row);
152
- }
153
- return weeks;
625
+ return { date: monthStart, title: this.monthTitle(monthStart), weeks };
626
+ });
627
+ }, /* @ts-ignore */
628
+ ...(ngDevMode ? [{ debugName: "months" }] : /* istanbul ignore next */ []));
629
+ // --- Vistas de meses y años ----------------------------------------------------------------
630
+ /** Título de la vista de meses (el año) o de años (la década). */
631
+ this.unitTitle = computed(() => {
632
+ const year = this.viewDate().getFullYear();
633
+ if (this.panelView() === 'month')
634
+ return String(year);
635
+ const start = year - (year % 10);
636
+ return `${start} – ${start + 9}`;
637
+ }, /* @ts-ignore */
638
+ ...(ngDevMode ? [{ debugName: "unitTitle" }] : /* istanbul ignore next */ []));
639
+ this.unitRows = computed(() => {
640
+ const view = this.panelView();
641
+ const year = this.viewDate().getFullYear();
642
+ const active = this.activeDate();
643
+ const now = new Date();
644
+ const unit = view === 'month' ? 'month' : 'year';
645
+ const single = this.single();
646
+ const [from, to, complete] = this.rangeBounds(unit);
647
+ const names = this.names();
648
+ const dates = view === 'month'
649
+ ? Array.from({ length: 12 }, (_, i) => new Date(year, i, 1))
650
+ : Array.from({ length: 12 }, (_, i) => new Date(year - (year % 10) - 1 + i, 0, 1));
651
+ const cells = dates.map((date, i) => {
652
+ const same = (a) => !!a && sameUnit(a, date, unit);
653
+ const isStart = same(from);
654
+ const isEnd = same(to);
655
+ return {
656
+ date,
657
+ text: view === 'month' ? names.monthShort[i] : String(date.getFullYear()),
658
+ label: view === 'month' ? `${names.monthLong[i]} ${year}` : String(date.getFullYear()),
659
+ outside: view === 'year' && (i === 0 || i === 11),
660
+ current: same(now),
661
+ selected: same(single) || isStart || (isEnd && complete),
662
+ rangeStart: isStart && !!to,
663
+ rangeEnd: isEnd && !!from,
664
+ inRange: !!from && !!to && date > from && date < to,
665
+ disabled: this.isDisabledIn(date, unit),
666
+ active: sameUnit(active, date, unit),
667
+ };
668
+ });
669
+ return [0, 1, 2, 3].map((r) => cells.slice(r * 3, r * 3 + 3));
154
670
  }, /* @ts-ignore */
155
- ...(ngDevMode ? [{ debugName: "weeks" }] : /* istanbul ignore next */ []));
671
+ ...(ngDevMode ? [{ debugName: "unitRows" }] : /* istanbul ignore next */ []));
672
+ // --- Atajos -------------------------------------------------------------------------
673
+ this.presetItems = computed(() => {
674
+ const unit = this.view();
675
+ const current = this.isRange() ? this.activeRange() : this.single();
676
+ return this.presets().map((preset) => {
677
+ const label = typeof preset === 'string' ? this.presetLabel(preset) : preset.label;
678
+ const raw = typeof preset === 'string' ? presetRange(preset) : typeof preset.value === 'function' ? preset.value() : preset.value;
679
+ const value = this.presetValue(raw);
680
+ const [start, end] = Array.isArray(value) ? value : [value, value];
681
+ const selected = Array.isArray(value)
682
+ ? Array.isArray(current) && !!current[1] && sameUnit(current[0], value[0], unit) && sameUnit(current[1], value[1], unit)
683
+ : current instanceof Date && sameUnit(current, value, unit);
684
+ const disabled = this.isDisabledIn(start, unit) || (!!end && this.isDisabledIn(end, unit));
685
+ return { label, value, selected, disabled };
686
+ });
687
+ }, /* @ts-ignore */
688
+ ...(ngDevMode ? [{ debugName: "presetItems" }] : /* istanbul ignore next */ []));
689
+ // --- Hora --------------------------------------------------------------------------
690
+ this.timeRows = computed(() => {
691
+ if (!this.withTime())
692
+ return [];
693
+ if (!this.isRange())
694
+ return [{ target: 'single', label: `${this.t().hours} / ${this.t().minutes}`, caption: '', enabled: true }];
695
+ const range = this.activeRange();
696
+ return [
697
+ { target: 'start', label: this.t().startTime, caption: this.t().startTime, enabled: !!range },
698
+ { target: 'end', label: this.t().endTime, caption: this.t().endTime, enabled: !!range?.[1] },
699
+ ];
700
+ }, /* @ts-ignore */
701
+ ...(ngDevMode ? [{ debugName: "timeRows" }] : /* istanbul ignore next */ []));
156
702
  this.onChange = () => { };
157
703
  this.onTouched = () => { };
158
- // Al abrir, sincroniza vista y día activo con la selección (o hoy) y enfoca.
704
+ // Al abrir o cambiar de vista, enfoca el elemento activo.
159
705
  effect(() => {
160
- if (this.open()) {
161
- queueMicrotask(() => this.focusActiveDay());
162
- }
706
+ this.panelView();
707
+ if (this.open())
708
+ this.focusActive();
163
709
  });
164
710
  }
165
711
  // --- ControlValueAccessor --------------------------------------------------
166
712
  writeValue(value) {
167
- const d = value instanceof Date && !Number.isNaN(value.getTime()) ? value : null;
168
- this.value.set(d);
169
- const anchor = d ?? new Date();
713
+ const v = normalizeValue(value);
714
+ if (Array.isArray(v))
715
+ this.range.set(v);
716
+ else
717
+ this.value.set(v);
718
+ this.draft.set(null);
719
+ this.typedInvalid.set(false);
720
+ const anchor = (Array.isArray(v) ? v[0] : v) ?? new Date();
170
721
  this.viewDate.set(startOfMonth(anchor));
171
- this.activeDate.set(stripTime(anchor));
722
+ this.activeDate.set(startOf(anchor, this.view()));
172
723
  }
173
724
  registerOnChange(fn) {
174
725
  this.onChange = fn;
@@ -185,267 +736,735 @@ class CbDatepicker {
185
736
  return;
186
737
  this.open() ? this.close() : this.openPanel();
187
738
  }
739
+ onFieldClick() {
740
+ // Si se puede escribir, el clic coloca el cursor; el calendario se abre con el botón o Alt + ↓.
741
+ if (!this.editable())
742
+ this.toggle();
743
+ }
188
744
  openPanel() {
189
- const anchor = this.value() ?? new Date();
745
+ const anchor = this.activeRange()?.[0] ?? this.single() ?? new Date();
746
+ this.panelView.set(this.view());
190
747
  this.viewDate.set(startOfMonth(anchor));
191
- this.activeDate.set(stripTime(anchor));
748
+ this.activeDate.set(startOf(anchor, this.view()));
192
749
  this.open.set(true);
193
750
  }
194
751
  close() {
195
752
  if (!this.open())
196
753
  return;
197
754
  this.open.set(false);
755
+ this.hoverDate.set(null);
198
756
  this.onTouched();
199
757
  queueMicrotask(() => this.fieldRef().nativeElement.focus());
200
758
  }
201
759
  onBlur() {
760
+ if (this.editable())
761
+ this.applyDraft();
202
762
  if (!this.open())
203
763
  this.onTouched();
204
764
  }
205
765
  // --- Selección -------------------------------------------------------------
206
- selectDate(date) {
207
- if (this.isDisabledDate(date))
766
+ /** Elige un valor en la unidad de `view` (día, mes o año), en modo simple o rango. */
767
+ pick(date) {
768
+ const unit = this.view();
769
+ if (this.isDisabledIn(date, unit))
770
+ return;
771
+ const picked = startOf(date, unit);
772
+ this.activeDate.set(picked);
773
+ if (this.isRange()) {
774
+ const range = this.activeRange();
775
+ if (!range || range[1]) {
776
+ // Primer clic: inicio (con la hora que tuviera); el panel sigue abierto para elegir el fin.
777
+ if (this.withTime() && range)
778
+ picked.setHours(range[0].getHours(), range[0].getMinutes());
779
+ this.commit([picked, null]);
780
+ return;
781
+ }
782
+ const [start] = range;
783
+ if (this.withTime()) {
784
+ const end = range[1];
785
+ picked.setHours(end?.getHours() ?? 0, end?.getMinutes() ?? 0);
786
+ }
787
+ this.commit(picked < startOf(start, unit) ? [picked, start] : [start, picked]);
788
+ // Con hora el panel sigue abierto para ajustarla.
789
+ if (!this.withTime())
790
+ this.close();
208
791
  return;
209
- const picked = stripTime(date);
210
- this.value.set(picked);
211
- this.onChange(picked);
792
+ }
793
+ if (this.withTime()) {
794
+ // Con hora, se conserva la hora elegida y el panel sigue abierto.
795
+ const { hours, minutes } = this.timeOf('single');
796
+ picked.setHours(hours, minutes);
797
+ this.commit(picked);
798
+ return;
799
+ }
800
+ this.commit(picked);
212
801
  this.close();
213
802
  }
803
+ selectDate(date) {
804
+ this.pick(date);
805
+ }
806
+ /** Clic en un mes o un año: lo elige (si es la unidad de `view`) o baja a la vista siguiente. */
807
+ selectUnit(date) {
808
+ const view = this.panelView();
809
+ if (view === this.view()) {
810
+ this.pick(date);
811
+ return;
812
+ }
813
+ if (view === 'year') {
814
+ // Navegación: del año a sus meses.
815
+ this.viewDate.set(new Date(date.getFullYear(), this.viewDate().getMonth(), 1));
816
+ this.activeDate.set(new Date(date.getFullYear(), this.activeDate().getMonth(), 1));
817
+ this.panelView.set('month');
818
+ return;
819
+ }
820
+ // Navegación: del mes a sus días (mismo día del mes, ajustado al último si no existe).
821
+ const day = this.navDay;
822
+ const last = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
823
+ this.viewDate.set(startOfMonth(date));
824
+ this.activeDate.set(new Date(date.getFullYear(), date.getMonth(), Math.min(day, last)));
825
+ this.panelView.set('date');
826
+ }
827
+ /** Del título de un mes a la vista de meses de su año. */
828
+ showMonths(month) {
829
+ this.navDay = this.activeDate().getDate();
830
+ this.viewDate.set(startOfMonth(month));
831
+ this.activeDate.set(startOfMonth(this.isSameMonthShown(this.activeDate()) ? this.activeDate() : month));
832
+ this.panelView.set('month');
833
+ }
834
+ showYears() {
835
+ this.activeDate.set(new Date(this.activeDate().getFullYear(), 0, 1));
836
+ this.panelView.set('year');
837
+ }
838
+ isSameMonthShown(date) {
839
+ const first = this.viewDate();
840
+ const last = addMonths(first, this.monthCount() - 1);
841
+ return date >= first && date < addMonths(last, 1);
842
+ }
214
843
  goToday() {
215
- const today = stripTime(new Date());
844
+ const today = startOf(new Date(), this.panelView());
216
845
  this.viewDate.set(startOfMonth(today));
217
846
  this.activeDate.set(today);
218
- this.focusActiveDay();
847
+ this.focusActive();
219
848
  }
220
849
  clear() {
221
- this.value.set(null);
222
- this.onChange(null);
850
+ this.commit(null);
223
851
  this.close();
224
852
  }
225
853
  shiftMonth(delta) {
226
854
  this.viewDate.set(addMonths(this.viewDate(), delta));
227
855
  }
856
+ /** Año anterior / siguiente (vista de meses) o década (vista de años). */
857
+ shiftPage(delta) {
858
+ const years = this.panelView() === 'month' ? delta : delta * 10;
859
+ this.viewDate.set(addMonths(this.viewDate(), years * 12));
860
+ }
861
+ // --- Hora ------------------------------------------------------------------
862
+ setHour(target, raw) {
863
+ const n = Number.parseInt(raw, 10);
864
+ if (Number.isNaN(n))
865
+ return;
866
+ let hours;
867
+ if (this.hourFormat() === '12') {
868
+ const h12 = clamp(n, 1, 12) % 12;
869
+ hours = this.timeOf(target).hours < 12 ? h12 : h12 + 12;
870
+ }
871
+ else {
872
+ hours = clamp(n, 0, 23);
873
+ }
874
+ this.setTime(target, hours, this.timeOf(target).minutes);
875
+ }
876
+ setMinute(target, raw) {
877
+ const n = Number.parseInt(raw, 10);
878
+ if (!Number.isNaN(n))
879
+ this.setTime(target, this.timeOf(target).hours, clamp(n, 0, 59));
880
+ }
881
+ togglePeriod(target) {
882
+ const { hours, minutes } = this.timeOf(target);
883
+ this.setTime(target, (hours + 12) % 24, minutes);
884
+ }
885
+ setTime(target, hours, minutes) {
886
+ if (target === 'single') {
887
+ const base = new Date(this.single() ?? this.activeDate());
888
+ base.setHours(hours, minutes, 0, 0);
889
+ this.commit(base);
890
+ return;
891
+ }
892
+ const range = this.activeRange();
893
+ if (!range)
894
+ return;
895
+ const [start, end] = range;
896
+ if (target === 'start') {
897
+ const next = new Date(start);
898
+ next.setHours(hours, minutes, 0, 0);
899
+ this.commit([next, end]);
900
+ }
901
+ else if (end) {
902
+ const next = new Date(end);
903
+ next.setHours(hours, minutes, 0, 0);
904
+ this.commit([start, next]);
905
+ }
906
+ }
907
+ pad(n) {
908
+ return String(n).padStart(2, '0');
909
+ }
910
+ // --- Campo editable ---------------------------------------------------------
911
+ /** Aplica lo escrito: fecha válida y elegible → valor; vacío → `null`; si no, marca el error. */
912
+ applyDraft() {
913
+ const text = this.draft();
914
+ if (text === null)
915
+ return;
916
+ if (!text.trim()) {
917
+ this.draft.set(null);
918
+ this.typedInvalid.set(false);
919
+ if (this.hasValue())
920
+ this.commit(null);
921
+ return;
922
+ }
923
+ let parsed = null;
924
+ if (this.isRange()) {
925
+ const parts = text.split(/\s+[–-]\s+/);
926
+ const start = this.parseOne(parts[0] ?? '');
927
+ const end = parts[1] ? this.parseOne(parts[1]) : null;
928
+ if (start && (parts.length === 1 || end))
929
+ parsed = end && end < start ? [end, start] : [start, end];
930
+ }
931
+ else {
932
+ parsed = this.parseOne(text);
933
+ }
934
+ if (!parsed) {
935
+ this.typedInvalid.set(true);
936
+ return;
937
+ }
938
+ this.typedInvalid.set(false);
939
+ this.draft.set(null);
940
+ this.commit(parsed);
941
+ const anchor = Array.isArray(parsed) ? parsed[0] : parsed;
942
+ this.viewDate.set(startOfMonth(anchor));
943
+ this.activeDate.set(startOf(anchor, this.view()));
944
+ }
945
+ /** Lee una fecha (y la hora, si `showTime`) con el formato del campo. */
946
+ parseOne(text) {
947
+ const pattern = this.pattern();
948
+ if (!pattern)
949
+ return null;
950
+ let datePart = text.trim();
951
+ let hours = 0;
952
+ let minutes = 0;
953
+ if (this.withTime()) {
954
+ const time = /\s+(\d{1,2}):(\d{2})(?:\s*([AaPp])\.?\s?[Mm]\.?)?$/.exec(datePart);
955
+ if (time) {
956
+ datePart = datePart.slice(0, time.index);
957
+ hours = Number(time[1]);
958
+ minutes = Number(time[2]);
959
+ if (time[3]) {
960
+ if (hours < 1 || hours > 12)
961
+ return null;
962
+ hours = (hours % 12) + (time[3].toLowerCase() === 'p' ? 12 : 0);
963
+ }
964
+ if (hours > 23 || minutes > 59)
965
+ return null;
966
+ }
967
+ }
968
+ const date = parseDate(datePart, pattern, this.names());
969
+ if (!date)
970
+ return null;
971
+ const value = startOf(date, this.view());
972
+ if (this.isDisabledIn(value, this.view()))
973
+ return null;
974
+ value.setHours(hours, minutes);
975
+ return value;
976
+ }
228
977
  // --- Teclado ---------------------------------------------------------------
229
978
  onFieldKeydown(event) {
979
+ if (this.editable()) {
980
+ if (event.key === 'Enter') {
981
+ event.preventDefault();
982
+ this.applyDraft();
983
+ }
984
+ else if (event.key === 'ArrowDown') {
985
+ event.preventDefault();
986
+ this.applyDraft();
987
+ this.openPanel();
988
+ }
989
+ else if (event.key === 'Escape' && this.draft() !== null) {
990
+ event.preventDefault();
991
+ this.draft.set(null);
992
+ this.typedInvalid.set(false);
993
+ }
994
+ return;
995
+ }
230
996
  if (['ArrowDown', 'Enter', ' '].includes(event.key)) {
231
997
  event.preventDefault();
232
998
  this.openPanel();
233
999
  }
234
1000
  }
235
1001
  onPanelKeydown(event) {
1002
+ if (event.key === 'Escape') {
1003
+ event.preventDefault();
1004
+ // En la vista de meses o años abierta para navegar, Escape vuelve a la vista anterior.
1005
+ const view = this.panelView();
1006
+ if (view !== this.view()) {
1007
+ this.panelView.set(view === 'year' && this.view() === 'date' ? 'month' : this.view());
1008
+ return;
1009
+ }
1010
+ this.close();
1011
+ return;
1012
+ }
1013
+ const target = event.target;
1014
+ if (target.classList?.contains('cb-datepicker__unit')) {
1015
+ this.onUnitKeydown(event);
1016
+ return;
1017
+ }
1018
+ // Solo los días se mueven con las flechas (no los campos de hora ni los botones).
1019
+ if (!target.classList?.contains('cb-datepicker__day'))
1020
+ return;
1021
+ const active = this.activeDate();
236
1022
  let next = null;
237
1023
  switch (event.key) {
238
1024
  case 'ArrowLeft':
239
- next = addDays(this.activeDate(), -1);
1025
+ next = addDays(active, -1);
240
1026
  break;
241
1027
  case 'ArrowRight':
242
- next = addDays(this.activeDate(), 1);
1028
+ next = addDays(active, 1);
243
1029
  break;
244
1030
  case 'ArrowUp':
245
- next = addDays(this.activeDate(), -7);
1031
+ next = addDays(active, -7);
246
1032
  break;
247
1033
  case 'ArrowDown':
248
- next = addDays(this.activeDate(), 7);
1034
+ next = addDays(active, 7);
249
1035
  break;
250
1036
  case 'Home':
251
- next = startOfWeek(this.activeDate(), this.firstDayOfWeek());
1037
+ next = startOfWeek(active, this.firstDayOfWeek());
252
1038
  break;
253
1039
  case 'End':
254
- next = addDays(startOfWeek(this.activeDate(), this.firstDayOfWeek()), 6);
1040
+ next = addDays(startOfWeek(active, this.firstDayOfWeek()), 6);
255
1041
  break;
1042
+ // Re Pág / Av Pág: mismo día del mes anterior o siguiente; con Mayús, del año.
256
1043
  case 'PageUp':
257
- next = addMonths(this.activeDate(), -1);
1044
+ next = sameDayShifted(active, event.shiftKey ? -12 : -1);
258
1045
  break;
259
1046
  case 'PageDown':
260
- next = addMonths(this.activeDate(), 1);
1047
+ next = sameDayShifted(active, event.shiftKey ? 12 : 1);
261
1048
  break;
262
1049
  case 'Enter':
263
1050
  case ' ':
264
1051
  event.preventDefault();
265
- this.selectDate(this.activeDate());
266
- return;
267
- case 'Escape':
268
- event.preventDefault();
269
- this.close();
1052
+ this.selectDate(active);
270
1053
  return;
271
1054
  default:
272
1055
  return;
273
1056
  }
274
1057
  event.preventDefault();
275
1058
  this.activeDate.set(next);
276
- if (next.getMonth() !== this.viewDate().getMonth() || next.getFullYear() !== this.viewDate().getFullYear()) {
1059
+ if (this.choosingEnd())
1060
+ this.hoverDate.set(next);
1061
+ // Si el día queda fuera de los meses visibles, se desplaza la vista lo justo.
1062
+ const first = this.viewDate();
1063
+ const last = addMonths(first, this.monthCount() - 1);
1064
+ if (next < first)
277
1065
  this.viewDate.set(startOfMonth(next));
1066
+ else if (next >= addMonths(last, 1))
1067
+ this.viewDate.set(addMonths(startOfMonth(next), -(this.monthCount() - 1)));
1068
+ this.focusActive();
1069
+ }
1070
+ /** Flechas en la cuadrícula de meses o años (3 columnas). */
1071
+ onUnitKeydown(event) {
1072
+ const view = this.panelView();
1073
+ const active = this.activeDate();
1074
+ const step = (n) => (view === 'month' ? addMonths(active, n) : new Date(active.getFullYear() + n, 0, 1));
1075
+ let next;
1076
+ switch (event.key) {
1077
+ case 'ArrowLeft':
1078
+ next = step(-1);
1079
+ break;
1080
+ case 'ArrowRight':
1081
+ next = step(1);
1082
+ break;
1083
+ case 'ArrowUp':
1084
+ next = step(-3);
1085
+ break;
1086
+ case 'ArrowDown':
1087
+ next = step(3);
1088
+ break;
1089
+ case 'PageUp':
1090
+ next = step(view === 'month' ? -12 : -10);
1091
+ break;
1092
+ case 'PageDown':
1093
+ next = step(view === 'month' ? 12 : 10);
1094
+ break;
1095
+ case 'Home':
1096
+ next = view === 'month' ? new Date(active.getFullYear(), 0, 1) : new Date(active.getFullYear() - (active.getFullYear() % 10), 0, 1);
1097
+ break;
1098
+ case 'End':
1099
+ next = view === 'month' ? new Date(active.getFullYear(), 11, 1) : new Date(active.getFullYear() - (active.getFullYear() % 10) + 9, 0, 1);
1100
+ break;
1101
+ case 'Enter':
1102
+ case ' ':
1103
+ event.preventDefault();
1104
+ this.selectUnit(active);
1105
+ return;
1106
+ default:
1107
+ return;
278
1108
  }
279
- this.focusActiveDay();
1109
+ event.preventDefault();
1110
+ this.activeDate.set(next);
1111
+ if (this.choosingEnd())
1112
+ this.hoverDate.set(next);
1113
+ const shown = this.viewDate().getFullYear();
1114
+ const visible = view === 'month' ? next.getFullYear() === shown : Math.floor(next.getFullYear() / 10) === Math.floor(shown / 10);
1115
+ if (!visible)
1116
+ this.viewDate.set(new Date(next.getFullYear(), view === 'month' ? 0 : this.viewDate().getMonth(), 1));
1117
+ this.focusActive();
280
1118
  }
281
- focusActiveDay() {
282
- queueMicrotask(() => {
283
- const el = this.panelRef()?.nativeElement.querySelector('.cb-datepicker__day.cb-is-active');
284
- el?.focus();
285
- });
1119
+ /** Enfoca el elemento activo tras el render: el panel vive en un overlay que se pinta en el ciclo siguiente. */
1120
+ focusActive() {
1121
+ afterNextRender(() => this.panelRef()
1122
+ ?.nativeElement.querySelector('.cb-datepicker__day.cb-is-active, .cb-datepicker__unit.cb-is-active')
1123
+ ?.focus(), { injector: this.injector });
286
1124
  }
287
1125
  // --- Helpers ---------------------------------------------------------------
288
- isDisabledDate(date) {
1126
+ /** Guarda en el modelo del modo activo y avisa a Angular Forms. */
1127
+ commit(value) {
1128
+ this.draft.set(null);
1129
+ this.typedInvalid.set(false);
1130
+ if (this.isRange())
1131
+ this.range.set(value);
1132
+ else
1133
+ this.value.set(value);
1134
+ this.onChange(value);
1135
+ }
1136
+ /** El día, mes o año queda entero fuera de [minDate, maxDate]. */
1137
+ isDisabledIn(date, unit) {
289
1138
  const min = this.minDate();
290
1139
  const max = this.maxDate();
291
- const d = stripTime(date);
292
- if (min && d < stripTime(min))
1140
+ const start = startOf(date, unit);
1141
+ const end = unit === 'date' ? start : unit === 'month' ? addDays(addMonths(start, 1), -1) : new Date(start.getFullYear(), 11, 31);
1142
+ if (min && end < stripTime(min))
293
1143
  return true;
294
- if (max && d > stripTime(max))
1144
+ if (max && start > stripTime(max))
295
1145
  return true;
296
1146
  return false;
297
1147
  }
298
1148
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: CbDatepicker, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
299
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: CbDatepicker, isStandalone: true, selector: "cb-datepicker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, requiredIndicator: { classPropertyName: "requiredIndicator", publicName: "requiredIndicator", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, firstDayOfWeek: { classPropertyName: "firstDayOfWeek", publicName: "firstDayOfWeek", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { classAttribute: "cb-datepicker-host" }, providers: [
1149
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: CbDatepicker, isStandalone: true, selector: "cb-datepicker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, range: { classPropertyName: "range", publicName: "range", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, requiredIndicator: { classPropertyName: "requiredIndicator", publicName: "requiredIndicator", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, firstDayOfWeek: { classPropertyName: "firstDayOfWeek", publicName: "firstDayOfWeek", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: false, transformFunction: null }, numberOfMonths: { classPropertyName: "numberOfMonths", publicName: "numberOfMonths", isSignal: true, isRequired: false, transformFunction: null }, showTime: { classPropertyName: "showTime", publicName: "showTime", isSignal: true, isRequired: false, transformFunction: null }, hourFormat: { classPropertyName: "hourFormat", publicName: "hourFormat", isSignal: true, isRequired: false, transformFunction: null }, stepMinute: { classPropertyName: "stepMinute", publicName: "stepMinute", isSignal: true, isRequired: false, transformFunction: null }, dateFormat: { classPropertyName: "dateFormat", publicName: "dateFormat", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, presets: { classPropertyName: "presets", publicName: "presets", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", range: "rangeChange" }, host: { classAttribute: "cb-datepicker-host" }, providers: [
300
1150
  {
301
1151
  provide: NG_VALUE_ACCESSOR,
302
1152
  useExisting: forwardRef(() => CbDatepicker),
303
1153
  multi: true,
304
1154
  },
305
- ], viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["trigger"], descendants: true, isSignal: true }, { propertyName: "fieldRef", first: true, predicate: ["field"], descendants: true, isSignal: true }, { propertyName: "panelRef", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: `
306
- @if (label()) {
307
- <label class="cb-datepicker__label" [attr.for]="inputId()">
308
- {{ label() }}
309
- @if (fieldState.indicator(); as mark) {
310
- <span
311
- [class]="'cb-datepicker__required cb-datepicker__required--' + mark.kind"
312
- [attr.aria-hidden]="mark.kind === 'optional' ? null : 'true'"
313
- >{{ mark.text }}</span>
314
- }
315
- </label>
316
- }
317
-
318
- <div
319
- #trigger
320
- class="cb-datepicker"
321
- [class]="'cb-datepicker--' + size()"
322
- [class.cb-is-open]="open()"
323
- [class.cb-is-invalid]="fieldState.invalid()"
324
- [class.cb-is-disabled]="isDisabled()"
325
- >
326
- <input
327
- #field
328
- class="cb-datepicker__field"
329
- [id]="inputId()"
330
- type="text"
331
- readonly
332
- role="combobox"
333
- aria-haspopup="dialog"
334
- [attr.aria-expanded]="open()"
335
- [attr.aria-invalid]="fieldState.invalid() ? 'true' : null"
336
- [attr.aria-describedby]="describedBy()"
337
- [value]="display()"
338
- [placeholder]="placeholder()"
339
- [disabled]="isDisabled()"
340
- [required]="fieldState.required()"
341
- (click)="toggle()"
342
- (keydown)="onFieldKeydown($event)"
343
- (blur)="onBlur()"
344
- />
345
- <button
346
- type="button"
347
- class="cb-datepicker__btn"
348
- tabindex="-1"
349
- aria-label="Abrir calendario"
350
- [disabled]="isDisabled()"
351
- (click)="toggle()"
352
- >
353
- <cb-icon name="calendar" [size]="18" aria-hidden="true" />
354
- </button>
355
- </div>
356
-
357
- <ng-template
358
- cdkConnectedOverlay
359
- [cdkConnectedOverlayOrigin]="trigger"
360
- [cdkConnectedOverlayOpen]="open()"
361
- [cdkConnectedOverlayPositions]="positions"
362
- [cdkConnectedOverlayHasBackdrop]="false"
363
- (overlayOutsideClick)="close()"
364
- (detach)="close()"
365
- >
366
- <div
367
- #panel
368
- class="cb-datepicker__panel"
369
- role="dialog"
370
- aria-modal="false"
371
- [attr.aria-label]="monthYearLabel()"
372
- tabindex="-1"
373
- (keydown)="onPanelKeydown($event)"
374
- >
375
- <div class="cb-datepicker__header">
376
- <button
377
- type="button"
378
- class="cb-datepicker__nav"
379
- aria-label="Mes anterior"
380
- (click)="shiftMonth(-1)"
381
- >
382
- <cb-icon name="chevron-left" [size]="18" aria-hidden="true" />
383
- </button>
384
- <span class="cb-datepicker__title" aria-live="polite">{{ monthYearLabel() }}</span>
385
- <button
386
- type="button"
387
- class="cb-datepicker__nav"
388
- aria-label="Mes siguiente"
389
- (click)="shiftMonth(1)"
390
- >
391
- <cb-icon name="chevron-right" [size]="18" aria-hidden="true" />
392
- </button>
393
- </div>
394
-
395
- <table class="cb-datepicker__grid" role="grid">
396
- <thead>
397
- <tr>
398
- @for (wd of weekdays(); track wd.index) {
399
- <th class="cb-datepicker__weekday" scope="col" [attr.abbr]="wd.long">{{ wd.short }}</th>
400
- }
401
- </tr>
402
- </thead>
403
- <tbody>
404
- @for (week of weeks(); track week[0].date.getTime()) {
405
- <tr>
406
- @for (cell of week; track cell.date.getTime()) {
407
- <td class="cb-datepicker__cell" role="gridcell">
408
- <button
409
- type="button"
410
- class="cb-datepicker__day"
411
- [class.cb-is-other-month]="cell.otherMonth"
412
- [class.cb-is-today]="cell.today"
413
- [class.cb-is-selected]="cell.selected"
414
- [class.cb-is-active]="cell.active"
415
- [disabled]="cell.disabled"
416
- [attr.tabindex]="cell.active ? 0 : -1"
417
- [attr.aria-selected]="cell.selected"
418
- [attr.aria-current]="cell.today ? 'date' : null"
419
- (click)="selectDate(cell.date)"
420
- >
421
- {{ cell.day }}
422
- </button>
423
- </td>
424
- }
425
- </tr>
426
- }
427
- </tbody>
428
- </table>
429
-
430
- <div class="cb-datepicker__footer">
431
- <button type="button" class="cb-datepicker__today" (click)="goToday()">Hoy</button>
432
- @if (value()) {
433
- <button type="button" class="cb-datepicker__clear" (click)="clear()">Limpiar</button>
434
- }
435
- </div>
436
- </div>
437
- </ng-template>
438
-
439
- @if (fieldState.invalid() && error()) {
440
- <p class="cb-datepicker__message cb-datepicker__message--error" [id]="errorId()" role="alert">
441
- {{ error() }}
442
- </p>
443
- } @else if (hint()) {
444
- <p class="cb-datepicker__message cb-datepicker__message--hint" [id]="hintId()">
445
- {{ hint() }}
446
- </p>
447
- }
448
- `, isInline: true, styles: [":host{display:block}.cb-datepicker__label{display:block;margin-bottom:var(--cb-space-1);font-family:var(--cb-font-sans);font-size:var(--cb-text-sm);font-weight:var(--cb-font-weight-medium);color:var(--cb-color-text)}.cb-datepicker__required{color:var(--cb-color-danger);margin-left:2px}.cb-datepicker{position:relative;display:flex;align-items:stretch;box-sizing:border-box;width:100%;background-color:var(--cb-color-bg);border:var(--cb-border-width) solid var(--cb-color-border-strong);border-radius:var(--cb-radius-md);transition:border-color var(--cb-duration-fast) var(--cb-ease),box-shadow var(--cb-duration-fast) var(--cb-ease)}.cb-datepicker:hover:not(.cb-is-disabled):not(:focus-within){border-color:var(--cb-color-text-muted)}.cb-datepicker:focus-within,.cb-datepicker.cb-is-open{border-color:var(--cb-color-primary-500);box-shadow:0 0 0 3px color-mix(in srgb,var(--cb-color-primary-500) 25%,transparent)}.cb-datepicker.cb-is-invalid{border-color:var(--cb-color-danger)}.cb-datepicker.cb-is-invalid:focus-within,.cb-datepicker.cb-is-invalid.cb-is-open{box-shadow:0 0 0 3px color-mix(in srgb,var(--cb-color-danger) 25%,transparent)}.cb-datepicker.cb-is-disabled{background-color:var(--cb-color-surface);opacity:.8}.cb-datepicker__field{flex:1 1 auto;min-width:0;box-sizing:border-box;font-family:var(--cb-font-sans);color:var(--cb-color-text);background:transparent;border:none;border-radius:inherit;cursor:pointer}.cb-datepicker__field::placeholder{color:var(--cb-color-text-muted)}.cb-datepicker__field:focus{outline:none}.cb-datepicker__field:disabled{color:var(--cb-color-text-muted);cursor:not-allowed}.cb-datepicker--sm .cb-datepicker__field{min-height:2rem;padding:var(--cb-space-1) var(--cb-space-2);font-size:var(--cb-text-sm)}.cb-datepicker--md .cb-datepicker__field{min-height:2.5rem;padding:var(--cb-space-2) var(--cb-space-3);font-size:var(--cb-text-base)}.cb-datepicker--lg .cb-datepicker__field{min-height:3rem;padding:var(--cb-space-3) var(--cb-space-4);font-size:var(--cb-text-lg)}.cb-datepicker__btn{display:flex;flex:none;align-items:center;justify-content:center;width:2.5rem;padding:0;color:var(--cb-color-text-muted);background:transparent;border:none;border-left:var(--cb-border-width) solid var(--cb-color-border);cursor:pointer}.cb-datepicker__btn:hover:not(:disabled){color:var(--cb-color-text)}.cb-datepicker__btn:disabled{cursor:not-allowed;opacity:.5}.cb-datepicker__btn cb-icon{color:currentColor}.cb-datepicker__panel{padding:var(--cb-space-3);background-color:var(--cb-color-surface-raised);border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-md);box-shadow:var(--cb-shadow-md);font-family:var(--cb-font-sans)}.cb-datepicker__panel:focus{outline:none}.cb-datepicker__header{display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--cb-space-2)}.cb-datepicker__title{font-size:var(--cb-text-base);font-weight:var(--cb-font-weight-semibold);color:var(--cb-color-text-strong)}.cb-datepicker__nav{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;color:var(--cb-color-text-muted);background:transparent;border:none;border-radius:var(--cb-radius-sm);cursor:pointer}.cb-datepicker__nav:hover{background-color:var(--cb-color-surface-100);color:var(--cb-color-text)}.cb-datepicker__nav:focus-visible{outline:none;box-shadow:0 0 0 2px color-mix(in srgb,var(--cb-color-primary-500) 35%,transparent)}.cb-datepicker__nav cb-icon{color:currentColor}.cb-datepicker__grid{border-collapse:collapse;width:100%}.cb-datepicker__weekday{padding:var(--cb-space-1);font-size:var(--cb-text-xs);font-weight:var(--cb-font-weight-medium);color:var(--cb-color-text-muted);text-align:center;text-transform:capitalize}.cb-datepicker__cell{padding:1px;text-align:center}.cb-datepicker__day{display:flex;align-items:center;justify-content:center;width:2.25rem;height:2.25rem;margin:0 auto;font-size:var(--cb-text-sm);color:var(--cb-color-text);background:transparent;border:none;border-radius:var(--cb-radius-full);cursor:pointer;transition:background-color var(--cb-duration-fast) var(--cb-ease)}.cb-datepicker__day:hover:not(:disabled):not(.cb-is-selected){background-color:var(--cb-color-surface-100)}.cb-datepicker__day:focus-visible{outline:none;box-shadow:0 0 0 2px color-mix(in srgb,var(--cb-color-primary-500) 45%,transparent)}.cb-datepicker__day.cb-is-other-month{color:var(--cb-color-text-muted);opacity:.6}.cb-datepicker__day.cb-is-today:not(.cb-is-selected){font-weight:var(--cb-font-weight-bold);color:var(--cb-color-primary-600)}.cb-datepicker__day.cb-is-selected{color:var(--cb-color-primary-contrast);background-color:var(--cb-color-primary-500);font-weight:var(--cb-font-weight-semibold)}.cb-datepicker__day:disabled{color:var(--cb-color-text-muted);opacity:.35;cursor:not-allowed}.cb-datepicker__footer{display:flex;justify-content:space-between;margin-top:var(--cb-space-2);padding-top:var(--cb-space-2);border-top:1px solid var(--cb-color-border)}.cb-datepicker__today,.cb-datepicker__clear{padding:var(--cb-space-1) var(--cb-space-2);font-family:inherit;font-size:var(--cb-text-sm);font-weight:var(--cb-font-weight-medium);color:var(--cb-color-primary-600);background:transparent;border:none;border-radius:var(--cb-radius-sm);cursor:pointer}.cb-datepicker__today:hover,.cb-datepicker__clear:hover{background-color:var(--cb-color-surface-100)}.cb-datepicker__clear{color:var(--cb-color-text-muted)}.cb-datepicker__message{margin:var(--cb-space-1) 0 0;font-size:var(--cb-text-sm);line-height:var(--cb-line-height-normal)}.cb-datepicker__message--hint{color:var(--cb-color-text-muted)}.cb-datepicker__message--error{color:var(--cb-color-danger)}.cb-datepicker__required--required,.cb-datepicker__required--optional{margin-left:.25rem;color:var(--cb-color-text-muted);font-size:.85em;font-weight:var(--cb-font-weight-normal, 400)}\n"], dependencies: [{ kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation", "cdkConnectedOverlayUsePopover", "cdkConnectedOverlayMatchWidth", "cdkConnectedOverlay"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "component", type: CbIcon, selector: "cb-icon", inputs: ["name", "size", "spin", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1155
+ ], viewQueries: [{ propertyName: "fieldRef", first: true, predicate: ["field"], descendants: true, isSignal: true }, { propertyName: "panelRef", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: `
1156
+ @if (label()) {
1157
+ <label class="cb-datepicker__label" [attr.for]="inputId()">
1158
+ {{ label() }}
1159
+ @if (fieldState.indicator(); as mark) {
1160
+ <span
1161
+ [class]="'cb-datepicker__required cb-datepicker__required--' + mark.kind"
1162
+ [attr.aria-hidden]="mark.kind === 'optional' ? null : 'true'"
1163
+ >{{ mark.text }}</span>
1164
+ }
1165
+ </label>
1166
+ }
1167
+
1168
+ <div
1169
+ #trigger
1170
+ class="cb-datepicker"
1171
+ [class]="'cb-datepicker--' + size()"
1172
+ [class.cb-is-open]="open()"
1173
+ [class.cb-is-invalid]="fieldInvalid()"
1174
+ [class.cb-is-disabled]="isDisabled()"
1175
+ >
1176
+ <input
1177
+ #field
1178
+ class="cb-datepicker__field"
1179
+ [id]="inputId()"
1180
+ type="text"
1181
+ [readOnly]="!editable()"
1182
+ role="combobox"
1183
+ aria-haspopup="dialog"
1184
+ [attr.aria-expanded]="open()"
1185
+ [attr.aria-invalid]="fieldInvalid() ? 'true' : null"
1186
+ [attr.aria-describedby]="describedBy()"
1187
+ [attr.autocomplete]="editable() ? 'off' : null"
1188
+ [value]="fieldText()"
1189
+ [placeholder]="placeholder() || (editable() ? formatHint() : '')"
1190
+ [disabled]="isDisabled()"
1191
+ [required]="fieldState.required()"
1192
+ (click)="onFieldClick()"
1193
+ (input)="draft.set($any($event.target).value)"
1194
+ (keydown)="onFieldKeydown($event)"
1195
+ (blur)="onBlur()"
1196
+ />
1197
+ <button
1198
+ type="button"
1199
+ class="cb-datepicker__btn"
1200
+ tabindex="-1"
1201
+ [attr.aria-label]="t().openCalendar"
1202
+ [disabled]="isDisabled()"
1203
+ (click)="toggle()"
1204
+ >
1205
+ <cb-icon name="calendar" [size]="18" aria-hidden="true" />
1206
+ </button>
1207
+ </div>
1208
+
1209
+ <ng-template
1210
+ cdkConnectedOverlay
1211
+ [cdkConnectedOverlayOrigin]="trigger"
1212
+ [cdkConnectedOverlayOpen]="open()"
1213
+ [cdkConnectedOverlayPositions]="positions"
1214
+ [cdkConnectedOverlayHasBackdrop]="false"
1215
+ (overlayOutsideClick)="close()"
1216
+ (detach)="close()"
1217
+ >
1218
+ <div
1219
+ #panel
1220
+ class="cb-datepicker__panel"
1221
+ [class.cb-datepicker__panel--multi]="panelView() === 'date' && monthCount() > 1"
1222
+ [class.cb-datepicker__panel--presets]="presetItems().length > 0"
1223
+ role="dialog"
1224
+ aria-modal="false"
1225
+ [attr.aria-label]="panelLabel()"
1226
+ tabindex="-1"
1227
+ (keydown)="onPanelKeydown($event)"
1228
+ >
1229
+ @if (presetItems().length) {
1230
+ <div class="cb-datepicker__presets" role="group" [attr.aria-label]="t().datePresets">
1231
+ @for (item of presetItems(); track $index) {
1232
+ <button
1233
+ type="button"
1234
+ class="cb-datepicker__preset"
1235
+ [class.cb-is-selected]="item.selected"
1236
+ [attr.aria-pressed]="item.selected"
1237
+ [disabled]="item.disabled"
1238
+ (click)="applyPreset(item.value)"
1239
+ >
1240
+ {{ item.label }}
1241
+ </button>
1242
+ }
1243
+ </div>
1244
+ }
1245
+ <div class="cb-datepicker__main">
1246
+ @switch (panelView()) {
1247
+ @case ('date') {
1248
+ <div class="cb-datepicker__months">
1249
+ @for (m of months(); track m.date.getTime(); let first = $first, last = $last) {
1250
+ <div class="cb-datepicker__month">
1251
+ <div class="cb-datepicker__header">
1252
+ @if (first) {
1253
+ <button type="button" class="cb-datepicker__nav" [attr.aria-label]="t().previousMonth" (click)="shiftMonth(-1)">
1254
+ <cb-icon name="chevron-left" [size]="18" aria-hidden="true" />
1255
+ </button>
1256
+ } @else {
1257
+ <span class="cb-datepicker__nav-spacer"></span>
1258
+ }
1259
+ <button
1260
+ type="button"
1261
+ class="cb-datepicker__title"
1262
+ [attr.aria-label]="m.title + '. ' + t().chooseMonth"
1263
+ (click)="showMonths(m.date)"
1264
+ >{{ m.title }}</button>
1265
+ @if (last) {
1266
+ <button type="button" class="cb-datepicker__nav" [attr.aria-label]="t().nextMonth" (click)="shiftMonth(1)">
1267
+ <cb-icon name="chevron-right" [size]="18" aria-hidden="true" />
1268
+ </button>
1269
+ } @else {
1270
+ <span class="cb-datepicker__nav-spacer"></span>
1271
+ }
1272
+ </div>
1273
+
1274
+ <table
1275
+ class="cb-datepicker__grid"
1276
+ role="grid"
1277
+ [attr.aria-label]="m.title"
1278
+ [class.cb-datepicker__grid--range]="isRange()"
1279
+ (mouseleave)="hoverDate.set(null)"
1280
+ >
1281
+ <thead>
1282
+ <tr>
1283
+ @for (wd of weekdays(); track wd.index) {
1284
+ <th class="cb-datepicker__weekday" scope="col" [attr.abbr]="wd.long">{{ wd.short }}</th>
1285
+ }
1286
+ </tr>
1287
+ </thead>
1288
+ <tbody>
1289
+ @for (week of m.weeks; track week[0].date.getTime()) {
1290
+ <tr>
1291
+ @for (cell of week; track cell.date.getTime()) {
1292
+ @if (cell.hidden) {
1293
+ <td class="cb-datepicker__cell" role="gridcell"></td>
1294
+ } @else {
1295
+ <td
1296
+ class="cb-datepicker__cell"
1297
+ role="gridcell"
1298
+ [class.cb-is-in-range]="cell.inRange"
1299
+ [class.cb-is-range-start]="cell.rangeStart"
1300
+ [class.cb-is-range-end]="cell.rangeEnd"
1301
+ [attr.aria-selected]="cell.selected || cell.inRange"
1302
+ >
1303
+ <!-- aria-disabled (no disabled) para que las flechas puedan pasar por los días no elegibles. -->
1304
+ <button
1305
+ type="button"
1306
+ class="cb-datepicker__day"
1307
+ [class.cb-is-other-month]="cell.otherMonth"
1308
+ [class.cb-is-today]="cell.today"
1309
+ [class.cb-is-selected]="cell.selected"
1310
+ [class.cb-is-active]="cell.active"
1311
+ [class.cb-is-disabled]="cell.disabled"
1312
+ [attr.aria-disabled]="cell.disabled || null"
1313
+ [attr.aria-label]="cell.label"
1314
+ [attr.tabindex]="cell.active ? 0 : -1"
1315
+ [attr.aria-current]="cell.today ? 'date' : null"
1316
+ (click)="selectDate(cell.date)"
1317
+ (mouseenter)="hoverDate.set(cell.date)"
1318
+ >
1319
+ {{ cell.day }}
1320
+ </button>
1321
+ </td>
1322
+ }
1323
+ }
1324
+ </tr>
1325
+ }
1326
+ </tbody>
1327
+ </table>
1328
+ </div>
1329
+ }
1330
+ </div>
1331
+ }
1332
+ @default {
1333
+ <div class="cb-datepicker__header">
1334
+ <button
1335
+ type="button"
1336
+ class="cb-datepicker__nav"
1337
+ [attr.aria-label]="panelView() === 'month' ? t().previousYear : t().previousDecade"
1338
+ (click)="shiftPage(-1)"
1339
+ >
1340
+ <cb-icon name="chevron-left" [size]="18" aria-hidden="true" />
1341
+ </button>
1342
+ @if (panelView() === 'month') {
1343
+ <button type="button" class="cb-datepicker__title" [attr.aria-label]="unitTitle() + '. ' + t().chooseYear" (click)="showYears()">
1344
+ {{ unitTitle() }}
1345
+ </button>
1346
+ } @else {
1347
+ <span class="cb-datepicker__title cb-datepicker__title--static">{{ unitTitle() }}</span>
1348
+ }
1349
+ <button
1350
+ type="button"
1351
+ class="cb-datepicker__nav"
1352
+ [attr.aria-label]="panelView() === 'month' ? t().nextYear : t().nextDecade"
1353
+ (click)="shiftPage(1)"
1354
+ >
1355
+ <cb-icon name="chevron-right" [size]="18" aria-hidden="true" />
1356
+ </button>
1357
+ </div>
1358
+ <div class="cb-datepicker__units" role="grid" [attr.aria-label]="unitTitle()" (mouseleave)="hoverDate.set(null)">
1359
+ @for (row of unitRows(); track $index) {
1360
+ <div class="cb-datepicker__unit-row" role="row">
1361
+ @for (cell of row; track cell.date.getTime()) {
1362
+ <div
1363
+ class="cb-datepicker__cell cb-datepicker__unit-cell"
1364
+ role="gridcell"
1365
+ [class.cb-is-in-range]="cell.inRange"
1366
+ [class.cb-is-range-start]="cell.rangeStart"
1367
+ [class.cb-is-range-end]="cell.rangeEnd"
1368
+ [attr.aria-selected]="cell.selected || cell.inRange"
1369
+ >
1370
+ <button
1371
+ type="button"
1372
+ class="cb-datepicker__unit"
1373
+ [class.cb-is-other-month]="cell.outside"
1374
+ [class.cb-is-today]="cell.current"
1375
+ [class.cb-is-selected]="cell.selected"
1376
+ [class.cb-is-active]="cell.active"
1377
+ [class.cb-is-disabled]="cell.disabled"
1378
+ [attr.aria-disabled]="cell.disabled || null"
1379
+ [attr.aria-label]="cell.label"
1380
+ [attr.aria-current]="cell.current ? (panelView() === 'month' ? 'date' : 'true') : null"
1381
+ [attr.tabindex]="cell.active ? 0 : -1"
1382
+ (click)="selectUnit(cell.date)"
1383
+ (mouseenter)="hoverDate.set(cell.date)"
1384
+ >
1385
+ {{ cell.text }}
1386
+ </button>
1387
+ </div>
1388
+ }
1389
+ </div>
1390
+ }
1391
+ </div>
1392
+ }
1393
+ }
1394
+
1395
+ @if (isRange()) {
1396
+ <span class="cb-datepicker__sr" aria-live="polite">{{ choosingEnd() ? t().chooseEndDate : '' }}</span>
1397
+ }
1398
+
1399
+ @for (row of timeRows(); track row.target) {
1400
+ <div class="cb-datepicker__time" role="group" [attr.aria-label]="row.label">
1401
+ @if (row.caption) {
1402
+ <span class="cb-datepicker__time-caption" aria-hidden="true">{{ row.caption }}</span>
1403
+ }
1404
+ <label class="cb-datepicker__time-field">
1405
+ <span class="cb-datepicker__sr">{{ t().hours }}</span>
1406
+ <input
1407
+ type="number"
1408
+ class="cb-datepicker__time-input"
1409
+ [min]="hourFormat() === '12' ? 1 : 0"
1410
+ [max]="hourFormat() === '12' ? 12 : 23"
1411
+ [value]="displayHour(row.target)"
1412
+ [disabled]="!row.enabled"
1413
+ (input)="setHour(row.target, $any($event.target).value)"
1414
+ />
1415
+ </label>
1416
+ <span class="cb-datepicker__time-sep" aria-hidden="true">:</span>
1417
+ <label class="cb-datepicker__time-field">
1418
+ <span class="cb-datepicker__sr">{{ t().minutes }}</span>
1419
+ <input
1420
+ type="number"
1421
+ class="cb-datepicker__time-input"
1422
+ min="0"
1423
+ max="59"
1424
+ [step]="stepMinute()"
1425
+ [value]="pad(timeOf(row.target).minutes)"
1426
+ [disabled]="!row.enabled"
1427
+ (input)="setMinute(row.target, $any($event.target).value)"
1428
+ />
1429
+ </label>
1430
+ @if (hourFormat() === '12') {
1431
+ <button
1432
+ type="button"
1433
+ class="cb-datepicker__period"
1434
+ [attr.aria-label]="t().period"
1435
+ [disabled]="!row.enabled"
1436
+ (click)="togglePeriod(row.target)"
1437
+ >
1438
+ {{ timeOf(row.target).hours < 12 ? 'AM' : 'PM' }}
1439
+ </button>
1440
+ }
1441
+ </div>
1442
+ }
1443
+
1444
+ <div class="cb-datepicker__footer">
1445
+ <button type="button" class="cb-datepicker__today" (click)="goToday()">{{ t().today }}</button>
1446
+ @if (hasValue()) {
1447
+ <button type="button" class="cb-datepicker__clear" (click)="clear()">{{ t().clear }}</button>
1448
+ }
1449
+ </div>
1450
+ </div>
1451
+ </div>
1452
+ </ng-template>
1453
+
1454
+ @if (typedInvalid()) {
1455
+ <p class="cb-datepicker__message cb-datepicker__message--error" [id]="errorId()" role="alert">
1456
+ {{ invalidMessage() }}
1457
+ </p>
1458
+ } @else if (fieldState.invalid() && error()) {
1459
+ <p class="cb-datepicker__message cb-datepicker__message--error" [id]="errorId()" role="alert">
1460
+ {{ error() }}
1461
+ </p>
1462
+ } @else if (hint()) {
1463
+ <p class="cb-datepicker__message cb-datepicker__message--hint" [id]="hintId()">
1464
+ {{ hint() }}
1465
+ </p>
1466
+ }
1467
+ `, isInline: true, styles: [":host{display:block}.cb-datepicker__label{display:block;margin-bottom:var(--cb-space-1);font-family:var(--cb-font-sans);font-size:var(--cb-text-sm);font-weight:var(--cb-font-weight-medium);color:var(--cb-color-text)}.cb-datepicker__required{color:var(--cb-color-danger);margin-left:2px}.cb-datepicker{position:relative;display:flex;align-items:stretch;box-sizing:border-box;width:100%;background-color:var(--cb-color-bg);border:var(--cb-border-width) solid var(--cb-color-border-strong);border-radius:var(--cb-radius-md);transition:border-color var(--cb-duration-fast) var(--cb-ease),box-shadow var(--cb-duration-fast) var(--cb-ease)}.cb-datepicker:hover:not(.cb-is-disabled):not(:focus-within){border-color:var(--cb-color-text-muted)}.cb-datepicker:focus-within,.cb-datepicker.cb-is-open{border-color:var(--cb-color-primary-500);box-shadow:0 0 0 3px color-mix(in srgb,var(--cb-color-primary-500) 25%,transparent)}.cb-datepicker.cb-is-invalid{border-color:var(--cb-color-danger)}.cb-datepicker.cb-is-invalid:focus-within,.cb-datepicker.cb-is-invalid.cb-is-open{box-shadow:0 0 0 3px color-mix(in srgb,var(--cb-color-danger) 25%,transparent)}.cb-datepicker.cb-is-disabled{background-color:var(--cb-color-surface);opacity:.8}.cb-datepicker__field{flex:1 1 auto;min-width:0;box-sizing:border-box;font-family:var(--cb-font-sans);color:var(--cb-color-text);background:transparent;border:none;border-radius:inherit;cursor:pointer}.cb-datepicker__field::placeholder{color:var(--cb-color-text-muted)}.cb-datepicker__field:focus{outline:none}.cb-datepicker__field:disabled{color:var(--cb-color-text-muted);cursor:not-allowed}.cb-datepicker--sm .cb-datepicker__field{min-height:2rem;padding:var(--cb-space-1) var(--cb-space-2);font-size:var(--cb-text-sm)}.cb-datepicker--md .cb-datepicker__field{min-height:2.5rem;padding:var(--cb-space-2) var(--cb-space-3);font-size:var(--cb-text-base)}.cb-datepicker--lg .cb-datepicker__field{min-height:3rem;padding:var(--cb-space-3) var(--cb-space-4);font-size:var(--cb-text-lg)}.cb-datepicker__btn{display:flex;flex:none;align-items:center;justify-content:center;width:2.5rem;padding:0;color:var(--cb-color-text-muted);background:transparent;border:none;border-left:var(--cb-border-width) solid var(--cb-color-border);cursor:pointer}.cb-datepicker__btn:hover:not(:disabled){color:var(--cb-color-text)}.cb-datepicker__btn:disabled{cursor:not-allowed;opacity:.5}.cb-datepicker__btn cb-icon{color:currentColor}.cb-datepicker__panel{padding:var(--cb-space-3);background-color:var(--cb-color-surface-raised);border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-md);box-shadow:var(--cb-shadow-md);font-family:var(--cb-font-sans)}.cb-datepicker__panel:focus{outline:none}.cb-datepicker__header{display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--cb-space-2)}.cb-datepicker__title{padding:.25rem .5rem;font:inherit;font-size:var(--cb-text-base);font-weight:var(--cb-font-weight-semibold);color:var(--cb-color-text-strong);background:transparent;border:none;border-radius:var(--cb-radius-sm);cursor:pointer}.cb-datepicker__title:hover:not(.cb-datepicker__title--static){background-color:var(--cb-color-surface-hover)}.cb-datepicker__title:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:1px}.cb-datepicker__title--static{cursor:default}.cb-datepicker__nav-spacer{width:2rem}.cb-datepicker__panel--presets{display:flex;gap:var(--cb-space-3)}.cb-datepicker__presets{display:flex;flex-direction:column;gap:2px;min-width:9rem;padding-right:var(--cb-space-3);border-right:1px solid var(--cb-color-border)}.cb-datepicker__preset{padding:.4rem .65rem;font:inherit;font-size:var(--cb-text-sm);text-align:left;white-space:nowrap;color:var(--cb-color-text);background:transparent;border:none;border-radius:var(--cb-radius-sm);cursor:pointer}.cb-datepicker__preset:hover:not(:disabled){background-color:var(--cb-color-surface-hover)}.cb-datepicker__preset:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:1px}.cb-datepicker__preset.cb-is-selected{font-weight:var(--cb-font-weight-semibold);color:var(--cb-color-text-strong);background-color:color-mix(in srgb,var(--cb-color-primary-500) 18%,transparent);box-shadow:inset 3px 0 0 var(--cb-color-primary-500)}.cb-datepicker__preset:disabled{opacity:.45;cursor:not-allowed}@media(max-width:40rem){.cb-datepicker__panel--presets{flex-direction:column}.cb-datepicker__presets{flex-direction:row;flex-wrap:wrap;padding:0 0 var(--cb-space-2);border-right:none;border-bottom:1px solid var(--cb-color-border)}}@media(forced-colors:active){.cb-datepicker__preset.cb-is-selected{outline:2px solid Highlight}}.cb-datepicker__months{display:flex;flex-wrap:wrap;gap:var(--cb-space-4)}.cb-datepicker__panel--multi .cb-datepicker__month+.cb-datepicker__month{padding-left:var(--cb-space-4);border-left:1px solid var(--cb-color-border)}@media(max-width:40rem){.cb-datepicker__panel--multi .cb-datepicker__month+.cb-datepicker__month{padding-left:0;border-left:none}}.cb-datepicker__units{display:grid;gap:var(--cb-space-1);min-width:16.5rem}.cb-datepicker__unit-row{display:grid;grid-template-columns:repeat(3,1fr)}.cb-datepicker__unit-cell{padding:2px 0}.cb-datepicker__unit{display:flex;align-items:center;justify-content:center;width:100%;height:2.5rem;font:inherit;font-size:var(--cb-text-sm);color:var(--cb-color-text);text-transform:capitalize;background:transparent;border:none;border-radius:var(--cb-radius-full);cursor:pointer;transition:background-color var(--cb-duration-fast) var(--cb-ease)}.cb-datepicker__unit:hover:not(.cb-is-disabled):not(.cb-is-selected){background-color:var(--cb-color-surface-hover)}.cb-datepicker__unit:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:1px}.cb-datepicker__unit.cb-is-other-month{color:var(--cb-color-text-muted)}.cb-datepicker__unit.cb-is-today:not(.cb-is-selected){font-weight:var(--cb-font-weight-semibold);box-shadow:inset 0 0 0 1px var(--cb-color-primary-500)}.cb-datepicker__unit.cb-is-selected{color:var(--cb-color-primary-contrast);background-color:var(--cb-color-primary-500);font-weight:var(--cb-font-weight-semibold)}.cb-datepicker__unit.cb-is-disabled{color:var(--cb-color-text-muted);opacity:.45;cursor:not-allowed}.cb-datepicker__time+.cb-datepicker__time{margin-top:0;padding-top:var(--cb-space-1);border-top:none}.cb-datepicker__time-caption{min-width:6.5rem;margin-right:var(--cb-space-2);font-size:var(--cb-text-xs);color:var(--cb-color-text-muted)}.cb-datepicker__nav{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;color:var(--cb-color-text-muted);background:transparent;border:none;border-radius:var(--cb-radius-sm);cursor:pointer}.cb-datepicker__nav:hover{background-color:var(--cb-color-surface-hover);color:var(--cb-color-text)}.cb-datepicker__nav:focus-visible{outline:none;box-shadow:0 0 0 2px color-mix(in srgb,var(--cb-color-primary-500) 35%,transparent)}.cb-datepicker__nav cb-icon{color:currentColor}.cb-datepicker__grid{border-collapse:collapse;width:100%}.cb-datepicker__weekday{padding:var(--cb-space-1);font-size:var(--cb-text-xs);font-weight:var(--cb-font-weight-medium);color:var(--cb-color-text-muted);text-align:center;text-transform:capitalize}.cb-datepicker__cell{padding:1px;text-align:center}.cb-datepicker__day{display:flex;align-items:center;justify-content:center;width:2.25rem;height:2.25rem;margin:0 auto;font-size:var(--cb-text-sm);color:var(--cb-color-text);background:transparent;border:none;border-radius:var(--cb-radius-full);cursor:pointer;transition:background-color var(--cb-duration-fast) var(--cb-ease)}.cb-datepicker__day:hover:not(.cb-is-disabled):not(.cb-is-selected){background-color:var(--cb-color-surface-hover)}.cb-datepicker__day:focus-visible{outline:none;box-shadow:0 0 0 2px color-mix(in srgb,var(--cb-color-primary-500) 45%,transparent)}.cb-datepicker__day.cb-is-other-month{color:var(--cb-color-text-muted);opacity:.6}.cb-datepicker__day.cb-is-today:not(.cb-is-selected){font-weight:var(--cb-font-weight-bold);color:var(--cb-color-primary-600)}.cb-datepicker__day.cb-is-selected{color:var(--cb-color-primary-contrast);background-color:var(--cb-color-primary-500);font-weight:var(--cb-font-weight-semibold)}.cb-datepicker__day.cb-is-disabled{color:var(--cb-color-text-muted);opacity:.35;cursor:not-allowed}.cb-datepicker__cell.cb-is-in-range{background-color:color-mix(in srgb,var(--cb-color-primary-500) 14%,transparent)}.cb-datepicker__cell.cb-is-range-start{background:linear-gradient(to right,transparent 50%,color-mix(in srgb,var(--cb-color-primary-500) 14%,transparent) 50%)}.cb-datepicker__cell.cb-is-range-end{background:linear-gradient(to left,transparent 50%,color-mix(in srgb,var(--cb-color-primary-500) 14%,transparent) 50%)}.cb-datepicker__cell.cb-is-range-start.cb-is-range-end{background:none}.cb-datepicker__time{display:flex;align-items:center;justify-content:center;gap:var(--cb-space-1);margin-top:var(--cb-space-2);padding-top:var(--cb-space-2);border-top:1px solid var(--cb-color-border)}.cb-datepicker__time-input{box-sizing:border-box;width:3.25rem;padding:var(--cb-space-1);font-family:inherit;font-size:var(--cb-text-base);font-variant-numeric:tabular-nums;text-align:center;color:var(--cb-color-text);background-color:var(--cb-color-bg);border:1px solid var(--cb-color-border-strong);border-radius:var(--cb-radius-sm)}.cb-datepicker__time-input:focus-visible,.cb-datepicker__period:focus-visible{outline:none;border-color:var(--cb-color-primary-500);box-shadow:0 0 0 2px color-mix(in srgb,var(--cb-color-primary-500) 35%,transparent)}.cb-datepicker__time-sep{font-weight:var(--cb-font-weight-semibold);color:var(--cb-color-text-muted)}.cb-datepicker__period{margin-left:var(--cb-space-1);padding:var(--cb-space-1) var(--cb-space-2);font-family:inherit;font-size:var(--cb-text-sm);font-weight:var(--cb-font-weight-semibold);color:var(--cb-color-text);background-color:var(--cb-color-surface-hover);border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-sm);cursor:pointer}.cb-datepicker__sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.cb-datepicker__footer{display:flex;justify-content:space-between;margin-top:var(--cb-space-2);padding-top:var(--cb-space-2);border-top:1px solid var(--cb-color-border)}.cb-datepicker__today,.cb-datepicker__clear{padding:var(--cb-space-1) var(--cb-space-2);font-family:inherit;font-size:var(--cb-text-sm);font-weight:var(--cb-font-weight-medium);color:var(--cb-color-primary-600);background:transparent;border:none;border-radius:var(--cb-radius-sm);cursor:pointer}.cb-datepicker__today:hover,.cb-datepicker__clear:hover{background-color:var(--cb-color-surface-hover)}.cb-datepicker__clear{color:var(--cb-color-text-muted)}.cb-datepicker__message{margin:var(--cb-space-1) 0 0;font-size:var(--cb-text-sm);line-height:var(--cb-line-height-normal)}.cb-datepicker__message--hint{color:var(--cb-color-text-muted)}.cb-datepicker__message--error{color:var(--cb-color-danger)}.cb-datepicker__required--required,.cb-datepicker__required--optional{margin-left:.25rem;color:var(--cb-color-text-muted);font-size:.85em;font-weight:var(--cb-font-weight-normal, 400)}\n"], dependencies: [{ kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation", "cdkConnectedOverlayUsePopover", "cdkConnectedOverlayMatchWidth", "cdkConnectedOverlay"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "component", type: CbIcon, selector: "cb-icon", inputs: ["name", "size", "spin", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
449
1468
  }
450
1469
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: CbDatepicker, decorators: [{
451
1470
  type: Component,
@@ -455,160 +1474,374 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
455
1474
  useExisting: forwardRef(() => CbDatepicker),
456
1475
  multi: true,
457
1476
  },
458
- ], template: `
459
- @if (label()) {
460
- <label class="cb-datepicker__label" [attr.for]="inputId()">
461
- {{ label() }}
462
- @if (fieldState.indicator(); as mark) {
463
- <span
464
- [class]="'cb-datepicker__required cb-datepicker__required--' + mark.kind"
465
- [attr.aria-hidden]="mark.kind === 'optional' ? null : 'true'"
466
- >{{ mark.text }}</span>
467
- }
468
- </label>
1477
+ ], template: `
1478
+ @if (label()) {
1479
+ <label class="cb-datepicker__label" [attr.for]="inputId()">
1480
+ {{ label() }}
1481
+ @if (fieldState.indicator(); as mark) {
1482
+ <span
1483
+ [class]="'cb-datepicker__required cb-datepicker__required--' + mark.kind"
1484
+ [attr.aria-hidden]="mark.kind === 'optional' ? null : 'true'"
1485
+ >{{ mark.text }}</span>
1486
+ }
1487
+ </label>
1488
+ }
1489
+
1490
+ <div
1491
+ #trigger
1492
+ class="cb-datepicker"
1493
+ [class]="'cb-datepicker--' + size()"
1494
+ [class.cb-is-open]="open()"
1495
+ [class.cb-is-invalid]="fieldInvalid()"
1496
+ [class.cb-is-disabled]="isDisabled()"
1497
+ >
1498
+ <input
1499
+ #field
1500
+ class="cb-datepicker__field"
1501
+ [id]="inputId()"
1502
+ type="text"
1503
+ [readOnly]="!editable()"
1504
+ role="combobox"
1505
+ aria-haspopup="dialog"
1506
+ [attr.aria-expanded]="open()"
1507
+ [attr.aria-invalid]="fieldInvalid() ? 'true' : null"
1508
+ [attr.aria-describedby]="describedBy()"
1509
+ [attr.autocomplete]="editable() ? 'off' : null"
1510
+ [value]="fieldText()"
1511
+ [placeholder]="placeholder() || (editable() ? formatHint() : '')"
1512
+ [disabled]="isDisabled()"
1513
+ [required]="fieldState.required()"
1514
+ (click)="onFieldClick()"
1515
+ (input)="draft.set($any($event.target).value)"
1516
+ (keydown)="onFieldKeydown($event)"
1517
+ (blur)="onBlur()"
1518
+ />
1519
+ <button
1520
+ type="button"
1521
+ class="cb-datepicker__btn"
1522
+ tabindex="-1"
1523
+ [attr.aria-label]="t().openCalendar"
1524
+ [disabled]="isDisabled()"
1525
+ (click)="toggle()"
1526
+ >
1527
+ <cb-icon name="calendar" [size]="18" aria-hidden="true" />
1528
+ </button>
1529
+ </div>
1530
+
1531
+ <ng-template
1532
+ cdkConnectedOverlay
1533
+ [cdkConnectedOverlayOrigin]="trigger"
1534
+ [cdkConnectedOverlayOpen]="open()"
1535
+ [cdkConnectedOverlayPositions]="positions"
1536
+ [cdkConnectedOverlayHasBackdrop]="false"
1537
+ (overlayOutsideClick)="close()"
1538
+ (detach)="close()"
1539
+ >
1540
+ <div
1541
+ #panel
1542
+ class="cb-datepicker__panel"
1543
+ [class.cb-datepicker__panel--multi]="panelView() === 'date' && monthCount() > 1"
1544
+ [class.cb-datepicker__panel--presets]="presetItems().length > 0"
1545
+ role="dialog"
1546
+ aria-modal="false"
1547
+ [attr.aria-label]="panelLabel()"
1548
+ tabindex="-1"
1549
+ (keydown)="onPanelKeydown($event)"
1550
+ >
1551
+ @if (presetItems().length) {
1552
+ <div class="cb-datepicker__presets" role="group" [attr.aria-label]="t().datePresets">
1553
+ @for (item of presetItems(); track $index) {
1554
+ <button
1555
+ type="button"
1556
+ class="cb-datepicker__preset"
1557
+ [class.cb-is-selected]="item.selected"
1558
+ [attr.aria-pressed]="item.selected"
1559
+ [disabled]="item.disabled"
1560
+ (click)="applyPreset(item.value)"
1561
+ >
1562
+ {{ item.label }}
1563
+ </button>
1564
+ }
1565
+ </div>
1566
+ }
1567
+ <div class="cb-datepicker__main">
1568
+ @switch (panelView()) {
1569
+ @case ('date') {
1570
+ <div class="cb-datepicker__months">
1571
+ @for (m of months(); track m.date.getTime(); let first = $first, last = $last) {
1572
+ <div class="cb-datepicker__month">
1573
+ <div class="cb-datepicker__header">
1574
+ @if (first) {
1575
+ <button type="button" class="cb-datepicker__nav" [attr.aria-label]="t().previousMonth" (click)="shiftMonth(-1)">
1576
+ <cb-icon name="chevron-left" [size]="18" aria-hidden="true" />
1577
+ </button>
1578
+ } @else {
1579
+ <span class="cb-datepicker__nav-spacer"></span>
1580
+ }
1581
+ <button
1582
+ type="button"
1583
+ class="cb-datepicker__title"
1584
+ [attr.aria-label]="m.title + '. ' + t().chooseMonth"
1585
+ (click)="showMonths(m.date)"
1586
+ >{{ m.title }}</button>
1587
+ @if (last) {
1588
+ <button type="button" class="cb-datepicker__nav" [attr.aria-label]="t().nextMonth" (click)="shiftMonth(1)">
1589
+ <cb-icon name="chevron-right" [size]="18" aria-hidden="true" />
1590
+ </button>
1591
+ } @else {
1592
+ <span class="cb-datepicker__nav-spacer"></span>
1593
+ }
1594
+ </div>
1595
+
1596
+ <table
1597
+ class="cb-datepicker__grid"
1598
+ role="grid"
1599
+ [attr.aria-label]="m.title"
1600
+ [class.cb-datepicker__grid--range]="isRange()"
1601
+ (mouseleave)="hoverDate.set(null)"
1602
+ >
1603
+ <thead>
1604
+ <tr>
1605
+ @for (wd of weekdays(); track wd.index) {
1606
+ <th class="cb-datepicker__weekday" scope="col" [attr.abbr]="wd.long">{{ wd.short }}</th>
1607
+ }
1608
+ </tr>
1609
+ </thead>
1610
+ <tbody>
1611
+ @for (week of m.weeks; track week[0].date.getTime()) {
1612
+ <tr>
1613
+ @for (cell of week; track cell.date.getTime()) {
1614
+ @if (cell.hidden) {
1615
+ <td class="cb-datepicker__cell" role="gridcell"></td>
1616
+ } @else {
1617
+ <td
1618
+ class="cb-datepicker__cell"
1619
+ role="gridcell"
1620
+ [class.cb-is-in-range]="cell.inRange"
1621
+ [class.cb-is-range-start]="cell.rangeStart"
1622
+ [class.cb-is-range-end]="cell.rangeEnd"
1623
+ [attr.aria-selected]="cell.selected || cell.inRange"
1624
+ >
1625
+ <!-- aria-disabled (no disabled) para que las flechas puedan pasar por los días no elegibles. -->
1626
+ <button
1627
+ type="button"
1628
+ class="cb-datepicker__day"
1629
+ [class.cb-is-other-month]="cell.otherMonth"
1630
+ [class.cb-is-today]="cell.today"
1631
+ [class.cb-is-selected]="cell.selected"
1632
+ [class.cb-is-active]="cell.active"
1633
+ [class.cb-is-disabled]="cell.disabled"
1634
+ [attr.aria-disabled]="cell.disabled || null"
1635
+ [attr.aria-label]="cell.label"
1636
+ [attr.tabindex]="cell.active ? 0 : -1"
1637
+ [attr.aria-current]="cell.today ? 'date' : null"
1638
+ (click)="selectDate(cell.date)"
1639
+ (mouseenter)="hoverDate.set(cell.date)"
1640
+ >
1641
+ {{ cell.day }}
1642
+ </button>
1643
+ </td>
1644
+ }
1645
+ }
1646
+ </tr>
1647
+ }
1648
+ </tbody>
1649
+ </table>
1650
+ </div>
1651
+ }
1652
+ </div>
1653
+ }
1654
+ @default {
1655
+ <div class="cb-datepicker__header">
1656
+ <button
1657
+ type="button"
1658
+ class="cb-datepicker__nav"
1659
+ [attr.aria-label]="panelView() === 'month' ? t().previousYear : t().previousDecade"
1660
+ (click)="shiftPage(-1)"
1661
+ >
1662
+ <cb-icon name="chevron-left" [size]="18" aria-hidden="true" />
1663
+ </button>
1664
+ @if (panelView() === 'month') {
1665
+ <button type="button" class="cb-datepicker__title" [attr.aria-label]="unitTitle() + '. ' + t().chooseYear" (click)="showYears()">
1666
+ {{ unitTitle() }}
1667
+ </button>
1668
+ } @else {
1669
+ <span class="cb-datepicker__title cb-datepicker__title--static">{{ unitTitle() }}</span>
1670
+ }
1671
+ <button
1672
+ type="button"
1673
+ class="cb-datepicker__nav"
1674
+ [attr.aria-label]="panelView() === 'month' ? t().nextYear : t().nextDecade"
1675
+ (click)="shiftPage(1)"
1676
+ >
1677
+ <cb-icon name="chevron-right" [size]="18" aria-hidden="true" />
1678
+ </button>
1679
+ </div>
1680
+ <div class="cb-datepicker__units" role="grid" [attr.aria-label]="unitTitle()" (mouseleave)="hoverDate.set(null)">
1681
+ @for (row of unitRows(); track $index) {
1682
+ <div class="cb-datepicker__unit-row" role="row">
1683
+ @for (cell of row; track cell.date.getTime()) {
1684
+ <div
1685
+ class="cb-datepicker__cell cb-datepicker__unit-cell"
1686
+ role="gridcell"
1687
+ [class.cb-is-in-range]="cell.inRange"
1688
+ [class.cb-is-range-start]="cell.rangeStart"
1689
+ [class.cb-is-range-end]="cell.rangeEnd"
1690
+ [attr.aria-selected]="cell.selected || cell.inRange"
1691
+ >
1692
+ <button
1693
+ type="button"
1694
+ class="cb-datepicker__unit"
1695
+ [class.cb-is-other-month]="cell.outside"
1696
+ [class.cb-is-today]="cell.current"
1697
+ [class.cb-is-selected]="cell.selected"
1698
+ [class.cb-is-active]="cell.active"
1699
+ [class.cb-is-disabled]="cell.disabled"
1700
+ [attr.aria-disabled]="cell.disabled || null"
1701
+ [attr.aria-label]="cell.label"
1702
+ [attr.aria-current]="cell.current ? (panelView() === 'month' ? 'date' : 'true') : null"
1703
+ [attr.tabindex]="cell.active ? 0 : -1"
1704
+ (click)="selectUnit(cell.date)"
1705
+ (mouseenter)="hoverDate.set(cell.date)"
1706
+ >
1707
+ {{ cell.text }}
1708
+ </button>
1709
+ </div>
1710
+ }
1711
+ </div>
1712
+ }
1713
+ </div>
1714
+ }
1715
+ }
1716
+
1717
+ @if (isRange()) {
1718
+ <span class="cb-datepicker__sr" aria-live="polite">{{ choosingEnd() ? t().chooseEndDate : '' }}</span>
1719
+ }
1720
+
1721
+ @for (row of timeRows(); track row.target) {
1722
+ <div class="cb-datepicker__time" role="group" [attr.aria-label]="row.label">
1723
+ @if (row.caption) {
1724
+ <span class="cb-datepicker__time-caption" aria-hidden="true">{{ row.caption }}</span>
1725
+ }
1726
+ <label class="cb-datepicker__time-field">
1727
+ <span class="cb-datepicker__sr">{{ t().hours }}</span>
1728
+ <input
1729
+ type="number"
1730
+ class="cb-datepicker__time-input"
1731
+ [min]="hourFormat() === '12' ? 1 : 0"
1732
+ [max]="hourFormat() === '12' ? 12 : 23"
1733
+ [value]="displayHour(row.target)"
1734
+ [disabled]="!row.enabled"
1735
+ (input)="setHour(row.target, $any($event.target).value)"
1736
+ />
1737
+ </label>
1738
+ <span class="cb-datepicker__time-sep" aria-hidden="true">:</span>
1739
+ <label class="cb-datepicker__time-field">
1740
+ <span class="cb-datepicker__sr">{{ t().minutes }}</span>
1741
+ <input
1742
+ type="number"
1743
+ class="cb-datepicker__time-input"
1744
+ min="0"
1745
+ max="59"
1746
+ [step]="stepMinute()"
1747
+ [value]="pad(timeOf(row.target).minutes)"
1748
+ [disabled]="!row.enabled"
1749
+ (input)="setMinute(row.target, $any($event.target).value)"
1750
+ />
1751
+ </label>
1752
+ @if (hourFormat() === '12') {
1753
+ <button
1754
+ type="button"
1755
+ class="cb-datepicker__period"
1756
+ [attr.aria-label]="t().period"
1757
+ [disabled]="!row.enabled"
1758
+ (click)="togglePeriod(row.target)"
1759
+ >
1760
+ {{ timeOf(row.target).hours < 12 ? 'AM' : 'PM' }}
1761
+ </button>
1762
+ }
1763
+ </div>
1764
+ }
1765
+
1766
+ <div class="cb-datepicker__footer">
1767
+ <button type="button" class="cb-datepicker__today" (click)="goToday()">{{ t().today }}</button>
1768
+ @if (hasValue()) {
1769
+ <button type="button" class="cb-datepicker__clear" (click)="clear()">{{ t().clear }}</button>
1770
+ }
1771
+ </div>
1772
+ </div>
1773
+ </div>
1774
+ </ng-template>
1775
+
1776
+ @if (typedInvalid()) {
1777
+ <p class="cb-datepicker__message cb-datepicker__message--error" [id]="errorId()" role="alert">
1778
+ {{ invalidMessage() }}
1779
+ </p>
1780
+ } @else if (fieldState.invalid() && error()) {
1781
+ <p class="cb-datepicker__message cb-datepicker__message--error" [id]="errorId()" role="alert">
1782
+ {{ error() }}
1783
+ </p>
1784
+ } @else if (hint()) {
1785
+ <p class="cb-datepicker__message cb-datepicker__message--hint" [id]="hintId()">
1786
+ {{ hint() }}
1787
+ </p>
1788
+ }
1789
+ `, styles: [":host{display:block}.cb-datepicker__label{display:block;margin-bottom:var(--cb-space-1);font-family:var(--cb-font-sans);font-size:var(--cb-text-sm);font-weight:var(--cb-font-weight-medium);color:var(--cb-color-text)}.cb-datepicker__required{color:var(--cb-color-danger);margin-left:2px}.cb-datepicker{position:relative;display:flex;align-items:stretch;box-sizing:border-box;width:100%;background-color:var(--cb-color-bg);border:var(--cb-border-width) solid var(--cb-color-border-strong);border-radius:var(--cb-radius-md);transition:border-color var(--cb-duration-fast) var(--cb-ease),box-shadow var(--cb-duration-fast) var(--cb-ease)}.cb-datepicker:hover:not(.cb-is-disabled):not(:focus-within){border-color:var(--cb-color-text-muted)}.cb-datepicker:focus-within,.cb-datepicker.cb-is-open{border-color:var(--cb-color-primary-500);box-shadow:0 0 0 3px color-mix(in srgb,var(--cb-color-primary-500) 25%,transparent)}.cb-datepicker.cb-is-invalid{border-color:var(--cb-color-danger)}.cb-datepicker.cb-is-invalid:focus-within,.cb-datepicker.cb-is-invalid.cb-is-open{box-shadow:0 0 0 3px color-mix(in srgb,var(--cb-color-danger) 25%,transparent)}.cb-datepicker.cb-is-disabled{background-color:var(--cb-color-surface);opacity:.8}.cb-datepicker__field{flex:1 1 auto;min-width:0;box-sizing:border-box;font-family:var(--cb-font-sans);color:var(--cb-color-text);background:transparent;border:none;border-radius:inherit;cursor:pointer}.cb-datepicker__field::placeholder{color:var(--cb-color-text-muted)}.cb-datepicker__field:focus{outline:none}.cb-datepicker__field:disabled{color:var(--cb-color-text-muted);cursor:not-allowed}.cb-datepicker--sm .cb-datepicker__field{min-height:2rem;padding:var(--cb-space-1) var(--cb-space-2);font-size:var(--cb-text-sm)}.cb-datepicker--md .cb-datepicker__field{min-height:2.5rem;padding:var(--cb-space-2) var(--cb-space-3);font-size:var(--cb-text-base)}.cb-datepicker--lg .cb-datepicker__field{min-height:3rem;padding:var(--cb-space-3) var(--cb-space-4);font-size:var(--cb-text-lg)}.cb-datepicker__btn{display:flex;flex:none;align-items:center;justify-content:center;width:2.5rem;padding:0;color:var(--cb-color-text-muted);background:transparent;border:none;border-left:var(--cb-border-width) solid var(--cb-color-border);cursor:pointer}.cb-datepicker__btn:hover:not(:disabled){color:var(--cb-color-text)}.cb-datepicker__btn:disabled{cursor:not-allowed;opacity:.5}.cb-datepicker__btn cb-icon{color:currentColor}.cb-datepicker__panel{padding:var(--cb-space-3);background-color:var(--cb-color-surface-raised);border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-md);box-shadow:var(--cb-shadow-md);font-family:var(--cb-font-sans)}.cb-datepicker__panel:focus{outline:none}.cb-datepicker__header{display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--cb-space-2)}.cb-datepicker__title{padding:.25rem .5rem;font:inherit;font-size:var(--cb-text-base);font-weight:var(--cb-font-weight-semibold);color:var(--cb-color-text-strong);background:transparent;border:none;border-radius:var(--cb-radius-sm);cursor:pointer}.cb-datepicker__title:hover:not(.cb-datepicker__title--static){background-color:var(--cb-color-surface-hover)}.cb-datepicker__title:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:1px}.cb-datepicker__title--static{cursor:default}.cb-datepicker__nav-spacer{width:2rem}.cb-datepicker__panel--presets{display:flex;gap:var(--cb-space-3)}.cb-datepicker__presets{display:flex;flex-direction:column;gap:2px;min-width:9rem;padding-right:var(--cb-space-3);border-right:1px solid var(--cb-color-border)}.cb-datepicker__preset{padding:.4rem .65rem;font:inherit;font-size:var(--cb-text-sm);text-align:left;white-space:nowrap;color:var(--cb-color-text);background:transparent;border:none;border-radius:var(--cb-radius-sm);cursor:pointer}.cb-datepicker__preset:hover:not(:disabled){background-color:var(--cb-color-surface-hover)}.cb-datepicker__preset:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:1px}.cb-datepicker__preset.cb-is-selected{font-weight:var(--cb-font-weight-semibold);color:var(--cb-color-text-strong);background-color:color-mix(in srgb,var(--cb-color-primary-500) 18%,transparent);box-shadow:inset 3px 0 0 var(--cb-color-primary-500)}.cb-datepicker__preset:disabled{opacity:.45;cursor:not-allowed}@media(max-width:40rem){.cb-datepicker__panel--presets{flex-direction:column}.cb-datepicker__presets{flex-direction:row;flex-wrap:wrap;padding:0 0 var(--cb-space-2);border-right:none;border-bottom:1px solid var(--cb-color-border)}}@media(forced-colors:active){.cb-datepicker__preset.cb-is-selected{outline:2px solid Highlight}}.cb-datepicker__months{display:flex;flex-wrap:wrap;gap:var(--cb-space-4)}.cb-datepicker__panel--multi .cb-datepicker__month+.cb-datepicker__month{padding-left:var(--cb-space-4);border-left:1px solid var(--cb-color-border)}@media(max-width:40rem){.cb-datepicker__panel--multi .cb-datepicker__month+.cb-datepicker__month{padding-left:0;border-left:none}}.cb-datepicker__units{display:grid;gap:var(--cb-space-1);min-width:16.5rem}.cb-datepicker__unit-row{display:grid;grid-template-columns:repeat(3,1fr)}.cb-datepicker__unit-cell{padding:2px 0}.cb-datepicker__unit{display:flex;align-items:center;justify-content:center;width:100%;height:2.5rem;font:inherit;font-size:var(--cb-text-sm);color:var(--cb-color-text);text-transform:capitalize;background:transparent;border:none;border-radius:var(--cb-radius-full);cursor:pointer;transition:background-color var(--cb-duration-fast) var(--cb-ease)}.cb-datepicker__unit:hover:not(.cb-is-disabled):not(.cb-is-selected){background-color:var(--cb-color-surface-hover)}.cb-datepicker__unit:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:1px}.cb-datepicker__unit.cb-is-other-month{color:var(--cb-color-text-muted)}.cb-datepicker__unit.cb-is-today:not(.cb-is-selected){font-weight:var(--cb-font-weight-semibold);box-shadow:inset 0 0 0 1px var(--cb-color-primary-500)}.cb-datepicker__unit.cb-is-selected{color:var(--cb-color-primary-contrast);background-color:var(--cb-color-primary-500);font-weight:var(--cb-font-weight-semibold)}.cb-datepicker__unit.cb-is-disabled{color:var(--cb-color-text-muted);opacity:.45;cursor:not-allowed}.cb-datepicker__time+.cb-datepicker__time{margin-top:0;padding-top:var(--cb-space-1);border-top:none}.cb-datepicker__time-caption{min-width:6.5rem;margin-right:var(--cb-space-2);font-size:var(--cb-text-xs);color:var(--cb-color-text-muted)}.cb-datepicker__nav{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;color:var(--cb-color-text-muted);background:transparent;border:none;border-radius:var(--cb-radius-sm);cursor:pointer}.cb-datepicker__nav:hover{background-color:var(--cb-color-surface-hover);color:var(--cb-color-text)}.cb-datepicker__nav:focus-visible{outline:none;box-shadow:0 0 0 2px color-mix(in srgb,var(--cb-color-primary-500) 35%,transparent)}.cb-datepicker__nav cb-icon{color:currentColor}.cb-datepicker__grid{border-collapse:collapse;width:100%}.cb-datepicker__weekday{padding:var(--cb-space-1);font-size:var(--cb-text-xs);font-weight:var(--cb-font-weight-medium);color:var(--cb-color-text-muted);text-align:center;text-transform:capitalize}.cb-datepicker__cell{padding:1px;text-align:center}.cb-datepicker__day{display:flex;align-items:center;justify-content:center;width:2.25rem;height:2.25rem;margin:0 auto;font-size:var(--cb-text-sm);color:var(--cb-color-text);background:transparent;border:none;border-radius:var(--cb-radius-full);cursor:pointer;transition:background-color var(--cb-duration-fast) var(--cb-ease)}.cb-datepicker__day:hover:not(.cb-is-disabled):not(.cb-is-selected){background-color:var(--cb-color-surface-hover)}.cb-datepicker__day:focus-visible{outline:none;box-shadow:0 0 0 2px color-mix(in srgb,var(--cb-color-primary-500) 45%,transparent)}.cb-datepicker__day.cb-is-other-month{color:var(--cb-color-text-muted);opacity:.6}.cb-datepicker__day.cb-is-today:not(.cb-is-selected){font-weight:var(--cb-font-weight-bold);color:var(--cb-color-primary-600)}.cb-datepicker__day.cb-is-selected{color:var(--cb-color-primary-contrast);background-color:var(--cb-color-primary-500);font-weight:var(--cb-font-weight-semibold)}.cb-datepicker__day.cb-is-disabled{color:var(--cb-color-text-muted);opacity:.35;cursor:not-allowed}.cb-datepicker__cell.cb-is-in-range{background-color:color-mix(in srgb,var(--cb-color-primary-500) 14%,transparent)}.cb-datepicker__cell.cb-is-range-start{background:linear-gradient(to right,transparent 50%,color-mix(in srgb,var(--cb-color-primary-500) 14%,transparent) 50%)}.cb-datepicker__cell.cb-is-range-end{background:linear-gradient(to left,transparent 50%,color-mix(in srgb,var(--cb-color-primary-500) 14%,transparent) 50%)}.cb-datepicker__cell.cb-is-range-start.cb-is-range-end{background:none}.cb-datepicker__time{display:flex;align-items:center;justify-content:center;gap:var(--cb-space-1);margin-top:var(--cb-space-2);padding-top:var(--cb-space-2);border-top:1px solid var(--cb-color-border)}.cb-datepicker__time-input{box-sizing:border-box;width:3.25rem;padding:var(--cb-space-1);font-family:inherit;font-size:var(--cb-text-base);font-variant-numeric:tabular-nums;text-align:center;color:var(--cb-color-text);background-color:var(--cb-color-bg);border:1px solid var(--cb-color-border-strong);border-radius:var(--cb-radius-sm)}.cb-datepicker__time-input:focus-visible,.cb-datepicker__period:focus-visible{outline:none;border-color:var(--cb-color-primary-500);box-shadow:0 0 0 2px color-mix(in srgb,var(--cb-color-primary-500) 35%,transparent)}.cb-datepicker__time-sep{font-weight:var(--cb-font-weight-semibold);color:var(--cb-color-text-muted)}.cb-datepicker__period{margin-left:var(--cb-space-1);padding:var(--cb-space-1) var(--cb-space-2);font-family:inherit;font-size:var(--cb-text-sm);font-weight:var(--cb-font-weight-semibold);color:var(--cb-color-text);background-color:var(--cb-color-surface-hover);border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-sm);cursor:pointer}.cb-datepicker__sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.cb-datepicker__footer{display:flex;justify-content:space-between;margin-top:var(--cb-space-2);padding-top:var(--cb-space-2);border-top:1px solid var(--cb-color-border)}.cb-datepicker__today,.cb-datepicker__clear{padding:var(--cb-space-1) var(--cb-space-2);font-family:inherit;font-size:var(--cb-text-sm);font-weight:var(--cb-font-weight-medium);color:var(--cb-color-primary-600);background:transparent;border:none;border-radius:var(--cb-radius-sm);cursor:pointer}.cb-datepicker__today:hover,.cb-datepicker__clear:hover{background-color:var(--cb-color-surface-hover)}.cb-datepicker__clear{color:var(--cb-color-text-muted)}.cb-datepicker__message{margin:var(--cb-space-1) 0 0;font-size:var(--cb-text-sm);line-height:var(--cb-line-height-normal)}.cb-datepicker__message--hint{color:var(--cb-color-text-muted)}.cb-datepicker__message--error{color:var(--cb-color-danger)}.cb-datepicker__required--required,.cb-datepicker__required--optional{margin-left:.25rem;color:var(--cb-color-text-muted);font-size:.85em;font-weight:var(--cb-font-weight-normal, 400)}\n"] }]
1790
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], range: [{ type: i0.Input, args: [{ isSignal: true, alias: "range", required: false }] }, { type: i0.Output, args: ["rangeChange"] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], requiredIndicator: [{ type: i0.Input, args: [{ isSignal: true, alias: "requiredIndicator", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], firstDayOfWeek: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstDayOfWeek", required: false }] }], minDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDate", required: false }] }], maxDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDate", required: false }] }], selectionMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionMode", required: false }] }], view: [{ type: i0.Input, args: [{ isSignal: true, alias: "view", required: false }] }], numberOfMonths: [{ type: i0.Input, args: [{ isSignal: true, alias: "numberOfMonths", required: false }] }], showTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTime", required: false }] }], hourFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "hourFormat", required: false }] }], stepMinute: [{ type: i0.Input, args: [{ isSignal: true, alias: "stepMinute", required: false }] }], dateFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFormat", required: false }] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }], presets: [{ type: i0.Input, args: [{ isSignal: true, alias: "presets", required: false }] }], fieldRef: [{ type: i0.ViewChild, args: ['field', { isSignal: true }] }], panelRef: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }] } });
1791
+ /** Rango de un atajo predefinido, relativo a hoy. */
1792
+ function presetRange(key) {
1793
+ const today = stripTime(new Date());
1794
+ const y = today.getFullYear();
1795
+ const m = today.getMonth();
1796
+ switch (key) {
1797
+ case 'today':
1798
+ return [today, today];
1799
+ case 'yesterday':
1800
+ return [addDays(today, -1), addDays(today, -1)];
1801
+ case 'last7Days':
1802
+ return [addDays(today, -6), today];
1803
+ case 'last30Days':
1804
+ return [addDays(today, -29), today];
1805
+ case 'thisMonth':
1806
+ return [new Date(y, m, 1), new Date(y, m + 1, 0)];
1807
+ case 'lastMonth':
1808
+ return [new Date(y, m - 1, 1), new Date(y, m, 0)];
1809
+ case 'thisYear':
1810
+ return [new Date(y, 0, 1), new Date(y, 11, 31)];
1811
+ case 'lastYear':
1812
+ return [new Date(y - 1, 0, 1), new Date(y - 1, 11, 31)];
469
1813
  }
470
-
471
- <div
472
- #trigger
473
- class="cb-datepicker"
474
- [class]="'cb-datepicker--' + size()"
475
- [class.cb-is-open]="open()"
476
- [class.cb-is-invalid]="fieldState.invalid()"
477
- [class.cb-is-disabled]="isDisabled()"
478
- >
479
- <input
480
- #field
481
- class="cb-datepicker__field"
482
- [id]="inputId()"
483
- type="text"
484
- readonly
485
- role="combobox"
486
- aria-haspopup="dialog"
487
- [attr.aria-expanded]="open()"
488
- [attr.aria-invalid]="fieldState.invalid() ? 'true' : null"
489
- [attr.aria-describedby]="describedBy()"
490
- [value]="display()"
491
- [placeholder]="placeholder()"
492
- [disabled]="isDisabled()"
493
- [required]="fieldState.required()"
494
- (click)="toggle()"
495
- (keydown)="onFieldKeydown($event)"
496
- (blur)="onBlur()"
497
- />
498
- <button
499
- type="button"
500
- class="cb-datepicker__btn"
501
- tabindex="-1"
502
- aria-label="Abrir calendario"
503
- [disabled]="isDisabled()"
504
- (click)="toggle()"
505
- >
506
- <cb-icon name="calendar" [size]="18" aria-hidden="true" />
507
- </button>
508
- </div>
509
-
510
- <ng-template
511
- cdkConnectedOverlay
512
- [cdkConnectedOverlayOrigin]="trigger"
513
- [cdkConnectedOverlayOpen]="open()"
514
- [cdkConnectedOverlayPositions]="positions"
515
- [cdkConnectedOverlayHasBackdrop]="false"
516
- (overlayOutsideClick)="close()"
517
- (detach)="close()"
518
- >
519
- <div
520
- #panel
521
- class="cb-datepicker__panel"
522
- role="dialog"
523
- aria-modal="false"
524
- [attr.aria-label]="monthYearLabel()"
525
- tabindex="-1"
526
- (keydown)="onPanelKeydown($event)"
527
- >
528
- <div class="cb-datepicker__header">
529
- <button
530
- type="button"
531
- class="cb-datepicker__nav"
532
- aria-label="Mes anterior"
533
- (click)="shiftMonth(-1)"
534
- >
535
- <cb-icon name="chevron-left" [size]="18" aria-hidden="true" />
536
- </button>
537
- <span class="cb-datepicker__title" aria-live="polite">{{ monthYearLabel() }}</span>
538
- <button
539
- type="button"
540
- class="cb-datepicker__nav"
541
- aria-label="Mes siguiente"
542
- (click)="shiftMonth(1)"
543
- >
544
- <cb-icon name="chevron-right" [size]="18" aria-hidden="true" />
545
- </button>
546
- </div>
547
-
548
- <table class="cb-datepicker__grid" role="grid">
549
- <thead>
550
- <tr>
551
- @for (wd of weekdays(); track wd.index) {
552
- <th class="cb-datepicker__weekday" scope="col" [attr.abbr]="wd.long">{{ wd.short }}</th>
553
- }
554
- </tr>
555
- </thead>
556
- <tbody>
557
- @for (week of weeks(); track week[0].date.getTime()) {
558
- <tr>
559
- @for (cell of week; track cell.date.getTime()) {
560
- <td class="cb-datepicker__cell" role="gridcell">
561
- <button
562
- type="button"
563
- class="cb-datepicker__day"
564
- [class.cb-is-other-month]="cell.otherMonth"
565
- [class.cb-is-today]="cell.today"
566
- [class.cb-is-selected]="cell.selected"
567
- [class.cb-is-active]="cell.active"
568
- [disabled]="cell.disabled"
569
- [attr.tabindex]="cell.active ? 0 : -1"
570
- [attr.aria-selected]="cell.selected"
571
- [attr.aria-current]="cell.today ? 'date' : null"
572
- (click)="selectDate(cell.date)"
573
- >
574
- {{ cell.day }}
575
- </button>
576
- </td>
577
- }
578
- </tr>
579
- }
580
- </tbody>
581
- </table>
582
-
583
- <div class="cb-datepicker__footer">
584
- <button type="button" class="cb-datepicker__today" (click)="goToday()">Hoy</button>
585
- @if (value()) {
586
- <button type="button" class="cb-datepicker__clear" (click)="clear()">Limpiar</button>
587
- }
588
- </div>
589
- </div>
590
- </ng-template>
591
-
592
- @if (fieldState.invalid() && error()) {
593
- <p class="cb-datepicker__message cb-datepicker__message--error" [id]="errorId()" role="alert">
594
- {{ error() }}
595
- </p>
596
- } @else if (hint()) {
597
- <p class="cb-datepicker__message cb-datepicker__message--hint" [id]="hintId()">
598
- {{ hint() }}
599
- </p>
600
- }
601
- `, styles: [":host{display:block}.cb-datepicker__label{display:block;margin-bottom:var(--cb-space-1);font-family:var(--cb-font-sans);font-size:var(--cb-text-sm);font-weight:var(--cb-font-weight-medium);color:var(--cb-color-text)}.cb-datepicker__required{color:var(--cb-color-danger);margin-left:2px}.cb-datepicker{position:relative;display:flex;align-items:stretch;box-sizing:border-box;width:100%;background-color:var(--cb-color-bg);border:var(--cb-border-width) solid var(--cb-color-border-strong);border-radius:var(--cb-radius-md);transition:border-color var(--cb-duration-fast) var(--cb-ease),box-shadow var(--cb-duration-fast) var(--cb-ease)}.cb-datepicker:hover:not(.cb-is-disabled):not(:focus-within){border-color:var(--cb-color-text-muted)}.cb-datepicker:focus-within,.cb-datepicker.cb-is-open{border-color:var(--cb-color-primary-500);box-shadow:0 0 0 3px color-mix(in srgb,var(--cb-color-primary-500) 25%,transparent)}.cb-datepicker.cb-is-invalid{border-color:var(--cb-color-danger)}.cb-datepicker.cb-is-invalid:focus-within,.cb-datepicker.cb-is-invalid.cb-is-open{box-shadow:0 0 0 3px color-mix(in srgb,var(--cb-color-danger) 25%,transparent)}.cb-datepicker.cb-is-disabled{background-color:var(--cb-color-surface);opacity:.8}.cb-datepicker__field{flex:1 1 auto;min-width:0;box-sizing:border-box;font-family:var(--cb-font-sans);color:var(--cb-color-text);background:transparent;border:none;border-radius:inherit;cursor:pointer}.cb-datepicker__field::placeholder{color:var(--cb-color-text-muted)}.cb-datepicker__field:focus{outline:none}.cb-datepicker__field:disabled{color:var(--cb-color-text-muted);cursor:not-allowed}.cb-datepicker--sm .cb-datepicker__field{min-height:2rem;padding:var(--cb-space-1) var(--cb-space-2);font-size:var(--cb-text-sm)}.cb-datepicker--md .cb-datepicker__field{min-height:2.5rem;padding:var(--cb-space-2) var(--cb-space-3);font-size:var(--cb-text-base)}.cb-datepicker--lg .cb-datepicker__field{min-height:3rem;padding:var(--cb-space-3) var(--cb-space-4);font-size:var(--cb-text-lg)}.cb-datepicker__btn{display:flex;flex:none;align-items:center;justify-content:center;width:2.5rem;padding:0;color:var(--cb-color-text-muted);background:transparent;border:none;border-left:var(--cb-border-width) solid var(--cb-color-border);cursor:pointer}.cb-datepicker__btn:hover:not(:disabled){color:var(--cb-color-text)}.cb-datepicker__btn:disabled{cursor:not-allowed;opacity:.5}.cb-datepicker__btn cb-icon{color:currentColor}.cb-datepicker__panel{padding:var(--cb-space-3);background-color:var(--cb-color-surface-raised);border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-md);box-shadow:var(--cb-shadow-md);font-family:var(--cb-font-sans)}.cb-datepicker__panel:focus{outline:none}.cb-datepicker__header{display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--cb-space-2)}.cb-datepicker__title{font-size:var(--cb-text-base);font-weight:var(--cb-font-weight-semibold);color:var(--cb-color-text-strong)}.cb-datepicker__nav{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;color:var(--cb-color-text-muted);background:transparent;border:none;border-radius:var(--cb-radius-sm);cursor:pointer}.cb-datepicker__nav:hover{background-color:var(--cb-color-surface-100);color:var(--cb-color-text)}.cb-datepicker__nav:focus-visible{outline:none;box-shadow:0 0 0 2px color-mix(in srgb,var(--cb-color-primary-500) 35%,transparent)}.cb-datepicker__nav cb-icon{color:currentColor}.cb-datepicker__grid{border-collapse:collapse;width:100%}.cb-datepicker__weekday{padding:var(--cb-space-1);font-size:var(--cb-text-xs);font-weight:var(--cb-font-weight-medium);color:var(--cb-color-text-muted);text-align:center;text-transform:capitalize}.cb-datepicker__cell{padding:1px;text-align:center}.cb-datepicker__day{display:flex;align-items:center;justify-content:center;width:2.25rem;height:2.25rem;margin:0 auto;font-size:var(--cb-text-sm);color:var(--cb-color-text);background:transparent;border:none;border-radius:var(--cb-radius-full);cursor:pointer;transition:background-color var(--cb-duration-fast) var(--cb-ease)}.cb-datepicker__day:hover:not(:disabled):not(.cb-is-selected){background-color:var(--cb-color-surface-100)}.cb-datepicker__day:focus-visible{outline:none;box-shadow:0 0 0 2px color-mix(in srgb,var(--cb-color-primary-500) 45%,transparent)}.cb-datepicker__day.cb-is-other-month{color:var(--cb-color-text-muted);opacity:.6}.cb-datepicker__day.cb-is-today:not(.cb-is-selected){font-weight:var(--cb-font-weight-bold);color:var(--cb-color-primary-600)}.cb-datepicker__day.cb-is-selected{color:var(--cb-color-primary-contrast);background-color:var(--cb-color-primary-500);font-weight:var(--cb-font-weight-semibold)}.cb-datepicker__day:disabled{color:var(--cb-color-text-muted);opacity:.35;cursor:not-allowed}.cb-datepicker__footer{display:flex;justify-content:space-between;margin-top:var(--cb-space-2);padding-top:var(--cb-space-2);border-top:1px solid var(--cb-color-border)}.cb-datepicker__today,.cb-datepicker__clear{padding:var(--cb-space-1) var(--cb-space-2);font-family:inherit;font-size:var(--cb-text-sm);font-weight:var(--cb-font-weight-medium);color:var(--cb-color-primary-600);background:transparent;border:none;border-radius:var(--cb-radius-sm);cursor:pointer}.cb-datepicker__today:hover,.cb-datepicker__clear:hover{background-color:var(--cb-color-surface-100)}.cb-datepicker__clear{color:var(--cb-color-text-muted)}.cb-datepicker__message{margin:var(--cb-space-1) 0 0;font-size:var(--cb-text-sm);line-height:var(--cb-line-height-normal)}.cb-datepicker__message--hint{color:var(--cb-color-text-muted)}.cb-datepicker__message--error{color:var(--cb-color-danger)}.cb-datepicker__required--required,.cb-datepicker__required--optional{margin-left:.25rem;color:var(--cb-color-text-muted);font-size:.85em;font-weight:var(--cb-font-weight-normal, 400)}\n"] }]
602
- }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], requiredIndicator: [{ type: i0.Input, args: [{ isSignal: true, alias: "requiredIndicator", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], firstDayOfWeek: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstDayOfWeek", required: false }] }], minDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDate", required: false }] }], maxDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDate", required: false }] }], triggerRef: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], fieldRef: [{ type: i0.ViewChild, args: ['field', { isSignal: true }] }], panelRef: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }] } });
1814
+ }
603
1815
  function toBoolean(value) {
604
1816
  return value !== false && value != null && `${value}` !== 'false';
605
1817
  }
1818
+ function isValidDate(d) {
1819
+ return d instanceof Date && !Number.isNaN(d.getTime());
1820
+ }
1821
+ /** Acepta una fecha, un rango `[inicio, fin]` o `null`; descarta fechas inválidas. */
1822
+ function normalizeValue(value) {
1823
+ if (Array.isArray(value)) {
1824
+ const [start, end] = value;
1825
+ return isValidDate(start) ? [start, isValidDate(end) ? end : null] : null;
1826
+ }
1827
+ return isValidDate(value) ? value : null;
1828
+ }
1829
+ function clamp(n, min, max) {
1830
+ return Math.min(max, Math.max(min, n));
1831
+ }
606
1832
  function stripTime(d) {
607
1833
  return new Date(d.getFullYear(), d.getMonth(), d.getDate());
608
1834
  }
609
1835
  function startOfMonth(d) {
610
1836
  return new Date(d.getFullYear(), d.getMonth(), 1);
611
1837
  }
1838
+ /** Inicio del día, del mes o del año. */
1839
+ function startOf(d, unit) {
1840
+ return unit === 'year' ? new Date(d.getFullYear(), 0, 1) : unit === 'month' ? startOfMonth(d) : stripTime(d);
1841
+ }
1842
+ function sameUnit(a, b, unit) {
1843
+ return startOf(a, unit).getTime() === startOf(b, unit).getTime();
1844
+ }
612
1845
  function addDays(d, n) {
613
1846
  const r = new Date(d);
614
1847
  r.setDate(r.getDate() + n);
@@ -617,14 +1850,18 @@ function addDays(d, n) {
617
1850
  function addMonths(d, n) {
618
1851
  return new Date(d.getFullYear(), d.getMonth() + n, 1);
619
1852
  }
1853
+ /** Mismo día `n` meses después, ajustado al último día si el mes es más corto (31 ene → 28 feb). */
1854
+ function sameDayShifted(d, n) {
1855
+ const target = addMonths(d, n);
1856
+ const lastDay = new Date(target.getFullYear(), target.getMonth() + 1, 0).getDate();
1857
+ return new Date(target.getFullYear(), target.getMonth(), Math.min(d.getDate(), lastDay));
1858
+ }
620
1859
  function startOfWeek(d, firstDay) {
621
1860
  const offset = (d.getDay() - firstDay + 7) % 7;
622
1861
  return addDays(d, -offset);
623
1862
  }
624
1863
  function isSameDay(a, b) {
625
- return (a.getFullYear() === b.getFullYear() &&
626
- a.getMonth() === b.getMonth() &&
627
- a.getDate() === b.getDate());
1864
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
628
1865
  }
629
1866
 
630
1867
  /*