@mk-kit/ui 0.34.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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +115 -0
  3. package/block-editor/README.md +254 -0
  4. package/fesm2022/mk-kit-ui-block-editor.mjs +2158 -0
  5. package/fesm2022/mk-kit-ui-block-editor.mjs.map +1 -0
  6. package/fesm2022/mk-kit-ui-button.mjs +81 -0
  7. package/fesm2022/mk-kit-ui-button.mjs.map +1 -0
  8. package/fesm2022/mk-kit-ui-checkbox.mjs +136 -0
  9. package/fesm2022/mk-kit-ui-checkbox.mjs.map +1 -0
  10. package/fesm2022/mk-kit-ui-chip.mjs +122 -0
  11. package/fesm2022/mk-kit-ui-chip.mjs.map +1 -0
  12. package/fesm2022/mk-kit-ui-context-menu.mjs +144 -0
  13. package/fesm2022/mk-kit-ui-context-menu.mjs.map +1 -0
  14. package/fesm2022/mk-kit-ui-core.mjs +1576 -0
  15. package/fesm2022/mk-kit-ui-core.mjs.map +1 -0
  16. package/fesm2022/mk-kit-ui-data.mjs +6055 -0
  17. package/fesm2022/mk-kit-ui-data.mjs.map +1 -0
  18. package/fesm2022/mk-kit-ui-datetime.mjs +3409 -0
  19. package/fesm2022/mk-kit-ui-datetime.mjs.map +1 -0
  20. package/fesm2022/mk-kit-ui-directives.mjs +1779 -0
  21. package/fesm2022/mk-kit-ui-directives.mjs.map +1 -0
  22. package/fesm2022/mk-kit-ui-dnd.mjs +1073 -0
  23. package/fesm2022/mk-kit-ui-dnd.mjs.map +1 -0
  24. package/fesm2022/mk-kit-ui-feedback.mjs +2426 -0
  25. package/fesm2022/mk-kit-ui-feedback.mjs.map +1 -0
  26. package/fesm2022/mk-kit-ui-forms.mjs +9208 -0
  27. package/fesm2022/mk-kit-ui-forms.mjs.map +1 -0
  28. package/fesm2022/mk-kit-ui-icon.mjs +470 -0
  29. package/fesm2022/mk-kit-ui-icon.mjs.map +1 -0
  30. package/fesm2022/mk-kit-ui-media.mjs +896 -0
  31. package/fesm2022/mk-kit-ui-media.mjs.map +1 -0
  32. package/fesm2022/mk-kit-ui-navigation.mjs +2542 -0
  33. package/fesm2022/mk-kit-ui-navigation.mjs.map +1 -0
  34. package/fesm2022/mk-kit-ui-rich-text.mjs +565 -0
  35. package/fesm2022/mk-kit-ui-rich-text.mjs.map +1 -0
  36. package/fesm2022/mk-kit-ui-table.mjs +1378 -0
  37. package/fesm2022/mk-kit-ui-table.mjs.map +1 -0
  38. package/fesm2022/mk-kit-ui.mjs +32 -0
  39. package/fesm2022/mk-kit-ui.mjs.map +1 -0
  40. package/package.json +130 -0
  41. package/schematics/collection.json +10 -0
  42. package/schematics/ng-add/index.js +113 -0
  43. package/schematics/ng-add/schema.json +22 -0
  44. package/schematics/package.json +3 -0
  45. package/styles/mk-kit.css +750 -0
  46. package/types/mk-kit-ui-block-editor.d.ts +292 -0
  47. package/types/mk-kit-ui-button.d.ts +40 -0
  48. package/types/mk-kit-ui-checkbox.d.ts +62 -0
  49. package/types/mk-kit-ui-chip.d.ts +59 -0
  50. package/types/mk-kit-ui-context-menu.d.ts +57 -0
  51. package/types/mk-kit-ui-core.d.ts +1105 -0
  52. package/types/mk-kit-ui-data.d.ts +2580 -0
  53. package/types/mk-kit-ui-datetime.d.ts +1171 -0
  54. package/types/mk-kit-ui-directives.d.ts +807 -0
  55. package/types/mk-kit-ui-dnd.d.ts +423 -0
  56. package/types/mk-kit-ui-feedback.d.ts +1270 -0
  57. package/types/mk-kit-ui-forms.d.ts +3586 -0
  58. package/types/mk-kit-ui-icon.d.ts +108 -0
  59. package/types/mk-kit-ui-media.d.ts +549 -0
  60. package/types/mk-kit-ui-navigation.d.ts +1169 -0
  61. package/types/mk-kit-ui-rich-text.d.ts +187 -0
  62. package/types/mk-kit-ui-table.d.ts +739 -0
  63. package/types/mk-kit-ui.d.ts +17 -0
@@ -0,0 +1,3409 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, ElementRef, Injector, model, input, numberAttribute, booleanAttribute, output, signal, computed, effect, untracked, afterNextRender, forwardRef, ChangeDetectionStrategy, Component, viewChild, viewChildren } from '@angular/core';
3
+ import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
4
+ import { MK_I18N, MkLiveAnnouncer, mkUniqueId, mkValidatorChange, MkAnchoredPanel } from '@mk-kit/ui/core';
5
+ import { MkFormField } from '@mk-kit/ui/forms';
6
+ import { DOCUMENT } from '@angular/common';
7
+
8
+ /** English full month names, January (index 0) → December (index 11). */
9
+ const MONTH_NAMES = [
10
+ 'January',
11
+ 'February',
12
+ 'March',
13
+ 'April',
14
+ 'May',
15
+ 'June',
16
+ 'July',
17
+ 'August',
18
+ 'September',
19
+ 'October',
20
+ 'November',
21
+ 'December',
22
+ ];
23
+ /** English abbreviated month names (`MMM`). */
24
+ const MONTH_NAMES_SHORT = [
25
+ 'Jan',
26
+ 'Feb',
27
+ 'Mar',
28
+ 'Apr',
29
+ 'May',
30
+ 'Jun',
31
+ 'Jul',
32
+ 'Aug',
33
+ 'Sep',
34
+ 'Oct',
35
+ 'Nov',
36
+ 'Dec',
37
+ ];
38
+ /** English full weekday names, Sunday (index 0) → Saturday (index 6). */
39
+ const WEEKDAY_NAMES = [
40
+ 'Sunday',
41
+ 'Monday',
42
+ 'Tuesday',
43
+ 'Wednesday',
44
+ 'Thursday',
45
+ 'Friday',
46
+ 'Saturday',
47
+ ];
48
+ /** English abbreviated weekday names (`ddd`). */
49
+ const WEEKDAY_NAMES_SHORT = [
50
+ 'Sun',
51
+ 'Mon',
52
+ 'Tue',
53
+ 'Wed',
54
+ 'Thu',
55
+ 'Fri',
56
+ 'Sat',
57
+ ];
58
+ /** English single-letter weekday names (narrow). */
59
+ const WEEKDAY_NAMES_NARROW = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
60
+ function pad2$1(n) {
61
+ return n < 10 ? `0${n}` : `${n}`;
62
+ }
63
+ /** True when both dates fall on the same calendar day (local time). */
64
+ function isSameDay(a, b) {
65
+ return (a.getFullYear() === b.getFullYear() &&
66
+ a.getMonth() === b.getMonth() &&
67
+ a.getDate() === b.getDate());
68
+ }
69
+ /** True when both dates fall in the same calendar month of the same year. */
70
+ function isSameMonth(a, b) {
71
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth();
72
+ }
73
+ /** Midnight (00:00:00.000) on the first day of `date`'s month. */
74
+ function startOfMonth(date) {
75
+ return new Date(date.getFullYear(), date.getMonth(), 1);
76
+ }
77
+ /** Midnight on the last day of `date`'s month. */
78
+ function endOfMonth(date) {
79
+ return new Date(date.getFullYear(), date.getMonth() + 1, 0);
80
+ }
81
+ /** A new date `count` months after `date` (negative to subtract). Clamps the
82
+ * day of month so e.g. Jan 31 + 1 month = Feb 28/29, not Mar 3. */
83
+ function addMonths(date, count) {
84
+ const targetMonth = date.getMonth() + count;
85
+ const result = new Date(date.getFullYear(), targetMonth, 1, date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
86
+ const lastDay = new Date(result.getFullYear(), result.getMonth() + 1, 0).getDate();
87
+ result.setDate(Math.min(date.getDate(), lastDay));
88
+ return result;
89
+ }
90
+ /** A new date `count` days after `date` (negative to subtract). */
91
+ function addDays(date, count) {
92
+ const result = new Date(date);
93
+ result.setDate(result.getDate() + count);
94
+ return result;
95
+ }
96
+ /** Midnight (00:00:00.000) on `date`'s calendar day. */
97
+ function startOfDay(date) {
98
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate());
99
+ }
100
+ /**
101
+ * The first day of the week containing `date`, at midnight. `firstDayOfWeek`
102
+ * is the week's starting weekday (0 = Sunday … 6 = Saturday).
103
+ */
104
+ function startOfWeek(date, firstDayOfWeek = 0) {
105
+ const start = startOfDay(date);
106
+ const diff = (start.getDay() - firstDayOfWeek + 7) % 7;
107
+ return addDays(start, -diff);
108
+ }
109
+ /** The last day of the week containing `date`, at midnight. */
110
+ function endOfWeek(date, firstDayOfWeek = 0) {
111
+ return addDays(startOfWeek(date, firstDayOfWeek), 6);
112
+ }
113
+ /**
114
+ * ISO-8601 week number (1–53) of `date`. Weeks start on Monday and week 1 is
115
+ * the week containing the first Thursday of the year.
116
+ */
117
+ function getISOWeek(date) {
118
+ // Shift to the Thursday of this ISO week, then count weeks from Jan 1.
119
+ const d = new Date(date.getFullYear(), date.getMonth(), date.getDate());
120
+ const day = (d.getDay() + 6) % 7; // Monday = 0 … Sunday = 6
121
+ d.setDate(d.getDate() - day + 3);
122
+ const firstThursday = new Date(d.getFullYear(), 0, 4);
123
+ const firstDay = (firstThursday.getDay() + 6) % 7;
124
+ firstThursday.setDate(firstThursday.getDate() - firstDay + 3);
125
+ return (1 + Math.round((d.getTime() - firstThursday.getTime()) / (7 * 86400000)));
126
+ }
127
+ /** True when `a` is strictly before `b` (full timestamp comparison). */
128
+ function isBefore(a, b) {
129
+ return a.getTime() < b.getTime();
130
+ }
131
+ /** True when `a` is strictly after `b` (full timestamp comparison). */
132
+ function isAfter(a, b) {
133
+ return a.getTime() > b.getTime();
134
+ }
135
+ /** Clamp `date` into the inclusive `[min, max]` range (either bound optional). */
136
+ function clampDate(date, min, max) {
137
+ if (min && isBefore(date, min))
138
+ return new Date(min);
139
+ if (max && isAfter(date, max))
140
+ return new Date(max);
141
+ return date;
142
+ }
143
+ /** Format `date` as an ISO calendar date `YYYY-MM-DD` (local time). */
144
+ function formatISODate(date) {
145
+ return `${date.getFullYear().toString().padStart(4, '0')}-${pad2$1(date.getMonth() + 1)}-${pad2$1(date.getDate())}`;
146
+ }
147
+ /** Parse a `YYYY-MM-DD` string into a local-time `Date`, or `null` if invalid. */
148
+ function parseISODate(value) {
149
+ if (!value)
150
+ return null;
151
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value.trim());
152
+ if (!match)
153
+ return null;
154
+ const year = Number(match[1]);
155
+ const month = Number(match[2]) - 1;
156
+ const day = Number(match[3]);
157
+ const date = new Date(year, month, day);
158
+ // Reject rollovers like 2024-02-31.
159
+ if (date.getFullYear() !== year ||
160
+ date.getMonth() !== month ||
161
+ date.getDate() !== day) {
162
+ return null;
163
+ }
164
+ return date;
165
+ }
166
+ /**
167
+ * Format `date` using a small pattern set. Supported tokens:
168
+ * `yyyy` (4-digit year), `MMMM` (full month), `MMM` (short month),
169
+ * `MM` (2-digit month), `dd` (2-digit day), `d` (day), `ddd` (short weekday).
170
+ * Longer tokens are matched first so `MMMM` wins over `MM`.
171
+ * Month/weekday names come from `names` when given, else English.
172
+ */
173
+ function formatDate(date, pattern, names) {
174
+ const tokens = [
175
+ ['yyyy', () => date.getFullYear().toString().padStart(4, '0')],
176
+ ['MMMM', () => (names?.months ?? MONTH_NAMES)[date.getMonth()]],
177
+ ['MMM', () => (names?.monthsShort ?? MONTH_NAMES_SHORT)[date.getMonth()]],
178
+ ['MM', () => pad2$1(date.getMonth() + 1)],
179
+ // Longest-first: 'ddd' (short weekday) must be matched before 'dd'/'d',
180
+ // otherwise 'ddd' is consumed as 'dd' + 'd' and produces day numbers.
181
+ ['ddd', () => (names?.weekdaysShort ?? WEEKDAY_NAMES_SHORT)[date.getDay()]],
182
+ ['dd', () => pad2$1(date.getDate())],
183
+ ['d', () => date.getDate().toString()],
184
+ ];
185
+ let result = '';
186
+ let i = 0;
187
+ outer: while (i < pattern.length) {
188
+ for (const [token, resolve] of tokens) {
189
+ if (pattern.startsWith(token, i)) {
190
+ result += resolve();
191
+ i += token.length;
192
+ continue outer;
193
+ }
194
+ }
195
+ result += pattern[i];
196
+ i += 1;
197
+ }
198
+ return result;
199
+ }
200
+ /**
201
+ * Build a fixed 6×7 matrix of `Date` cells for `viewDate`'s month, including
202
+ * the leading days from the previous month and trailing days from the next so
203
+ * every row is full. `firstDayOfWeek` is 0 (Sunday) … 6 (Saturday).
204
+ */
205
+ function buildMonthMatrix(viewDate, firstDayOfWeek = 0) {
206
+ const first = startOfMonth(viewDate);
207
+ const firstDow = first.getDay();
208
+ const offset = (firstDow - firstDayOfWeek + 7) % 7;
209
+ const gridStart = addDays(first, -offset);
210
+ const weeks = [];
211
+ let cursor = gridStart;
212
+ for (let week = 0; week < 6; week++) {
213
+ const row = [];
214
+ for (let day = 0; day < 7; day++) {
215
+ row.push(cursor);
216
+ cursor = addDays(cursor, 1);
217
+ }
218
+ weeks.push(row);
219
+ }
220
+ return weeks;
221
+ }
222
+ /** Full month names, index 0 = January (from `names`, else English). */
223
+ function getMonthNames(names) {
224
+ return names?.months ?? MONTH_NAMES;
225
+ }
226
+ /**
227
+ * Weekday header labels ordered to start at `firstDayOfWeek`.
228
+ * `format` selects `'short'` (e.g. `Mon`) or `'narrow'` (e.g. `M`).
229
+ * Labels come from `names` when given, else English.
230
+ */
231
+ function getWeekdayNames(firstDayOfWeek = 0, format = 'short', names) {
232
+ const source = format === 'narrow'
233
+ ? (names?.weekdaysNarrow ?? WEEKDAY_NAMES_NARROW)
234
+ : (names?.weekdaysShort ?? WEEKDAY_NAMES_SHORT);
235
+ const start = ((firstDayOfWeek % 7) + 7) % 7;
236
+ const labels = [];
237
+ for (let i = 0; i < 7; i++) {
238
+ labels.push(source[(start + i) % 7]);
239
+ }
240
+ return labels;
241
+ }
242
+ /** Full weekday name for `date` (for screen-reader labels). */
243
+ function getWeekdayFullName(date, names) {
244
+ return (names?.weekdays ?? WEEKDAY_NAMES)[date.getDay()];
245
+ }
246
+
247
+ /**
248
+ * Calendar — an accessible month-grid date picker following the WAI-ARIA
249
+ * grid / date-picker pattern. It is the reusable core behind
250
+ * `mk-date-picker` and `mk-date-range-picker`.
251
+ *
252
+ * Keyboard (when a day has focus): Arrow keys move by day/week (crossing month
253
+ * boundaries updates the view), Home/End jump to the start/end of the week,
254
+ * PageUp/PageDown change month, Shift+PageUp/PageDown change year, and
255
+ * Enter/Space selects the focused day. A roving tabindex keeps a single tab
256
+ * stop. Month changes are announced via {@link MkLiveAnnouncer}.
257
+ *
258
+ * Exposes a two-way `value` model and implements `ControlValueAccessor`, so it
259
+ * also works directly with `ngModel` / `formControl` (single-date mode).
260
+ * Additive range inputs (`rangeMode`,
261
+ * `rangeStart`, `rangeEnd`) let it highlight an in-progress selection without
262
+ * affecting single-date use; range hosts listen to `dateSelected`.
263
+ *
264
+ * ```html
265
+ * <mk-calendar [(value)]="date" [min]="minDate" [firstDayOfWeek]="1" />
266
+ * ```
267
+ */
268
+ class MkCalendar {
269
+ host = inject(ElementRef);
270
+ i18n = inject(MK_I18N);
271
+ injector = inject(Injector);
272
+ announcer = inject(MkLiveAnnouncer);
273
+ /** Two-way selected date (single-date mode). */
274
+ value = model(null, /* @ts-ignore */
275
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
276
+ /** Earliest selectable date (inclusive, day granularity). */
277
+ min = input(null, /* @ts-ignore */
278
+ ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
279
+ /** Latest selectable date (inclusive, day granularity). */
280
+ max = input(null, /* @ts-ignore */
281
+ ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
282
+ /** First column of the week: 0 = Sunday … 6 = Saturday. */
283
+ firstDayOfWeek = input(0, { ...(ngDevMode ? { debugName: "firstDayOfWeek" } : /* istanbul ignore next */ {}), transform: numberAttribute });
284
+ /** Disable the whole calendar. */
285
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
286
+ /** Force the invalid visual + `aria-invalid` when used standalone. */
287
+ invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
288
+ /** Predicate marking individual days as disabled. */
289
+ disabledDate = input(null, /* @ts-ignore */
290
+ ...(ngDevMode ? [{ debugName: "disabledDate" }] : /* istanbul ignore next */ []));
291
+ /** Control size. */
292
+ size = input('md', /* @ts-ignore */
293
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
294
+ /**
295
+ * Stretch to fill the container instead of sizing to content.
296
+ *
297
+ * Off by default, because the common host is a date-picker popover that must
298
+ * stay a compact widget. Turn it on when the calendar is laid out as part of
299
+ * a page — a sidebar or a column — where sizing to content leaves the space
300
+ * beside it empty. Cells share the width evenly and `--_cell-size` becomes
301
+ * their minimum rather than their fixed size.
302
+ */
303
+ fullWidth = input(false, { ...(ngDevMode ? { debugName: "fullWidth" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
304
+ // --- Range mode (additive; does not affect single-date use) --------------
305
+ /** Enable range highlighting. Hosts drive selection via `dateSelected`. */
306
+ rangeMode = input(false, { ...(ngDevMode ? { debugName: "rangeMode" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
307
+ /** Range start endpoint to highlight (range mode). */
308
+ rangeStart = input(null, /* @ts-ignore */
309
+ ...(ngDevMode ? [{ debugName: "rangeStart" }] : /* istanbul ignore next */ []));
310
+ /** Range end endpoint to highlight (range mode). */
311
+ rangeEnd = input(null, /* @ts-ignore */
312
+ ...(ngDevMode ? [{ debugName: "rangeEnd" }] : /* istanbul ignore next */ []));
313
+ /** Emitted whenever a (non-disabled) day is activated. */
314
+ dateSelected = output();
315
+ /**
316
+ * Emitted (in range mode) as the pointer hovers days — the hovered day, or
317
+ * `null` on leave. Lets hosts preview a selection (e.g. a whole-week
318
+ * highlight in `mk-week-picker`).
319
+ */
320
+ dateHovered = output();
321
+ /** id of the visible month/year label — wire as the grid's label. */
322
+ labelId = mkUniqueId('mk-calendar-label');
323
+ /** Disabled state driven by forms (`setDisabledState`). */
324
+ cvaDisabled = signal(false, /* @ts-ignore */
325
+ ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
326
+ /** The day that currently owns the roving tabindex; also drives the view. */
327
+ focusedDate = signal(this.initialFocus(), /* @ts-ignore */
328
+ ...(ngDevMode ? [{ debugName: "focusedDate" }] : /* istanbul ignore next */ []));
329
+ /** Hovered day used to preview a range before the end is picked. */
330
+ hoveredDate = signal(null, /* @ts-ignore */
331
+ ...(ngDevMode ? [{ debugName: "hoveredDate" }] : /* istanbul ignore next */ []));
332
+ /** Disabled by the `disabled` input or by the bound form control. */
333
+ isControlDisabled = computed(() => this.disabled() || this.cvaDisabled(), /* @ts-ignore */
334
+ ...(ngDevMode ? [{ debugName: "isControlDisabled" }] : /* istanbul ignore next */ []));
335
+ isInvalid = computed(() => this.invalid(), /* @ts-ignore */
336
+ ...(ngDevMode ? [{ debugName: "isInvalid" }] : /* istanbul ignore next */ []));
337
+ /** First day of the visible month. */
338
+ viewMonth = computed(() => startOfMonth(this.focusedDate()), /* @ts-ignore */
339
+ ...(ngDevMode ? [{ debugName: "viewMonth" }] : /* istanbul ignore next */ []));
340
+ /** Human label for the visible month, e.g. `July 2026`. */
341
+ monthLabel = computed(() => formatDate(this.viewMonth(), 'MMMM yyyy', this.i18n.dateNames), /* @ts-ignore */
342
+ ...(ngDevMode ? [{ debugName: "monthLabel" }] : /* istanbul ignore next */ []));
343
+ /** 6×7 grid of dates for the visible month. */
344
+ weeks = computed(() => buildMonthMatrix(this.viewMonth(), this.firstDayOfWeek()), /* @ts-ignore */
345
+ ...(ngDevMode ? [{ debugName: "weeks" }] : /* istanbul ignore next */ []));
346
+ /** Weekday column headers ordered from `firstDayOfWeek`. */
347
+ weekdays = computed(() => this.weeks()[0].map((d) => ({
348
+ short: formatDate(d, 'ddd', this.i18n.dateNames),
349
+ full: getWeekdayFullName(d, this.i18n.dateNames),
350
+ })), /* @ts-ignore */
351
+ ...(ngDevMode ? [{ debugName: "weekdays" }] : /* istanbul ignore next */ []));
352
+ announcedMonth = this.monthLabel();
353
+ constructor() {
354
+ // Follow external value changes into the view/focus. Read focusedDate
355
+ // untracked so this effect only reacts to `value` — otherwise navigating
356
+ // months (which moves focusedDate) would re-run it and snap the view back.
357
+ effect(() => {
358
+ const v = this.value();
359
+ if (v && !isSameDay(v, untracked(this.focusedDate))) {
360
+ this.focusedDate.set(startOfDay(v));
361
+ }
362
+ });
363
+ // Announce month transitions for screen-reader users.
364
+ effect(() => {
365
+ const label = this.monthLabel();
366
+ if (label !== this.announcedMonth) {
367
+ this.announcedMonth = label;
368
+ this.announcer.announce(label);
369
+ }
370
+ });
371
+ }
372
+ // --- ControlValueAccessor ---------------------------------------------------
373
+ onChange = () => { };
374
+ onTouched = () => { };
375
+ writeValue(value) {
376
+ this.value.set(value instanceof Date ? startOfDay(value) : null);
377
+ }
378
+ registerOnChange(fn) {
379
+ this.onChange = fn;
380
+ }
381
+ registerOnTouched(fn) {
382
+ this.onTouched = fn;
383
+ }
384
+ setDisabledState(isDisabled) {
385
+ this.cvaDisabled.set(isDisabled);
386
+ }
387
+ /** Marks the control touched when focus leaves the calendar entirely. */
388
+ onFocusout(event) {
389
+ const next = event.relatedTarget;
390
+ if (!next || !this.host.nativeElement.contains(next))
391
+ this.onTouched();
392
+ }
393
+ initialFocus() {
394
+ const base = this.value() ?? new Date();
395
+ return startOfDay(clampDate(startOfDay(base), this.min(), this.max()));
396
+ }
397
+ // --- Day classification ---------------------------------------------------
398
+ isOutside(d) {
399
+ return !isSameMonth(d, this.viewMonth());
400
+ }
401
+ isDisabled(d) {
402
+ if (this.isControlDisabled())
403
+ return true;
404
+ const day = startOfDay(d);
405
+ const min = this.min();
406
+ const max = this.max();
407
+ if (min && isBefore(day, startOfDay(min)))
408
+ return true;
409
+ if (max && isAfter(day, startOfDay(max)))
410
+ return true;
411
+ return this.disabledDate()?.(d) ?? false;
412
+ }
413
+ isToday(d) {
414
+ return isSameDay(d, new Date());
415
+ }
416
+ isSelected(d) {
417
+ const v = this.value();
418
+ return !this.rangeMode() && !!v && isSameDay(d, v);
419
+ }
420
+ isFocused(d) {
421
+ return isSameDay(d, this.focusedDate());
422
+ }
423
+ /** Effective range end while previewing (falls back to the hovered day). */
424
+ previewEnd() {
425
+ return this.rangeEnd() ?? this.hoveredDate();
426
+ }
427
+ isRangeStart(d) {
428
+ const s = this.rangeStart();
429
+ return this.rangeMode() && !!s && isSameDay(d, s);
430
+ }
431
+ isRangeEnd(d) {
432
+ if (!this.rangeMode())
433
+ return false;
434
+ const end = this.previewEnd();
435
+ return !!end && isSameDay(d, end);
436
+ }
437
+ isInRange(d) {
438
+ if (!this.rangeMode())
439
+ return false;
440
+ const s = this.rangeStart();
441
+ const end = this.previewEnd();
442
+ if (!s || !end)
443
+ return false;
444
+ const [lo, hi] = isAfter(end, s) ? [s, end] : [end, s];
445
+ const day = startOfDay(d);
446
+ return isAfter(day, startOfDay(lo)) && isBefore(day, startOfDay(hi));
447
+ }
448
+ /**
449
+ * `aria-selected` state of a day's gridcell. Single-date mode mirrors
450
+ * `isSelected`; range mode exposes the committed endpoints and the days
451
+ * between them (hover previews stay visual-only so the accessibility tree
452
+ * doesn't churn while the pointer moves).
453
+ */
454
+ isAriaSelected(d) {
455
+ if (!this.rangeMode())
456
+ return this.isSelected(d);
457
+ const s = this.rangeStart();
458
+ const e = this.rangeEnd();
459
+ if (s && isSameDay(d, s))
460
+ return true;
461
+ if (e && isSameDay(d, e))
462
+ return true;
463
+ if (!s || !e)
464
+ return false;
465
+ const [lo, hi] = isAfter(e, s) ? [s, e] : [e, s];
466
+ const day = startOfDay(d);
467
+ return isAfter(day, startOfDay(lo)) && isBefore(day, startOfDay(hi));
468
+ }
469
+ dayLabel(d) {
470
+ return formatDate(d, 'MMMM d, yyyy', this.i18n.dateNames);
471
+ }
472
+ // --- Interaction ----------------------------------------------------------
473
+ select(d) {
474
+ if (this.isDisabled(d))
475
+ return;
476
+ const day = startOfDay(d);
477
+ this.focusedDate.set(day);
478
+ if (!this.rangeMode()) {
479
+ this.value.set(day);
480
+ this.onChange(day);
481
+ }
482
+ this.dateSelected.emit(day);
483
+ this.focusActiveCell();
484
+ }
485
+ onHover(d) {
486
+ if (!this.rangeMode())
487
+ return;
488
+ const day = startOfDay(d);
489
+ this.hoveredDate.set(day);
490
+ this.dateHovered.emit(day);
491
+ }
492
+ clearHover() {
493
+ if (!this.rangeMode())
494
+ return;
495
+ this.hoveredDate.set(null);
496
+ this.dateHovered.emit(null);
497
+ }
498
+ prevMonth() {
499
+ this.moveFocus(addMonths(this.focusedDate(), -1));
500
+ }
501
+ nextMonth() {
502
+ this.moveFocus(addMonths(this.focusedDate(), 1));
503
+ }
504
+ prevYear() {
505
+ this.moveFocus(addMonths(this.focusedDate(), -12));
506
+ }
507
+ nextYear() {
508
+ this.moveFocus(addMonths(this.focusedDate(), 12));
509
+ }
510
+ onGridKeydown(event) {
511
+ const e = event;
512
+ const current = this.focusedDate();
513
+ let next = null;
514
+ switch (e.key) {
515
+ case 'ArrowLeft':
516
+ next = addDays(current, -1);
517
+ break;
518
+ case 'ArrowRight':
519
+ next = addDays(current, 1);
520
+ break;
521
+ case 'ArrowUp':
522
+ next = addDays(current, -7);
523
+ break;
524
+ case 'ArrowDown':
525
+ next = addDays(current, 7);
526
+ break;
527
+ case 'Home':
528
+ next = addDays(current, -this.weekdayOffset(current));
529
+ break;
530
+ case 'End':
531
+ next = addDays(current, 6 - this.weekdayOffset(current));
532
+ break;
533
+ case 'PageUp':
534
+ next = addMonths(current, e.shiftKey ? -12 : -1);
535
+ break;
536
+ case 'PageDown':
537
+ next = addMonths(current, e.shiftKey ? 12 : 1);
538
+ break;
539
+ case 'Enter':
540
+ case ' ':
541
+ e.preventDefault();
542
+ this.select(current);
543
+ return;
544
+ default:
545
+ return;
546
+ }
547
+ e.preventDefault();
548
+ if (next)
549
+ this.moveFocus(next);
550
+ }
551
+ weekdayOffset(d) {
552
+ return (d.getDay() - this.firstDayOfWeek() + 7) % 7;
553
+ }
554
+ moveFocus(target) {
555
+ this.focusedDate.set(startOfDay(target));
556
+ this.focusActiveCell();
557
+ }
558
+ focusActiveCell() {
559
+ afterNextRender({
560
+ write: () => {
561
+ const el = this.host.nativeElement.querySelector('.mk-calendar__day[tabindex="0"]');
562
+ el?.focus();
563
+ },
564
+ }, { injector: this.injector });
565
+ }
566
+ // --- Validator ------------------------------------------------------------
567
+ validatorChange = mkValidatorChange(() => {
568
+ this.min();
569
+ this.max();
570
+ this.disabledDate();
571
+ });
572
+ /**
573
+ * Reports `mkMinDate` / `mkMaxDate` for a date outside `[min]`/`[max]`, and
574
+ * `mkDateFilter` for one rejected by `[disabledDate]`.
575
+ */
576
+ validate(control) {
577
+ const v = control.value;
578
+ if (!(v instanceof Date) || Number.isNaN(v.getTime()))
579
+ return null;
580
+ const day = startOfDay(v);
581
+ const min = this.min();
582
+ if (min && isBefore(day, startOfDay(min)))
583
+ return { mkMinDate: { min, actual: v } };
584
+ const max = this.max();
585
+ if (max && isAfter(day, startOfDay(max)))
586
+ return { mkMaxDate: { max, actual: v } };
587
+ return this.disabledDate()?.(v) ? { mkDateFilter: true } : null;
588
+ }
589
+ registerOnValidatorChange(fn) {
590
+ this.validatorChange.register(fn);
591
+ }
592
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkCalendar, deps: [], target: i0.ɵɵFactoryTarget.Component });
593
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkCalendar, isStandalone: true, selector: "mk-calendar", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, firstDayOfWeek: { classPropertyName: "firstDayOfWeek", publicName: "firstDayOfWeek", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, disabledDate: { classPropertyName: "disabledDate", publicName: "disabledDate", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, fullWidth: { classPropertyName: "fullWidth", publicName: "fullWidth", isSignal: true, isRequired: false, transformFunction: null }, rangeMode: { classPropertyName: "rangeMode", publicName: "rangeMode", isSignal: true, isRequired: false, transformFunction: null }, rangeStart: { classPropertyName: "rangeStart", publicName: "rangeStart", isSignal: true, isRequired: false, transformFunction: null }, rangeEnd: { classPropertyName: "rangeEnd", publicName: "rangeEnd", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", dateSelected: "dateSelected", dateHovered: "dateHovered" }, host: { listeners: { "focusout": "onFocusout($event)" }, properties: { "class.mk-calendar--disabled": "isControlDisabled()", "class.mk-calendar--sm": "size() === 'sm'", "class.mk-calendar--md": "size() === 'md'", "class.mk-calendar--lg": "size() === 'lg'", "class.mk-calendar--full": "fullWidth()", "class.mk-calendar--range": "rangeMode()", "class.mk-calendar--invalid": "isInvalid()", "attr.aria-invalid": "isInvalid() || null" }, classAttribute: "mk-calendar" }, providers: [
594
+ {
595
+ provide: NG_VALUE_ACCESSOR,
596
+ useExisting: forwardRef(() => MkCalendar),
597
+ multi: true,
598
+ },
599
+ {
600
+ provide: NG_VALIDATORS,
601
+ useExisting: forwardRef(() => MkCalendar),
602
+ multi: true,
603
+ },
604
+ ], ngImport: i0, template: "<div class=\"mk-calendar__header\">\n <button\n type=\"button\"\n class=\"mk-calendar__nav\"\n [attr.aria-label]=\"i18n.previousMonth\"\n (click)=\"prevMonth()\"\n >\n <svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" aria-hidden=\"true\">\n <path\n d=\"M15 5l-7 7 7 7\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n\n <span class=\"mk-calendar__label\" [id]=\"labelId\" aria-live=\"polite\">{{\n monthLabel()\n }}</span>\n\n <button\n type=\"button\"\n class=\"mk-calendar__nav\"\n [attr.aria-label]=\"i18n.nextMonth\"\n (click)=\"nextMonth()\"\n >\n <svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" aria-hidden=\"true\">\n <path\n d=\"M9 5l7 7-7 7\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n</div>\n\n<table\n class=\"mk-calendar__grid\"\n role=\"grid\"\n [attr.aria-labelledby]=\"labelId\"\n (keydown)=\"onGridKeydown($event)\"\n>\n <thead>\n <tr>\n @for (wd of weekdays(); track $index) {\n <th class=\"mk-calendar__weekday\" scope=\"col\">\n <abbr [title]=\"wd.full\">{{ wd.short }}</abbr>\n </th>\n }\n </tr>\n </thead>\n <tbody (mouseleave)=\"clearHover()\">\n @for (week of weeks(); track $index) {\n <tr>\n @for (day of week; track day.getTime()) {\n <td\n class=\"mk-calendar__cell\"\n role=\"gridcell\"\n [attr.aria-selected]=\"isAriaSelected(day) || null\"\n >\n <button\n type=\"button\"\n class=\"mk-calendar__day\"\n [class.mk-calendar__day--outside]=\"isOutside(day)\"\n [class.mk-calendar__day--today]=\"isToday(day)\"\n [class.mk-calendar__day--selected]=\"isSelected(day)\"\n [class.mk-calendar__day--range-start]=\"isRangeStart(day)\"\n [class.mk-calendar__day--range-end]=\"isRangeEnd(day)\"\n [class.mk-calendar__day--in-range]=\"isInRange(day)\"\n [class.mk-calendar__day--disabled]=\"isDisabled(day)\"\n [attr.tabindex]=\"isFocused(day) ? 0 : -1\"\n [attr.aria-label]=\"dayLabel(day)\"\n [attr.aria-current]=\"isToday(day) ? 'date' : null\"\n [attr.aria-disabled]=\"isDisabled(day) || null\"\n (click)=\"select(day)\"\n (mouseenter)=\"onHover(day)\"\n >\n <span class=\"mk-calendar__day-num\">{{ day.getDate() }}</span>\n </button>\n </td>\n }\n </tr>\n }\n </tbody>\n</table>\n", styles: ["@charset \"UTF-8\";:host{--_cell-size: 2.5rem;display:inline-block;width:fit-content;max-width:100%;padding:var(--mk-space-4);font-family:var(--mk-font-sans);color:var(--mk-text);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-lg);box-shadow:var(--mk-shadow-xs)}:host(.mk-calendar--full){display:block;width:100%;min-width:min-content}:host(.mk-calendar--full) .mk-calendar__grid{width:100%}:host(.mk-calendar--full) .mk-calendar__weekday{width:auto;min-width:var(--_cell-size)}:host(.mk-calendar--full) .mk-calendar__day{width:100%;min-width:var(--_cell-size)}:host(.mk-calendar--sm){--_cell-size: 2.1rem;padding:var(--mk-space-3);font-size:var(--mk-font-size-sm)}:host(.mk-calendar--lg){--_cell-size: 2.9rem;font-size:var(--mk-font-size-lg)}.mk-calendar__header{display:flex;align-items:center;gap:var(--mk-space-1);margin-bottom:var(--mk-space-2)}.mk-calendar__label{flex:1 1 auto;text-align:center;font-weight:var(--mk-font-weight-semibold);font-size:var(--mk-font-size-md);white-space:nowrap}.mk-calendar__nav{display:inline-flex;align-items:center;justify-content:center;width:var(--mk-control-height-sm);height:var(--mk-control-height-sm);flex:none;color:var(--mk-text-muted);background:transparent;border:var(--mk-border-width) solid transparent;border-radius:var(--mk-radius-md);cursor:pointer;transition:background-color var(--mk-duration-fast) var(--mk-ease-standard),color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-calendar__nav:hover{background-color:var(--mk-hover-overlay);color:var(--mk-text)}.mk-calendar__nav:active{background-color:var(--mk-active-overlay)}.mk-calendar__nav:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}.mk-calendar__nav svg{display:block}.mk-calendar__grid{border-collapse:separate;border-spacing:0;table-layout:fixed}.mk-calendar__weekday{width:var(--_cell-size);padding:0 0 var(--mk-space-2);font-size:var(--mk-font-size-xs);font-weight:var(--mk-font-weight-semibold);letter-spacing:var(--mk-letter-spacing-wide);text-transform:uppercase;color:var(--mk-text-subtle)}.mk-calendar__weekday abbr{text-decoration:none;cursor:default}.mk-calendar__cell{padding:1px;text-align:center}.mk-calendar__day{display:inline-flex;align-items:center;justify-content:center;position:relative;width:var(--_cell-size);height:var(--_cell-size);font-family:inherit;font-size:var(--mk-font-size-sm);color:var(--mk-text);background:transparent;border:var(--mk-border-width) solid transparent;border-radius:var(--mk-radius-md);cursor:pointer;transition:background-color var(--mk-duration-fast) var(--mk-ease-standard),color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-calendar__day:hover:not(.mk-calendar__day--disabled){background-color:var(--mk-hover-overlay)}.mk-calendar__day:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(var(--mk-focus-ring-offset) * -1)}.mk-calendar__day--outside{color:var(--mk-text-disabled)}.mk-calendar__day--today:after{content:\"\";position:absolute;left:50%;bottom:4px;width:4px;height:4px;border-radius:var(--mk-radius-circle);background-color:var(--mk-primary);transform:translate(-50%)}.mk-calendar__day--in-range{background-color:var(--mk-primary-subtle);border-radius:0}.mk-calendar__day--selected,.mk-calendar__day--range-start,.mk-calendar__day--range-end{background-color:var(--mk-primary);color:var(--mk-primary-contrast);font-weight:var(--mk-font-weight-semibold)}.mk-calendar__day--selected:hover,.mk-calendar__day--range-start:hover,.mk-calendar__day--range-end:hover{background-color:var(--mk-primary-hover)}.mk-calendar__day--range-start{border-start-end-radius:0;border-end-end-radius:0}.mk-calendar__day--range-end{border-start-start-radius:0;border-end-start-radius:0}.mk-calendar__day--today.mk-calendar__day--selected:after,.mk-calendar__day--today.mk-calendar__day--range-start:after,.mk-calendar__day--today.mk-calendar__day--range-end:after{background-color:var(--mk-primary-contrast)}.mk-calendar__day--disabled{color:var(--mk-text-disabled);cursor:not-allowed;opacity:.6}.mk-calendar__day-num{line-height:1}@media(prefers-reduced-motion:reduce){.mk-calendar__nav,.mk-calendar__day{transition:none}}.mk-calendar--disabled{opacity:.55;pointer-events:none}:host(.mk-calendar--invalid){border-color:var(--mk-danger)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
605
+ }
606
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkCalendar, decorators: [{
607
+ type: Component,
608
+ args: [{ selector: 'mk-calendar', changeDetection: ChangeDetectionStrategy.OnPush, providers: [
609
+ {
610
+ provide: NG_VALUE_ACCESSOR,
611
+ useExisting: forwardRef(() => MkCalendar),
612
+ multi: true,
613
+ },
614
+ {
615
+ provide: NG_VALIDATORS,
616
+ useExisting: forwardRef(() => MkCalendar),
617
+ multi: true,
618
+ },
619
+ ], host: {
620
+ class: 'mk-calendar',
621
+ '[class.mk-calendar--disabled]': 'isControlDisabled()',
622
+ '(focusout)': 'onFocusout($event)',
623
+ '[class.mk-calendar--sm]': "size() === 'sm'",
624
+ '[class.mk-calendar--md]': "size() === 'md'",
625
+ '[class.mk-calendar--lg]': "size() === 'lg'",
626
+ '[class.mk-calendar--full]': 'fullWidth()',
627
+ '[class.mk-calendar--range]': 'rangeMode()',
628
+ '[class.mk-calendar--invalid]': 'isInvalid()',
629
+ '[attr.aria-invalid]': 'isInvalid() || null',
630
+ }, template: "<div class=\"mk-calendar__header\">\n <button\n type=\"button\"\n class=\"mk-calendar__nav\"\n [attr.aria-label]=\"i18n.previousMonth\"\n (click)=\"prevMonth()\"\n >\n <svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" aria-hidden=\"true\">\n <path\n d=\"M15 5l-7 7 7 7\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n\n <span class=\"mk-calendar__label\" [id]=\"labelId\" aria-live=\"polite\">{{\n monthLabel()\n }}</span>\n\n <button\n type=\"button\"\n class=\"mk-calendar__nav\"\n [attr.aria-label]=\"i18n.nextMonth\"\n (click)=\"nextMonth()\"\n >\n <svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" aria-hidden=\"true\">\n <path\n d=\"M9 5l7 7-7 7\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n</div>\n\n<table\n class=\"mk-calendar__grid\"\n role=\"grid\"\n [attr.aria-labelledby]=\"labelId\"\n (keydown)=\"onGridKeydown($event)\"\n>\n <thead>\n <tr>\n @for (wd of weekdays(); track $index) {\n <th class=\"mk-calendar__weekday\" scope=\"col\">\n <abbr [title]=\"wd.full\">{{ wd.short }}</abbr>\n </th>\n }\n </tr>\n </thead>\n <tbody (mouseleave)=\"clearHover()\">\n @for (week of weeks(); track $index) {\n <tr>\n @for (day of week; track day.getTime()) {\n <td\n class=\"mk-calendar__cell\"\n role=\"gridcell\"\n [attr.aria-selected]=\"isAriaSelected(day) || null\"\n >\n <button\n type=\"button\"\n class=\"mk-calendar__day\"\n [class.mk-calendar__day--outside]=\"isOutside(day)\"\n [class.mk-calendar__day--today]=\"isToday(day)\"\n [class.mk-calendar__day--selected]=\"isSelected(day)\"\n [class.mk-calendar__day--range-start]=\"isRangeStart(day)\"\n [class.mk-calendar__day--range-end]=\"isRangeEnd(day)\"\n [class.mk-calendar__day--in-range]=\"isInRange(day)\"\n [class.mk-calendar__day--disabled]=\"isDisabled(day)\"\n [attr.tabindex]=\"isFocused(day) ? 0 : -1\"\n [attr.aria-label]=\"dayLabel(day)\"\n [attr.aria-current]=\"isToday(day) ? 'date' : null\"\n [attr.aria-disabled]=\"isDisabled(day) || null\"\n (click)=\"select(day)\"\n (mouseenter)=\"onHover(day)\"\n >\n <span class=\"mk-calendar__day-num\">{{ day.getDate() }}</span>\n </button>\n </td>\n }\n </tr>\n }\n </tbody>\n</table>\n", styles: ["@charset \"UTF-8\";:host{--_cell-size: 2.5rem;display:inline-block;width:fit-content;max-width:100%;padding:var(--mk-space-4);font-family:var(--mk-font-sans);color:var(--mk-text);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-lg);box-shadow:var(--mk-shadow-xs)}:host(.mk-calendar--full){display:block;width:100%;min-width:min-content}:host(.mk-calendar--full) .mk-calendar__grid{width:100%}:host(.mk-calendar--full) .mk-calendar__weekday{width:auto;min-width:var(--_cell-size)}:host(.mk-calendar--full) .mk-calendar__day{width:100%;min-width:var(--_cell-size)}:host(.mk-calendar--sm){--_cell-size: 2.1rem;padding:var(--mk-space-3);font-size:var(--mk-font-size-sm)}:host(.mk-calendar--lg){--_cell-size: 2.9rem;font-size:var(--mk-font-size-lg)}.mk-calendar__header{display:flex;align-items:center;gap:var(--mk-space-1);margin-bottom:var(--mk-space-2)}.mk-calendar__label{flex:1 1 auto;text-align:center;font-weight:var(--mk-font-weight-semibold);font-size:var(--mk-font-size-md);white-space:nowrap}.mk-calendar__nav{display:inline-flex;align-items:center;justify-content:center;width:var(--mk-control-height-sm);height:var(--mk-control-height-sm);flex:none;color:var(--mk-text-muted);background:transparent;border:var(--mk-border-width) solid transparent;border-radius:var(--mk-radius-md);cursor:pointer;transition:background-color var(--mk-duration-fast) var(--mk-ease-standard),color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-calendar__nav:hover{background-color:var(--mk-hover-overlay);color:var(--mk-text)}.mk-calendar__nav:active{background-color:var(--mk-active-overlay)}.mk-calendar__nav:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}.mk-calendar__nav svg{display:block}.mk-calendar__grid{border-collapse:separate;border-spacing:0;table-layout:fixed}.mk-calendar__weekday{width:var(--_cell-size);padding:0 0 var(--mk-space-2);font-size:var(--mk-font-size-xs);font-weight:var(--mk-font-weight-semibold);letter-spacing:var(--mk-letter-spacing-wide);text-transform:uppercase;color:var(--mk-text-subtle)}.mk-calendar__weekday abbr{text-decoration:none;cursor:default}.mk-calendar__cell{padding:1px;text-align:center}.mk-calendar__day{display:inline-flex;align-items:center;justify-content:center;position:relative;width:var(--_cell-size);height:var(--_cell-size);font-family:inherit;font-size:var(--mk-font-size-sm);color:var(--mk-text);background:transparent;border:var(--mk-border-width) solid transparent;border-radius:var(--mk-radius-md);cursor:pointer;transition:background-color var(--mk-duration-fast) var(--mk-ease-standard),color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-calendar__day:hover:not(.mk-calendar__day--disabled){background-color:var(--mk-hover-overlay)}.mk-calendar__day:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(var(--mk-focus-ring-offset) * -1)}.mk-calendar__day--outside{color:var(--mk-text-disabled)}.mk-calendar__day--today:after{content:\"\";position:absolute;left:50%;bottom:4px;width:4px;height:4px;border-radius:var(--mk-radius-circle);background-color:var(--mk-primary);transform:translate(-50%)}.mk-calendar__day--in-range{background-color:var(--mk-primary-subtle);border-radius:0}.mk-calendar__day--selected,.mk-calendar__day--range-start,.mk-calendar__day--range-end{background-color:var(--mk-primary);color:var(--mk-primary-contrast);font-weight:var(--mk-font-weight-semibold)}.mk-calendar__day--selected:hover,.mk-calendar__day--range-start:hover,.mk-calendar__day--range-end:hover{background-color:var(--mk-primary-hover)}.mk-calendar__day--range-start{border-start-end-radius:0;border-end-end-radius:0}.mk-calendar__day--range-end{border-start-start-radius:0;border-end-start-radius:0}.mk-calendar__day--today.mk-calendar__day--selected:after,.mk-calendar__day--today.mk-calendar__day--range-start:after,.mk-calendar__day--today.mk-calendar__day--range-end:after{background-color:var(--mk-primary-contrast)}.mk-calendar__day--disabled{color:var(--mk-text-disabled);cursor:not-allowed;opacity:.6}.mk-calendar__day-num{line-height:1}@media(prefers-reduced-motion:reduce){.mk-calendar__nav,.mk-calendar__day{transition:none}}.mk-calendar--disabled{opacity:.55;pointer-events:none}:host(.mk-calendar--invalid){border-color:var(--mk-danger)}\n"] }]
631
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], firstDayOfWeek: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstDayOfWeek", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], disabledDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabledDate", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], fullWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullWidth", required: false }] }], rangeMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangeMode", required: false }] }], rangeStart: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangeStart", required: false }] }], rangeEnd: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangeEnd", required: false }] }], dateSelected: [{ type: i0.Output, args: ["dateSelected"] }], dateHovered: [{ type: i0.Output, args: ["dateHovered"] }] } });
632
+
633
+ /**
634
+ * DatePicker — a text field paired with a calendar popover. Type a date (parsed
635
+ * on blur/Enter) or pick one from the `mk-calendar`. Implements
636
+ * `ControlValueAccessor` and a two-way `value` model, so it works with
637
+ * `[(ngModel)]`, reactive forms and `[(value)]`.
638
+ *
639
+ * The calendar opens below the field, closes on Escape / outside click / after
640
+ * a selection, and wires `aria-expanded` / `aria-haspopup` / `aria-invalid`.
641
+ * When nested in an `mk-form-field` it adopts the field's id and aria wiring.
642
+ *
643
+ * ```html
644
+ * <mk-date-picker [(value)]="date" [min]="minDate" clearable />
645
+ * ```
646
+ */
647
+ class MkDatePicker {
648
+ i18n = inject(MK_I18N);
649
+ field = inject(MkFormField, { optional: true });
650
+ host = inject(ElementRef);
651
+ injector = inject(Injector);
652
+ inputRef = viewChild('textInput', /* @ts-ignore */
653
+ ...(ngDevMode ? [{ debugName: "inputRef" }] : /* istanbul ignore next */ []));
654
+ /** The calendar panel — lives in the top layer once opened. */
655
+ panelRef = viewChild('panel', /* @ts-ignore */
656
+ ...(ngDevMode ? [{ debugName: "panelRef" }] : /* istanbul ignore next */ []));
657
+ /** Two-way selected date. */
658
+ value = model(null, /* @ts-ignore */
659
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
660
+ /** Earliest selectable date (inclusive). */
661
+ min = input(null, /* @ts-ignore */
662
+ ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
663
+ /** Latest selectable date (inclusive). */
664
+ max = input(null, /* @ts-ignore */
665
+ ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
666
+ /** Predicate marking individual days as disabled (threaded to the calendar). */
667
+ disabledDate = input(null, /* @ts-ignore */
668
+ ...(ngDevMode ? [{ debugName: "disabledDate" }] : /* istanbul ignore next */ []));
669
+ /** Placeholder shown when empty. */
670
+ placeholder = input(this.i18n.selectDate, /* @ts-ignore */
671
+ ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
672
+ /** Pattern used to render the selected date in the field. */
673
+ displayFormat = input('MMM d, yyyy', /* @ts-ignore */
674
+ ...(ngDevMode ? [{ debugName: "displayFormat" }] : /* istanbul ignore next */ []));
675
+ /** Disable the control. */
676
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
677
+ /** Show a clear button when a date is selected. */
678
+ clearable = input(false, { ...(ngDevMode ? { debugName: "clearable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
679
+ /** Force invalid styling + `aria-invalid`. */
680
+ invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
681
+ /** First column of the calendar week (0 = Sunday). */
682
+ firstDayOfWeek = input(0, { ...(ngDevMode ? { debugName: "firstDayOfWeek" } : /* istanbul ignore next */ {}), transform: numberAttribute });
683
+ /** Control size. Ignored when nested in an `mk-form-field`. */
684
+ size = input('md', /* @ts-ignore */
685
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
686
+ open = signal(false, /* @ts-ignore */
687
+ ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
688
+ /** Editable text mirror of the field. */
689
+ inputText = signal('', /* @ts-ignore */
690
+ ...(ngDevMode ? [{ debugName: "inputText" }] : /* istanbul ignore next */ []));
691
+ cvaDisabled = signal(false, /* @ts-ignore */
692
+ ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
693
+ onChange = () => { };
694
+ onTouched = () => { };
695
+ inputId = this.field?.controlId ?? mkUniqueId('mk-date-picker');
696
+ panelId = mkUniqueId('mk-date-picker-panel');
697
+ effectiveSize = computed(() => this.field ? this.field.size() : this.size(), /* @ts-ignore */
698
+ ...(ngDevMode ? [{ debugName: "effectiveSize" }] : /* istanbul ignore next */ []));
699
+ isDisabled = computed(() => this.disabled() || this.cvaDisabled(), /* @ts-ignore */
700
+ ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
701
+ isInvalid = computed(() => this.invalid() || (this.field?.hasError() ?? false), /* @ts-ignore */
702
+ ...(ngDevMode ? [{ debugName: "isInvalid" }] : /* istanbul ignore next */ []));
703
+ isRequired = computed(() => this.field?.isRequired() ?? false, /* @ts-ignore */
704
+ ...(ngDevMode ? [{ debugName: "isRequired" }] : /* istanbul ignore next */ []));
705
+ describedBy = computed(() => this.field?.describedBy() ?? null, /* @ts-ignore */
706
+ ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
707
+ showClear = computed(() => this.clearable() && !!this.value() && !this.isDisabled(), /* @ts-ignore */
708
+ ...(ngDevMode ? [{ debugName: "showClear" }] : /* istanbul ignore next */ []));
709
+ constructor() {
710
+ // Keep the text field in sync with the model (typing does not touch value).
711
+ effect(() => {
712
+ const v = this.value();
713
+ this.inputText.set(v ? formatDate(v, this.displayFormat(), this.i18n.dateNames) : '');
714
+ });
715
+ }
716
+ toggle() {
717
+ if (this.isDisabled())
718
+ return;
719
+ this.open() ? this.close() : this.openPanel();
720
+ }
721
+ openPanel() {
722
+ if (this.isDisabled() || this.open())
723
+ return;
724
+ this.open.set(true);
725
+ afterNextRender({
726
+ write: () => {
727
+ const el = this.panelRef()?.nativeElement.querySelector('.mk-calendar__day[tabindex="0"]');
728
+ el?.focus();
729
+ },
730
+ }, { injector: this.injector });
731
+ }
732
+ close() {
733
+ if (this.open())
734
+ this.open.set(false);
735
+ }
736
+ onCalendarPick(date) {
737
+ this.setValue(date);
738
+ this.close();
739
+ this.inputRef()?.nativeElement.focus();
740
+ }
741
+ onInput(event) {
742
+ this.inputText.set(event.target.value);
743
+ }
744
+ onInputKeydown(event) {
745
+ const e = event;
746
+ if (this.isDisabled())
747
+ return;
748
+ switch (e.key) {
749
+ case 'Enter':
750
+ e.preventDefault();
751
+ this.commitInput();
752
+ break;
753
+ case 'ArrowDown':
754
+ e.preventDefault();
755
+ this.openPanel();
756
+ break;
757
+ case 'Escape':
758
+ if (this.open()) {
759
+ e.preventDefault();
760
+ this.close();
761
+ }
762
+ break;
763
+ }
764
+ }
765
+ clear() {
766
+ this.setValue(null);
767
+ this.inputRef()?.nativeElement.focus();
768
+ }
769
+ commitInput() {
770
+ const text = this.inputText().trim();
771
+ if (!text) {
772
+ this.setValue(null);
773
+ return;
774
+ }
775
+ const parsed = this.parse(text);
776
+ if (parsed) {
777
+ this.setValue(clampDate(parsed, this.min(), this.max()));
778
+ }
779
+ else {
780
+ // Invalid entry — revert to the current model's display.
781
+ const v = this.value();
782
+ this.inputText.set(v ? formatDate(v, this.displayFormat(), this.i18n.dateNames) : '');
783
+ }
784
+ }
785
+ parse(text) {
786
+ const iso = parseISODate(text);
787
+ if (iso)
788
+ return iso;
789
+ const ms = Date.parse(text);
790
+ return Number.isNaN(ms) ? null : startOfDay(new Date(ms));
791
+ }
792
+ setValue(date) {
793
+ this.value.set(date);
794
+ this.onChange(date);
795
+ }
796
+ onFocusOut(event) {
797
+ const related = event.relatedTarget;
798
+ // Ignore focus moving into the field or the (top-layer) calendar panel.
799
+ if (related && this.host.nativeElement.contains(related))
800
+ return;
801
+ if (related && this.panelRef()?.nativeElement.contains(related))
802
+ return;
803
+ this.commitInput();
804
+ this.close();
805
+ this.onTouched();
806
+ }
807
+ /**
808
+ * Escape pressed inside the (teleported) calendar panel — close and return
809
+ * focus to the input. `preventDefault` + `stopPropagation` keep any outer
810
+ * Escape handling (e.g. a containing dialog) from also firing.
811
+ */
812
+ onPanelEscape(event) {
813
+ event.preventDefault();
814
+ event.stopPropagation();
815
+ this.close();
816
+ this.inputRef()?.nativeElement.focus();
817
+ }
818
+ /**
819
+ * Focus left the teleported panel. The host `(focusout)` never sees this —
820
+ * the panel lives under `document.body` — so Tab-out of the calendar is
821
+ * handled here: close unless focus moved back into the field or the panel.
822
+ */
823
+ onPanelFocusout(event) {
824
+ const related = event.relatedTarget;
825
+ // Focus fell to a non-focusable spot (body) — keep open; an outside
826
+ // pointerdown is dismissed by the anchored panel itself.
827
+ if (!related)
828
+ return;
829
+ if (this.host.nativeElement.contains(related))
830
+ return;
831
+ if (this.panelRef()?.nativeElement.contains(related))
832
+ return;
833
+ this.commitInput();
834
+ this.close();
835
+ this.onTouched();
836
+ }
837
+ // --- ControlValueAccessor -------------------------------------------------
838
+ writeValue(value) {
839
+ this.value.set(value ?? null);
840
+ }
841
+ registerOnChange(fn) {
842
+ this.onChange = fn;
843
+ }
844
+ registerOnTouched(fn) {
845
+ this.onTouched = fn;
846
+ }
847
+ setDisabledState(isDisabled) {
848
+ this.cvaDisabled.set(isDisabled);
849
+ }
850
+ // --- Validator ------------------------------------------------------------
851
+ validatorChange = mkValidatorChange(() => {
852
+ this.min();
853
+ this.max();
854
+ });
855
+ /**
856
+ * Reports `mkMinDate` / `mkMaxDate` against the `[min]` and `[max]` inputs.
857
+ */
858
+ validate(control) {
859
+ const v = control.value;
860
+ if (!(v instanceof Date) || Number.isNaN(v.getTime()))
861
+ return null;
862
+ const day = startOfDay(v);
863
+ const min = this.min();
864
+ if (min && isBefore(day, startOfDay(min)))
865
+ return { mkMinDate: { min, actual: v } };
866
+ const max = this.max();
867
+ if (max && isAfter(day, startOfDay(max)))
868
+ return { mkMaxDate: { max, actual: v } };
869
+ return null;
870
+ }
871
+ registerOnValidatorChange(fn) {
872
+ this.validatorChange.register(fn);
873
+ }
874
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkDatePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
875
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkDatePicker, isStandalone: true, selector: "mk-date-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, disabledDate: { classPropertyName: "disabledDate", publicName: "disabledDate", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, displayFormat: { classPropertyName: "displayFormat", publicName: "displayFormat", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, firstDayOfWeek: { classPropertyName: "firstDayOfWeek", publicName: "firstDayOfWeek", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { listeners: { "focusout": "onFocusOut($event)" }, properties: { "class.mk-date-picker--sm": "effectiveSize() === 'sm'", "class.mk-date-picker--md": "effectiveSize() === 'md'", "class.mk-date-picker--lg": "effectiveSize() === 'lg'", "class.mk-date-picker--open": "open()", "class.mk-date-picker--invalid": "isInvalid()", "class.mk-date-picker--disabled": "isDisabled()" }, classAttribute: "mk-date-picker" }, providers: [
876
+ {
877
+ provide: NG_VALUE_ACCESSOR,
878
+ useExisting: forwardRef(() => MkDatePicker),
879
+ multi: true,
880
+ },
881
+ {
882
+ provide: NG_VALIDATORS,
883
+ useExisting: forwardRef(() => MkDatePicker),
884
+ multi: true,
885
+ },
886
+ ], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["textInput"], descendants: true, isSignal: true }, { propertyName: "panelRef", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "<div #field class=\"mk-date-picker__field\">\n <input\n #textInput\n type=\"text\"\n class=\"mk-date-picker__input\"\n [id]=\"inputId\"\n [value]=\"inputText()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n autocomplete=\"off\"\n role=\"combobox\"\n aria-haspopup=\"dialog\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-invalid]=\"isInvalid() || null\"\n [attr.aria-required]=\"isRequired() || null\"\n [attr.aria-describedby]=\"describedBy()\"\n (input)=\"onInput($event)\"\n (keydown)=\"onInputKeydown($event)\"\n />\n\n @if (showClear()) {\n <button\n type=\"button\"\n class=\"mk-date-picker__clear\"\n [attr.aria-label]=\"i18n.clear\"\n (click)=\"clear()\"\n >\n <span class=\"mk-date-picker__clear-icon\" aria-hidden=\"true\"></span>\n </button>\n }\n\n <button\n type=\"button\"\n class=\"mk-date-picker__toggle\"\n [attr.aria-label]=\"i18n.openCalendar\"\n aria-haspopup=\"dialog\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"panelId\"\n [disabled]=\"isDisabled()\"\n (click)=\"toggle()\"\n >\n <svg\n class=\"mk-date-picker__icon\"\n viewBox=\"0 0 24 24\"\n width=\"18\"\n height=\"18\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n aria-hidden=\"true\"\n >\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"17\" rx=\"2\" />\n <path d=\"M3 9h18M8 2v4M16 2v4\" />\n </svg>\n </button>\n</div>\n\n@if (open()) {\n <div\n #panel\n class=\"mk-date-picker__panel\"\n mkAnchoredPanel\n [mkAnchoredPanelFor]=\"field\"\n (dismiss)=\"close()\"\n [id]=\"panelId\"\n role=\"dialog\"\n [attr.aria-label]=\"i18n.chooseDate\"\n (keydown.escape)=\"onPanelEscape($event)\"\n (focusout)=\"onPanelFocusout($event)\"\n >\n <mk-calendar\n [value]=\"value()\"\n (valueChange)=\"onCalendarPick($event)\"\n [min]=\"min()\"\n [max]=\"max()\"\n [firstDayOfWeek]=\"firstDayOfWeek()\"\n [size]=\"effectiveSize()\"\n [disabledDate]=\"disabledDate()\"\n />\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative;width:100%;font-family:var(--mk-font-sans)}.mk-date-picker__field{display:flex;align-items:center;width:100%;height:var(--mk-control-height-md);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-md);transition:border-color var(--mk-duration-fast) var(--mk-ease-standard),box-shadow var(--mk-duration-fast) var(--mk-ease-standard)}:host(.mk-date-picker--sm) .mk-date-picker__field{height:var(--mk-control-height-sm);border-radius:var(--mk-radius-sm)}:host(.mk-date-picker--lg) .mk-date-picker__field{height:var(--mk-control-height-lg)}.mk-date-picker__field:hover{border-color:var(--mk-border-strong)}.mk-date-picker__field:focus-within{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset);border-color:var(--mk-primary)}:host(.mk-date-picker--invalid) .mk-date-picker__field{border-color:var(--mk-danger)}:host(.mk-date-picker--invalid) .mk-date-picker__field:focus-within{outline-color:var(--mk-danger)}:host(.mk-date-picker--disabled) .mk-date-picker__field{background-color:var(--mk-surface-2);border-color:var(--mk-border-subtle);cursor:not-allowed;opacity:.7}.mk-date-picker__input{flex:1 1 auto;min-width:0;height:100%;padding:0 var(--mk-space-3);font-family:inherit;font-size:var(--mk-font-size-md);color:var(--mk-text);background:transparent;border:0;outline:none}:host(.mk-date-picker--sm) .mk-date-picker__input{padding:0 var(--mk-space-2);font-size:var(--mk-font-size-sm)}:host(.mk-date-picker--lg) .mk-date-picker__input{padding:0 var(--mk-space-4);font-size:var(--mk-font-size-lg)}.mk-date-picker__input::placeholder{color:var(--mk-text-subtle);opacity:1}.mk-date-picker__input:disabled{color:var(--mk-text-disabled);cursor:not-allowed}.mk-date-picker__toggle,.mk-date-picker__clear{display:inline-flex;align-items:center;justify-content:center;flex:none;height:100%;padding:0 var(--mk-space-2);color:var(--mk-text-muted);background:transparent;border:0;cursor:pointer;transition:color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-date-picker__toggle:hover:not(:disabled),.mk-date-picker__clear:hover{color:var(--mk-text)}.mk-date-picker__toggle:focus-visible,.mk-date-picker__clear:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(var(--mk-focus-ring-offset) * -1);border-radius:var(--mk-radius-sm)}.mk-date-picker__toggle:disabled{cursor:not-allowed}.mk-date-picker__clear-icon{position:relative;width:.85rem;height:.85rem}.mk-date-picker__clear-icon:before,.mk-date-picker__clear-icon:after{content:\"\";position:absolute;top:50%;left:0;width:100%;height:2px;background-color:currentColor}.mk-date-picker__clear-icon:before{transform:rotate(45deg)}.mk-date-picker__clear-icon:after{transform:rotate(-45deg)}.mk-date-picker__panel{box-shadow:var(--mk-shadow-lg);border-radius:var(--mk-radius-lg)}@media(prefers-reduced-motion:reduce){.mk-date-picker__field,.mk-date-picker__toggle,.mk-date-picker__clear{transition:none}}@media(pointer:coarse){.mk-date-picker__input{font-size:max(var(--mk-font-size-md),16px)}:host(.mk-date-picker--sm) .mk-date-picker__input{font-size:max(var(--mk-font-size-sm),16px)}}\n"], dependencies: [{ kind: "component", type: MkCalendar, selector: "mk-calendar", inputs: ["value", "min", "max", "firstDayOfWeek", "disabled", "invalid", "disabledDate", "size", "fullWidth", "rangeMode", "rangeStart", "rangeEnd"], outputs: ["valueChange", "dateSelected", "dateHovered"] }, { kind: "directive", type: MkAnchoredPanel, selector: "[mkAnchoredPanel]", inputs: ["mkAnchoredPanelFor", "anchorRect", "placement", "gap", "matchWidth", "flip", "clamp"], outputs: ["dismiss"], exportAs: ["mkAnchoredPanel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
887
+ }
888
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkDatePicker, decorators: [{
889
+ type: Component,
890
+ args: [{ selector: 'mk-date-picker', changeDetection: ChangeDetectionStrategy.OnPush, imports: [MkCalendar, MkAnchoredPanel], host: {
891
+ class: 'mk-date-picker',
892
+ '[class.mk-date-picker--sm]': "effectiveSize() === 'sm'",
893
+ '[class.mk-date-picker--md]': "effectiveSize() === 'md'",
894
+ '[class.mk-date-picker--lg]': "effectiveSize() === 'lg'",
895
+ '[class.mk-date-picker--open]': 'open()',
896
+ '[class.mk-date-picker--invalid]': 'isInvalid()',
897
+ '[class.mk-date-picker--disabled]': 'isDisabled()',
898
+ '(focusout)': 'onFocusOut($event)',
899
+ }, providers: [
900
+ {
901
+ provide: NG_VALUE_ACCESSOR,
902
+ useExisting: forwardRef(() => MkDatePicker),
903
+ multi: true,
904
+ },
905
+ {
906
+ provide: NG_VALIDATORS,
907
+ useExisting: forwardRef(() => MkDatePicker),
908
+ multi: true,
909
+ },
910
+ ], template: "<div #field class=\"mk-date-picker__field\">\n <input\n #textInput\n type=\"text\"\n class=\"mk-date-picker__input\"\n [id]=\"inputId\"\n [value]=\"inputText()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n autocomplete=\"off\"\n role=\"combobox\"\n aria-haspopup=\"dialog\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-invalid]=\"isInvalid() || null\"\n [attr.aria-required]=\"isRequired() || null\"\n [attr.aria-describedby]=\"describedBy()\"\n (input)=\"onInput($event)\"\n (keydown)=\"onInputKeydown($event)\"\n />\n\n @if (showClear()) {\n <button\n type=\"button\"\n class=\"mk-date-picker__clear\"\n [attr.aria-label]=\"i18n.clear\"\n (click)=\"clear()\"\n >\n <span class=\"mk-date-picker__clear-icon\" aria-hidden=\"true\"></span>\n </button>\n }\n\n <button\n type=\"button\"\n class=\"mk-date-picker__toggle\"\n [attr.aria-label]=\"i18n.openCalendar\"\n aria-haspopup=\"dialog\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"panelId\"\n [disabled]=\"isDisabled()\"\n (click)=\"toggle()\"\n >\n <svg\n class=\"mk-date-picker__icon\"\n viewBox=\"0 0 24 24\"\n width=\"18\"\n height=\"18\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n aria-hidden=\"true\"\n >\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"17\" rx=\"2\" />\n <path d=\"M3 9h18M8 2v4M16 2v4\" />\n </svg>\n </button>\n</div>\n\n@if (open()) {\n <div\n #panel\n class=\"mk-date-picker__panel\"\n mkAnchoredPanel\n [mkAnchoredPanelFor]=\"field\"\n (dismiss)=\"close()\"\n [id]=\"panelId\"\n role=\"dialog\"\n [attr.aria-label]=\"i18n.chooseDate\"\n (keydown.escape)=\"onPanelEscape($event)\"\n (focusout)=\"onPanelFocusout($event)\"\n >\n <mk-calendar\n [value]=\"value()\"\n (valueChange)=\"onCalendarPick($event)\"\n [min]=\"min()\"\n [max]=\"max()\"\n [firstDayOfWeek]=\"firstDayOfWeek()\"\n [size]=\"effectiveSize()\"\n [disabledDate]=\"disabledDate()\"\n />\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative;width:100%;font-family:var(--mk-font-sans)}.mk-date-picker__field{display:flex;align-items:center;width:100%;height:var(--mk-control-height-md);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-md);transition:border-color var(--mk-duration-fast) var(--mk-ease-standard),box-shadow var(--mk-duration-fast) var(--mk-ease-standard)}:host(.mk-date-picker--sm) .mk-date-picker__field{height:var(--mk-control-height-sm);border-radius:var(--mk-radius-sm)}:host(.mk-date-picker--lg) .mk-date-picker__field{height:var(--mk-control-height-lg)}.mk-date-picker__field:hover{border-color:var(--mk-border-strong)}.mk-date-picker__field:focus-within{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset);border-color:var(--mk-primary)}:host(.mk-date-picker--invalid) .mk-date-picker__field{border-color:var(--mk-danger)}:host(.mk-date-picker--invalid) .mk-date-picker__field:focus-within{outline-color:var(--mk-danger)}:host(.mk-date-picker--disabled) .mk-date-picker__field{background-color:var(--mk-surface-2);border-color:var(--mk-border-subtle);cursor:not-allowed;opacity:.7}.mk-date-picker__input{flex:1 1 auto;min-width:0;height:100%;padding:0 var(--mk-space-3);font-family:inherit;font-size:var(--mk-font-size-md);color:var(--mk-text);background:transparent;border:0;outline:none}:host(.mk-date-picker--sm) .mk-date-picker__input{padding:0 var(--mk-space-2);font-size:var(--mk-font-size-sm)}:host(.mk-date-picker--lg) .mk-date-picker__input{padding:0 var(--mk-space-4);font-size:var(--mk-font-size-lg)}.mk-date-picker__input::placeholder{color:var(--mk-text-subtle);opacity:1}.mk-date-picker__input:disabled{color:var(--mk-text-disabled);cursor:not-allowed}.mk-date-picker__toggle,.mk-date-picker__clear{display:inline-flex;align-items:center;justify-content:center;flex:none;height:100%;padding:0 var(--mk-space-2);color:var(--mk-text-muted);background:transparent;border:0;cursor:pointer;transition:color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-date-picker__toggle:hover:not(:disabled),.mk-date-picker__clear:hover{color:var(--mk-text)}.mk-date-picker__toggle:focus-visible,.mk-date-picker__clear:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(var(--mk-focus-ring-offset) * -1);border-radius:var(--mk-radius-sm)}.mk-date-picker__toggle:disabled{cursor:not-allowed}.mk-date-picker__clear-icon{position:relative;width:.85rem;height:.85rem}.mk-date-picker__clear-icon:before,.mk-date-picker__clear-icon:after{content:\"\";position:absolute;top:50%;left:0;width:100%;height:2px;background-color:currentColor}.mk-date-picker__clear-icon:before{transform:rotate(45deg)}.mk-date-picker__clear-icon:after{transform:rotate(-45deg)}.mk-date-picker__panel{box-shadow:var(--mk-shadow-lg);border-radius:var(--mk-radius-lg)}@media(prefers-reduced-motion:reduce){.mk-date-picker__field,.mk-date-picker__toggle,.mk-date-picker__clear{transition:none}}@media(pointer:coarse){.mk-date-picker__input{font-size:max(var(--mk-font-size-md),16px)}:host(.mk-date-picker--sm) .mk-date-picker__input{font-size:max(var(--mk-font-size-sm),16px)}}\n"] }]
911
+ }], ctorParameters: () => [], propDecorators: { inputRef: [{ type: i0.ViewChild, args: ['textInput', { isSignal: true }] }], panelRef: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], disabledDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabledDate", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], displayFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayFormat", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], firstDayOfWeek: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstDayOfWeek", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
912
+
913
+ function pad2(n) {
914
+ return n < 10 ? `0${n}` : `${n}`;
915
+ }
916
+ /**
917
+ * TimePicker — a 24-hour-canonical time field with an editable input plus a
918
+ * scrollable list of times generated from `step`. The time itself is always
919
+ * 24h canonical regardless of the `hour12` display. Implements
920
+ * `ControlValueAccessor` and a two-way `value` model.
921
+ *
922
+ * The form value is either a `HH:mm` string (`"14:30"`, default) or a `Date`
923
+ * carrying the time in its **local** hours/minutes (`valueFormat="date"`, for
924
+ * hosts whose model is a datetime — e.g. migrating off Material's
925
+ * `mat-timepicker`); both modes read back either shape and `null` when empty.
926
+ * Only the *emitted* value depends on `valueFormat` — internally the time is
927
+ * always normalised to `HH:mm`, which is also what `min` / `max` compare.
928
+ *
929
+ * In `date` mode seconds and milliseconds are zeroed, and the **date part** is
930
+ * taken from the current model value when that is already a `Date` (so editing
931
+ * the time repeatedly never drifts the day); otherwise today's local date is
932
+ * used. Assign a `Date` first if the day matters.
933
+ *
934
+ * Type a time (`14:30`, `2:30 pm`) or pick from the ARIA listbox
935
+ * (Up/Down/Home/End/Enter/Esc; the active option scrolls into view). When
936
+ * nested in an `mk-form-field` it adopts the field's id and aria wiring.
937
+ *
938
+ * ```html
939
+ * <mk-time-picker [(value)]="time" [step]="15" hour12 clearable />
940
+ * <mk-time-picker valueFormat="date" [(value)]="pickupAt" [step]="15" />
941
+ * ```
942
+ */
943
+ class MkTimePicker {
944
+ i18n = inject(MK_I18N);
945
+ field = inject(MkFormField, { optional: true });
946
+ host = inject(ElementRef);
947
+ injector = inject(Injector);
948
+ inputRef = viewChild('textInput', /* @ts-ignore */
949
+ ...(ngDevMode ? [{ debugName: "inputRef" }] : /* istanbul ignore next */ []));
950
+ /** The listbox panel — lives in the top layer once opened. */
951
+ listRef = viewChild('list', /* @ts-ignore */
952
+ ...(ngDevMode ? [{ debugName: "listRef" }] : /* istanbul ignore next */ []));
953
+ /**
954
+ * Two-way selected time: a canonical `HH:mm` (24h) string, a `Date` when
955
+ * `valueFormat="date"`, or `null`. Either shape may be assigned in either
956
+ * mode; it is normalised to the configured format.
957
+ */
958
+ value = model(null, /* @ts-ignore */
959
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
960
+ /** Shape of the form value: a `HH:mm` string or a `Date`. */
961
+ valueFormat = input('string', /* @ts-ignore */
962
+ ...(ngDevMode ? [{ debugName: "valueFormat" }] : /* istanbul ignore next */ []));
963
+ /** Earliest selectable time `HH:mm` (inclusive). */
964
+ min = input(null, /* @ts-ignore */
965
+ ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
966
+ /** Latest selectable time `HH:mm` (inclusive). */
967
+ max = input(null, /* @ts-ignore */
968
+ ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
969
+ /** Interval between generated options, in minutes. */
970
+ step = input(30, { ...(ngDevMode ? { debugName: "step" } : /* istanbul ignore next */ {}), transform: numberAttribute });
971
+ /** Display 12-hour time with AM/PM (the model stays 24h). */
972
+ hour12 = input(false, { ...(ngDevMode ? { debugName: "hour12" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
973
+ /** Disable the control. */
974
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
975
+ /** Placeholder shown when empty. */
976
+ placeholder = input(this.i18n.selectTime, /* @ts-ignore */
977
+ ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
978
+ /** Show a clear button when a time is selected. */
979
+ clearable = input(false, { ...(ngDevMode ? { debugName: "clearable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
980
+ /** Force invalid styling + `aria-invalid`. */
981
+ invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
982
+ /** Control size. Ignored when nested in an `mk-form-field`. */
983
+ size = input('md', /* @ts-ignore */
984
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
985
+ open = signal(false, /* @ts-ignore */
986
+ ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
987
+ activeIndex = signal(-1, /* @ts-ignore */
988
+ ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
989
+ inputText = signal('', /* @ts-ignore */
990
+ ...(ngDevMode ? [{ debugName: "inputText" }] : /* istanbul ignore next */ []));
991
+ /** The canonical `HH:mm` time behind whatever shape `value` carries. */
992
+ time = signal(null, /* @ts-ignore */
993
+ ...(ngDevMode ? [{ debugName: "time" }] : /* istanbul ignore next */ []));
994
+ cvaDisabled = signal(false, /* @ts-ignore */
995
+ ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
996
+ onChange = () => { };
997
+ onTouched = () => { };
998
+ /** The last value this component itself wrote into the `value` model. */
999
+ lastDerived = null;
1000
+ /** The day `date`-mode values land on — see {@link toDate}. */
1001
+ datePart = null;
1002
+ inputId = this.field?.controlId ?? mkUniqueId('mk-time-picker');
1003
+ listId = mkUniqueId('mk-time-picker-list');
1004
+ effectiveSize = computed(() => this.field ? this.field.size() : this.size(), /* @ts-ignore */
1005
+ ...(ngDevMode ? [{ debugName: "effectiveSize" }] : /* istanbul ignore next */ []));
1006
+ isDisabled = computed(() => this.disabled() || this.cvaDisabled(), /* @ts-ignore */
1007
+ ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
1008
+ isInvalid = computed(() => this.invalid() || (this.field?.hasError() ?? false), /* @ts-ignore */
1009
+ ...(ngDevMode ? [{ debugName: "isInvalid" }] : /* istanbul ignore next */ []));
1010
+ isRequired = computed(() => this.field?.isRequired() ?? false, /* @ts-ignore */
1011
+ ...(ngDevMode ? [{ debugName: "isRequired" }] : /* istanbul ignore next */ []));
1012
+ describedBy = computed(() => this.field?.describedBy() ?? null, /* @ts-ignore */
1013
+ ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
1014
+ showClear = computed(() => this.clearable() && !!this.time() && !this.isDisabled(), /* @ts-ignore */
1015
+ ...(ngDevMode ? [{ debugName: "showClear" }] : /* istanbul ignore next */ []));
1016
+ /** Generated, min/max-filtered list of selectable times. */
1017
+ options = computed(() => {
1018
+ const step = Math.max(1, Math.floor(this.step()));
1019
+ const min = this.min();
1020
+ const max = this.max();
1021
+ const out = [];
1022
+ for (let mins = 0; mins < 24 * 60; mins += step) {
1023
+ const value = `${pad2(Math.floor(mins / 60))}:${pad2(mins % 60)}`;
1024
+ if (min && value < min)
1025
+ continue;
1026
+ if (max && value > max)
1027
+ continue;
1028
+ out.push({ value, label: this.display(value) });
1029
+ }
1030
+ return out;
1031
+ }, /* @ts-ignore */
1032
+ ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
1033
+ selectedIndex = computed(() => this.options().findIndex((o) => o.value === this.time()), /* @ts-ignore */
1034
+ ...(ngDevMode ? [{ debugName: "selectedIndex" }] : /* istanbul ignore next */ []));
1035
+ constructor() {
1036
+ effect(() => {
1037
+ const v = this.time();
1038
+ // hour12 is read so the label follows the display mode.
1039
+ this.inputText.set(v ? this.display(v) : '');
1040
+ });
1041
+ // Parse values assigned from outside ([(value)] writes we didn't derive),
1042
+ // and re-shape the model when `valueFormat` changes.
1043
+ effect(() => {
1044
+ const v = this.value();
1045
+ const external = v !== this.lastDerived;
1046
+ this.valueFormat();
1047
+ untracked(() => {
1048
+ if (external)
1049
+ this.applyExternal(v);
1050
+ this.deriveValue(false);
1051
+ });
1052
+ });
1053
+ }
1054
+ optionId(index) {
1055
+ return `${this.listId}-opt-${index}`;
1056
+ }
1057
+ display(hhmm) {
1058
+ const [h, m] = hhmm.split(':').map(Number);
1059
+ if (this.hour12()) {
1060
+ const ap = h < 12 ? 'AM' : 'PM';
1061
+ const hh = h % 12 || 12;
1062
+ return `${hh}:${pad2(m)} ${ap}`;
1063
+ }
1064
+ return `${pad2(h)}:${pad2(m)}`;
1065
+ }
1066
+ parse(text) {
1067
+ const m = /^\s*(\d{1,2})(?::(\d{2}))?\s*(am|pm)?\s*$/i.exec(text);
1068
+ if (!m)
1069
+ return null;
1070
+ let h = Number(m[1]);
1071
+ const min = m[2] ? Number(m[2]) : 0;
1072
+ const ap = m[3]?.toLowerCase();
1073
+ if (ap) {
1074
+ if (h < 1 || h > 12)
1075
+ return null;
1076
+ if (ap === 'pm' && h !== 12)
1077
+ h += 12;
1078
+ if (ap === 'am' && h === 12)
1079
+ h = 0;
1080
+ }
1081
+ if (h > 23 || min > 59)
1082
+ return null;
1083
+ return `${pad2(h)}:${pad2(min)}`;
1084
+ }
1085
+ toggle() {
1086
+ if (this.isDisabled())
1087
+ return;
1088
+ this.open() ? this.close() : this.openList();
1089
+ }
1090
+ openList() {
1091
+ if (this.isDisabled() || this.open())
1092
+ return;
1093
+ this.open.set(true);
1094
+ const sel = this.selectedIndex();
1095
+ this.activeIndex.set(sel >= 0 ? sel : 0);
1096
+ this.scrollActiveIntoView();
1097
+ }
1098
+ close() {
1099
+ if (this.open()) {
1100
+ this.open.set(false);
1101
+ this.activeIndex.set(-1);
1102
+ }
1103
+ }
1104
+ selectOption(index) {
1105
+ const opt = this.options()[index];
1106
+ if (!opt)
1107
+ return;
1108
+ this.setValue(opt.value);
1109
+ this.close();
1110
+ this.inputRef()?.nativeElement.focus();
1111
+ }
1112
+ clear() {
1113
+ this.setValue(null);
1114
+ this.inputRef()?.nativeElement.focus();
1115
+ }
1116
+ onInput(event) {
1117
+ this.inputText.set(event.target.value);
1118
+ }
1119
+ onInputKeydown(event) {
1120
+ const e = event;
1121
+ if (this.isDisabled())
1122
+ return;
1123
+ if (!this.open()) {
1124
+ if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
1125
+ e.preventDefault();
1126
+ this.openList();
1127
+ }
1128
+ else if (e.key === 'Enter') {
1129
+ e.preventDefault();
1130
+ this.commitInput();
1131
+ }
1132
+ return;
1133
+ }
1134
+ switch (e.key) {
1135
+ case 'ArrowDown':
1136
+ e.preventDefault();
1137
+ this.moveActive(1);
1138
+ break;
1139
+ case 'ArrowUp':
1140
+ e.preventDefault();
1141
+ this.moveActive(-1);
1142
+ break;
1143
+ case 'Home':
1144
+ e.preventDefault();
1145
+ this.activeIndex.set(0);
1146
+ this.scrollActiveIntoView();
1147
+ break;
1148
+ case 'End':
1149
+ e.preventDefault();
1150
+ this.activeIndex.set(this.options().length - 1);
1151
+ this.scrollActiveIntoView();
1152
+ break;
1153
+ case 'Enter':
1154
+ e.preventDefault();
1155
+ if (this.activeIndex() >= 0)
1156
+ this.selectOption(this.activeIndex());
1157
+ else
1158
+ this.commitInput();
1159
+ break;
1160
+ case 'Escape':
1161
+ e.preventDefault();
1162
+ this.close();
1163
+ break;
1164
+ }
1165
+ }
1166
+ moveActive(delta) {
1167
+ const len = this.options().length;
1168
+ if (!len)
1169
+ return;
1170
+ const next = (this.activeIndex() + delta + len) % len;
1171
+ this.activeIndex.set(next);
1172
+ this.scrollActiveIntoView();
1173
+ }
1174
+ commitInput() {
1175
+ const text = this.inputText().trim();
1176
+ if (!text) {
1177
+ this.setValue(null);
1178
+ this.close();
1179
+ return;
1180
+ }
1181
+ const parsed = this.parse(text);
1182
+ if (parsed && this.withinBounds(parsed)) {
1183
+ this.setValue(parsed);
1184
+ }
1185
+ else {
1186
+ const v = this.time();
1187
+ this.inputText.set(v ? this.display(v) : '');
1188
+ }
1189
+ this.close();
1190
+ }
1191
+ withinBounds(value) {
1192
+ const min = this.min();
1193
+ const max = this.max();
1194
+ if (min && value < min)
1195
+ return false;
1196
+ if (max && value > max)
1197
+ return false;
1198
+ return true;
1199
+ }
1200
+ // --- Value plumbing -------------------------------------------------------
1201
+ setValue(value) {
1202
+ this.time.set(value);
1203
+ this.deriveValue(true);
1204
+ }
1205
+ /** Compute the outward value and push it to the model (+ form when `emit`). */
1206
+ deriveValue(emit) {
1207
+ const hhmm = this.time();
1208
+ const next = hhmm === null ? null : this.valueFormat() === 'date' ? this.toDate(hhmm) : hhmm;
1209
+ // Keep the previous instance when it means the same time, so repeated
1210
+ // edits don't churn `Date` identities (nor the date part they carry).
1211
+ if (!this.sameValue(next, this.lastDerived) || this.value() !== this.lastDerived) {
1212
+ this.lastDerived = next;
1213
+ this.value.set(next);
1214
+ }
1215
+ if (emit)
1216
+ this.onChange(this.lastDerived);
1217
+ }
1218
+ sameValue(a, b) {
1219
+ if (a === b)
1220
+ return true;
1221
+ if (a instanceof Date && b instanceof Date)
1222
+ return a.getTime() === b.getTime();
1223
+ return false;
1224
+ }
1225
+ /**
1226
+ * Interpret an externally-assigned value (`writeValue` or `[value]`): it
1227
+ * seeds the canonical time and, when it is a `Date`, the day that `date`-mode
1228
+ * values land on.
1229
+ */
1230
+ applyExternal(v) {
1231
+ this.datePart = v instanceof Date && !Number.isNaN(v.getTime()) ? new Date(v) : null;
1232
+ this.time.set(this.toHhmm(v));
1233
+ }
1234
+ /** Normalise any accepted shape to canonical `HH:mm`, or `null`. */
1235
+ toHhmm(v) {
1236
+ if (v instanceof Date) {
1237
+ return Number.isNaN(v.getTime()) ? null : `${pad2(v.getHours())}:${pad2(v.getMinutes())}`;
1238
+ }
1239
+ if (typeof v !== 'string' || !v.trim())
1240
+ return null;
1241
+ return this.parse(v);
1242
+ }
1243
+ /**
1244
+ * Put `hh:mm` on a `Date`. The date part comes from the current value when
1245
+ * that is already a `Date` (so repeated edits never drift the day),
1246
+ * otherwise from today — always via the local accessors, never the UTC ones.
1247
+ */
1248
+ toDate(hhmm) {
1249
+ const [h, m] = hhmm.split(':').map(Number);
1250
+ const base = this.datePart ? new Date(this.datePart) : new Date();
1251
+ base.setHours(h, m, 0, 0);
1252
+ this.datePart = base;
1253
+ return base;
1254
+ }
1255
+ scrollActiveIntoView() {
1256
+ afterNextRender({
1257
+ write: () => {
1258
+ const el = this.listRef()?.nativeElement.querySelector('.mk-time-picker__option--active');
1259
+ el?.scrollIntoView({ block: 'nearest' });
1260
+ },
1261
+ }, { injector: this.injector });
1262
+ }
1263
+ onFocusOut(event) {
1264
+ const related = event.relatedTarget;
1265
+ if (related && this.host.nativeElement.contains(related))
1266
+ return;
1267
+ if (related && this.listRef()?.nativeElement.contains(related))
1268
+ return;
1269
+ this.commitInput();
1270
+ this.onTouched();
1271
+ }
1272
+ // --- ControlValueAccessor -------------------------------------------------
1273
+ writeValue(value) {
1274
+ this.applyExternal(value);
1275
+ this.deriveValue(false);
1276
+ }
1277
+ registerOnChange(fn) {
1278
+ this.onChange = fn;
1279
+ }
1280
+ registerOnTouched(fn) {
1281
+ this.onTouched = fn;
1282
+ }
1283
+ setDisabledState(isDisabled) {
1284
+ this.cvaDisabled.set(isDisabled);
1285
+ }
1286
+ // --- Validator ------------------------------------------------------------
1287
+ validatorChange = mkValidatorChange(() => {
1288
+ this.min();
1289
+ this.max();
1290
+ });
1291
+ /**
1292
+ * Reports `mkMinTime` / `mkMaxTime` against the `[min]` and `[max]` inputs.
1293
+ * The control value is normalised to `HH:mm` first (so a `Date` model
1294
+ * validates too); both sides are then zero-padded `HH:mm`, so a
1295
+ * lexicographic compare is a chronological one.
1296
+ */
1297
+ validate(control) {
1298
+ const v = this.toHhmm(control.value);
1299
+ if (!v)
1300
+ return null;
1301
+ const min = this.min();
1302
+ if (min && v < min)
1303
+ return { mkMinTime: { min, actual: v } };
1304
+ const max = this.max();
1305
+ if (max && v > max)
1306
+ return { mkMaxTime: { max, actual: v } };
1307
+ return null;
1308
+ }
1309
+ registerOnValidatorChange(fn) {
1310
+ this.validatorChange.register(fn);
1311
+ }
1312
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkTimePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
1313
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkTimePicker, isStandalone: true, selector: "mk-time-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, valueFormat: { classPropertyName: "valueFormat", publicName: "valueFormat", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, hour12: { classPropertyName: "hour12", publicName: "hour12", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { listeners: { "focusout": "onFocusOut($event)" }, properties: { "class.mk-time-picker--sm": "effectiveSize() === 'sm'", "class.mk-time-picker--md": "effectiveSize() === 'md'", "class.mk-time-picker--lg": "effectiveSize() === 'lg'", "class.mk-time-picker--open": "open()", "class.mk-time-picker--invalid": "isInvalid()", "class.mk-time-picker--disabled": "isDisabled()" }, classAttribute: "mk-time-picker" }, providers: [
1314
+ {
1315
+ provide: NG_VALUE_ACCESSOR,
1316
+ useExisting: forwardRef(() => MkTimePicker),
1317
+ multi: true,
1318
+ },
1319
+ {
1320
+ provide: NG_VALIDATORS,
1321
+ useExisting: forwardRef(() => MkTimePicker),
1322
+ multi: true,
1323
+ },
1324
+ ], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["textInput"], descendants: true, isSignal: true }, { propertyName: "listRef", first: true, predicate: ["list"], descendants: true, isSignal: true }], ngImport: i0, template: "<div #field class=\"mk-time-picker__field\">\n <input\n #textInput\n type=\"text\"\n class=\"mk-time-picker__input\"\n [id]=\"inputId\"\n [value]=\"inputText()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n autocomplete=\"off\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-autocomplete=\"none\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listId\"\n [attr.aria-activedescendant]=\"\n open() && activeIndex() >= 0 ? optionId(activeIndex()) : null\n \"\n [attr.aria-invalid]=\"isInvalid() || null\"\n [attr.aria-required]=\"isRequired() || null\"\n [attr.aria-describedby]=\"describedBy()\"\n (input)=\"onInput($event)\"\n (keydown)=\"onInputKeydown($event)\"\n />\n\n @if (showClear()) {\n <button\n type=\"button\"\n class=\"mk-time-picker__clear\"\n [attr.aria-label]=\"i18n.clear\"\n (click)=\"clear()\"\n >\n <span class=\"mk-time-picker__clear-icon\" aria-hidden=\"true\"></span>\n </button>\n }\n\n <button\n type=\"button\"\n class=\"mk-time-picker__toggle\"\n [attr.aria-label]=\"i18n.openTimeList\"\n aria-haspopup=\"listbox\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listId\"\n [disabled]=\"isDisabled()\"\n (click)=\"toggle()\"\n >\n <svg\n class=\"mk-time-picker__icon\"\n viewBox=\"0 0 24 24\"\n width=\"18\"\n height=\"18\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n aria-hidden=\"true\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M12 7v5l3 2\" />\n </svg>\n </button>\n</div>\n\n@if (open()) {\n <ul\n #list\n class=\"mk-time-picker__list\"\n mkAnchoredPanel\n [mkAnchoredPanelFor]=\"field\"\n [matchWidth]=\"true\"\n (dismiss)=\"close()\"\n [id]=\"listId\"\n role=\"listbox\"\n >\n @for (opt of options(); track opt.value; let i = $index) {\n <li\n class=\"mk-time-picker__option\"\n [id]=\"optionId(i)\"\n role=\"option\"\n [attr.aria-selected]=\"i === selectedIndex()\"\n [class.mk-time-picker__option--active]=\"i === activeIndex()\"\n [class.mk-time-picker__option--selected]=\"i === selectedIndex()\"\n (mousedown)=\"$event.preventDefault()\"\n (mousemove)=\"activeIndex.set(i)\"\n (click)=\"selectOption(i)\"\n >\n {{ opt.label }}\n </li>\n } @empty {\n <li class=\"mk-time-picker__empty\" role=\"presentation\">\n {{ i18n.noOptions }}\n </li>\n }\n </ul>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative;width:100%;font-family:var(--mk-font-sans)}.mk-time-picker__field{display:flex;align-items:center;width:100%;height:var(--mk-control-height-md);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-md);transition:border-color var(--mk-duration-fast) var(--mk-ease-standard),box-shadow var(--mk-duration-fast) var(--mk-ease-standard)}:host(.mk-time-picker--sm) .mk-time-picker__field{height:var(--mk-control-height-sm);border-radius:var(--mk-radius-sm)}:host(.mk-time-picker--lg) .mk-time-picker__field{height:var(--mk-control-height-lg)}.mk-time-picker__field:hover{border-color:var(--mk-border-strong)}.mk-time-picker__field:focus-within{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset);border-color:var(--mk-primary)}:host(.mk-time-picker--invalid) .mk-time-picker__field{border-color:var(--mk-danger)}:host(.mk-time-picker--invalid) .mk-time-picker__field:focus-within{outline-color:var(--mk-danger)}:host(.mk-time-picker--disabled) .mk-time-picker__field{background-color:var(--mk-surface-2);border-color:var(--mk-border-subtle);cursor:not-allowed;opacity:.7}.mk-time-picker__input{flex:1 1 auto;min-width:0;height:100%;padding:0 var(--mk-space-3);font-family:inherit;font-size:var(--mk-font-size-md);color:var(--mk-text);background:transparent;border:0;outline:none}:host(.mk-time-picker--sm) .mk-time-picker__input{padding:0 var(--mk-space-2);font-size:var(--mk-font-size-sm)}:host(.mk-time-picker--lg) .mk-time-picker__input{padding:0 var(--mk-space-4);font-size:var(--mk-font-size-lg)}.mk-time-picker__input::placeholder{color:var(--mk-text-subtle);opacity:1}.mk-time-picker__input:disabled{color:var(--mk-text-disabled);cursor:not-allowed}.mk-time-picker__toggle,.mk-time-picker__clear{display:inline-flex;align-items:center;justify-content:center;flex:none;height:100%;padding:0 var(--mk-space-2);color:var(--mk-text-muted);background:transparent;border:0;cursor:pointer;transition:color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-time-picker__toggle:hover:not(:disabled),.mk-time-picker__clear:hover{color:var(--mk-text)}.mk-time-picker__toggle:focus-visible,.mk-time-picker__clear:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(var(--mk-focus-ring-offset) * -1);border-radius:var(--mk-radius-sm)}.mk-time-picker__toggle:disabled{cursor:not-allowed}.mk-time-picker__clear-icon{position:relative;width:.85rem;height:.85rem}.mk-time-picker__clear-icon:before,.mk-time-picker__clear-icon:after{content:\"\";position:absolute;top:50%;left:0;width:100%;height:2px;background-color:currentColor}.mk-time-picker__clear-icon:before{transform:rotate(45deg)}.mk-time-picker__clear-icon:after{transform:rotate(-45deg)}.mk-time-picker__list{padding:var(--mk-space-1);list-style:none;max-height:15rem;overflow-y:auto;background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-md);box-shadow:var(--mk-shadow-lg)}.mk-time-picker__option{padding:var(--mk-space-2) var(--mk-space-3);font-size:var(--mk-font-size-md);color:var(--mk-text);border-radius:var(--mk-radius-sm);cursor:pointer;-webkit-user-select:none;user-select:none}.mk-time-picker__option--active{background-color:var(--mk-selected-bg)}.mk-time-picker__option--selected{color:var(--mk-selected-text);font-weight:var(--mk-font-weight-medium)}.mk-time-picker__empty{padding:var(--mk-space-2) var(--mk-space-3);color:var(--mk-text-subtle);font-size:var(--mk-font-size-sm)}@media(prefers-reduced-motion:reduce){.mk-time-picker__field,.mk-time-picker__toggle,.mk-time-picker__clear{transition:none}}@media(pointer:coarse){.mk-time-picker__input{font-size:max(var(--mk-font-size-md),16px)}:host(.mk-time-picker--sm) .mk-time-picker__input{font-size:max(var(--mk-font-size-sm),16px)}}\n"], dependencies: [{ kind: "directive", type: MkAnchoredPanel, selector: "[mkAnchoredPanel]", inputs: ["mkAnchoredPanelFor", "anchorRect", "placement", "gap", "matchWidth", "flip", "clamp"], outputs: ["dismiss"], exportAs: ["mkAnchoredPanel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1325
+ }
1326
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkTimePicker, decorators: [{
1327
+ type: Component,
1328
+ args: [{ selector: 'mk-time-picker', changeDetection: ChangeDetectionStrategy.OnPush, imports: [MkAnchoredPanel], host: {
1329
+ class: 'mk-time-picker',
1330
+ '[class.mk-time-picker--sm]': "effectiveSize() === 'sm'",
1331
+ '[class.mk-time-picker--md]': "effectiveSize() === 'md'",
1332
+ '[class.mk-time-picker--lg]': "effectiveSize() === 'lg'",
1333
+ '[class.mk-time-picker--open]': 'open()',
1334
+ '[class.mk-time-picker--invalid]': 'isInvalid()',
1335
+ '[class.mk-time-picker--disabled]': 'isDisabled()',
1336
+ '(focusout)': 'onFocusOut($event)',
1337
+ }, providers: [
1338
+ {
1339
+ provide: NG_VALUE_ACCESSOR,
1340
+ useExisting: forwardRef(() => MkTimePicker),
1341
+ multi: true,
1342
+ },
1343
+ {
1344
+ provide: NG_VALIDATORS,
1345
+ useExisting: forwardRef(() => MkTimePicker),
1346
+ multi: true,
1347
+ },
1348
+ ], template: "<div #field class=\"mk-time-picker__field\">\n <input\n #textInput\n type=\"text\"\n class=\"mk-time-picker__input\"\n [id]=\"inputId\"\n [value]=\"inputText()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n autocomplete=\"off\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-autocomplete=\"none\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listId\"\n [attr.aria-activedescendant]=\"\n open() && activeIndex() >= 0 ? optionId(activeIndex()) : null\n \"\n [attr.aria-invalid]=\"isInvalid() || null\"\n [attr.aria-required]=\"isRequired() || null\"\n [attr.aria-describedby]=\"describedBy()\"\n (input)=\"onInput($event)\"\n (keydown)=\"onInputKeydown($event)\"\n />\n\n @if (showClear()) {\n <button\n type=\"button\"\n class=\"mk-time-picker__clear\"\n [attr.aria-label]=\"i18n.clear\"\n (click)=\"clear()\"\n >\n <span class=\"mk-time-picker__clear-icon\" aria-hidden=\"true\"></span>\n </button>\n }\n\n <button\n type=\"button\"\n class=\"mk-time-picker__toggle\"\n [attr.aria-label]=\"i18n.openTimeList\"\n aria-haspopup=\"listbox\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listId\"\n [disabled]=\"isDisabled()\"\n (click)=\"toggle()\"\n >\n <svg\n class=\"mk-time-picker__icon\"\n viewBox=\"0 0 24 24\"\n width=\"18\"\n height=\"18\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n aria-hidden=\"true\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M12 7v5l3 2\" />\n </svg>\n </button>\n</div>\n\n@if (open()) {\n <ul\n #list\n class=\"mk-time-picker__list\"\n mkAnchoredPanel\n [mkAnchoredPanelFor]=\"field\"\n [matchWidth]=\"true\"\n (dismiss)=\"close()\"\n [id]=\"listId\"\n role=\"listbox\"\n >\n @for (opt of options(); track opt.value; let i = $index) {\n <li\n class=\"mk-time-picker__option\"\n [id]=\"optionId(i)\"\n role=\"option\"\n [attr.aria-selected]=\"i === selectedIndex()\"\n [class.mk-time-picker__option--active]=\"i === activeIndex()\"\n [class.mk-time-picker__option--selected]=\"i === selectedIndex()\"\n (mousedown)=\"$event.preventDefault()\"\n (mousemove)=\"activeIndex.set(i)\"\n (click)=\"selectOption(i)\"\n >\n {{ opt.label }}\n </li>\n } @empty {\n <li class=\"mk-time-picker__empty\" role=\"presentation\">\n {{ i18n.noOptions }}\n </li>\n }\n </ul>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative;width:100%;font-family:var(--mk-font-sans)}.mk-time-picker__field{display:flex;align-items:center;width:100%;height:var(--mk-control-height-md);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-md);transition:border-color var(--mk-duration-fast) var(--mk-ease-standard),box-shadow var(--mk-duration-fast) var(--mk-ease-standard)}:host(.mk-time-picker--sm) .mk-time-picker__field{height:var(--mk-control-height-sm);border-radius:var(--mk-radius-sm)}:host(.mk-time-picker--lg) .mk-time-picker__field{height:var(--mk-control-height-lg)}.mk-time-picker__field:hover{border-color:var(--mk-border-strong)}.mk-time-picker__field:focus-within{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset);border-color:var(--mk-primary)}:host(.mk-time-picker--invalid) .mk-time-picker__field{border-color:var(--mk-danger)}:host(.mk-time-picker--invalid) .mk-time-picker__field:focus-within{outline-color:var(--mk-danger)}:host(.mk-time-picker--disabled) .mk-time-picker__field{background-color:var(--mk-surface-2);border-color:var(--mk-border-subtle);cursor:not-allowed;opacity:.7}.mk-time-picker__input{flex:1 1 auto;min-width:0;height:100%;padding:0 var(--mk-space-3);font-family:inherit;font-size:var(--mk-font-size-md);color:var(--mk-text);background:transparent;border:0;outline:none}:host(.mk-time-picker--sm) .mk-time-picker__input{padding:0 var(--mk-space-2);font-size:var(--mk-font-size-sm)}:host(.mk-time-picker--lg) .mk-time-picker__input{padding:0 var(--mk-space-4);font-size:var(--mk-font-size-lg)}.mk-time-picker__input::placeholder{color:var(--mk-text-subtle);opacity:1}.mk-time-picker__input:disabled{color:var(--mk-text-disabled);cursor:not-allowed}.mk-time-picker__toggle,.mk-time-picker__clear{display:inline-flex;align-items:center;justify-content:center;flex:none;height:100%;padding:0 var(--mk-space-2);color:var(--mk-text-muted);background:transparent;border:0;cursor:pointer;transition:color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-time-picker__toggle:hover:not(:disabled),.mk-time-picker__clear:hover{color:var(--mk-text)}.mk-time-picker__toggle:focus-visible,.mk-time-picker__clear:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(var(--mk-focus-ring-offset) * -1);border-radius:var(--mk-radius-sm)}.mk-time-picker__toggle:disabled{cursor:not-allowed}.mk-time-picker__clear-icon{position:relative;width:.85rem;height:.85rem}.mk-time-picker__clear-icon:before,.mk-time-picker__clear-icon:after{content:\"\";position:absolute;top:50%;left:0;width:100%;height:2px;background-color:currentColor}.mk-time-picker__clear-icon:before{transform:rotate(45deg)}.mk-time-picker__clear-icon:after{transform:rotate(-45deg)}.mk-time-picker__list{padding:var(--mk-space-1);list-style:none;max-height:15rem;overflow-y:auto;background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-md);box-shadow:var(--mk-shadow-lg)}.mk-time-picker__option{padding:var(--mk-space-2) var(--mk-space-3);font-size:var(--mk-font-size-md);color:var(--mk-text);border-radius:var(--mk-radius-sm);cursor:pointer;-webkit-user-select:none;user-select:none}.mk-time-picker__option--active{background-color:var(--mk-selected-bg)}.mk-time-picker__option--selected{color:var(--mk-selected-text);font-weight:var(--mk-font-weight-medium)}.mk-time-picker__empty{padding:var(--mk-space-2) var(--mk-space-3);color:var(--mk-text-subtle);font-size:var(--mk-font-size-sm)}@media(prefers-reduced-motion:reduce){.mk-time-picker__field,.mk-time-picker__toggle,.mk-time-picker__clear{transition:none}}@media(pointer:coarse){.mk-time-picker__input{font-size:max(var(--mk-font-size-md),16px)}:host(.mk-time-picker--sm) .mk-time-picker__input{font-size:max(var(--mk-font-size-sm),16px)}}\n"] }]
1349
+ }], ctorParameters: () => [], propDecorators: { inputRef: [{ type: i0.ViewChild, args: ['textInput', { isSignal: true }] }], listRef: [{ type: i0.ViewChild, args: ['list', { isSignal: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], valueFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueFormat", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], hour12: [{ type: i0.Input, args: [{ isSignal: true, alias: "hour12", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
1350
+
1351
+ /**
1352
+ * DateRangePicker — select a start + end date from a single calendar popover.
1353
+ * The first click sets the start, the second sets the end (endpoints are
1354
+ * swapped if picked in reverse), and the hovered range is previewed live.
1355
+ * Implements `ControlValueAccessor` and a two-way `value` model of
1356
+ * {@link MkDateRange}.
1357
+ *
1358
+ * ```html
1359
+ * <mk-date-range-picker [(value)]="range" [min]="minDate" clearable />
1360
+ * ```
1361
+ */
1362
+ class MkDateRangePicker {
1363
+ i18n = inject(MK_I18N);
1364
+ field = inject(MkFormField, { optional: true });
1365
+ host = inject(ElementRef);
1366
+ injector = inject(Injector);
1367
+ triggerRef = viewChild('trigger', /* @ts-ignore */
1368
+ ...(ngDevMode ? [{ debugName: "triggerRef" }] : /* istanbul ignore next */ []));
1369
+ /** The calendar panel — lives in the top layer once opened. */
1370
+ panelRef = viewChild('panel', /* @ts-ignore */
1371
+ ...(ngDevMode ? [{ debugName: "panelRef" }] : /* istanbul ignore next */ []));
1372
+ /** Two-way selected range. */
1373
+ value = model({ start: null, end: null }, /* @ts-ignore */
1374
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
1375
+ /** Earliest selectable date (inclusive). */
1376
+ min = input(null, /* @ts-ignore */
1377
+ ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
1378
+ /** Latest selectable date (inclusive). */
1379
+ max = input(null, /* @ts-ignore */
1380
+ ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
1381
+ /** Predicate marking individual days as disabled. */
1382
+ disabledDate = input(null, /* @ts-ignore */
1383
+ ...(ngDevMode ? [{ debugName: "disabledDate" }] : /* istanbul ignore next */ []));
1384
+ /** Placeholder shown when no range is selected. */
1385
+ placeholder = input(this.i18n.selectRange, /* @ts-ignore */
1386
+ ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
1387
+ /** Pattern used to render each endpoint. */
1388
+ displayFormat = input('MMM d, yyyy', /* @ts-ignore */
1389
+ ...(ngDevMode ? [{ debugName: "displayFormat" }] : /* istanbul ignore next */ []));
1390
+ /** Disable the control. */
1391
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1392
+ /** Show a clear button when a range is selected. */
1393
+ clearable = input(false, { ...(ngDevMode ? { debugName: "clearable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1394
+ /** Force invalid styling + `aria-invalid`. */
1395
+ invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1396
+ /** First column of the calendar week (0 = Sunday). */
1397
+ firstDayOfWeek = input(0, { ...(ngDevMode ? { debugName: "firstDayOfWeek" } : /* istanbul ignore next */ {}), transform: numberAttribute });
1398
+ /** Control size. Ignored when nested in an `mk-form-field`. */
1399
+ size = input('md', /* @ts-ignore */
1400
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
1401
+ open = signal(false, /* @ts-ignore */
1402
+ ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
1403
+ /** In-progress start endpoint (drives calendar highlighting). */
1404
+ selStart = signal(null, /* @ts-ignore */
1405
+ ...(ngDevMode ? [{ debugName: "selStart" }] : /* istanbul ignore next */ []));
1406
+ /** In-progress end endpoint. */
1407
+ selEnd = signal(null, /* @ts-ignore */
1408
+ ...(ngDevMode ? [{ debugName: "selEnd" }] : /* istanbul ignore next */ []));
1409
+ cvaDisabled = signal(false, /* @ts-ignore */
1410
+ ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
1411
+ onChange = () => { };
1412
+ onTouched = () => { };
1413
+ triggerId = this.field?.controlId ?? mkUniqueId('mk-date-range');
1414
+ panelId = mkUniqueId('mk-date-range-panel');
1415
+ effectiveSize = computed(() => this.field ? this.field.size() : this.size(), /* @ts-ignore */
1416
+ ...(ngDevMode ? [{ debugName: "effectiveSize" }] : /* istanbul ignore next */ []));
1417
+ isDisabled = computed(() => this.disabled() || this.cvaDisabled(), /* @ts-ignore */
1418
+ ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
1419
+ isInvalid = computed(() => this.invalid() || (this.field?.hasError() ?? false), /* @ts-ignore */
1420
+ ...(ngDevMode ? [{ debugName: "isInvalid" }] : /* istanbul ignore next */ []));
1421
+ isRequired = computed(() => this.field?.isRequired() ?? false, /* @ts-ignore */
1422
+ ...(ngDevMode ? [{ debugName: "isRequired" }] : /* istanbul ignore next */ []));
1423
+ labelledBy = computed(() => this.field?.labelId ?? null, /* @ts-ignore */
1424
+ ...(ngDevMode ? [{ debugName: "labelledBy" }] : /* istanbul ignore next */ []));
1425
+ describedBy = computed(() => this.field?.describedBy() ?? null, /* @ts-ignore */
1426
+ ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
1427
+ showClear = computed(() => {
1428
+ const v = this.value();
1429
+ return this.clearable() && !!(v.start || v.end) && !this.isDisabled();
1430
+ }, /* @ts-ignore */
1431
+ ...(ngDevMode ? [{ debugName: "showClear" }] : /* istanbul ignore next */ []));
1432
+ /** Display label, e.g. `Jul 1, 2026 – Jul 9, 2026`. */
1433
+ displayLabel = computed(() => {
1434
+ const { start, end } = this.value();
1435
+ const fmt = this.displayFormat();
1436
+ if (!start && !end)
1437
+ return '';
1438
+ const names = this.i18n.dateNames;
1439
+ const s = start ? formatDate(start, fmt, names) : '…';
1440
+ const e = end ? formatDate(end, fmt, names) : '…';
1441
+ return `${s} – ${e}`;
1442
+ }, /* @ts-ignore */
1443
+ ...(ngDevMode ? [{ debugName: "displayLabel" }] : /* istanbul ignore next */ []));
1444
+ toggle() {
1445
+ if (this.isDisabled())
1446
+ return;
1447
+ this.open() ? this.close() : this.openPanel();
1448
+ }
1449
+ openPanel() {
1450
+ if (this.isDisabled() || this.open())
1451
+ return;
1452
+ const v = this.value();
1453
+ this.selStart.set(v.start);
1454
+ this.selEnd.set(v.end);
1455
+ this.open.set(true);
1456
+ afterNextRender({
1457
+ write: () => {
1458
+ const el = this.panelRef()?.nativeElement.querySelector('.mk-calendar__day[tabindex="0"]');
1459
+ el?.focus();
1460
+ },
1461
+ }, { injector: this.injector });
1462
+ }
1463
+ close() {
1464
+ if (this.open())
1465
+ this.open.set(false);
1466
+ }
1467
+ onPick(date) {
1468
+ const start = this.selStart();
1469
+ const end = this.selEnd();
1470
+ const day = startOfDay(date);
1471
+ if (!start || (start && end)) {
1472
+ // Begin a fresh range.
1473
+ this.selStart.set(day);
1474
+ this.selEnd.set(null);
1475
+ return;
1476
+ }
1477
+ // Complete the range, swapping if picked in reverse.
1478
+ if (isBefore(day, start)) {
1479
+ this.selStart.set(day);
1480
+ this.selEnd.set(start);
1481
+ }
1482
+ else {
1483
+ this.selEnd.set(day);
1484
+ }
1485
+ this.commit();
1486
+ this.close();
1487
+ this.triggerRef()?.nativeElement.focus();
1488
+ }
1489
+ clear() {
1490
+ this.selStart.set(null);
1491
+ this.selEnd.set(null);
1492
+ this.setValue({ start: null, end: null });
1493
+ this.triggerRef()?.nativeElement.focus();
1494
+ }
1495
+ commit() {
1496
+ this.setValue({ start: this.selStart(), end: this.selEnd() });
1497
+ }
1498
+ setValue(range) {
1499
+ this.value.set(range);
1500
+ this.onChange(range);
1501
+ }
1502
+ onFocusOut(event) {
1503
+ const related = event.relatedTarget;
1504
+ if (related && this.host.nativeElement.contains(related))
1505
+ return;
1506
+ if (related && this.panelRef()?.nativeElement.contains(related))
1507
+ return;
1508
+ this.close();
1509
+ this.onTouched();
1510
+ }
1511
+ /**
1512
+ * Escape pressed inside the (teleported) calendar panel — close and return
1513
+ * focus to the trigger. `preventDefault` + `stopPropagation` keep any outer
1514
+ * Escape handling (e.g. a containing dialog) from also firing.
1515
+ */
1516
+ onPanelEscape(event) {
1517
+ event.preventDefault();
1518
+ event.stopPropagation();
1519
+ this.close();
1520
+ this.triggerRef()?.nativeElement.focus();
1521
+ }
1522
+ /**
1523
+ * Focus left the teleported panel. The host `(focusout)` never sees this —
1524
+ * the panel lives under `document.body` — so Tab-out of the calendar is
1525
+ * handled here: close unless focus moved back into the field or the panel.
1526
+ */
1527
+ onPanelFocusout(event) {
1528
+ const related = event.relatedTarget;
1529
+ // Focus fell to a non-focusable spot (body) — keep open; an outside
1530
+ // pointerdown is dismissed by the anchored panel itself.
1531
+ if (!related)
1532
+ return;
1533
+ if (this.host.nativeElement.contains(related))
1534
+ return;
1535
+ if (this.panelRef()?.nativeElement.contains(related))
1536
+ return;
1537
+ this.close();
1538
+ this.onTouched();
1539
+ }
1540
+ // --- ControlValueAccessor -------------------------------------------------
1541
+ writeValue(value) {
1542
+ this.value.set(value ?? { start: null, end: null });
1543
+ }
1544
+ registerOnChange(fn) {
1545
+ this.onChange = fn;
1546
+ }
1547
+ registerOnTouched(fn) {
1548
+ this.onTouched = fn;
1549
+ }
1550
+ setDisabledState(isDisabled) {
1551
+ this.cvaDisabled.set(isDisabled);
1552
+ }
1553
+ // --- Validator ------------------------------------------------------------
1554
+ validatorChange = mkValidatorChange(() => {
1555
+ this.min();
1556
+ this.max();
1557
+ this.disabledDate();
1558
+ });
1559
+ /**
1560
+ * Reports `mkDateRangeIncomplete` for a half-picked range, `mkMinDate` /
1561
+ * `mkMaxDate` for ends outside `[min]`/`[max]`, and `mkDateFilter` for an
1562
+ * end rejected by `[disabledDate]`. A fully empty range passes; compose
1563
+ * with `Validators.required` to reject it.
1564
+ */
1565
+ validate(control) {
1566
+ const v = control.value;
1567
+ const start = v?.start instanceof Date ? v.start : null;
1568
+ const end = v?.end instanceof Date ? v.end : null;
1569
+ if (!start && !end)
1570
+ return null;
1571
+ if (!start || !end)
1572
+ return { mkDateRangeIncomplete: true };
1573
+ const min = this.min();
1574
+ if (min && isBefore(startOfDay(start), startOfDay(min))) {
1575
+ return { mkMinDate: { min, actual: start } };
1576
+ }
1577
+ const max = this.max();
1578
+ if (max && isAfter(startOfDay(end), startOfDay(max))) {
1579
+ return { mkMaxDate: { max, actual: end } };
1580
+ }
1581
+ const filter = this.disabledDate();
1582
+ if (filter?.(start) || filter?.(end))
1583
+ return { mkDateFilter: true };
1584
+ return null;
1585
+ }
1586
+ registerOnValidatorChange(fn) {
1587
+ this.validatorChange.register(fn);
1588
+ }
1589
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkDateRangePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
1590
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkDateRangePicker, isStandalone: true, selector: "mk-date-range-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, disabledDate: { classPropertyName: "disabledDate", publicName: "disabledDate", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, displayFormat: { classPropertyName: "displayFormat", publicName: "displayFormat", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, firstDayOfWeek: { classPropertyName: "firstDayOfWeek", publicName: "firstDayOfWeek", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { listeners: { "focusout": "onFocusOut($event)" }, properties: { "class.mk-date-range-picker--sm": "effectiveSize() === 'sm'", "class.mk-date-range-picker--md": "effectiveSize() === 'md'", "class.mk-date-range-picker--lg": "effectiveSize() === 'lg'", "class.mk-date-range-picker--open": "open()", "class.mk-date-range-picker--invalid": "isInvalid()", "class.mk-date-range-picker--disabled": "isDisabled()" }, classAttribute: "mk-date-range-picker" }, providers: [
1591
+ {
1592
+ provide: NG_VALUE_ACCESSOR,
1593
+ useExisting: forwardRef(() => MkDateRangePicker),
1594
+ multi: true,
1595
+ },
1596
+ {
1597
+ provide: NG_VALIDATORS,
1598
+ useExisting: forwardRef(() => MkDateRangePicker),
1599
+ multi: true,
1600
+ },
1601
+ ], viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["trigger"], descendants: true, isSignal: true }, { propertyName: "panelRef", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "<div #field class=\"mk-date-range-picker__field\">\n <button\n #trigger\n type=\"button\"\n class=\"mk-date-range-picker__trigger\"\n [id]=\"triggerId\"\n aria-haspopup=\"dialog\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-labelledby]=\"labelledBy()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"isInvalid() || null\"\n [attr.aria-required]=\"isRequired() || null\"\n [disabled]=\"isDisabled()\"\n (click)=\"toggle()\"\n >\n @if (displayLabel()) {\n <span class=\"mk-date-range-picker__value\">{{ displayLabel() }}</span>\n } @else {\n <span class=\"mk-date-range-picker__value mk-date-range-picker__value--placeholder\">{{\n placeholder()\n }}</span>\n }\n </button>\n\n @if (showClear()) {\n <button\n type=\"button\"\n class=\"mk-date-range-picker__clear\"\n [attr.aria-label]=\"i18n.clear\"\n (click)=\"clear()\"\n >\n <span class=\"mk-date-range-picker__clear-icon\" aria-hidden=\"true\"></span>\n </button>\n }\n\n <button\n type=\"button\"\n class=\"mk-date-range-picker__toggle\"\n [attr.aria-label]=\"i18n.openCalendar\"\n aria-haspopup=\"dialog\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"panelId\"\n [disabled]=\"isDisabled()\"\n (click)=\"toggle()\"\n >\n <svg\n class=\"mk-date-range-picker__icon\"\n viewBox=\"0 0 24 24\"\n width=\"18\"\n height=\"18\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n aria-hidden=\"true\"\n >\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"17\" rx=\"2\" />\n <path d=\"M3 9h18M8 2v4M16 2v4\" />\n </svg>\n </button>\n</div>\n\n@if (open()) {\n <div\n #panel\n class=\"mk-date-range-picker__panel\"\n mkAnchoredPanel\n [mkAnchoredPanelFor]=\"field\"\n (dismiss)=\"close()\"\n [id]=\"panelId\"\n role=\"dialog\"\n [attr.aria-label]=\"i18n.chooseDateRange\"\n (keydown.escape)=\"onPanelEscape($event)\"\n (focusout)=\"onPanelFocusout($event)\"\n >\n <mk-calendar\n [rangeMode]=\"true\"\n [rangeStart]=\"selStart()\"\n [rangeEnd]=\"selEnd()\"\n (dateSelected)=\"onPick($event)\"\n [min]=\"min()\"\n [max]=\"max()\"\n [disabledDate]=\"disabledDate()\"\n [firstDayOfWeek]=\"firstDayOfWeek()\"\n [size]=\"effectiveSize()\"\n />\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative;width:100%;font-family:var(--mk-font-sans)}.mk-date-range-picker__field{display:flex;align-items:center;width:100%;height:var(--mk-control-height-md);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-md);transition:border-color var(--mk-duration-fast) var(--mk-ease-standard),box-shadow var(--mk-duration-fast) var(--mk-ease-standard)}:host(.mk-date-range-picker--sm) .mk-date-range-picker__field{height:var(--mk-control-height-sm);border-radius:var(--mk-radius-sm)}:host(.mk-date-range-picker--lg) .mk-date-range-picker__field{height:var(--mk-control-height-lg)}.mk-date-range-picker__field:hover{border-color:var(--mk-border-strong)}.mk-date-range-picker__field:focus-within{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset);border-color:var(--mk-primary)}:host(.mk-date-range-picker--invalid) .mk-date-range-picker__field{border-color:var(--mk-danger)}:host(.mk-date-range-picker--invalid) .mk-date-range-picker__field:focus-within{outline-color:var(--mk-danger)}:host(.mk-date-range-picker--disabled) .mk-date-range-picker__field{background-color:var(--mk-surface-2);border-color:var(--mk-border-subtle);cursor:not-allowed;opacity:.7}.mk-date-range-picker__trigger{flex:1 1 auto;min-width:0;height:100%;padding:0 var(--mk-space-3);font-family:inherit;font-size:var(--mk-font-size-md);color:var(--mk-text);text-align:start;background:transparent;border:0;outline:none;cursor:pointer}:host(.mk-date-range-picker--sm) .mk-date-range-picker__trigger{padding:0 var(--mk-space-2);font-size:var(--mk-font-size-sm)}:host(.mk-date-range-picker--lg) .mk-date-range-picker__trigger{padding:0 var(--mk-space-4);font-size:var(--mk-font-size-lg)}.mk-date-range-picker__trigger:disabled{color:var(--mk-text-disabled);cursor:not-allowed}.mk-date-range-picker__value{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mk-date-range-picker__value--placeholder{color:var(--mk-text-subtle)}.mk-date-range-picker__toggle,.mk-date-range-picker__clear{display:inline-flex;align-items:center;justify-content:center;flex:none;height:100%;padding:0 var(--mk-space-2);color:var(--mk-text-muted);background:transparent;border:0;cursor:pointer;transition:color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-date-range-picker__toggle:hover:not(:disabled),.mk-date-range-picker__clear:hover{color:var(--mk-text)}.mk-date-range-picker__toggle:focus-visible,.mk-date-range-picker__clear:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(var(--mk-focus-ring-offset) * -1);border-radius:var(--mk-radius-sm)}.mk-date-range-picker__toggle:disabled{cursor:not-allowed}.mk-date-range-picker__clear-icon{position:relative;width:.85rem;height:.85rem}.mk-date-range-picker__clear-icon:before,.mk-date-range-picker__clear-icon:after{content:\"\";position:absolute;top:50%;left:0;width:100%;height:2px;background-color:currentColor}.mk-date-range-picker__clear-icon:before{transform:rotate(45deg)}.mk-date-range-picker__clear-icon:after{transform:rotate(-45deg)}.mk-date-range-picker__panel{box-shadow:var(--mk-shadow-lg);border-radius:var(--mk-radius-lg)}@media(prefers-reduced-motion:reduce){.mk-date-range-picker__field,.mk-date-range-picker__toggle,.mk-date-range-picker__clear{transition:none}}\n"], dependencies: [{ kind: "component", type: MkCalendar, selector: "mk-calendar", inputs: ["value", "min", "max", "firstDayOfWeek", "disabled", "invalid", "disabledDate", "size", "fullWidth", "rangeMode", "rangeStart", "rangeEnd"], outputs: ["valueChange", "dateSelected", "dateHovered"] }, { kind: "directive", type: MkAnchoredPanel, selector: "[mkAnchoredPanel]", inputs: ["mkAnchoredPanelFor", "anchorRect", "placement", "gap", "matchWidth", "flip", "clamp"], outputs: ["dismiss"], exportAs: ["mkAnchoredPanel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1602
+ }
1603
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkDateRangePicker, decorators: [{
1604
+ type: Component,
1605
+ args: [{ selector: 'mk-date-range-picker', changeDetection: ChangeDetectionStrategy.OnPush, imports: [MkCalendar, MkAnchoredPanel], host: {
1606
+ class: 'mk-date-range-picker',
1607
+ '[class.mk-date-range-picker--sm]': "effectiveSize() === 'sm'",
1608
+ '[class.mk-date-range-picker--md]': "effectiveSize() === 'md'",
1609
+ '[class.mk-date-range-picker--lg]': "effectiveSize() === 'lg'",
1610
+ '[class.mk-date-range-picker--open]': 'open()',
1611
+ '[class.mk-date-range-picker--invalid]': 'isInvalid()',
1612
+ '[class.mk-date-range-picker--disabled]': 'isDisabled()',
1613
+ '(focusout)': 'onFocusOut($event)',
1614
+ }, providers: [
1615
+ {
1616
+ provide: NG_VALUE_ACCESSOR,
1617
+ useExisting: forwardRef(() => MkDateRangePicker),
1618
+ multi: true,
1619
+ },
1620
+ {
1621
+ provide: NG_VALIDATORS,
1622
+ useExisting: forwardRef(() => MkDateRangePicker),
1623
+ multi: true,
1624
+ },
1625
+ ], template: "<div #field class=\"mk-date-range-picker__field\">\n <button\n #trigger\n type=\"button\"\n class=\"mk-date-range-picker__trigger\"\n [id]=\"triggerId\"\n aria-haspopup=\"dialog\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-labelledby]=\"labelledBy()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"isInvalid() || null\"\n [attr.aria-required]=\"isRequired() || null\"\n [disabled]=\"isDisabled()\"\n (click)=\"toggle()\"\n >\n @if (displayLabel()) {\n <span class=\"mk-date-range-picker__value\">{{ displayLabel() }}</span>\n } @else {\n <span class=\"mk-date-range-picker__value mk-date-range-picker__value--placeholder\">{{\n placeholder()\n }}</span>\n }\n </button>\n\n @if (showClear()) {\n <button\n type=\"button\"\n class=\"mk-date-range-picker__clear\"\n [attr.aria-label]=\"i18n.clear\"\n (click)=\"clear()\"\n >\n <span class=\"mk-date-range-picker__clear-icon\" aria-hidden=\"true\"></span>\n </button>\n }\n\n <button\n type=\"button\"\n class=\"mk-date-range-picker__toggle\"\n [attr.aria-label]=\"i18n.openCalendar\"\n aria-haspopup=\"dialog\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"panelId\"\n [disabled]=\"isDisabled()\"\n (click)=\"toggle()\"\n >\n <svg\n class=\"mk-date-range-picker__icon\"\n viewBox=\"0 0 24 24\"\n width=\"18\"\n height=\"18\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n aria-hidden=\"true\"\n >\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"17\" rx=\"2\" />\n <path d=\"M3 9h18M8 2v4M16 2v4\" />\n </svg>\n </button>\n</div>\n\n@if (open()) {\n <div\n #panel\n class=\"mk-date-range-picker__panel\"\n mkAnchoredPanel\n [mkAnchoredPanelFor]=\"field\"\n (dismiss)=\"close()\"\n [id]=\"panelId\"\n role=\"dialog\"\n [attr.aria-label]=\"i18n.chooseDateRange\"\n (keydown.escape)=\"onPanelEscape($event)\"\n (focusout)=\"onPanelFocusout($event)\"\n >\n <mk-calendar\n [rangeMode]=\"true\"\n [rangeStart]=\"selStart()\"\n [rangeEnd]=\"selEnd()\"\n (dateSelected)=\"onPick($event)\"\n [min]=\"min()\"\n [max]=\"max()\"\n [disabledDate]=\"disabledDate()\"\n [firstDayOfWeek]=\"firstDayOfWeek()\"\n [size]=\"effectiveSize()\"\n />\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative;width:100%;font-family:var(--mk-font-sans)}.mk-date-range-picker__field{display:flex;align-items:center;width:100%;height:var(--mk-control-height-md);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-md);transition:border-color var(--mk-duration-fast) var(--mk-ease-standard),box-shadow var(--mk-duration-fast) var(--mk-ease-standard)}:host(.mk-date-range-picker--sm) .mk-date-range-picker__field{height:var(--mk-control-height-sm);border-radius:var(--mk-radius-sm)}:host(.mk-date-range-picker--lg) .mk-date-range-picker__field{height:var(--mk-control-height-lg)}.mk-date-range-picker__field:hover{border-color:var(--mk-border-strong)}.mk-date-range-picker__field:focus-within{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset);border-color:var(--mk-primary)}:host(.mk-date-range-picker--invalid) .mk-date-range-picker__field{border-color:var(--mk-danger)}:host(.mk-date-range-picker--invalid) .mk-date-range-picker__field:focus-within{outline-color:var(--mk-danger)}:host(.mk-date-range-picker--disabled) .mk-date-range-picker__field{background-color:var(--mk-surface-2);border-color:var(--mk-border-subtle);cursor:not-allowed;opacity:.7}.mk-date-range-picker__trigger{flex:1 1 auto;min-width:0;height:100%;padding:0 var(--mk-space-3);font-family:inherit;font-size:var(--mk-font-size-md);color:var(--mk-text);text-align:start;background:transparent;border:0;outline:none;cursor:pointer}:host(.mk-date-range-picker--sm) .mk-date-range-picker__trigger{padding:0 var(--mk-space-2);font-size:var(--mk-font-size-sm)}:host(.mk-date-range-picker--lg) .mk-date-range-picker__trigger{padding:0 var(--mk-space-4);font-size:var(--mk-font-size-lg)}.mk-date-range-picker__trigger:disabled{color:var(--mk-text-disabled);cursor:not-allowed}.mk-date-range-picker__value{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mk-date-range-picker__value--placeholder{color:var(--mk-text-subtle)}.mk-date-range-picker__toggle,.mk-date-range-picker__clear{display:inline-flex;align-items:center;justify-content:center;flex:none;height:100%;padding:0 var(--mk-space-2);color:var(--mk-text-muted);background:transparent;border:0;cursor:pointer;transition:color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-date-range-picker__toggle:hover:not(:disabled),.mk-date-range-picker__clear:hover{color:var(--mk-text)}.mk-date-range-picker__toggle:focus-visible,.mk-date-range-picker__clear:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(var(--mk-focus-ring-offset) * -1);border-radius:var(--mk-radius-sm)}.mk-date-range-picker__toggle:disabled{cursor:not-allowed}.mk-date-range-picker__clear-icon{position:relative;width:.85rem;height:.85rem}.mk-date-range-picker__clear-icon:before,.mk-date-range-picker__clear-icon:after{content:\"\";position:absolute;top:50%;left:0;width:100%;height:2px;background-color:currentColor}.mk-date-range-picker__clear-icon:before{transform:rotate(45deg)}.mk-date-range-picker__clear-icon:after{transform:rotate(-45deg)}.mk-date-range-picker__panel{box-shadow:var(--mk-shadow-lg);border-radius:var(--mk-radius-lg)}@media(prefers-reduced-motion:reduce){.mk-date-range-picker__field,.mk-date-range-picker__toggle,.mk-date-range-picker__clear{transition:none}}\n"] }]
1626
+ }], propDecorators: { triggerRef: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], panelRef: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], disabledDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabledDate", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], displayFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayFormat", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], firstDayOfWeek: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstDayOfWeek", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
1627
+
1628
+ /**
1629
+ * MonthPicker — a compact field + popover for picking a **month** (`MMM yyyy`)
1630
+ * or a **year**. The panel shows a 12-month grid (with year nav) or a 12-year
1631
+ * grid (with decade nav). Value is a `Date` at the first day of the selection.
1632
+ * Implements `ControlValueAccessor` + a two-way `value` model; the panel renders
1633
+ * in the top layer so it is never clipped.
1634
+ *
1635
+ * ```html
1636
+ * <mk-month-picker [(value)]="month" />
1637
+ * <mk-month-picker mode="year" [(value)]="year" />
1638
+ * ```
1639
+ */
1640
+ class MkMonthPicker {
1641
+ i18n = inject(MK_I18N);
1642
+ field = inject(MkFormField, { optional: true });
1643
+ host = inject(ElementRef);
1644
+ injector = inject(Injector);
1645
+ triggerRef = viewChild('trigger', /* @ts-ignore */
1646
+ ...(ngDevMode ? [{ debugName: "triggerRef" }] : /* istanbul ignore next */ []));
1647
+ panelRef = viewChild('panel', /* @ts-ignore */
1648
+ ...(ngDevMode ? [{ debugName: "panelRef" }] : /* istanbul ignore next */ []));
1649
+ cellRefs = viewChildren('cell', /* @ts-ignore */
1650
+ ...(ngDevMode ? [{ debugName: "cellRefs" }] : /* istanbul ignore next */ []));
1651
+ /** Two-way selected date (first of the month / year). */
1652
+ value = model(null, /* @ts-ignore */
1653
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
1654
+ /** Pick a month or a whole year. */
1655
+ mode = input('month', /* @ts-ignore */
1656
+ ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
1657
+ /** Earliest selectable date. */
1658
+ min = input(null, /* @ts-ignore */
1659
+ ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
1660
+ /** Latest selectable date. */
1661
+ max = input(null, /* @ts-ignore */
1662
+ ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
1663
+ /** Placeholder shown when empty. */
1664
+ placeholder = input('', /* @ts-ignore */
1665
+ ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
1666
+ /** Display pattern (defaults to `MMM yyyy` / `yyyy`). */
1667
+ displayFormat = input('', /* @ts-ignore */
1668
+ ...(ngDevMode ? [{ debugName: "displayFormat" }] : /* istanbul ignore next */ []));
1669
+ /** Disable the control. */
1670
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1671
+ /** Show a clear button when a value is selected. */
1672
+ clearable = input(false, { ...(ngDevMode ? { debugName: "clearable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1673
+ /** Force invalid styling. */
1674
+ invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1675
+ /** Control size. Ignored when nested in an `mk-form-field`. */
1676
+ size = input('md', /* @ts-ignore */
1677
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
1678
+ open = signal(false, /* @ts-ignore */
1679
+ ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
1680
+ /** The year currently shown in the panel (base for the month/decade grid). */
1681
+ viewYear = signal(new Date().getFullYear(), /* @ts-ignore */
1682
+ ...(ngDevMode ? [{ debugName: "viewYear" }] : /* istanbul ignore next */ []));
1683
+ activeIndex = signal(0, /* @ts-ignore */
1684
+ ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
1685
+ cvaDisabled = signal(false, /* @ts-ignore */
1686
+ ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
1687
+ onChange = () => { };
1688
+ onTouched = () => { };
1689
+ triggerId = this.field?.controlId ?? mkUniqueId('mk-month-picker');
1690
+ panelId = mkUniqueId('mk-month-picker-panel');
1691
+ effectiveSize = computed(() => this.field ? this.field.size() : this.size(), /* @ts-ignore */
1692
+ ...(ngDevMode ? [{ debugName: "effectiveSize" }] : /* istanbul ignore next */ []));
1693
+ isDisabled = computed(() => this.disabled() || this.cvaDisabled(), /* @ts-ignore */
1694
+ ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
1695
+ isInvalid = computed(() => this.invalid() || (this.field?.hasError() ?? false), /* @ts-ignore */
1696
+ ...(ngDevMode ? [{ debugName: "isInvalid" }] : /* istanbul ignore next */ []));
1697
+ isRequired = computed(() => this.field?.isRequired() ?? false, /* @ts-ignore */
1698
+ ...(ngDevMode ? [{ debugName: "isRequired" }] : /* istanbul ignore next */ []));
1699
+ labelledBy = computed(() => this.field?.labelId ?? null, /* @ts-ignore */
1700
+ ...(ngDevMode ? [{ debugName: "labelledBy" }] : /* istanbul ignore next */ []));
1701
+ describedBy = computed(() => this.field?.describedBy() ?? null, /* @ts-ignore */
1702
+ ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
1703
+ resolvedPlaceholder = computed(() => this.placeholder() ||
1704
+ (this.mode() === 'year' ? this.i18n.selectYear : this.i18n.selectMonth), /* @ts-ignore */
1705
+ ...(ngDevMode ? [{ debugName: "resolvedPlaceholder" }] : /* istanbul ignore next */ []));
1706
+ pattern = computed(() => this.displayFormat() || (this.mode() === 'year' ? 'yyyy' : 'MMM yyyy'), /* @ts-ignore */
1707
+ ...(ngDevMode ? [{ debugName: "pattern" }] : /* istanbul ignore next */ []));
1708
+ displayLabel = computed(() => {
1709
+ const v = this.value();
1710
+ return v ? formatDate(v, this.pattern(), this.i18n.dateNames) : '';
1711
+ }, /* @ts-ignore */
1712
+ ...(ngDevMode ? [{ debugName: "displayLabel" }] : /* istanbul ignore next */ []));
1713
+ /** The decade start when in year mode. */
1714
+ decadeStart = computed(() => Math.floor(this.viewYear() / 12) * 12, /* @ts-ignore */
1715
+ ...(ngDevMode ? [{ debugName: "decadeStart" }] : /* istanbul ignore next */ []));
1716
+ /** Header label (year, or decade range). */
1717
+ headerLabel = computed(() => {
1718
+ if (this.mode() === 'year') {
1719
+ const s = this.decadeStart();
1720
+ return `${s} – ${s + 11}`;
1721
+ }
1722
+ return `${this.viewYear()}`;
1723
+ }, /* @ts-ignore */
1724
+ ...(ngDevMode ? [{ debugName: "headerLabel" }] : /* istanbul ignore next */ []));
1725
+ /** Abbreviated month labels for the grid, from the active locale. */
1726
+ months = this.i18n.dateNames.monthsShort;
1727
+ /** The 12 grid cells (months or years). */
1728
+ cells = computed(() => {
1729
+ const value = this.value();
1730
+ if (this.mode() === 'year') {
1731
+ const start = this.decadeStart();
1732
+ return Array.from({ length: 12 }, (_, i) => {
1733
+ const year = start + i;
1734
+ const date = new Date(year, 0, 1);
1735
+ return {
1736
+ label: `${year}`,
1737
+ year,
1738
+ month: 0,
1739
+ disabled: this.outOfRange(date, new Date(year, 11, 31)),
1740
+ selected: value?.getFullYear() === year,
1741
+ };
1742
+ });
1743
+ }
1744
+ const year = this.viewYear();
1745
+ return this.months.map((label, m) => {
1746
+ const date = new Date(year, m, 1);
1747
+ return {
1748
+ label,
1749
+ year,
1750
+ month: m,
1751
+ disabled: this.outOfRange(date, endOfMonth(date)),
1752
+ selected: value?.getFullYear() === year && value?.getMonth() === m,
1753
+ };
1754
+ });
1755
+ }, /* @ts-ignore */
1756
+ ...(ngDevMode ? [{ debugName: "cells" }] : /* istanbul ignore next */ []));
1757
+ constructor() {
1758
+ // Keep the panel's view year in sync with the selected value.
1759
+ effect(() => {
1760
+ const v = this.value();
1761
+ if (v)
1762
+ this.viewYear.set(v.getFullYear());
1763
+ });
1764
+ }
1765
+ toggle() {
1766
+ if (this.isDisabled())
1767
+ return;
1768
+ this.open() ? this.close() : this.openPanel();
1769
+ }
1770
+ openPanel() {
1771
+ if (this.isDisabled() || this.open())
1772
+ return;
1773
+ const v = this.value();
1774
+ if (v)
1775
+ this.viewYear.set(v.getFullYear());
1776
+ // Focus the selected (or first enabled) cell once the panel is in the DOM.
1777
+ const cells = this.cells();
1778
+ const sel = cells.findIndex((c) => c.selected);
1779
+ this.activeIndex.set(sel >= 0 ? sel : cells.findIndex((c) => !c.disabled));
1780
+ this.open.set(true);
1781
+ afterNextRender(() => this.focusActive(), { injector: this.injector });
1782
+ }
1783
+ close(restoreFocus = false) {
1784
+ if (!this.open())
1785
+ return;
1786
+ this.open.set(false);
1787
+ if (restoreFocus)
1788
+ this.triggerRef()?.nativeElement.focus();
1789
+ }
1790
+ shiftView(delta) {
1791
+ const step = this.mode() === 'year' ? 12 : 1;
1792
+ this.viewYear.set(this.viewYear() + delta * step);
1793
+ }
1794
+ selectCell(cell) {
1795
+ if (cell.disabled)
1796
+ return;
1797
+ const date = new Date(cell.year, this.mode() === 'year' ? 0 : cell.month, 1);
1798
+ this.value.set(date);
1799
+ this.onChange(date);
1800
+ this.close(true);
1801
+ }
1802
+ clear(event) {
1803
+ event.stopPropagation();
1804
+ this.value.set(null);
1805
+ this.onChange(null);
1806
+ this.triggerRef()?.nativeElement.focus();
1807
+ }
1808
+ onGridKeydown(event) {
1809
+ const cols = 3;
1810
+ const total = this.cells().length;
1811
+ let next = this.activeIndex();
1812
+ switch (event.key) {
1813
+ case 'ArrowRight':
1814
+ next += 1;
1815
+ break;
1816
+ case 'ArrowLeft':
1817
+ next -= 1;
1818
+ break;
1819
+ case 'ArrowDown':
1820
+ next += cols;
1821
+ break;
1822
+ case 'ArrowUp':
1823
+ next -= cols;
1824
+ break;
1825
+ case 'Home':
1826
+ next = 0;
1827
+ break;
1828
+ case 'End':
1829
+ next = total - 1;
1830
+ break;
1831
+ case 'Escape':
1832
+ event.preventDefault();
1833
+ this.close(true);
1834
+ return;
1835
+ default:
1836
+ return;
1837
+ }
1838
+ event.preventDefault();
1839
+ this.activeIndex.set(Math.max(0, Math.min(total - 1, next)));
1840
+ this.focusActive();
1841
+ }
1842
+ focusActive() {
1843
+ this.cellRefs()[this.activeIndex()]?.nativeElement.focus();
1844
+ }
1845
+ onFocusOut(event) {
1846
+ const related = event.relatedTarget;
1847
+ if (related && this.host.nativeElement.contains(related))
1848
+ return;
1849
+ if (related && this.panelRef()?.nativeElement.contains(related))
1850
+ return;
1851
+ this.close();
1852
+ this.onTouched();
1853
+ }
1854
+ outOfRange(start, end) {
1855
+ const min = this.min();
1856
+ const max = this.max();
1857
+ if (min && isBefore(end, min))
1858
+ return true;
1859
+ if (max && isAfter(start, max))
1860
+ return true;
1861
+ return false;
1862
+ }
1863
+ // --- ControlValueAccessor -------------------------------------------------
1864
+ writeValue(value) {
1865
+ this.value.set(value ?? null);
1866
+ }
1867
+ registerOnChange(fn) {
1868
+ this.onChange = fn;
1869
+ }
1870
+ registerOnTouched(fn) {
1871
+ this.onTouched = fn;
1872
+ }
1873
+ setDisabledState(isDisabled) {
1874
+ this.cvaDisabled.set(isDisabled);
1875
+ }
1876
+ // --- Validator ------------------------------------------------------------
1877
+ validatorChange = mkValidatorChange(() => {
1878
+ this.min();
1879
+ this.max();
1880
+ });
1881
+ /**
1882
+ * Reports `mkMinDate` / `mkMaxDate` against the `[min]` and `[max]` inputs,
1883
+ * compared at month granularity.
1884
+ */
1885
+ validate(control) {
1886
+ const v = control.value;
1887
+ if (!(v instanceof Date) || Number.isNaN(v.getTime()))
1888
+ return null;
1889
+ const month = startOfMonth(v);
1890
+ const min = this.min();
1891
+ if (min && isBefore(month, startOfMonth(min)))
1892
+ return { mkMinDate: { min, actual: v } };
1893
+ const max = this.max();
1894
+ if (max && isAfter(month, startOfMonth(max)))
1895
+ return { mkMaxDate: { max, actual: v } };
1896
+ return null;
1897
+ }
1898
+ registerOnValidatorChange(fn) {
1899
+ this.validatorChange.register(fn);
1900
+ }
1901
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkMonthPicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
1902
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkMonthPicker, isStandalone: true, selector: "mk-month-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, displayFormat: { classPropertyName: "displayFormat", publicName: "displayFormat", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { listeners: { "focusout": "onFocusOut($event)" }, properties: { "class.mk-month-picker--sm": "effectiveSize() === 'sm'", "class.mk-month-picker--lg": "effectiveSize() === 'lg'", "class.mk-month-picker--open": "open()", "class.mk-month-picker--invalid": "isInvalid()", "class.mk-month-picker--disabled": "isDisabled()" }, classAttribute: "mk-month-picker" }, providers: [
1903
+ {
1904
+ provide: NG_VALUE_ACCESSOR,
1905
+ useExisting: forwardRef(() => MkMonthPicker),
1906
+ multi: true,
1907
+ },
1908
+ {
1909
+ provide: NG_VALIDATORS,
1910
+ useExisting: forwardRef(() => MkMonthPicker),
1911
+ multi: true,
1912
+ },
1913
+ ], viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["trigger"], descendants: true, isSignal: true }, { propertyName: "panelRef", first: true, predicate: ["panel"], descendants: true, isSignal: true }, { propertyName: "cellRefs", predicate: ["cell"], descendants: true, isSignal: true }], ngImport: i0, template: "<div #field class=\"mk-month-picker__field\">\n <button\n #trigger\n type=\"button\"\n class=\"mk-month-picker__trigger\"\n [id]=\"triggerId\"\n aria-haspopup=\"dialog\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-labelledby]=\"labelledBy()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"isInvalid() || null\"\n [attr.aria-required]=\"isRequired() || null\"\n [disabled]=\"isDisabled()\"\n (click)=\"toggle()\"\n >\n @if (displayLabel()) {\n <span class=\"mk-month-picker__value\">{{ displayLabel() }}</span>\n } @else {\n <span class=\"mk-month-picker__value mk-month-picker__value--placeholder\">{{\n resolvedPlaceholder()\n }}</span>\n }\n <span class=\"mk-month-picker__icon\" aria-hidden=\"true\"></span>\n </button>\n\n @if (clearable() && value() && !isDisabled()) {\n <button\n type=\"button\"\n class=\"mk-month-picker__clear\"\n [attr.aria-label]=\"i18n.clear\"\n (click)=\"clear($event)\"\n >\n <span class=\"mk-month-picker__clear-icon\" aria-hidden=\"true\"></span>\n </button>\n }\n</div>\n\n@if (open()) {\n <div\n #panel\n class=\"mk-month-picker__panel\"\n role=\"dialog\"\n [id]=\"panelId\"\n [attr.aria-label]=\"mode() === 'year' ? i18n.chooseYear : i18n.chooseMonth\"\n mkAnchoredPanel\n [mkAnchoredPanelFor]=\"field\"\n (dismiss)=\"close()\"\n >\n <div class=\"mk-month-picker__header\">\n <button\n type=\"button\"\n class=\"mk-month-picker__nav\"\n [attr.aria-label]=\"mode() === 'year' ? i18n.previousYears : i18n.previousYear\"\n (click)=\"shiftView(-1)\"\n >\n \u2039\n </button>\n <span class=\"mk-month-picker__header-label\" aria-live=\"polite\">{{ headerLabel() }}</span>\n <button\n type=\"button\"\n class=\"mk-month-picker__nav\"\n [attr.aria-label]=\"mode() === 'year' ? i18n.nextYears : i18n.nextYear\"\n (click)=\"shiftView(1)\"\n >\n \u203A\n </button>\n </div>\n\n <div class=\"mk-month-picker__grid\" role=\"grid\" (keydown)=\"onGridKeydown($event)\">\n @for (cell of cells(); track cell.label; let i = $index) {\n <button\n #cell\n type=\"button\"\n class=\"mk-month-picker__cell\"\n [class.mk-month-picker__cell--selected]=\"cell.selected\"\n [attr.tabindex]=\"i === activeIndex() ? 0 : -1\"\n [attr.aria-selected]=\"cell.selected\"\n [disabled]=\"cell.disabled\"\n (click)=\"selectCell(cell)\"\n >\n {{ cell.label }}\n </button>\n }\n </div>\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative;width:100%;font-family:var(--mk-font-sans)}.mk-month-picker__field{display:flex;align-items:center;position:relative}.mk-month-picker__trigger{flex:1 1 auto;display:flex;align-items:center;gap:var(--mk-space-2);width:100%;height:var(--mk-control-height-md);padding:0 var(--mk-space-3);text-align:start;font:inherit;font-size:var(--mk-font-size-md);color:var(--mk-text);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-md);cursor:pointer}:host(.mk-month-picker--sm) .mk-month-picker__trigger{height:var(--mk-control-height-sm);padding:0 var(--mk-space-2);font-size:var(--mk-font-size-sm);border-radius:var(--mk-radius-sm)}:host(.mk-month-picker--lg) .mk-month-picker__trigger{height:var(--mk-control-height-lg);padding:0 var(--mk-space-4);font-size:var(--mk-font-size-lg)}.mk-month-picker__trigger:hover:not(:disabled){border-color:var(--mk-border-strong)}.mk-month-picker__trigger:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset);border-color:var(--mk-primary)}:host(.mk-month-picker--invalid) .mk-month-picker__trigger{border-color:var(--mk-danger)}:host(.mk-month-picker--disabled) .mk-month-picker__trigger{background-color:var(--mk-surface-2);color:var(--mk-text-disabled);cursor:not-allowed;opacity:.7}.mk-month-picker__value{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mk-month-picker__value--placeholder{color:var(--mk-text-subtle)}.mk-month-picker__icon{flex:none;width:1rem;height:1rem;border:2px solid var(--mk-text-muted);border-radius:2px;position:relative}.mk-month-picker__icon:before{content:\"\";position:absolute;top:-3px;left:2px;right:2px;border-top:2px solid var(--mk-text-muted)}.mk-month-picker__clear{position:absolute;inset-inline-end:var(--mk-space-8);display:grid;place-items:center;width:1.25rem;height:1.25rem;color:var(--mk-text-muted);background:transparent;border:none;border-radius:var(--mk-radius-circle);cursor:pointer}.mk-month-picker__clear:hover{background-color:var(--mk-hover-overlay);color:var(--mk-text)}.mk-month-picker__clear-icon:before,.mk-month-picker__clear-icon:after{content:\"\";position:absolute;width:.7rem;height:2px;background-color:currentColor}.mk-month-picker__clear-icon:before{transform:rotate(45deg)}.mk-month-picker__clear-icon:after{transform:rotate(-45deg)}.mk-month-picker__panel{width:17rem;padding:var(--mk-space-2);background:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-lg);box-shadow:var(--mk-shadow-lg)}.mk-month-picker__header{display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--mk-space-1)}.mk-month-picker__header-label{font-weight:var(--mk-font-weight-semibold);color:var(--mk-text)}.mk-month-picker__nav{display:grid;place-items:center;width:2rem;height:2rem;font-size:var(--mk-font-size-lg);color:var(--mk-text-muted);background:transparent;border:none;border-radius:var(--mk-radius-sm);cursor:pointer}.mk-month-picker__nav:hover{background-color:var(--mk-hover-overlay);color:var(--mk-text)}.mk-month-picker__nav:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}.mk-month-picker__grid{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--mk-space-1)}.mk-month-picker__cell{padding:var(--mk-space-2) 0;font-size:var(--mk-font-size-sm);color:var(--mk-text);background:transparent;border:none;border-radius:var(--mk-radius-sm);cursor:pointer}.mk-month-picker__cell:hover:not(:disabled){background-color:var(--mk-hover-overlay)}.mk-month-picker__cell:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-month-picker__cell--selected{background-color:var(--mk-primary);color:var(--mk-primary-contrast)}.mk-month-picker__cell--selected:hover:not(:disabled){background-color:var(--mk-primary)}.mk-month-picker__cell:disabled{color:var(--mk-text-disabled);cursor:not-allowed}\n"], dependencies: [{ kind: "directive", type: MkAnchoredPanel, selector: "[mkAnchoredPanel]", inputs: ["mkAnchoredPanelFor", "anchorRect", "placement", "gap", "matchWidth", "flip", "clamp"], outputs: ["dismiss"], exportAs: ["mkAnchoredPanel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1914
+ }
1915
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkMonthPicker, decorators: [{
1916
+ type: Component,
1917
+ args: [{ selector: 'mk-month-picker', changeDetection: ChangeDetectionStrategy.OnPush, imports: [MkAnchoredPanel], host: {
1918
+ class: 'mk-month-picker',
1919
+ '[class.mk-month-picker--sm]': "effectiveSize() === 'sm'",
1920
+ '[class.mk-month-picker--lg]': "effectiveSize() === 'lg'",
1921
+ '[class.mk-month-picker--open]': 'open()',
1922
+ '[class.mk-month-picker--invalid]': 'isInvalid()',
1923
+ '[class.mk-month-picker--disabled]': 'isDisabled()',
1924
+ '(focusout)': 'onFocusOut($event)',
1925
+ }, providers: [
1926
+ {
1927
+ provide: NG_VALUE_ACCESSOR,
1928
+ useExisting: forwardRef(() => MkMonthPicker),
1929
+ multi: true,
1930
+ },
1931
+ {
1932
+ provide: NG_VALIDATORS,
1933
+ useExisting: forwardRef(() => MkMonthPicker),
1934
+ multi: true,
1935
+ },
1936
+ ], template: "<div #field class=\"mk-month-picker__field\">\n <button\n #trigger\n type=\"button\"\n class=\"mk-month-picker__trigger\"\n [id]=\"triggerId\"\n aria-haspopup=\"dialog\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-labelledby]=\"labelledBy()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"isInvalid() || null\"\n [attr.aria-required]=\"isRequired() || null\"\n [disabled]=\"isDisabled()\"\n (click)=\"toggle()\"\n >\n @if (displayLabel()) {\n <span class=\"mk-month-picker__value\">{{ displayLabel() }}</span>\n } @else {\n <span class=\"mk-month-picker__value mk-month-picker__value--placeholder\">{{\n resolvedPlaceholder()\n }}</span>\n }\n <span class=\"mk-month-picker__icon\" aria-hidden=\"true\"></span>\n </button>\n\n @if (clearable() && value() && !isDisabled()) {\n <button\n type=\"button\"\n class=\"mk-month-picker__clear\"\n [attr.aria-label]=\"i18n.clear\"\n (click)=\"clear($event)\"\n >\n <span class=\"mk-month-picker__clear-icon\" aria-hidden=\"true\"></span>\n </button>\n }\n</div>\n\n@if (open()) {\n <div\n #panel\n class=\"mk-month-picker__panel\"\n role=\"dialog\"\n [id]=\"panelId\"\n [attr.aria-label]=\"mode() === 'year' ? i18n.chooseYear : i18n.chooseMonth\"\n mkAnchoredPanel\n [mkAnchoredPanelFor]=\"field\"\n (dismiss)=\"close()\"\n >\n <div class=\"mk-month-picker__header\">\n <button\n type=\"button\"\n class=\"mk-month-picker__nav\"\n [attr.aria-label]=\"mode() === 'year' ? i18n.previousYears : i18n.previousYear\"\n (click)=\"shiftView(-1)\"\n >\n \u2039\n </button>\n <span class=\"mk-month-picker__header-label\" aria-live=\"polite\">{{ headerLabel() }}</span>\n <button\n type=\"button\"\n class=\"mk-month-picker__nav\"\n [attr.aria-label]=\"mode() === 'year' ? i18n.nextYears : i18n.nextYear\"\n (click)=\"shiftView(1)\"\n >\n \u203A\n </button>\n </div>\n\n <div class=\"mk-month-picker__grid\" role=\"grid\" (keydown)=\"onGridKeydown($event)\">\n @for (cell of cells(); track cell.label; let i = $index) {\n <button\n #cell\n type=\"button\"\n class=\"mk-month-picker__cell\"\n [class.mk-month-picker__cell--selected]=\"cell.selected\"\n [attr.tabindex]=\"i === activeIndex() ? 0 : -1\"\n [attr.aria-selected]=\"cell.selected\"\n [disabled]=\"cell.disabled\"\n (click)=\"selectCell(cell)\"\n >\n {{ cell.label }}\n </button>\n }\n </div>\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative;width:100%;font-family:var(--mk-font-sans)}.mk-month-picker__field{display:flex;align-items:center;position:relative}.mk-month-picker__trigger{flex:1 1 auto;display:flex;align-items:center;gap:var(--mk-space-2);width:100%;height:var(--mk-control-height-md);padding:0 var(--mk-space-3);text-align:start;font:inherit;font-size:var(--mk-font-size-md);color:var(--mk-text);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-md);cursor:pointer}:host(.mk-month-picker--sm) .mk-month-picker__trigger{height:var(--mk-control-height-sm);padding:0 var(--mk-space-2);font-size:var(--mk-font-size-sm);border-radius:var(--mk-radius-sm)}:host(.mk-month-picker--lg) .mk-month-picker__trigger{height:var(--mk-control-height-lg);padding:0 var(--mk-space-4);font-size:var(--mk-font-size-lg)}.mk-month-picker__trigger:hover:not(:disabled){border-color:var(--mk-border-strong)}.mk-month-picker__trigger:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset);border-color:var(--mk-primary)}:host(.mk-month-picker--invalid) .mk-month-picker__trigger{border-color:var(--mk-danger)}:host(.mk-month-picker--disabled) .mk-month-picker__trigger{background-color:var(--mk-surface-2);color:var(--mk-text-disabled);cursor:not-allowed;opacity:.7}.mk-month-picker__value{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mk-month-picker__value--placeholder{color:var(--mk-text-subtle)}.mk-month-picker__icon{flex:none;width:1rem;height:1rem;border:2px solid var(--mk-text-muted);border-radius:2px;position:relative}.mk-month-picker__icon:before{content:\"\";position:absolute;top:-3px;left:2px;right:2px;border-top:2px solid var(--mk-text-muted)}.mk-month-picker__clear{position:absolute;inset-inline-end:var(--mk-space-8);display:grid;place-items:center;width:1.25rem;height:1.25rem;color:var(--mk-text-muted);background:transparent;border:none;border-radius:var(--mk-radius-circle);cursor:pointer}.mk-month-picker__clear:hover{background-color:var(--mk-hover-overlay);color:var(--mk-text)}.mk-month-picker__clear-icon:before,.mk-month-picker__clear-icon:after{content:\"\";position:absolute;width:.7rem;height:2px;background-color:currentColor}.mk-month-picker__clear-icon:before{transform:rotate(45deg)}.mk-month-picker__clear-icon:after{transform:rotate(-45deg)}.mk-month-picker__panel{width:17rem;padding:var(--mk-space-2);background:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-lg);box-shadow:var(--mk-shadow-lg)}.mk-month-picker__header{display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--mk-space-1)}.mk-month-picker__header-label{font-weight:var(--mk-font-weight-semibold);color:var(--mk-text)}.mk-month-picker__nav{display:grid;place-items:center;width:2rem;height:2rem;font-size:var(--mk-font-size-lg);color:var(--mk-text-muted);background:transparent;border:none;border-radius:var(--mk-radius-sm);cursor:pointer}.mk-month-picker__nav:hover{background-color:var(--mk-hover-overlay);color:var(--mk-text)}.mk-month-picker__nav:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}.mk-month-picker__grid{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--mk-space-1)}.mk-month-picker__cell{padding:var(--mk-space-2) 0;font-size:var(--mk-font-size-sm);color:var(--mk-text);background:transparent;border:none;border-radius:var(--mk-radius-sm);cursor:pointer}.mk-month-picker__cell:hover:not(:disabled){background-color:var(--mk-hover-overlay)}.mk-month-picker__cell:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-month-picker__cell--selected{background-color:var(--mk-primary);color:var(--mk-primary-contrast)}.mk-month-picker__cell--selected:hover:not(:disabled){background-color:var(--mk-primary)}.mk-month-picker__cell:disabled{color:var(--mk-text-disabled);cursor:not-allowed}\n"] }]
1937
+ }], ctorParameters: () => [], propDecorators: { triggerRef: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], panelRef: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }], cellRefs: [{ type: i0.ViewChildren, args: ['cell', { isSignal: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], displayFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayFormat", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
1938
+
1939
+ /**
1940
+ * WeekPicker — pick a whole calendar week from a popover calendar. Hovering a
1941
+ * day previews its entire week; clicking selects it. The value is an
1942
+ * {@link MkWeek} (`{ start, end }`, aligned to `firstDayOfWeek`). Implements
1943
+ * `ControlValueAccessor` and a two-way `value` model; the panel renders in the
1944
+ * top layer so it is never clipped.
1945
+ *
1946
+ * ```html
1947
+ * <mk-week-picker [(value)]="week" [firstDayOfWeek]="1" clearable />
1948
+ * ```
1949
+ */
1950
+ class MkWeekPicker {
1951
+ i18n = inject(MK_I18N);
1952
+ field = inject(MkFormField, { optional: true });
1953
+ host = inject(ElementRef);
1954
+ injector = inject(Injector);
1955
+ triggerRef = viewChild('trigger', /* @ts-ignore */
1956
+ ...(ngDevMode ? [{ debugName: "triggerRef" }] : /* istanbul ignore next */ []));
1957
+ panelRef = viewChild('panel', /* @ts-ignore */
1958
+ ...(ngDevMode ? [{ debugName: "panelRef" }] : /* istanbul ignore next */ []));
1959
+ /** Two-way selected week. */
1960
+ value = model(null, /* @ts-ignore */
1961
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
1962
+ /** Earliest selectable date (inclusive). */
1963
+ min = input(null, /* @ts-ignore */
1964
+ ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
1965
+ /** Latest selectable date (inclusive). */
1966
+ max = input(null, /* @ts-ignore */
1967
+ ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
1968
+ /** First column of the calendar week (0 = Sunday). Also anchors the week. */
1969
+ firstDayOfWeek = input(0, { ...(ngDevMode ? { debugName: "firstDayOfWeek" } : /* istanbul ignore next */ {}), transform: numberAttribute });
1970
+ /** Placeholder shown when empty. */
1971
+ placeholder = input(this.i18n.selectWeek, /* @ts-ignore */
1972
+ ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
1973
+ /** Pattern used to render each endpoint of the week range. */
1974
+ displayFormat = input('MMM d', /* @ts-ignore */
1975
+ ...(ngDevMode ? [{ debugName: "displayFormat" }] : /* istanbul ignore next */ []));
1976
+ /** Prefix the label with the ISO week number, e.g. `W28 · …`. */
1977
+ showWeekNumber = input(false, { ...(ngDevMode ? { debugName: "showWeekNumber" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1978
+ /** Disable the control. */
1979
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1980
+ /** Show a clear button when a week is selected. */
1981
+ clearable = input(false, { ...(ngDevMode ? { debugName: "clearable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1982
+ /** Force invalid styling + `aria-invalid`. */
1983
+ invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1984
+ /** Control size. Ignored when nested in an `mk-form-field`. */
1985
+ size = input('md', /* @ts-ignore */
1986
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
1987
+ open = signal(false, /* @ts-ignore */
1988
+ ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
1989
+ /** The week currently previewed under the pointer (drives highlighting). */
1990
+ hoverWeek = signal(null, /* @ts-ignore */
1991
+ ...(ngDevMode ? [{ debugName: "hoverWeek" }] : /* istanbul ignore next */ []));
1992
+ cvaDisabled = signal(false, /* @ts-ignore */
1993
+ ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
1994
+ onChange = () => { };
1995
+ onTouched = () => { };
1996
+ triggerId = this.field?.controlId ?? mkUniqueId('mk-week-picker');
1997
+ panelId = mkUniqueId('mk-week-picker-panel');
1998
+ effectiveSize = computed(() => this.field ? this.field.size() : this.size(), /* @ts-ignore */
1999
+ ...(ngDevMode ? [{ debugName: "effectiveSize" }] : /* istanbul ignore next */ []));
2000
+ isDisabled = computed(() => this.disabled() || this.cvaDisabled(), /* @ts-ignore */
2001
+ ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
2002
+ isInvalid = computed(() => this.invalid() || (this.field?.hasError() ?? false), /* @ts-ignore */
2003
+ ...(ngDevMode ? [{ debugName: "isInvalid" }] : /* istanbul ignore next */ []));
2004
+ isRequired = computed(() => this.field?.isRequired() ?? false, /* @ts-ignore */
2005
+ ...(ngDevMode ? [{ debugName: "isRequired" }] : /* istanbul ignore next */ []));
2006
+ labelledBy = computed(() => this.field?.labelId ?? null, /* @ts-ignore */
2007
+ ...(ngDevMode ? [{ debugName: "labelledBy" }] : /* istanbul ignore next */ []));
2008
+ describedBy = computed(() => this.field?.describedBy() ?? null, /* @ts-ignore */
2009
+ ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
2010
+ showClear = computed(() => this.clearable() && !!this.value() && !this.isDisabled(), /* @ts-ignore */
2011
+ ...(ngDevMode ? [{ debugName: "showClear" }] : /* istanbul ignore next */ []));
2012
+ /** The week to highlight: the hovered one while previewing, else the value. */
2013
+ highlightWeek = computed(() => this.hoverWeek() ?? this.value(), /* @ts-ignore */
2014
+ ...(ngDevMode ? [{ debugName: "highlightWeek" }] : /* istanbul ignore next */ []));
2015
+ /** Display label, e.g. `W28 · Jul 6 – Jul 12, 2026`. */
2016
+ displayLabel = computed(() => {
2017
+ const week = this.value();
2018
+ if (!week)
2019
+ return '';
2020
+ const fmt = this.displayFormat();
2021
+ const names = this.i18n.dateNames;
2022
+ const range = `${formatDate(week.start, fmt, names)} – ${formatDate(week.end, `${fmt}, yyyy`, names)}`;
2023
+ return this.showWeekNumber() ? `W${getISOWeek(week.start)} · ${range}` : range;
2024
+ }, /* @ts-ignore */
2025
+ ...(ngDevMode ? [{ debugName: "displayLabel" }] : /* istanbul ignore next */ []));
2026
+ toggle() {
2027
+ if (this.isDisabled())
2028
+ return;
2029
+ this.open() ? this.close() : this.openPanel();
2030
+ }
2031
+ openPanel() {
2032
+ if (this.isDisabled() || this.open())
2033
+ return;
2034
+ this.open.set(true);
2035
+ afterNextRender({
2036
+ write: () => {
2037
+ const el = this.panelRef()?.nativeElement.querySelector('.mk-calendar__day[tabindex="0"]');
2038
+ el?.focus();
2039
+ },
2040
+ }, { injector: this.injector });
2041
+ }
2042
+ close() {
2043
+ if (this.open()) {
2044
+ this.open.set(false);
2045
+ this.hoverWeek.set(null);
2046
+ }
2047
+ }
2048
+ /** A day inside the calendar was activated — select its whole week. */
2049
+ onPick(date) {
2050
+ const week = this.weekOf(date);
2051
+ this.value.set(week);
2052
+ this.onChange(week);
2053
+ this.close();
2054
+ this.triggerRef()?.nativeElement.focus();
2055
+ }
2056
+ /** Preview the hovered day's week. */
2057
+ onHover(date) {
2058
+ this.hoverWeek.set(date ? this.weekOf(date) : null);
2059
+ }
2060
+ clear() {
2061
+ this.value.set(null);
2062
+ this.onChange(null);
2063
+ this.triggerRef()?.nativeElement.focus();
2064
+ }
2065
+ weekOf(date) {
2066
+ const fdow = this.firstDayOfWeek();
2067
+ return { start: startOfWeek(date, fdow), end: endOfWeek(date, fdow) };
2068
+ }
2069
+ /** Reference date the calendar should open on (the selected week's start). */
2070
+ calendarValue = computed(() => this.value()?.start ?? null, /* @ts-ignore */
2071
+ ...(ngDevMode ? [{ debugName: "calendarValue" }] : /* istanbul ignore next */ []));
2072
+ onFocusOut(event) {
2073
+ const related = event.relatedTarget;
2074
+ if (related && this.host.nativeElement.contains(related))
2075
+ return;
2076
+ if (related && this.panelRef()?.nativeElement.contains(related))
2077
+ return;
2078
+ this.close();
2079
+ this.onTouched();
2080
+ }
2081
+ // --- ControlValueAccessor -------------------------------------------------
2082
+ writeValue(value) {
2083
+ // Re-anchor any incoming date to a full week for consistent highlighting.
2084
+ if (value?.start)
2085
+ this.value.set(this.weekOf(value.start));
2086
+ else
2087
+ this.value.set(null);
2088
+ }
2089
+ registerOnChange(fn) {
2090
+ this.onChange = fn;
2091
+ }
2092
+ registerOnTouched(fn) {
2093
+ this.onTouched = fn;
2094
+ }
2095
+ setDisabledState(isDisabled) {
2096
+ this.cvaDisabled.set(isDisabled);
2097
+ }
2098
+ /** Expose the raw highlight endpoints to the template. */
2099
+ highlightStart = computed(() => this.highlightWeek()?.start ?? null, /* @ts-ignore */
2100
+ ...(ngDevMode ? [{ debugName: "highlightStart" }] : /* istanbul ignore next */ []));
2101
+ highlightEnd = computed(() => this.highlightWeek()?.end ?? null, /* @ts-ignore */
2102
+ ...(ngDevMode ? [{ debugName: "highlightEnd" }] : /* istanbul ignore next */ []));
2103
+ // --- Validator ------------------------------------------------------------
2104
+ validatorChange = mkValidatorChange(() => {
2105
+ this.min();
2106
+ this.max();
2107
+ });
2108
+ /**
2109
+ * Reports `mkMinDate` / `mkMaxDate` when the selected week starts before
2110
+ * `[min]` or ends after `[max]`.
2111
+ */
2112
+ validate(control) {
2113
+ const v = control.value;
2114
+ const start = v?.start;
2115
+ const end = v?.end;
2116
+ if (!(start instanceof Date) || !(end instanceof Date))
2117
+ return null;
2118
+ const min = this.min();
2119
+ if (min && isBefore(startOfDay(start), startOfDay(min))) {
2120
+ return { mkMinDate: { min, actual: start } };
2121
+ }
2122
+ const max = this.max();
2123
+ if (max && isAfter(startOfDay(end), startOfDay(max))) {
2124
+ return { mkMaxDate: { max, actual: end } };
2125
+ }
2126
+ return null;
2127
+ }
2128
+ registerOnValidatorChange(fn) {
2129
+ this.validatorChange.register(fn);
2130
+ }
2131
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkWeekPicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
2132
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkWeekPicker, isStandalone: true, selector: "mk-week-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, firstDayOfWeek: { classPropertyName: "firstDayOfWeek", publicName: "firstDayOfWeek", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, displayFormat: { classPropertyName: "displayFormat", publicName: "displayFormat", isSignal: true, isRequired: false, transformFunction: null }, showWeekNumber: { classPropertyName: "showWeekNumber", publicName: "showWeekNumber", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { listeners: { "focusout": "onFocusOut($event)" }, properties: { "class.mk-week-picker--sm": "effectiveSize() === 'sm'", "class.mk-week-picker--lg": "effectiveSize() === 'lg'", "class.mk-week-picker--open": "open()", "class.mk-week-picker--invalid": "isInvalid()", "class.mk-week-picker--disabled": "isDisabled()" }, classAttribute: "mk-week-picker" }, providers: [
2133
+ {
2134
+ provide: NG_VALUE_ACCESSOR,
2135
+ useExisting: forwardRef(() => MkWeekPicker),
2136
+ multi: true,
2137
+ },
2138
+ {
2139
+ provide: NG_VALIDATORS,
2140
+ useExisting: forwardRef(() => MkWeekPicker),
2141
+ multi: true,
2142
+ },
2143
+ ], viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["trigger"], descendants: true, isSignal: true }, { propertyName: "panelRef", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "<div #field class=\"mk-week-picker__field\">\n <button\n #trigger\n type=\"button\"\n class=\"mk-week-picker__trigger\"\n [id]=\"triggerId\"\n aria-haspopup=\"dialog\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-labelledby]=\"labelledBy()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"isInvalid() || null\"\n [attr.aria-required]=\"isRequired() || null\"\n [disabled]=\"isDisabled()\"\n (click)=\"toggle()\"\n >\n @if (displayLabel()) {\n <span class=\"mk-week-picker__value\">{{ displayLabel() }}</span>\n } @else {\n <span class=\"mk-week-picker__value mk-week-picker__value--placeholder\">{{\n placeholder()\n }}</span>\n }\n <svg\n class=\"mk-week-picker__icon\"\n viewBox=\"0 0 24 24\"\n width=\"18\"\n height=\"18\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n aria-hidden=\"true\"\n >\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"17\" rx=\"2\" />\n <path d=\"M3 9h18M8 2v4M16 2v4\" />\n </svg>\n </button>\n\n @if (showClear()) {\n <button\n type=\"button\"\n class=\"mk-week-picker__clear\"\n [attr.aria-label]=\"i18n.clear\"\n (click)=\"clear()\"\n >\n <span class=\"mk-week-picker__clear-icon\" aria-hidden=\"true\"></span>\n </button>\n }\n</div>\n\n@if (open()) {\n <div\n #panel\n class=\"mk-week-picker__panel\"\n mkAnchoredPanel\n [mkAnchoredPanelFor]=\"field\"\n (dismiss)=\"close()\"\n [id]=\"panelId\"\n role=\"dialog\"\n [attr.aria-label]=\"i18n.chooseWeek\"\n >\n <mk-calendar\n [value]=\"calendarValue()\"\n [rangeMode]=\"true\"\n [rangeStart]=\"highlightStart()\"\n [rangeEnd]=\"highlightEnd()\"\n (dateSelected)=\"onPick($event)\"\n (dateHovered)=\"onHover($event)\"\n [min]=\"min()\"\n [max]=\"max()\"\n [firstDayOfWeek]=\"firstDayOfWeek()\"\n [size]=\"effectiveSize()\"\n />\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative;width:100%;font-family:var(--mk-font-sans)}.mk-week-picker__field{display:flex;align-items:center;width:100%;height:var(--mk-control-height-md);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-md);transition:border-color var(--mk-duration-fast) var(--mk-ease-standard),box-shadow var(--mk-duration-fast) var(--mk-ease-standard)}:host(.mk-week-picker--sm) .mk-week-picker__field{height:var(--mk-control-height-sm);border-radius:var(--mk-radius-sm)}:host(.mk-week-picker--lg) .mk-week-picker__field{height:var(--mk-control-height-lg)}.mk-week-picker__field:hover{border-color:var(--mk-border-strong)}.mk-week-picker__field:focus-within{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset);border-color:var(--mk-primary)}:host(.mk-week-picker--invalid) .mk-week-picker__field{border-color:var(--mk-danger)}:host(.mk-week-picker--invalid) .mk-week-picker__field:focus-within{outline-color:var(--mk-danger)}:host(.mk-week-picker--disabled) .mk-week-picker__field{background-color:var(--mk-surface-2);border-color:var(--mk-border-subtle);cursor:not-allowed;opacity:.7}.mk-week-picker__trigger{display:flex;align-items:center;gap:var(--mk-space-2);flex:1 1 auto;min-width:0;height:100%;padding:0 var(--mk-space-3);font-family:inherit;font-size:var(--mk-font-size-md);color:var(--mk-text);text-align:start;background:transparent;border:0;outline:none;cursor:pointer}:host(.mk-week-picker--sm) .mk-week-picker__trigger{padding:0 var(--mk-space-2);font-size:var(--mk-font-size-sm)}:host(.mk-week-picker--lg) .mk-week-picker__trigger{padding:0 var(--mk-space-4);font-size:var(--mk-font-size-lg)}.mk-week-picker__trigger:disabled{color:var(--mk-text-disabled);cursor:not-allowed}.mk-week-picker__value{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mk-week-picker__value--placeholder{color:var(--mk-text-subtle)}.mk-week-picker__icon{flex:none;color:var(--mk-text-muted)}.mk-week-picker__clear{display:inline-flex;align-items:center;justify-content:center;flex:none;height:100%;padding:0 var(--mk-space-2);color:var(--mk-text-muted);background:transparent;border:0;cursor:pointer;transition:color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-week-picker__clear:hover{color:var(--mk-text)}.mk-week-picker__clear:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(var(--mk-focus-ring-offset) * -1);border-radius:var(--mk-radius-sm)}.mk-week-picker__clear-icon{position:relative;width:.85rem;height:.85rem}.mk-week-picker__clear-icon:before,.mk-week-picker__clear-icon:after{content:\"\";position:absolute;top:50%;left:0;width:100%;height:2px;background-color:currentColor}.mk-week-picker__clear-icon:before{transform:rotate(45deg)}.mk-week-picker__clear-icon:after{transform:rotate(-45deg)}.mk-week-picker__panel{box-shadow:var(--mk-shadow-lg);border-radius:var(--mk-radius-lg)}@media(prefers-reduced-motion:reduce){.mk-week-picker__field,.mk-week-picker__clear{transition:none}}\n"], dependencies: [{ kind: "component", type: MkCalendar, selector: "mk-calendar", inputs: ["value", "min", "max", "firstDayOfWeek", "disabled", "invalid", "disabledDate", "size", "fullWidth", "rangeMode", "rangeStart", "rangeEnd"], outputs: ["valueChange", "dateSelected", "dateHovered"] }, { kind: "directive", type: MkAnchoredPanel, selector: "[mkAnchoredPanel]", inputs: ["mkAnchoredPanelFor", "anchorRect", "placement", "gap", "matchWidth", "flip", "clamp"], outputs: ["dismiss"], exportAs: ["mkAnchoredPanel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2144
+ }
2145
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkWeekPicker, decorators: [{
2146
+ type: Component,
2147
+ args: [{ selector: 'mk-week-picker', changeDetection: ChangeDetectionStrategy.OnPush, imports: [MkCalendar, MkAnchoredPanel], host: {
2148
+ class: 'mk-week-picker',
2149
+ '[class.mk-week-picker--sm]': "effectiveSize() === 'sm'",
2150
+ '[class.mk-week-picker--lg]': "effectiveSize() === 'lg'",
2151
+ '[class.mk-week-picker--open]': 'open()',
2152
+ '[class.mk-week-picker--invalid]': 'isInvalid()',
2153
+ '[class.mk-week-picker--disabled]': 'isDisabled()',
2154
+ '(focusout)': 'onFocusOut($event)',
2155
+ }, providers: [
2156
+ {
2157
+ provide: NG_VALUE_ACCESSOR,
2158
+ useExisting: forwardRef(() => MkWeekPicker),
2159
+ multi: true,
2160
+ },
2161
+ {
2162
+ provide: NG_VALIDATORS,
2163
+ useExisting: forwardRef(() => MkWeekPicker),
2164
+ multi: true,
2165
+ },
2166
+ ], template: "<div #field class=\"mk-week-picker__field\">\n <button\n #trigger\n type=\"button\"\n class=\"mk-week-picker__trigger\"\n [id]=\"triggerId\"\n aria-haspopup=\"dialog\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-labelledby]=\"labelledBy()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"isInvalid() || null\"\n [attr.aria-required]=\"isRequired() || null\"\n [disabled]=\"isDisabled()\"\n (click)=\"toggle()\"\n >\n @if (displayLabel()) {\n <span class=\"mk-week-picker__value\">{{ displayLabel() }}</span>\n } @else {\n <span class=\"mk-week-picker__value mk-week-picker__value--placeholder\">{{\n placeholder()\n }}</span>\n }\n <svg\n class=\"mk-week-picker__icon\"\n viewBox=\"0 0 24 24\"\n width=\"18\"\n height=\"18\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n aria-hidden=\"true\"\n >\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"17\" rx=\"2\" />\n <path d=\"M3 9h18M8 2v4M16 2v4\" />\n </svg>\n </button>\n\n @if (showClear()) {\n <button\n type=\"button\"\n class=\"mk-week-picker__clear\"\n [attr.aria-label]=\"i18n.clear\"\n (click)=\"clear()\"\n >\n <span class=\"mk-week-picker__clear-icon\" aria-hidden=\"true\"></span>\n </button>\n }\n</div>\n\n@if (open()) {\n <div\n #panel\n class=\"mk-week-picker__panel\"\n mkAnchoredPanel\n [mkAnchoredPanelFor]=\"field\"\n (dismiss)=\"close()\"\n [id]=\"panelId\"\n role=\"dialog\"\n [attr.aria-label]=\"i18n.chooseWeek\"\n >\n <mk-calendar\n [value]=\"calendarValue()\"\n [rangeMode]=\"true\"\n [rangeStart]=\"highlightStart()\"\n [rangeEnd]=\"highlightEnd()\"\n (dateSelected)=\"onPick($event)\"\n (dateHovered)=\"onHover($event)\"\n [min]=\"min()\"\n [max]=\"max()\"\n [firstDayOfWeek]=\"firstDayOfWeek()\"\n [size]=\"effectiveSize()\"\n />\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative;width:100%;font-family:var(--mk-font-sans)}.mk-week-picker__field{display:flex;align-items:center;width:100%;height:var(--mk-control-height-md);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-md);transition:border-color var(--mk-duration-fast) var(--mk-ease-standard),box-shadow var(--mk-duration-fast) var(--mk-ease-standard)}:host(.mk-week-picker--sm) .mk-week-picker__field{height:var(--mk-control-height-sm);border-radius:var(--mk-radius-sm)}:host(.mk-week-picker--lg) .mk-week-picker__field{height:var(--mk-control-height-lg)}.mk-week-picker__field:hover{border-color:var(--mk-border-strong)}.mk-week-picker__field:focus-within{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset);border-color:var(--mk-primary)}:host(.mk-week-picker--invalid) .mk-week-picker__field{border-color:var(--mk-danger)}:host(.mk-week-picker--invalid) .mk-week-picker__field:focus-within{outline-color:var(--mk-danger)}:host(.mk-week-picker--disabled) .mk-week-picker__field{background-color:var(--mk-surface-2);border-color:var(--mk-border-subtle);cursor:not-allowed;opacity:.7}.mk-week-picker__trigger{display:flex;align-items:center;gap:var(--mk-space-2);flex:1 1 auto;min-width:0;height:100%;padding:0 var(--mk-space-3);font-family:inherit;font-size:var(--mk-font-size-md);color:var(--mk-text);text-align:start;background:transparent;border:0;outline:none;cursor:pointer}:host(.mk-week-picker--sm) .mk-week-picker__trigger{padding:0 var(--mk-space-2);font-size:var(--mk-font-size-sm)}:host(.mk-week-picker--lg) .mk-week-picker__trigger{padding:0 var(--mk-space-4);font-size:var(--mk-font-size-lg)}.mk-week-picker__trigger:disabled{color:var(--mk-text-disabled);cursor:not-allowed}.mk-week-picker__value{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mk-week-picker__value--placeholder{color:var(--mk-text-subtle)}.mk-week-picker__icon{flex:none;color:var(--mk-text-muted)}.mk-week-picker__clear{display:inline-flex;align-items:center;justify-content:center;flex:none;height:100%;padding:0 var(--mk-space-2);color:var(--mk-text-muted);background:transparent;border:0;cursor:pointer;transition:color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-week-picker__clear:hover{color:var(--mk-text)}.mk-week-picker__clear:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(var(--mk-focus-ring-offset) * -1);border-radius:var(--mk-radius-sm)}.mk-week-picker__clear-icon{position:relative;width:.85rem;height:.85rem}.mk-week-picker__clear-icon:before,.mk-week-picker__clear-icon:after{content:\"\";position:absolute;top:50%;left:0;width:100%;height:2px;background-color:currentColor}.mk-week-picker__clear-icon:before{transform:rotate(45deg)}.mk-week-picker__clear-icon:after{transform:rotate(-45deg)}.mk-week-picker__panel{box-shadow:var(--mk-shadow-lg);border-radius:var(--mk-radius-lg)}@media(prefers-reduced-motion:reduce){.mk-week-picker__field,.mk-week-picker__clear{transition:none}}\n"] }]
2167
+ }], propDecorators: { triggerRef: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], panelRef: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], firstDayOfWeek: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstDayOfWeek", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], displayFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayFormat", required: false }] }], showWeekNumber: [{ type: i0.Input, args: [{ isSignal: true, alias: "showWeekNumber", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
2168
+
2169
+ function minutes(d) {
2170
+ return d.getHours() * 60 + d.getMinutes();
2171
+ }
2172
+ /**
2173
+ * Position a day's timed events on an hour grid, packing overlaps side by
2174
+ * side instead of stacking them: within a cluster of transitively-overlapping
2175
+ * events each takes the lowest free lane, and every member of the cluster is
2176
+ * widened to the same lane count so columns line up (the layout every
2177
+ * scheduler UI uses).
2178
+ *
2179
+ * Pure — no Angular, no DOM — so the packing has direct unit tests.
2180
+ */
2181
+ function layoutTimedEvents(events, day, startHour, endHour) {
2182
+ const rangeMin = (endHour - startHour) * 60;
2183
+ if (rangeMin <= 0)
2184
+ return [];
2185
+ const timed = events
2186
+ .filter((e) => e.start && isSameDay(e.start, day))
2187
+ .sort((a, b) => a.start.getTime() - b.start.getTime() ||
2188
+ (b.end?.getTime() ?? 0) - (a.end?.getTime() ?? 0));
2189
+ const placed = [];
2190
+ let cluster = [];
2191
+ let clusterEnd = -1;
2192
+ const closeCluster = () => {
2193
+ const lanes = Math.max(1, ...cluster.map((p) => p.lane + 1));
2194
+ for (const p of cluster)
2195
+ p.lanes = lanes;
2196
+ cluster = [];
2197
+ clusterEnd = -1;
2198
+ };
2199
+ for (const event of timed) {
2200
+ const startMin = minutes(event.start);
2201
+ // A missing end reads as 30 minutes — long enough to click.
2202
+ const endMin = event.end && isSameDay(event.end, day)
2203
+ ? Math.max(minutes(event.end), startMin + 15)
2204
+ : startMin + 30;
2205
+ if (cluster.length && startMin >= clusterEnd)
2206
+ closeCluster();
2207
+ // Lowest lane whose latest occupant has finished.
2208
+ const laneEnds = [];
2209
+ for (const p of cluster) {
2210
+ laneEnds[p.lane] = Math.max(laneEnds[p.lane] ?? 0, p.endMin);
2211
+ }
2212
+ let lane = 0;
2213
+ while ((laneEnds[lane] ?? 0) > startMin)
2214
+ lane++;
2215
+ const clampedStart = Math.max(startMin, startHour * 60);
2216
+ const clampedEnd = Math.min(endMin, endHour * 60);
2217
+ if (clampedEnd <= startHour * 60 || clampedStart >= endHour * 60)
2218
+ continue;
2219
+ const item = {
2220
+ event,
2221
+ startMin,
2222
+ endMin,
2223
+ lane,
2224
+ lanes: 1,
2225
+ top: ((clampedStart - startHour * 60) / rangeMin) * 100,
2226
+ height: Math.max(((clampedEnd - clampedStart) / rangeMin) * 100, 2.5),
2227
+ };
2228
+ cluster.push(item);
2229
+ placed.push(item);
2230
+ clusterEnd = Math.max(clusterEnd, endMin);
2231
+ }
2232
+ if (cluster.length)
2233
+ closeCluster();
2234
+ return placed.map(({ startMin: _s, endMin: _e, ...p }) => p);
2235
+ }
2236
+ /** Events on `day` with no start time — rendered in the all-day strip. */
2237
+ function allDayEvents(events, day) {
2238
+ return events.filter((e) => !e.start && isSameDay(e.date, day));
2239
+ }
2240
+
2241
+ /** Pixels the pointer must travel before a press turns into an edit drag. */
2242
+ const DRAG_THRESHOLD = 5;
2243
+ /**
2244
+ * Pixels a *touch* pointer may wander during the long-press delay before the
2245
+ * press is treated as a scroll and the pending drag is abandoned.
2246
+ */
2247
+ const TOUCH_SLOP = 10;
2248
+ /** Long-press delay (ms) before a touch pointer arms an edit drag. */
2249
+ const TOUCH_DELAY_MS = 300;
2250
+ /** Safety window (ms) for swallowing the synthetic click after a real drag. */
2251
+ const CLICK_SWALLOW_MS = 400;
2252
+ /** Minutes since local midnight. */
2253
+ function minutesOfDay(d) {
2254
+ return d.getHours() * 60 + d.getMinutes();
2255
+ }
2256
+ /** `minutes` since midnight as a zero-padded `HH:mm` label. */
2257
+ function minutesLabel(minutes) {
2258
+ const h = Math.floor(minutes / 60) % 24;
2259
+ const m = Math.round(minutes % 60);
2260
+ return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
2261
+ }
2262
+ function clampNumber(value, lo, hi) {
2263
+ return Math.min(Math.max(value, lo), Math.max(lo, hi));
2264
+ }
2265
+ /**
2266
+ * `[startMin, endMin]` of a timed event, mirroring the layout's defaults: a
2267
+ * missing end reads as 30 minutes, an end on the same day never less than 15.
2268
+ */
2269
+ function eventMinutes(event) {
2270
+ const start = event.start ? minutesOfDay(event.start) : 0;
2271
+ const end = event.end && event.start && isSameDay(event.end, event.start)
2272
+ ? Math.max(minutesOfDay(event.end), start + 15)
2273
+ : start + 30;
2274
+ return [start, end];
2275
+ }
2276
+ /**
2277
+ * Event calendar — a month-grid scheduler view. Renders a 6×7 grid of day
2278
+ * cells built from {@link buildMonthMatrix}; each cell lists the events whose
2279
+ * `date` falls on that day as coloured pills, capped at `maxPerDay` with a
2280
+ * "+N more" line for the remainder.
2281
+ *
2282
+ * Shares the month-grid rendering, header navigation, and weekday header of
2283
+ * `mk-calendar` but is display-only (no date selection). Exposes the viewed
2284
+ * month as a two-way `viewDate` model plus `dayClick` / `eventClick` outputs.
2285
+ *
2286
+ * With `editable`, the week/day time grids additionally support drag-to-move
2287
+ * and drag-to-resize (pointer *and* keyboard — WCAG 2.5.7): times snap to
2288
+ * `snapMinutes` and the would-be range is emitted via `eventMove` /
2289
+ * `eventResize` — the calendar never mutates `events` itself.
2290
+ *
2291
+ * ```html
2292
+ * <mk-event-calendar [events]="events" [(viewDate)]="month"
2293
+ * (eventClick)="open($event)" />
2294
+ * ```
2295
+ */
2296
+ class MkEventCalendar {
2297
+ i18n = inject(MK_I18N);
2298
+ doc = inject(DOCUMENT);
2299
+ hostEl = inject(ElementRef).nativeElement;
2300
+ announcer = inject(MkLiveAnnouncer);
2301
+ /** Events to plot onto the grid. */
2302
+ events = input([], /* @ts-ignore */
2303
+ ...(ngDevMode ? [{ debugName: "events" }] : /* istanbul ignore next */ []));
2304
+ /** Two-way month being viewed (any day within it). Defaults to today. */
2305
+ viewDate = model(new Date(), /* @ts-ignore */
2306
+ ...(ngDevMode ? [{ debugName: "viewDate" }] : /* istanbul ignore next */ []));
2307
+ /** First column of the week: 0 = Sunday … 6 = Saturday. */
2308
+ firstDayOfWeek = input(0, { ...(ngDevMode ? { debugName: "firstDayOfWeek" } : /* istanbul ignore next */ {}), transform: numberAttribute });
2309
+ /** Max event pills per day before collapsing the rest into "+N more". */
2310
+ maxPerDay = input(3, { ...(ngDevMode ? { debugName: "maxPerDay" } : /* istanbul ignore next */ {}), transform: numberAttribute });
2311
+ /** Presentation: the classic month grid, or a timed `week`/`day` grid. */
2312
+ view = model('month', /* @ts-ignore */
2313
+ ...(ngDevMode ? [{ debugName: "view" }] : /* istanbul ignore next */ []));
2314
+ /** First hour shown on the week/day time grid. */
2315
+ dayStartHour = input(8, { ...(ngDevMode ? { debugName: "dayStartHour" } : /* istanbul ignore next */ {}), transform: numberAttribute });
2316
+ /** Hour the time grid ends at (exclusive). */
2317
+ dayEndHour = input(22, { ...(ngDevMode ? { debugName: "dayEndHour" } : /* istanbul ignore next */ {}), transform: numberAttribute });
2318
+ /**
2319
+ * Week/day views: allow rescheduling events by dragging a pill (move) or its
2320
+ * bottom edge (resize end time), by pointer or keyboard. Off by default —
2321
+ * without it the grid renders exactly as before (no handles, no ARIA
2322
+ * additions, no drag behavior).
2323
+ */
2324
+ editable = input(false, { ...(ngDevMode ? { debugName: "editable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
2325
+ /** Snap grid (minutes) for drag/keyboard edits; also the minimum duration. */
2326
+ snapMinutes = input(15, { ...(ngDevMode ? { debugName: "snapMinutes" } : /* istanbul ignore next */ {}), transform: numberAttribute });
2327
+ /** Emitted when a day cell is activated (click / Enter). */
2328
+ dayClick = output();
2329
+ /**
2330
+ * Emitted when an event pill is clicked (does not also fire `dayClick`).
2331
+ * Mouse-only convenience: the pills are presentational (`aria-hidden`), so
2332
+ * keyboard / screen-reader users reach events via the day button — its label
2333
+ * announces the day's events and activating it fires `dayClick`.
2334
+ */
2335
+ eventClick = output();
2336
+ /** Emitted when the viewed range changes via the prev/next buttons. */
2337
+ monthChange = output();
2338
+ /** Week/day view: an empty hour slot was clicked — the slot's start instant. */
2339
+ slotClick = output();
2340
+ /**
2341
+ * Editable week/day grid: an event pill was dragged (or keyboard-moved) to a
2342
+ * new start time and/or day. Carries the would-be `start`/`end` — the
2343
+ * calendar does **not** mutate `events`; the consumer owns the data and
2344
+ * applies the change, typically:
2345
+ *
2346
+ * ```ts
2347
+ * onMove = ({ event, start, end }: MkCalendarEventEdit) =>
2348
+ * (this.events = this.events.map((e) =>
2349
+ * e === event ? { ...e, date: start, start, end } : e,
2350
+ * ));
2351
+ * ```
2352
+ */
2353
+ eventMove = output();
2354
+ /**
2355
+ * Editable week/day grid: the pill's bottom edge was dragged (or
2356
+ * Shift+Arrow-resized) — only `end` differs, never below `snapMinutes` of
2357
+ * duration. Same consumer-owns-the-data contract as `eventMove`.
2358
+ */
2359
+ eventResize = output();
2360
+ /** id of the visible month/year label — wired as the grid's label. */
2361
+ labelId = mkUniqueId('mk-event-calendar-label');
2362
+ /** First day of the visible month. */
2363
+ viewMonth = computed(() => startOfMonth(this.viewDate()), /* @ts-ignore */
2364
+ ...(ngDevMode ? [{ debugName: "viewMonth" }] : /* istanbul ignore next */ []));
2365
+ /** Header label — month, day, or week range, matching the active view. */
2366
+ monthLabel = computed(() => {
2367
+ const d = this.viewDate();
2368
+ const names = this.i18n.dateNames;
2369
+ switch (this.view()) {
2370
+ case 'day':
2371
+ return formatDate(d, 'MMMM d, yyyy', names);
2372
+ case 'week': {
2373
+ const from = startOfWeek(d, this.firstDayOfWeek());
2374
+ const to = addDays(from, 6);
2375
+ return `${formatDate(from, 'MMM d', names)} – ${formatDate(to, 'MMM d, yyyy', names)}`;
2376
+ }
2377
+ default:
2378
+ return formatDate(d, 'MMMM yyyy', names);
2379
+ }
2380
+ }, /* @ts-ignore */
2381
+ ...(ngDevMode ? [{ debugName: "monthLabel" }] : /* istanbul ignore next */ []));
2382
+ /** Days of the active time grid: one for `day`, seven for `week`. */
2383
+ gridDays = computed(() => {
2384
+ const d = startOfDay(this.viewDate());
2385
+ if (this.view() === 'day')
2386
+ return [d];
2387
+ const from = startOfWeek(d, this.firstDayOfWeek());
2388
+ return Array.from({ length: 7 }, (_, i) => addDays(from, i));
2389
+ }, /* @ts-ignore */
2390
+ ...(ngDevMode ? [{ debugName: "gridDays" }] : /* istanbul ignore next */ []));
2391
+ /** Hour rows of the time grid. */
2392
+ hours = computed(() => {
2393
+ const from = this.dayStartHour();
2394
+ const to = this.dayEndHour();
2395
+ return Array.from({ length: Math.max(to - from, 0) }, (_, i) => from + i);
2396
+ }, /* @ts-ignore */
2397
+ ...(ngDevMode ? [{ debugName: "hours" }] : /* istanbul ignore next */ []));
2398
+ /**
2399
+ * Timed events bucketed by their start's calendar day — a single pass over
2400
+ * the events, shared by every column of the week/day grid instead of
2401
+ * re-filtering (and re-sorting) the whole array once per day.
2402
+ */
2403
+ timedByDay = computed(() => {
2404
+ const map = new Map();
2405
+ for (const event of this.events()) {
2406
+ if (!event.start)
2407
+ continue;
2408
+ const key = startOfDay(event.start).getTime();
2409
+ const bucket = map.get(key);
2410
+ if (bucket)
2411
+ bucket.push(event);
2412
+ else
2413
+ map.set(key, [event]);
2414
+ }
2415
+ return map;
2416
+ }, /* @ts-ignore */
2417
+ ...(ngDevMode ? [{ debugName: "timedByDay" }] : /* istanbul ignore next */ []));
2418
+ /** Untimed events (all-day strip) bucketed by `date`'s calendar day. */
2419
+ allDayByDay = computed(() => {
2420
+ const map = new Map();
2421
+ for (const event of this.events()) {
2422
+ if (event.start)
2423
+ continue;
2424
+ const key = startOfDay(event.date).getTime();
2425
+ const bucket = map.get(key);
2426
+ if (bucket)
2427
+ bucket.push(event);
2428
+ else
2429
+ map.set(key, [event]);
2430
+ }
2431
+ return map;
2432
+ }, /* @ts-ignore */
2433
+ ...(ngDevMode ? [{ debugName: "allDayByDay" }] : /* istanbul ignore next */ []));
2434
+ /**
2435
+ * Per-day positioned events + all-day strip for the time grid. The column
2436
+ * head (`Mon 21`) is precomputed here so the template never calls
2437
+ * `formatDate` during change detection.
2438
+ */
2439
+ timedColumns = computed(() => {
2440
+ const timedByDay = this.timedByDay();
2441
+ const allDayByDay = this.allDayByDay();
2442
+ const startHour = this.dayStartHour();
2443
+ const endHour = this.dayEndHour();
2444
+ const names = this.i18n.dateNames;
2445
+ const today = new Date();
2446
+ // gridDays() are `startOfDay` instants, so `getTime()` matches the keys.
2447
+ return this.gridDays().map((day) => ({
2448
+ day,
2449
+ today: isSameDay(day, today),
2450
+ head: formatDate(day, 'ddd d', names),
2451
+ placements: layoutTimedEvents(timedByDay.get(day.getTime()) ?? [], day, startHour, endHour),
2452
+ allDay: allDayByDay.get(day.getTime()) ?? [],
2453
+ }));
2454
+ }, /* @ts-ignore */
2455
+ ...(ngDevMode ? [{ debugName: "timedColumns" }] : /* istanbul ignore next */ []));
2456
+ hourLabel(hour) {
2457
+ return `${String(hour).padStart(2, '0')}:00`;
2458
+ }
2459
+ placementTitle(p) {
2460
+ const from = p.event.start ? minutesLabel(minutesOfDay(p.event.start)) : '';
2461
+ return from ? `${from} ${p.event.title}` : p.event.title;
2462
+ }
2463
+ onSlotClick(day, hour) {
2464
+ const at = new Date(day);
2465
+ at.setHours(hour, 0, 0, 0);
2466
+ this.slotClick.emit(at);
2467
+ }
2468
+ // ===================================================================
2469
+ // Editable time grid — drag-to-move / drag-to-resize (pointer)
2470
+ // ===================================================================
2471
+ /** Template-facing state of the in-flight edit (`null` when idle). */
2472
+ editPreview = signal(null, /* @ts-ignore */
2473
+ ...(ngDevMode ? [{ debugName: "editPreview" }] : /* istanbul ignore next */ []));
2474
+ /** The active pointer gesture, if any (only one at a time). */
2475
+ pointerSession = null;
2476
+ /** The active keyboard "move mode" session, if any. */
2477
+ kbSession = null;
2478
+ /** Pending rAF id for the coalesced pointer-move pass. */
2479
+ editRaf = null;
2480
+ pendingX = 0;
2481
+ pendingY = 0;
2482
+ /** Set after a real drag so the trailing synthetic click never fires `eventClick`. */
2483
+ swallowNextClick = false;
2484
+ pointerMoveHandler = (e) => this.onEditPointerMove(e);
2485
+ pointerUpHandler = (e) => this.onEditPointerUp(e);
2486
+ pointerCancelHandler = () => this.abortPointerEdit();
2487
+ /** Escape aborts an in-flight pointer drag (document-level while dragging). */
2488
+ escHandler = (e) => {
2489
+ if (e.key === 'Escape' && this.pointerSession?.started) {
2490
+ e.stopPropagation();
2491
+ this.abortPointerEdit();
2492
+ }
2493
+ };
2494
+ /**
2495
+ * `touch-action: pan-y` keeps native scrolling alive while the long-press is
2496
+ * pending, so once armed the drag must eat `touchmove` itself
2497
+ * (`passive: false` — same pattern as `MkDrag`).
2498
+ */
2499
+ touchMoveEater = (e) => {
2500
+ if (this.pointerSession?.armed && e.cancelable)
2501
+ e.preventDefault();
2502
+ };
2503
+ /** Android fires `contextmenu` on long-press — keep it off the gesture. */
2504
+ contextMenuHandler = (e) => e.preventDefault();
2505
+ onBlockPointerDown(event, p, colIndex) {
2506
+ const e = event;
2507
+ if (!this.editable() || this.pointerSession || this.kbSession)
2508
+ return;
2509
+ if (e.button !== undefined && e.button !== 0)
2510
+ return;
2511
+ const el = e.currentTarget;
2512
+ const kind = e.target?.closest?.('.mk-event-calendar__resize-handle')
2513
+ ? 'resize'
2514
+ : 'move';
2515
+ const [startMin, endMin] = eventMinutes(p.event);
2516
+ this.pointerSession = {
2517
+ kind,
2518
+ event: p.event,
2519
+ el,
2520
+ pointerId: e.pointerId,
2521
+ started: false,
2522
+ armed: false,
2523
+ startX: e.clientX,
2524
+ startY: e.clientY,
2525
+ touchTimer: null,
2526
+ savedTouchAction: null,
2527
+ colRects: [],
2528
+ pxPerMin: 1,
2529
+ origDay: colIndex,
2530
+ origStartMin: startMin,
2531
+ origEndMin: endMin,
2532
+ day: colIndex,
2533
+ startMin,
2534
+ endMin,
2535
+ };
2536
+ try {
2537
+ el.setPointerCapture(e.pointerId);
2538
+ }
2539
+ catch {
2540
+ // Pointer already lifted (fast tap) — nothing left to capture.
2541
+ }
2542
+ el.addEventListener('pointermove', this.pointerMoveHandler);
2543
+ el.addEventListener('pointerup', this.pointerUpHandler);
2544
+ el.addEventListener('pointercancel', this.pointerCancelHandler);
2545
+ if (e.pointerType === 'touch') {
2546
+ // Touch long-press arming, as in MkDrag: until the timer fires this
2547
+ // press may just be the start of a scroll, so nothing is prevented yet.
2548
+ el.addEventListener('touchmove', this.touchMoveEater, { passive: false });
2549
+ el.addEventListener('contextmenu', this.contextMenuHandler);
2550
+ this.pointerSession.touchTimer =
2551
+ this.doc.defaultView?.setTimeout(() => this.armTouch(), TOUCH_DELAY_MS) ??
2552
+ null;
2553
+ }
2554
+ else {
2555
+ // Mouse / pen: armed immediately, the 5px threshold does the rest.
2556
+ this.pointerSession.armed = true;
2557
+ }
2558
+ }
2559
+ /** The long-press delay elapsed with the finger still down — arm the drag. */
2560
+ armTouch() {
2561
+ const s = this.pointerSession;
2562
+ if (!s)
2563
+ return;
2564
+ s.touchTimer = null;
2565
+ s.armed = true;
2566
+ s.savedTouchAction = s.el.style.touchAction;
2567
+ s.el.style.touchAction = 'none';
2568
+ }
2569
+ onEditPointerMove(e) {
2570
+ const s = this.pointerSession;
2571
+ if (!s || e.pointerId !== s.pointerId)
2572
+ return;
2573
+ if (!s.armed) {
2574
+ // Long-press still pending: real movement means the user is scrolling.
2575
+ if (Math.hypot(e.clientX - s.startX, e.clientY - s.startY) > TOUCH_SLOP) {
2576
+ this.teardownPointer(false);
2577
+ }
2578
+ return;
2579
+ }
2580
+ if (!s.started) {
2581
+ if (Math.hypot(e.clientX - s.startX, e.clientY - s.startY) < DRAG_THRESHOLD) {
2582
+ return;
2583
+ }
2584
+ this.beginEdit(s);
2585
+ }
2586
+ e.preventDefault();
2587
+ // Only record the coordinates — the math + signal write is rAF-coalesced
2588
+ // (one pass per frame, same pattern as the table's column resize).
2589
+ this.pendingX = e.clientX;
2590
+ this.pendingY = e.clientY;
2591
+ if (this.editRaf !== null)
2592
+ return;
2593
+ const raf = this.doc.defaultView?.requestAnimationFrame(() => {
2594
+ this.editRaf = null;
2595
+ this.applyPendingEdit();
2596
+ });
2597
+ if (raf === undefined)
2598
+ this.applyPendingEdit(); // no window — degrade to sync
2599
+ else
2600
+ this.editRaf = raf;
2601
+ }
2602
+ /**
2603
+ * The drag crossed the threshold — snapshot geometry once (column rects and
2604
+ * the px↔minute scale); every subsequent move works off this cache, so a
2605
+ * drag never forces layout.
2606
+ */
2607
+ beginEdit(s) {
2608
+ s.started = true;
2609
+ const cols = this.hostEl.querySelectorAll('.mk-event-calendar__col');
2610
+ s.colRects = Array.from(cols, (c) => c.getBoundingClientRect());
2611
+ const rangeMin = Math.max((this.dayEndHour() - this.dayStartHour()) * 60, 1);
2612
+ s.pxPerMin = (s.colRects[s.origDay]?.height || rangeMin) / rangeMin;
2613
+ this.doc.addEventListener('keydown', this.escHandler, true);
2614
+ this.updatePreview(s);
2615
+ }
2616
+ /** Per-frame move pass: snap, clamp, and publish the preview signal. */
2617
+ applyPendingEdit() {
2618
+ const s = this.pointerSession;
2619
+ if (!s || !s.started)
2620
+ return;
2621
+ const snap = Math.max(1, this.snapMinutes());
2622
+ const gridStart = this.dayStartHour() * 60;
2623
+ const gridEnd = this.dayEndHour() * 60;
2624
+ const deltaMin = (this.pendingY - s.startY) / s.pxPerMin;
2625
+ const snapped = Math.round(deltaMin / snap) * snap;
2626
+ if (s.kind === 'move') {
2627
+ const duration = s.origEndMin - s.origStartMin;
2628
+ s.startMin = clampNumber(s.origStartMin + snapped, gridStart, Math.max(gridStart, gridEnd - duration));
2629
+ s.endMin = s.startMin + duration;
2630
+ s.day = this.columnAt(s, this.pendingX);
2631
+ }
2632
+ else {
2633
+ s.endMin = clampNumber(s.origEndMin + snapped, s.startMin + snap, gridEnd);
2634
+ }
2635
+ this.updatePreview(s);
2636
+ }
2637
+ /** Column index under `x`, from the rects snapshotted at lift. */
2638
+ columnAt(s, x) {
2639
+ const rects = s.colRects;
2640
+ if (!rects.length || !rects[s.origDay]?.width)
2641
+ return s.day;
2642
+ for (let i = 0; i < rects.length; i++) {
2643
+ if (x >= rects[i].left && x < rects[i].right)
2644
+ return i;
2645
+ }
2646
+ return x < rects[0].left ? 0 : rects.length - 1;
2647
+ }
2648
+ onEditPointerUp(e) {
2649
+ const s = this.pointerSession;
2650
+ if (!s || e.pointerId !== s.pointerId)
2651
+ return;
2652
+ // Flush (don't drop) the last coalesced move so the drop lands exactly
2653
+ // where the pointer ended, not on the last painted frame.
2654
+ if (this.editRaf !== null) {
2655
+ this.doc.defaultView?.cancelAnimationFrame(this.editRaf);
2656
+ this.editRaf = null;
2657
+ this.applyPendingEdit();
2658
+ }
2659
+ const started = s.started;
2660
+ this.teardownPointer(started);
2661
+ if (!started)
2662
+ return; // was a click, never a drag — eventClick may follow
2663
+ this.editPreview.set(null);
2664
+ this.commitEdit(s, 'pointer');
2665
+ }
2666
+ /** Escape / pointercancel: the pill snaps back, nothing is emitted. */
2667
+ abortPointerEdit() {
2668
+ const s = this.pointerSession;
2669
+ if (!s)
2670
+ return;
2671
+ const started = s.started;
2672
+ this.teardownPointer(started);
2673
+ this.editPreview.set(null);
2674
+ if (started)
2675
+ this.announceCancelled('polite');
2676
+ }
2677
+ /** Undo everything `onBlockPointerDown`/`beginEdit` set up. */
2678
+ teardownPointer(swallowClick) {
2679
+ const s = this.pointerSession;
2680
+ if (!s)
2681
+ return;
2682
+ this.pointerSession = null;
2683
+ if (this.editRaf !== null) {
2684
+ this.doc.defaultView?.cancelAnimationFrame(this.editRaf);
2685
+ this.editRaf = null;
2686
+ }
2687
+ const el = s.el;
2688
+ if (s.pointerId !== null) {
2689
+ try {
2690
+ el.releasePointerCapture(s.pointerId);
2691
+ }
2692
+ catch {
2693
+ // Capture may already be gone.
2694
+ }
2695
+ }
2696
+ el.removeEventListener('pointermove', this.pointerMoveHandler);
2697
+ el.removeEventListener('pointerup', this.pointerUpHandler);
2698
+ el.removeEventListener('pointercancel', this.pointerCancelHandler);
2699
+ el.removeEventListener('touchmove', this.touchMoveEater);
2700
+ el.removeEventListener('contextmenu', this.contextMenuHandler);
2701
+ if (s.touchTimer !== null)
2702
+ this.doc.defaultView?.clearTimeout(s.touchTimer);
2703
+ if (s.savedTouchAction !== null)
2704
+ el.style.touchAction = s.savedTouchAction;
2705
+ this.doc.removeEventListener('keydown', this.escHandler, true);
2706
+ if (swallowClick) {
2707
+ // The browser fires a synthetic click right after pointerup, which would
2708
+ // re-fire `eventClick` from a drag (the carousel's click-swallower
2709
+ // pattern, kept as a flag because the Angular listener on the same
2710
+ // element registered first). The timeout covers gestures with no click.
2711
+ this.swallowNextClick = true;
2712
+ this.doc.defaultView?.setTimeout(() => (this.swallowNextClick = false), CLICK_SWALLOW_MS);
2713
+ }
2714
+ }
2715
+ // ===================================================================
2716
+ // Editable time grid — keyboard move mode (WCAG 2.5.7)
2717
+ // ===================================================================
2718
+ onBlockKeydown(event, p, colIndex) {
2719
+ const e = event;
2720
+ if (!this.editable())
2721
+ return;
2722
+ const s = this.kbSession;
2723
+ if (!s || s.event !== p.event) {
2724
+ // Enter/Space arms "move mode" (and, via preventDefault, keeps the
2725
+ // button's synthetic click — and thus `eventClick` — from firing).
2726
+ if ((e.key === 'Enter' || e.key === ' ') && !this.pointerSession) {
2727
+ e.preventDefault();
2728
+ this.armKeyboard(e.currentTarget, p, colIndex);
2729
+ }
2730
+ return;
2731
+ }
2732
+ const snap = Math.max(1, this.snapMinutes());
2733
+ const gridStart = this.dayStartHour() * 60;
2734
+ const gridEnd = this.dayEndHour() * 60;
2735
+ switch (e.key) {
2736
+ case 'Enter':
2737
+ case ' ':
2738
+ e.preventDefault();
2739
+ this.commitKeyboard();
2740
+ break;
2741
+ case 'Escape':
2742
+ e.preventDefault();
2743
+ this.cancelKeyboard();
2744
+ break;
2745
+ case 'ArrowUp':
2746
+ case 'ArrowDown': {
2747
+ e.preventDefault();
2748
+ const dir = e.key === 'ArrowUp' ? -1 : 1;
2749
+ if (e.shiftKey) {
2750
+ // Shift+Up/Down resizes the end, never below the snap duration.
2751
+ s.endMin = clampNumber(s.endMin + dir * snap, s.startMin + snap, gridEnd);
2752
+ }
2753
+ else {
2754
+ const duration = s.endMin - s.startMin;
2755
+ s.startMin = clampNumber(s.startMin + dir * snap, gridStart, Math.max(gridStart, gridEnd - duration));
2756
+ s.endMin = s.startMin + duration;
2757
+ }
2758
+ this.updatePreview(s);
2759
+ this.announceStep(s);
2760
+ break;
2761
+ }
2762
+ case 'ArrowLeft':
2763
+ case 'ArrowRight': {
2764
+ e.preventDefault();
2765
+ const dir = e.key === 'ArrowLeft' ? -1 : 1;
2766
+ s.day = clampNumber(s.day + dir, 0, this.gridDays().length - 1);
2767
+ this.updatePreview(s);
2768
+ this.announceStep(s);
2769
+ break;
2770
+ }
2771
+ default:
2772
+ break;
2773
+ }
2774
+ }
2775
+ /** Losing focus mid-move cancels the keyboard edit (no stuck state). */
2776
+ onBlockBlur() {
2777
+ if (this.kbSession)
2778
+ this.cancelKeyboard();
2779
+ }
2780
+ armKeyboard(el, p, colIndex) {
2781
+ const [startMin, endMin] = eventMinutes(p.event);
2782
+ const cols = this.hostEl.querySelectorAll('.mk-event-calendar__col');
2783
+ const colRects = Array.from(cols, (c) => c.getBoundingClientRect());
2784
+ const rangeMin = Math.max((this.dayEndHour() - this.dayStartHour()) * 60, 1);
2785
+ const s = {
2786
+ kind: 'move',
2787
+ event: p.event,
2788
+ el,
2789
+ pointerId: null,
2790
+ started: true,
2791
+ armed: true,
2792
+ startX: 0,
2793
+ startY: 0,
2794
+ touchTimer: null,
2795
+ savedTouchAction: null,
2796
+ colRects,
2797
+ pxPerMin: (colRects[colIndex]?.height || rangeMin) / rangeMin,
2798
+ origDay: colIndex,
2799
+ origStartMin: startMin,
2800
+ origEndMin: endMin,
2801
+ day: colIndex,
2802
+ startMin,
2803
+ endMin,
2804
+ };
2805
+ this.kbSession = s;
2806
+ this.updatePreview(s);
2807
+ this.announcer.announce(this.i18n.eventCalendarGrabbed(p.event.title, minutesLabel(startMin), minutesLabel(endMin)), 'assertive');
2808
+ }
2809
+ commitKeyboard() {
2810
+ const s = this.kbSession;
2811
+ if (!s)
2812
+ return;
2813
+ this.kbSession = null;
2814
+ this.editPreview.set(null);
2815
+ this.commitEdit(s, 'keyboard');
2816
+ }
2817
+ cancelKeyboard() {
2818
+ this.kbSession = null;
2819
+ this.editPreview.set(null);
2820
+ this.announceCancelled('assertive');
2821
+ }
2822
+ // ===================================================================
2823
+ // Editable time grid — shared commit / preview / template helpers
2824
+ // ===================================================================
2825
+ /**
2826
+ * Emit the edited range (if anything actually changed): `eventResize` when
2827
+ * only the end moved, `eventMove` otherwise. The `events` input is never
2828
+ * touched — applying the change is the consumer's job.
2829
+ */
2830
+ commitEdit(s, source) {
2831
+ const dayChanged = s.day !== s.origDay;
2832
+ const startChanged = s.startMin !== s.origStartMin;
2833
+ const endChanged = s.endMin !== s.origEndMin;
2834
+ if (!dayChanged && !startChanged && !endChanged)
2835
+ return;
2836
+ const day = this.gridDays()[s.day] ?? this.gridDays()[s.origDay];
2837
+ const start = new Date(day);
2838
+ start.setHours(0, s.startMin, 0, 0);
2839
+ const end = new Date(day);
2840
+ end.setHours(0, s.endMin, 0, 0);
2841
+ const detail = { event: s.event, start, end };
2842
+ const resize = !dayChanged && !startChanged;
2843
+ if (resize)
2844
+ this.eventResize.emit(detail);
2845
+ else
2846
+ this.eventMove.emit(detail);
2847
+ this.announcer.announce(resize
2848
+ ? this.i18n.eventCalendarResized(s.event.title, minutesLabel(s.endMin))
2849
+ : this.i18n.eventCalendarMoved(s.event.title, formatDate(day, 'MMMM d, yyyy', this.i18n.dateNames), minutesLabel(s.startMin), minutesLabel(s.endMin)), source === 'keyboard' ? 'assertive' : 'polite');
2850
+ }
2851
+ /** Publish the session's current target as template-facing preview state. */
2852
+ updatePreview(s) {
2853
+ const gridStart = this.dayStartHour() * 60;
2854
+ const gridEnd = this.dayEndHour() * 60;
2855
+ const rangeMin = Math.max(gridEnd - gridStart, 1);
2856
+ const clampedStart = Math.max(s.startMin, gridStart);
2857
+ const clampedEnd = Math.min(s.endMin, gridEnd);
2858
+ const dx = (s.colRects[s.day]?.left ?? 0) - (s.colRects[s.origDay]?.left ?? 0);
2859
+ const dy = (s.startMin - s.origStartMin) * s.pxPerMin;
2860
+ const durationChanged = s.endMin - s.startMin !== s.origEndMin - s.origStartMin;
2861
+ this.editPreview.set({
2862
+ event: s.event,
2863
+ day: s.day,
2864
+ transform: `translate(${dx}px, ${dy}px)`,
2865
+ heightPct: durationChanged
2866
+ ? Math.max(((clampedEnd - clampedStart) / rangeMin) * 100, 2.5)
2867
+ : null,
2868
+ label: `${minutesLabel(s.startMin)} – ${minutesLabel(s.endMin)}`,
2869
+ top: ((clampedStart - gridStart) / rangeMin) * 100,
2870
+ outlineHeight: Math.max(((clampedEnd - clampedStart) / rangeMin) * 100, 1),
2871
+ });
2872
+ }
2873
+ announceCancelled(politeness) {
2874
+ this.announcer.announce(this.i18n.eventCalendarEditCancelled, politeness);
2875
+ }
2876
+ /** Position update after each keyboard step. */
2877
+ announceStep(s) {
2878
+ const day = this.gridDays()[s.day];
2879
+ this.announcer.announce(this.i18n.eventCalendarPosition(s.event.title, formatDate(day, 'MMMM d, yyyy', this.i18n.dateNames), minutesLabel(s.startMin), minutesLabel(s.endMin)), 'assertive');
2880
+ }
2881
+ /** Is this placement the pill currently being edited? */
2882
+ isEditing(p) {
2883
+ return this.editPreview()?.event === p.event;
2884
+ }
2885
+ /** Lift transform for the dragged pill (`null` when idle). */
2886
+ blockTransform(p) {
2887
+ const d = this.editPreview();
2888
+ return d && d.event === p.event ? d.transform : null;
2889
+ }
2890
+ /** Height (%) — the resize preview overrides the layout's height. */
2891
+ blockHeight(p) {
2892
+ const d = this.editPreview();
2893
+ return d && d.event === p.event && d.heightPct !== null ? d.heightPct : p.height;
2894
+ }
2895
+ /** Would-be time range shown on the pill while it is being edited. */
2896
+ dragLabelFor(p) {
2897
+ const d = this.editPreview();
2898
+ return d && d.event === p.event ? d.label : null;
2899
+ }
2900
+ /** Drop-outline geometry when the edit currently targets column `i`. */
2901
+ dropOutlineFor(i) {
2902
+ const d = this.editPreview();
2903
+ return d && d.day === i ? d : null;
2904
+ }
2905
+ /**
2906
+ * Accessible name for an editable pill — the title plus its time range
2907
+ * (composed with, not replacing, the visible title text and tooltip).
2908
+ */
2909
+ blockAriaLabel(p) {
2910
+ const [startMin, endMin] = eventMinutes(p.event);
2911
+ return `${p.event.title}, ${minutesLabel(startMin)} – ${minutesLabel(endMin)}`;
2912
+ }
2913
+ /** `aria-roledescription` for editable pills. */
2914
+ movableRoledescription = this.i18n.eventCalendarMovableEvent;
2915
+ ngOnDestroy() {
2916
+ if (this.pointerSession)
2917
+ this.teardownPointer(false);
2918
+ this.kbSession = null;
2919
+ }
2920
+ /** Raw 6×7 grid of dates for the visible month. */
2921
+ weeks = computed(() => buildMonthMatrix(this.viewDate(), this.firstDayOfWeek()), /* @ts-ignore */
2922
+ ...(ngDevMode ? [{ debugName: "weeks" }] : /* istanbul ignore next */ []));
2923
+ /** Weekday column headers ordered from `firstDayOfWeek`. */
2924
+ weekdays = computed(() => this.weeks()[0].map((d) => ({
2925
+ short: formatDate(d, 'ddd', this.i18n.dateNames),
2926
+ full: getWeekdayFullName(d, this.i18n.dateNames),
2927
+ })), /* @ts-ignore */
2928
+ ...(ngDevMode ? [{ debugName: "weekdays" }] : /* istanbul ignore next */ []));
2929
+ /** Events grouped by local calendar day, keyed by `startOfDay` timestamp. */
2930
+ eventsByDay = computed(() => {
2931
+ const map = new Map();
2932
+ for (const event of this.events()) {
2933
+ const key = startOfDay(event.date).getTime();
2934
+ const bucket = map.get(key);
2935
+ if (bucket)
2936
+ bucket.push(event);
2937
+ else
2938
+ map.set(key, [event]);
2939
+ }
2940
+ return map;
2941
+ }, /* @ts-ignore */
2942
+ ...(ngDevMode ? [{ debugName: "eventsByDay" }] : /* istanbul ignore next */ []));
2943
+ /** 6×7 grid of day cells with their (capped) events resolved. */
2944
+ weekCells = computed(() => {
2945
+ const month = this.viewMonth();
2946
+ const today = new Date();
2947
+ const max = this.maxPerDay();
2948
+ const byDay = this.eventsByDay();
2949
+ return this.weeks().map((week) => week.map((date) => {
2950
+ const dayEvents = byDay.get(startOfDay(date).getTime()) ?? [];
2951
+ const cell = {
2952
+ date,
2953
+ outside: !isSameMonth(date, month),
2954
+ today: isSameDay(date, today),
2955
+ visible: dayEvents.slice(0, max),
2956
+ overflow: Math.max(0, dayEvents.length - max),
2957
+ };
2958
+ // Label formatted once per rebuild, not on every CD pass (42 cells).
2959
+ return { ...cell, label: this.dayLabel(cell) };
2960
+ }));
2961
+ }, /* @ts-ignore */
2962
+ ...(ngDevMode ? [{ debugName: "weekCells" }] : /* istanbul ignore next */ []));
2963
+ /**
2964
+ * Screen-reader label for a day cell. The button's `aria-label` replaces its
2965
+ * visual content, so the label also announces the day's events — count plus
2966
+ * up to three titles — e.g. `July 9, 2026, 2 events: Standup, Demo`.
2967
+ * Precomputed into {@link MkDayCell#label} by `weekCells`.
2968
+ */
2969
+ dayLabel(cell) {
2970
+ const formatted = formatDate(cell.date, 'MMMM d, yyyy', this.i18n.dateNames);
2971
+ const count = cell.visible.length + cell.overflow;
2972
+ if (count === 0)
2973
+ return formatted;
2974
+ const titles = cell.visible
2975
+ .slice(0, 3)
2976
+ .map((e) => e.title)
2977
+ .join(', ');
2978
+ return `${formatted}, ${this.i18n.dayEvents(count, titles)}`;
2979
+ }
2980
+ prevMonth() {
2981
+ this.shiftMonth(-1);
2982
+ }
2983
+ nextMonth() {
2984
+ this.shiftMonth(1);
2985
+ }
2986
+ shiftMonth(delta) {
2987
+ const view = this.view();
2988
+ const next = view === 'month'
2989
+ ? addMonths(this.viewDate(), delta)
2990
+ : addDays(this.viewDate(), delta * (view === 'week' ? 7 : 1));
2991
+ this.viewDate.set(next);
2992
+ this.monthChange.emit(next);
2993
+ }
2994
+ onDayClick(d) {
2995
+ this.dayClick.emit(d);
2996
+ }
2997
+ onEventClick(event, calendarEvent) {
2998
+ event.stopPropagation();
2999
+ // A real drag just ended — the browser's trailing synthetic click must not
3000
+ // read as an event activation.
3001
+ if (this.swallowNextClick) {
3002
+ this.swallowNextClick = false;
3003
+ event.preventDefault();
3004
+ return;
3005
+ }
3006
+ this.eventClick.emit(calendarEvent);
3007
+ }
3008
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkEventCalendar, deps: [], target: i0.ɵɵFactoryTarget.Component });
3009
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkEventCalendar, isStandalone: true, selector: "mk-event-calendar", inputs: { events: { classPropertyName: "events", publicName: "events", isSignal: true, isRequired: false, transformFunction: null }, viewDate: { classPropertyName: "viewDate", publicName: "viewDate", isSignal: true, isRequired: false, transformFunction: null }, firstDayOfWeek: { classPropertyName: "firstDayOfWeek", publicName: "firstDayOfWeek", isSignal: true, isRequired: false, transformFunction: null }, maxPerDay: { classPropertyName: "maxPerDay", publicName: "maxPerDay", isSignal: true, isRequired: false, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: false, transformFunction: null }, dayStartHour: { classPropertyName: "dayStartHour", publicName: "dayStartHour", isSignal: true, isRequired: false, transformFunction: null }, dayEndHour: { classPropertyName: "dayEndHour", publicName: "dayEndHour", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, snapMinutes: { classPropertyName: "snapMinutes", publicName: "snapMinutes", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { viewDate: "viewDateChange", view: "viewChange", dayClick: "dayClick", eventClick: "eventClick", monthChange: "monthChange", slotClick: "slotClick", eventMove: "eventMove", eventResize: "eventResize" }, host: { classAttribute: "mk-event-calendar" }, ngImport: i0, template: "<div class=\"mk-event-calendar__header\">\n <button\n type=\"button\"\n class=\"mk-event-calendar__nav\"\n [attr.aria-label]=\"i18n.previousMonth\"\n (click)=\"prevMonth()\"\n >\n <svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" aria-hidden=\"true\">\n <path\n d=\"M15 5l-7 7 7 7\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n\n <span class=\"mk-event-calendar__label\" [id]=\"labelId\" aria-live=\"polite\">{{\n monthLabel()\n }}</span>\n\n <button\n type=\"button\"\n class=\"mk-event-calendar__nav\"\n [attr.aria-label]=\"i18n.nextMonth\"\n (click)=\"nextMonth()\"\n >\n <svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" aria-hidden=\"true\">\n <path\n d=\"M9 5l7 7-7 7\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n</div>\n\n@if (view() === 'month') {\n<!--\n The days are a plain list of buttons (role=\"group\"), not an ARIA grid \u2014 there\n is no roving-tabindex arrow navigation here, so grid semantics would be wrong.\n-->\n<div class=\"mk-event-calendar__grid\" role=\"group\" [attr.aria-labelledby]=\"labelId\">\n <div class=\"mk-event-calendar__weekdays\" aria-hidden=\"true\">\n @for (wd of weekdays(); track $index) {\n <span class=\"mk-event-calendar__weekday\">\n <abbr [title]=\"wd.full\">{{ wd.short }}</abbr>\n </span>\n }\n </div>\n\n @for (week of weekCells(); track $index) {\n <div class=\"mk-event-calendar__week\">\n @for (cell of week; track cell.date.getTime()) {\n <button\n type=\"button\"\n class=\"mk-event-calendar__cell\"\n [class.mk-event-calendar__cell--outside]=\"cell.outside\"\n [class.mk-event-calendar__cell--today]=\"cell.today\"\n [attr.aria-label]=\"cell.label\"\n [attr.aria-current]=\"cell.today ? 'date' : null\"\n (click)=\"onDayClick(cell.date)\"\n >\n <span class=\"mk-event-calendar__day-num\">{{\n cell.date.getDate()\n }}</span>\n\n <!--\n Presentational for assistive tech: the day button's aria-label\n already announces the events, and nested interactive content inside\n a button is invalid. The pill (click) is a mouse-only shortcut to\n `eventClick`; keyboard users activate the day (`dayClick`) instead.\n -->\n <span class=\"mk-event-calendar__events\" aria-hidden=\"true\">\n @for (event of cell.visible; track $index) {\n <span\n class=\"mk-event-calendar__pill\"\n [style.background-color]=\"event.color || 'var(--mk-primary)'\"\n [title]=\"event.title\"\n (click)=\"onEventClick($event, event)\"\n >\n {{ event.title }}\n </span>\n }\n @if (cell.overflow > 0) {\n <span class=\"mk-event-calendar__more\">{{\n i18n.moreEvents(cell.overflow)\n }}</span>\n }\n </span>\n </button>\n }\n </div>\n }\n</div>\n} @else {\n <div\n class=\"mk-event-calendar__timegrid\"\n role=\"group\"\n [attr.aria-labelledby]=\"labelId\"\n >\n @if (view() === 'week') {\n <div class=\"mk-event-calendar__timegrid-head\" aria-hidden=\"true\">\n <span class=\"mk-event-calendar__gutter-spacer\"></span>\n @for (col of timedColumns(); track col.day.getTime()) {\n <span\n class=\"mk-event-calendar__col-head\"\n [class.mk-event-calendar__col-head--today]=\"col.today\"\n >\n {{ col.head }}\n </span>\n }\n </div>\n }\n\n <div class=\"mk-event-calendar__timegrid-body\">\n <div class=\"mk-event-calendar__gutter\" aria-hidden=\"true\">\n @for (h of hours(); track h) {\n <span class=\"mk-event-calendar__hour-label\">{{ hourLabel(h) }}</span>\n }\n </div>\n\n @for (col of timedColumns(); track col.day.getTime(); let ci = $index) {\n <div\n class=\"mk-event-calendar__col\"\n [class.mk-event-calendar__col--today]=\"col.today\"\n >\n @for (h of hours(); track h) {\n <button\n type=\"button\"\n class=\"mk-event-calendar__slot\"\n [attr.aria-label]=\"col.head + ' ' + hourLabel(h)\"\n (click)=\"onSlotClick(col.day, h)\"\n ></button>\n }\n\n @for (p of col.placements; track $index) {\n <button\n type=\"button\"\n class=\"mk-event-calendar__block\"\n [class.mk-event-calendar__block--editable]=\"editable()\"\n [class.mk-event-calendar__block--dragging]=\"isEditing(p)\"\n [style.top.%]=\"p.top\"\n [style.height.%]=\"blockHeight(p)\"\n [style.left.%]=\"(100 / p.lanes) * p.lane\"\n [style.width.%]=\"100 / p.lanes\"\n [style.transform]=\"blockTransform(p)\"\n [style.background-color]=\"p.event.color || 'var(--mk-primary)'\"\n [title]=\"placementTitle(p)\"\n [attr.aria-label]=\"editable() ? blockAriaLabel(p) : null\"\n [attr.aria-roledescription]=\"editable() ? movableRoledescription : null\"\n (click)=\"onEventClick($event, p.event)\"\n (pointerdown)=\"onBlockPointerDown($event, p, ci)\"\n (keydown)=\"onBlockKeydown($event, p, ci)\"\n (blur)=\"onBlockBlur()\"\n >\n @if (dragLabelFor(p); as lbl) {\n <span class=\"mk-event-calendar__drag-time\" aria-hidden=\"true\">{{\n lbl\n }}</span>\n }\n {{ p.event.title }}\n @if (editable()) {\n <span\n class=\"mk-event-calendar__resize-handle\"\n aria-hidden=\"true\"\n ></span>\n }\n </button>\n }\n\n @if (dropOutlineFor(ci); as outline) {\n <div\n class=\"mk-event-calendar__drop-outline\"\n aria-hidden=\"true\"\n [style.top.%]=\"outline.top\"\n [style.height.%]=\"outline.outlineHeight\"\n ></div>\n }\n\n @if (col.allDay.length) {\n <span class=\"mk-event-calendar__all-day\" aria-hidden=\"true\">\n @for (event of col.allDay; track $index) {\n <span\n class=\"mk-event-calendar__pill\"\n [style.background-color]=\"event.color || 'var(--mk-primary)'\"\n [title]=\"event.title\"\n (click)=\"onEventClick($event, event)\"\n >\n {{ event.title }}\n </span>\n }\n </span>\n }\n </div>\n }\n </div>\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;padding:var(--mk-space-4);font-family:var(--mk-font-sans);color:var(--mk-text);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-md)}.mk-event-calendar__header{display:flex;align-items:center;gap:var(--mk-space-1);margin-bottom:var(--mk-space-3)}.mk-event-calendar__label{flex:1 1 auto;text-align:center;font-weight:var(--mk-font-weight-semibold);font-size:var(--mk-font-size-md);white-space:nowrap}.mk-event-calendar__nav{display:inline-flex;align-items:center;justify-content:center;width:var(--mk-control-height-sm);height:var(--mk-control-height-sm);flex:none;color:var(--mk-text-muted);background:transparent;border:var(--mk-border-width) solid transparent;border-radius:var(--mk-radius-md);cursor:pointer;transition:background-color var(--mk-duration-fast) var(--mk-ease-standard),color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-event-calendar__nav:hover{background-color:var(--mk-hover-overlay);color:var(--mk-text)}.mk-event-calendar__nav:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}.mk-event-calendar__nav svg{display:block}.mk-event-calendar__weekdays,.mk-event-calendar__week{display:grid;grid-template-columns:repeat(7,1fr)}.mk-event-calendar__weekday{padding:0 0 var(--mk-space-2);text-align:center;font-size:var(--mk-font-size-xs);font-weight:var(--mk-font-weight-semibold);letter-spacing:var(--mk-letter-spacing-wide);text-transform:uppercase;color:var(--mk-text-subtle)}.mk-event-calendar__weekday abbr{text-decoration:none;cursor:default}.mk-event-calendar__cell{display:flex;flex-direction:column;align-items:stretch;gap:var(--mk-space-1);min-height:5.5rem;padding:var(--mk-space-1);font-family:inherit;text-align:start;color:var(--mk-text);background:transparent;border:var(--mk-border-width) solid var(--mk-border-subtle);cursor:pointer;transition:background-color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-event-calendar__cell:hover{background-color:var(--mk-hover-overlay)}.mk-event-calendar__cell:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(var(--mk-focus-ring-offset) * -1)}.mk-event-calendar__cell--outside{color:var(--mk-text-muted);background-color:var(--mk-surface-2)}.mk-event-calendar__day-num{align-self:flex-start;display:inline-flex;align-items:center;justify-content:center;min-width:1.5rem;height:1.5rem;padding:0 .25rem;font-size:var(--mk-font-size-sm);line-height:1;border-radius:var(--mk-radius-md)}.mk-event-calendar__cell--today .mk-event-calendar__day-num{color:var(--mk-primary-contrast);background-color:var(--mk-primary);font-weight:var(--mk-font-weight-semibold)}.mk-event-calendar__events{display:flex;flex-direction:column;gap:2px;min-width:0}.mk-event-calendar__pill{display:block;overflow:hidden;padding:1px var(--mk-space-1);font-size:var(--mk-font-size-xs);line-height:1.4;color:var(--mk-primary-contrast);white-space:nowrap;text-overflow:ellipsis;border-radius:var(--mk-radius-md);cursor:pointer}.mk-event-calendar__more{font-size:var(--mk-font-size-xs);color:var(--mk-text-muted)}@media(prefers-reduced-motion:reduce){.mk-event-calendar__nav,.mk-event-calendar__cell{transition:none}}.mk-event-calendar__timegrid{border:var(--mk-border-width) solid var(--mk-border-subtle);border-radius:var(--mk-radius-md);overflow:hidden}.mk-event-calendar__timegrid-head{display:flex;border-bottom:var(--mk-border-width) solid var(--mk-border-subtle);background:var(--mk-surface-2)}.mk-event-calendar__gutter-spacer{flex:0 0 3.25rem}.mk-event-calendar__col-head{flex:1;padding:var(--mk-space-2);text-align:center;font-size:var(--mk-font-size-xs);color:var(--mk-text-muted)}.mk-event-calendar__col-head--today{color:var(--mk-primary);font-weight:var(--mk-font-weight-semibold)}.mk-event-calendar__timegrid-body{display:flex}.mk-event-calendar__gutter{flex:0 0 3.25rem;display:flex;flex-direction:column}.mk-event-calendar__hour-label{flex:1 0 3rem;padding:2px var(--mk-space-2) 0 0;text-align:right;font-size:var(--mk-font-size-xs);color:var(--mk-text-subtle)}.mk-event-calendar__col{position:relative;flex:1;display:flex;flex-direction:column;border-inline-start:var(--mk-border-width) solid var(--mk-border-subtle)}.mk-event-calendar__col--today{background:color-mix(in srgb,var(--mk-primary) 4%,transparent)}.mk-event-calendar__slot{flex:1 0 3rem;border:0;border-top:var(--mk-border-width) solid var(--mk-border-subtle);background:transparent;cursor:pointer;padding:0}.mk-event-calendar__slot:hover{background:var(--mk-hover-overlay)}.mk-event-calendar__slot:focus-visible{outline:2px solid var(--mk-focus-ring);outline-offset:-2px}.mk-event-calendar__block{position:absolute;z-index:1;overflow:hidden;margin:1px;padding:2px var(--mk-space-1);border:0;border-radius:var(--mk-radius-sm);color:var(--mk-primary-contrast);font-size:var(--mk-font-size-xs);text-align:start;cursor:pointer;min-height:1rem}.mk-event-calendar__block:focus-visible{outline:2px solid var(--mk-focus-ring);outline-offset:1px}.mk-event-calendar__block--editable{touch-action:pan-y;-webkit-user-select:none;user-select:none}.mk-event-calendar__block--dragging{z-index:3;opacity:.85;box-shadow:var(--mk-shadow-lg);will-change:transform}.mk-event-calendar__drag-time{display:block;font-weight:var(--mk-font-weight-semibold);white-space:nowrap}.mk-event-calendar__resize-handle{position:absolute;inset-inline:0;bottom:0;height:6px;cursor:ns-resize}@media(pointer:coarse){.mk-event-calendar__resize-handle:before{content:\"\";position:absolute;inset:min(0px,(100% - 24px) / 2)}}.mk-event-calendar__drop-outline{position:absolute;inset-inline:1px;z-index:2;pointer-events:none;border:var(--mk-border-width-strong) dashed var(--mk-primary);border-radius:var(--mk-radius-sm);background:color-mix(in srgb,var(--mk-primary) 8%,transparent)}.mk-event-calendar__all-day{position:absolute;inset-inline:2px;top:2px;z-index:2;display:flex;gap:2px;flex-wrap:wrap}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3010
+ }
3011
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkEventCalendar, decorators: [{
3012
+ type: Component,
3013
+ args: [{ selector: 'mk-event-calendar', changeDetection: ChangeDetectionStrategy.OnPush, host: {
3014
+ class: 'mk-event-calendar',
3015
+ }, template: "<div class=\"mk-event-calendar__header\">\n <button\n type=\"button\"\n class=\"mk-event-calendar__nav\"\n [attr.aria-label]=\"i18n.previousMonth\"\n (click)=\"prevMonth()\"\n >\n <svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" aria-hidden=\"true\">\n <path\n d=\"M15 5l-7 7 7 7\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n\n <span class=\"mk-event-calendar__label\" [id]=\"labelId\" aria-live=\"polite\">{{\n monthLabel()\n }}</span>\n\n <button\n type=\"button\"\n class=\"mk-event-calendar__nav\"\n [attr.aria-label]=\"i18n.nextMonth\"\n (click)=\"nextMonth()\"\n >\n <svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" aria-hidden=\"true\">\n <path\n d=\"M9 5l7 7-7 7\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n</div>\n\n@if (view() === 'month') {\n<!--\n The days are a plain list of buttons (role=\"group\"), not an ARIA grid \u2014 there\n is no roving-tabindex arrow navigation here, so grid semantics would be wrong.\n-->\n<div class=\"mk-event-calendar__grid\" role=\"group\" [attr.aria-labelledby]=\"labelId\">\n <div class=\"mk-event-calendar__weekdays\" aria-hidden=\"true\">\n @for (wd of weekdays(); track $index) {\n <span class=\"mk-event-calendar__weekday\">\n <abbr [title]=\"wd.full\">{{ wd.short }}</abbr>\n </span>\n }\n </div>\n\n @for (week of weekCells(); track $index) {\n <div class=\"mk-event-calendar__week\">\n @for (cell of week; track cell.date.getTime()) {\n <button\n type=\"button\"\n class=\"mk-event-calendar__cell\"\n [class.mk-event-calendar__cell--outside]=\"cell.outside\"\n [class.mk-event-calendar__cell--today]=\"cell.today\"\n [attr.aria-label]=\"cell.label\"\n [attr.aria-current]=\"cell.today ? 'date' : null\"\n (click)=\"onDayClick(cell.date)\"\n >\n <span class=\"mk-event-calendar__day-num\">{{\n cell.date.getDate()\n }}</span>\n\n <!--\n Presentational for assistive tech: the day button's aria-label\n already announces the events, and nested interactive content inside\n a button is invalid. The pill (click) is a mouse-only shortcut to\n `eventClick`; keyboard users activate the day (`dayClick`) instead.\n -->\n <span class=\"mk-event-calendar__events\" aria-hidden=\"true\">\n @for (event of cell.visible; track $index) {\n <span\n class=\"mk-event-calendar__pill\"\n [style.background-color]=\"event.color || 'var(--mk-primary)'\"\n [title]=\"event.title\"\n (click)=\"onEventClick($event, event)\"\n >\n {{ event.title }}\n </span>\n }\n @if (cell.overflow > 0) {\n <span class=\"mk-event-calendar__more\">{{\n i18n.moreEvents(cell.overflow)\n }}</span>\n }\n </span>\n </button>\n }\n </div>\n }\n</div>\n} @else {\n <div\n class=\"mk-event-calendar__timegrid\"\n role=\"group\"\n [attr.aria-labelledby]=\"labelId\"\n >\n @if (view() === 'week') {\n <div class=\"mk-event-calendar__timegrid-head\" aria-hidden=\"true\">\n <span class=\"mk-event-calendar__gutter-spacer\"></span>\n @for (col of timedColumns(); track col.day.getTime()) {\n <span\n class=\"mk-event-calendar__col-head\"\n [class.mk-event-calendar__col-head--today]=\"col.today\"\n >\n {{ col.head }}\n </span>\n }\n </div>\n }\n\n <div class=\"mk-event-calendar__timegrid-body\">\n <div class=\"mk-event-calendar__gutter\" aria-hidden=\"true\">\n @for (h of hours(); track h) {\n <span class=\"mk-event-calendar__hour-label\">{{ hourLabel(h) }}</span>\n }\n </div>\n\n @for (col of timedColumns(); track col.day.getTime(); let ci = $index) {\n <div\n class=\"mk-event-calendar__col\"\n [class.mk-event-calendar__col--today]=\"col.today\"\n >\n @for (h of hours(); track h) {\n <button\n type=\"button\"\n class=\"mk-event-calendar__slot\"\n [attr.aria-label]=\"col.head + ' ' + hourLabel(h)\"\n (click)=\"onSlotClick(col.day, h)\"\n ></button>\n }\n\n @for (p of col.placements; track $index) {\n <button\n type=\"button\"\n class=\"mk-event-calendar__block\"\n [class.mk-event-calendar__block--editable]=\"editable()\"\n [class.mk-event-calendar__block--dragging]=\"isEditing(p)\"\n [style.top.%]=\"p.top\"\n [style.height.%]=\"blockHeight(p)\"\n [style.left.%]=\"(100 / p.lanes) * p.lane\"\n [style.width.%]=\"100 / p.lanes\"\n [style.transform]=\"blockTransform(p)\"\n [style.background-color]=\"p.event.color || 'var(--mk-primary)'\"\n [title]=\"placementTitle(p)\"\n [attr.aria-label]=\"editable() ? blockAriaLabel(p) : null\"\n [attr.aria-roledescription]=\"editable() ? movableRoledescription : null\"\n (click)=\"onEventClick($event, p.event)\"\n (pointerdown)=\"onBlockPointerDown($event, p, ci)\"\n (keydown)=\"onBlockKeydown($event, p, ci)\"\n (blur)=\"onBlockBlur()\"\n >\n @if (dragLabelFor(p); as lbl) {\n <span class=\"mk-event-calendar__drag-time\" aria-hidden=\"true\">{{\n lbl\n }}</span>\n }\n {{ p.event.title }}\n @if (editable()) {\n <span\n class=\"mk-event-calendar__resize-handle\"\n aria-hidden=\"true\"\n ></span>\n }\n </button>\n }\n\n @if (dropOutlineFor(ci); as outline) {\n <div\n class=\"mk-event-calendar__drop-outline\"\n aria-hidden=\"true\"\n [style.top.%]=\"outline.top\"\n [style.height.%]=\"outline.outlineHeight\"\n ></div>\n }\n\n @if (col.allDay.length) {\n <span class=\"mk-event-calendar__all-day\" aria-hidden=\"true\">\n @for (event of col.allDay; track $index) {\n <span\n class=\"mk-event-calendar__pill\"\n [style.background-color]=\"event.color || 'var(--mk-primary)'\"\n [title]=\"event.title\"\n (click)=\"onEventClick($event, event)\"\n >\n {{ event.title }}\n </span>\n }\n </span>\n }\n </div>\n }\n </div>\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;padding:var(--mk-space-4);font-family:var(--mk-font-sans);color:var(--mk-text);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-md)}.mk-event-calendar__header{display:flex;align-items:center;gap:var(--mk-space-1);margin-bottom:var(--mk-space-3)}.mk-event-calendar__label{flex:1 1 auto;text-align:center;font-weight:var(--mk-font-weight-semibold);font-size:var(--mk-font-size-md);white-space:nowrap}.mk-event-calendar__nav{display:inline-flex;align-items:center;justify-content:center;width:var(--mk-control-height-sm);height:var(--mk-control-height-sm);flex:none;color:var(--mk-text-muted);background:transparent;border:var(--mk-border-width) solid transparent;border-radius:var(--mk-radius-md);cursor:pointer;transition:background-color var(--mk-duration-fast) var(--mk-ease-standard),color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-event-calendar__nav:hover{background-color:var(--mk-hover-overlay);color:var(--mk-text)}.mk-event-calendar__nav:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}.mk-event-calendar__nav svg{display:block}.mk-event-calendar__weekdays,.mk-event-calendar__week{display:grid;grid-template-columns:repeat(7,1fr)}.mk-event-calendar__weekday{padding:0 0 var(--mk-space-2);text-align:center;font-size:var(--mk-font-size-xs);font-weight:var(--mk-font-weight-semibold);letter-spacing:var(--mk-letter-spacing-wide);text-transform:uppercase;color:var(--mk-text-subtle)}.mk-event-calendar__weekday abbr{text-decoration:none;cursor:default}.mk-event-calendar__cell{display:flex;flex-direction:column;align-items:stretch;gap:var(--mk-space-1);min-height:5.5rem;padding:var(--mk-space-1);font-family:inherit;text-align:start;color:var(--mk-text);background:transparent;border:var(--mk-border-width) solid var(--mk-border-subtle);cursor:pointer;transition:background-color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-event-calendar__cell:hover{background-color:var(--mk-hover-overlay)}.mk-event-calendar__cell:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(var(--mk-focus-ring-offset) * -1)}.mk-event-calendar__cell--outside{color:var(--mk-text-muted);background-color:var(--mk-surface-2)}.mk-event-calendar__day-num{align-self:flex-start;display:inline-flex;align-items:center;justify-content:center;min-width:1.5rem;height:1.5rem;padding:0 .25rem;font-size:var(--mk-font-size-sm);line-height:1;border-radius:var(--mk-radius-md)}.mk-event-calendar__cell--today .mk-event-calendar__day-num{color:var(--mk-primary-contrast);background-color:var(--mk-primary);font-weight:var(--mk-font-weight-semibold)}.mk-event-calendar__events{display:flex;flex-direction:column;gap:2px;min-width:0}.mk-event-calendar__pill{display:block;overflow:hidden;padding:1px var(--mk-space-1);font-size:var(--mk-font-size-xs);line-height:1.4;color:var(--mk-primary-contrast);white-space:nowrap;text-overflow:ellipsis;border-radius:var(--mk-radius-md);cursor:pointer}.mk-event-calendar__more{font-size:var(--mk-font-size-xs);color:var(--mk-text-muted)}@media(prefers-reduced-motion:reduce){.mk-event-calendar__nav,.mk-event-calendar__cell{transition:none}}.mk-event-calendar__timegrid{border:var(--mk-border-width) solid var(--mk-border-subtle);border-radius:var(--mk-radius-md);overflow:hidden}.mk-event-calendar__timegrid-head{display:flex;border-bottom:var(--mk-border-width) solid var(--mk-border-subtle);background:var(--mk-surface-2)}.mk-event-calendar__gutter-spacer{flex:0 0 3.25rem}.mk-event-calendar__col-head{flex:1;padding:var(--mk-space-2);text-align:center;font-size:var(--mk-font-size-xs);color:var(--mk-text-muted)}.mk-event-calendar__col-head--today{color:var(--mk-primary);font-weight:var(--mk-font-weight-semibold)}.mk-event-calendar__timegrid-body{display:flex}.mk-event-calendar__gutter{flex:0 0 3.25rem;display:flex;flex-direction:column}.mk-event-calendar__hour-label{flex:1 0 3rem;padding:2px var(--mk-space-2) 0 0;text-align:right;font-size:var(--mk-font-size-xs);color:var(--mk-text-subtle)}.mk-event-calendar__col{position:relative;flex:1;display:flex;flex-direction:column;border-inline-start:var(--mk-border-width) solid var(--mk-border-subtle)}.mk-event-calendar__col--today{background:color-mix(in srgb,var(--mk-primary) 4%,transparent)}.mk-event-calendar__slot{flex:1 0 3rem;border:0;border-top:var(--mk-border-width) solid var(--mk-border-subtle);background:transparent;cursor:pointer;padding:0}.mk-event-calendar__slot:hover{background:var(--mk-hover-overlay)}.mk-event-calendar__slot:focus-visible{outline:2px solid var(--mk-focus-ring);outline-offset:-2px}.mk-event-calendar__block{position:absolute;z-index:1;overflow:hidden;margin:1px;padding:2px var(--mk-space-1);border:0;border-radius:var(--mk-radius-sm);color:var(--mk-primary-contrast);font-size:var(--mk-font-size-xs);text-align:start;cursor:pointer;min-height:1rem}.mk-event-calendar__block:focus-visible{outline:2px solid var(--mk-focus-ring);outline-offset:1px}.mk-event-calendar__block--editable{touch-action:pan-y;-webkit-user-select:none;user-select:none}.mk-event-calendar__block--dragging{z-index:3;opacity:.85;box-shadow:var(--mk-shadow-lg);will-change:transform}.mk-event-calendar__drag-time{display:block;font-weight:var(--mk-font-weight-semibold);white-space:nowrap}.mk-event-calendar__resize-handle{position:absolute;inset-inline:0;bottom:0;height:6px;cursor:ns-resize}@media(pointer:coarse){.mk-event-calendar__resize-handle:before{content:\"\";position:absolute;inset:min(0px,(100% - 24px) / 2)}}.mk-event-calendar__drop-outline{position:absolute;inset-inline:1px;z-index:2;pointer-events:none;border:var(--mk-border-width-strong) dashed var(--mk-primary);border-radius:var(--mk-radius-sm);background:color-mix(in srgb,var(--mk-primary) 8%,transparent)}.mk-event-calendar__all-day{position:absolute;inset-inline:2px;top:2px;z-index:2;display:flex;gap:2px;flex-wrap:wrap}\n"] }]
3016
+ }], propDecorators: { events: [{ type: i0.Input, args: [{ isSignal: true, alias: "events", required: false }] }], viewDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewDate", required: false }] }, { type: i0.Output, args: ["viewDateChange"] }], firstDayOfWeek: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstDayOfWeek", required: false }] }], maxPerDay: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxPerDay", required: false }] }], view: [{ type: i0.Input, args: [{ isSignal: true, alias: "view", required: false }] }, { type: i0.Output, args: ["viewChange"] }], dayStartHour: [{ type: i0.Input, args: [{ isSignal: true, alias: "dayStartHour", required: false }] }], dayEndHour: [{ type: i0.Input, args: [{ isSignal: true, alias: "dayEndHour", required: false }] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }], snapMinutes: [{ type: i0.Input, args: [{ isSignal: true, alias: "snapMinutes", required: false }] }], dayClick: [{ type: i0.Output, args: ["dayClick"] }], eventClick: [{ type: i0.Output, args: ["eventClick"] }], monthChange: [{ type: i0.Output, args: ["monthChange"] }], slotClick: [{ type: i0.Output, args: ["slotClick"] }], eventMove: [{ type: i0.Output, args: ["eventMove"] }], eventResize: [{ type: i0.Output, args: ["eventResize"] }] } });
3017
+
3018
+ /** Number of days in month `m` (1–12) of `year`. */
3019
+ function daysInMonth(m, year) {
3020
+ return new Date(year, m, 0).getDate();
3021
+ }
3022
+ /**
3023
+ * MiniDate — a compact, popover-free inline date editor with segmented
3024
+ * day / month / year spin fields (like a native `<input type="date">` but
3025
+ * themed and signal-driven). Each segment is an ARIA `spinbutton`: Arrow
3026
+ * Up/Down step the value (day wraps within the month), Home/End jump to the
3027
+ * segment's minimum/maximum, Arrow Left/Right move between segments, and
3028
+ * typing digits fills the focused segment and auto-advances (OTP-style). Implements `ControlValueAccessor` and a two-way
3029
+ * `value` model over a real `Date | null`.
3030
+ *
3031
+ * A non-null `Date` is emitted only when all three segments are set and valid;
3032
+ * the day is clamped to the month's length (e.g. Feb 31 → 28/29) and the
3033
+ * result is clamped to `[min, max]`.
3034
+ *
3035
+ * ```html
3036
+ * <mk-mini-date [(value)]="date" order="dmy" [min]="minDate" />
3037
+ * ```
3038
+ */
3039
+ class MkMiniDate {
3040
+ field = inject(MkFormField, { optional: true });
3041
+ i18n = inject(MK_I18N);
3042
+ segEls = viewChildren('seg', /* @ts-ignore */
3043
+ ...(ngDevMode ? [{ debugName: "segEls" }] : /* istanbul ignore next */ []));
3044
+ /** Minimum selectable date (inclusive). */
3045
+ min = input(null, /* @ts-ignore */
3046
+ ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
3047
+ /** Maximum selectable date (inclusive). */
3048
+ max = input(null, /* @ts-ignore */
3049
+ ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
3050
+ /** Two-way value — a real `Date`, or `null` while incomplete. */
3051
+ value = model(null, /* @ts-ignore */
3052
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
3053
+ /** Disable the control. */
3054
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3055
+ /** Force invalid styling when standalone. */
3056
+ invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3057
+ /** Control size. Ignored inside an `mk-form-field`. */
3058
+ size = input('md', /* @ts-ignore */
3059
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
3060
+ /** Segment order. */
3061
+ order = input('dmy', /* @ts-ignore */
3062
+ ...(ngDevMode ? [{ debugName: "order" }] : /* istanbul ignore next */ []));
3063
+ /** Lowest selectable year (for the year spinbutton range). */
3064
+ yearMin = input(1900, { ...(ngDevMode ? { debugName: "yearMin" } : /* istanbul ignore next */ {}), transform: numberAttribute });
3065
+ /** Highest selectable year (for the year spinbutton range). */
3066
+ yearMax = input(2100, { ...(ngDevMode ? { debugName: "yearMax" } : /* istanbul ignore next */ {}), transform: numberAttribute });
3067
+ segId = mkUniqueId('mk-mini-date');
3068
+ // --- Segment state --------------------------------------------------------
3069
+ daySeg = signal(null, /* @ts-ignore */
3070
+ ...(ngDevMode ? [{ debugName: "daySeg" }] : /* istanbul ignore next */ []));
3071
+ monthSeg = signal(null, /* @ts-ignore */
3072
+ ...(ngDevMode ? [{ debugName: "monthSeg" }] : /* istanbul ignore next */ []));
3073
+ yearSeg = signal(null, /* @ts-ignore */
3074
+ ...(ngDevMode ? [{ debugName: "yearSeg" }] : /* istanbul ignore next */ []));
3075
+ /** Per-segment digit buffer used for OTP-style typing/auto-advance. */
3076
+ buffer = '';
3077
+ bufferKey = null;
3078
+ cvaDisabled = signal(false, /* @ts-ignore */
3079
+ ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
3080
+ onChange = () => { };
3081
+ onTouched = () => { };
3082
+ effectiveSize = computed(() => this.field ? this.field.size() : this.size(), /* @ts-ignore */
3083
+ ...(ngDevMode ? [{ debugName: "effectiveSize" }] : /* istanbul ignore next */ []));
3084
+ isDisabled = computed(() => this.disabled() || this.cvaDisabled(), /* @ts-ignore */
3085
+ ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
3086
+ isInvalid = computed(() => this.invalid() || (this.field?.hasError() ?? false), /* @ts-ignore */
3087
+ ...(ngDevMode ? [{ debugName: "isInvalid" }] : /* istanbul ignore next */ []));
3088
+ isRequired = computed(() => this.field?.isRequired() ?? false, /* @ts-ignore */
3089
+ ...(ngDevMode ? [{ debugName: "isRequired" }] : /* istanbul ignore next */ []));
3090
+ labelledBy = computed(() => this.field?.labelId ?? null, /* @ts-ignore */
3091
+ ...(ngDevMode ? [{ debugName: "labelledBy" }] : /* istanbul ignore next */ []));
3092
+ describedBy = computed(() => this.field?.describedBy() ?? null, /* @ts-ignore */
3093
+ ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
3094
+ /** Days available in the current month/year (defaults to a leap year). */
3095
+ monthLength = computed(() => daysInMonth(this.monthSeg() ?? 1, this.yearSeg() ?? 2000), /* @ts-ignore */
3096
+ ...(ngDevMode ? [{ debugName: "monthLength" }] : /* istanbul ignore next */ []));
3097
+ /** Ordered, rendered segment descriptors. */
3098
+ parts = computed(() => {
3099
+ const names = this.i18n.dateNames;
3100
+ const byKey = {
3101
+ day: {
3102
+ key: 'day',
3103
+ display: this.pad(this.daySeg(), 2, 'dd'),
3104
+ filled: this.daySeg() != null,
3105
+ valueNow: this.daySeg(),
3106
+ valueMin: 1,
3107
+ valueMax: this.monthLength(),
3108
+ valueText: this.daySeg() != null ? String(this.daySeg()) : this.i18n.empty,
3109
+ label: this.i18n.daySegment,
3110
+ },
3111
+ month: {
3112
+ key: 'month',
3113
+ display: this.monthSeg() != null
3114
+ ? names.monthsShort[this.monthSeg() - 1]
3115
+ : 'mmm',
3116
+ filled: this.monthSeg() != null,
3117
+ valueNow: this.monthSeg(),
3118
+ valueMin: 1,
3119
+ valueMax: 12,
3120
+ valueText: this.monthSeg() != null
3121
+ ? names.months[this.monthSeg() - 1]
3122
+ : this.i18n.empty,
3123
+ label: this.i18n.monthSegment,
3124
+ },
3125
+ year: {
3126
+ key: 'year',
3127
+ display: this.pad(this.yearSeg(), 4, 'yyyy'),
3128
+ filled: this.yearSeg() != null,
3129
+ valueNow: this.yearSeg(),
3130
+ valueMin: this.yearMin(),
3131
+ valueMax: this.yearMax(),
3132
+ valueText: this.yearSeg() != null ? String(this.yearSeg()) : this.i18n.empty,
3133
+ label: this.i18n.yearSegment,
3134
+ },
3135
+ };
3136
+ return this.order()
3137
+ .split('')
3138
+ .map((c) => c === 'd' ? byKey.day : c === 'm' ? byKey.month : byKey.year);
3139
+ }, /* @ts-ignore */
3140
+ ...(ngDevMode ? [{ debugName: "parts" }] : /* istanbul ignore next */ []));
3141
+ // --- Interaction ----------------------------------------------------------
3142
+ onSegKeydown(key, index, e) {
3143
+ if (this.isDisabled())
3144
+ return;
3145
+ switch (e.key) {
3146
+ case 'ArrowUp':
3147
+ e.preventDefault();
3148
+ this.step(key, 1);
3149
+ break;
3150
+ case 'ArrowDown':
3151
+ e.preventDefault();
3152
+ this.step(key, -1);
3153
+ break;
3154
+ case 'ArrowLeft':
3155
+ e.preventDefault();
3156
+ this.focusSeg(index - 1);
3157
+ break;
3158
+ case 'ArrowRight':
3159
+ e.preventDefault();
3160
+ this.focusSeg(index + 1);
3161
+ break;
3162
+ case 'Home':
3163
+ e.preventDefault();
3164
+ this.setSegment(key, this.segMin(key));
3165
+ this.resetBuffer();
3166
+ break;
3167
+ case 'End':
3168
+ e.preventDefault();
3169
+ this.setSegment(key, this.segMax(key));
3170
+ this.resetBuffer();
3171
+ break;
3172
+ case 'Backspace':
3173
+ case 'Delete':
3174
+ e.preventDefault();
3175
+ this.setSegment(key, null);
3176
+ this.resetBuffer();
3177
+ if (e.key === 'Backspace')
3178
+ this.focusSeg(index - 1);
3179
+ break;
3180
+ default:
3181
+ if (/^[0-9]$/.test(e.key)) {
3182
+ e.preventDefault();
3183
+ this.typeDigit(key, index, e.key);
3184
+ }
3185
+ }
3186
+ }
3187
+ onSegFocus(key) {
3188
+ // A fresh focus starts a new typing buffer for that segment.
3189
+ if (this.bufferKey !== key)
3190
+ this.resetBuffer();
3191
+ }
3192
+ onBlur() {
3193
+ this.onTouched();
3194
+ }
3195
+ /** A segment's minimum value (Home key). */
3196
+ segMin(key) {
3197
+ return key === 'year' ? this.yearMin() : 1;
3198
+ }
3199
+ /** A segment's maximum value (End key). */
3200
+ segMax(key) {
3201
+ if (key === 'day')
3202
+ return this.monthLength();
3203
+ if (key === 'month')
3204
+ return 12;
3205
+ return this.yearMax();
3206
+ }
3207
+ /** Step a segment by `delta`; the day wraps within the current month. */
3208
+ step(key, delta) {
3209
+ if (key === 'day') {
3210
+ const len = this.monthLength();
3211
+ const cur = this.daySeg() ?? (delta > 0 ? 0 : len + 1);
3212
+ const next = ((cur - 1 + delta) % len + len) % len + 1;
3213
+ this.setSegment('day', next);
3214
+ }
3215
+ else if (key === 'month') {
3216
+ const cur = this.monthSeg() ?? (delta > 0 ? 0 : 13);
3217
+ const next = ((cur - 1 + delta) % 12 + 12) % 12 + 1;
3218
+ this.setSegment('month', next);
3219
+ }
3220
+ else {
3221
+ const base = this.yearSeg() ?? new Date().getFullYear() - delta;
3222
+ const next = Math.min(this.yearMax(), Math.max(this.yearMin(), base + delta));
3223
+ this.setSegment('year', next);
3224
+ }
3225
+ this.resetBuffer();
3226
+ }
3227
+ /** OTP-style digit entry with auto-advance to the next segment when full. */
3228
+ typeDigit(key, index, digit) {
3229
+ if (this.bufferKey !== key)
3230
+ this.buffer = '';
3231
+ this.bufferKey = key;
3232
+ const maxDigits = key === 'year' ? 4 : 2;
3233
+ const max = key === 'day'
3234
+ ? this.monthLength()
3235
+ : key === 'month'
3236
+ ? 12
3237
+ : this.yearMax();
3238
+ let buf = this.buffer + digit;
3239
+ let num = Number(buf);
3240
+ // If the running value overflows the max, restart the buffer with this digit.
3241
+ if (num > max || buf.length > maxDigits) {
3242
+ buf = digit;
3243
+ num = Number(buf);
3244
+ }
3245
+ this.buffer = buf;
3246
+ this.setSegment(key, num === 0 ? null : num);
3247
+ // Advance when the segment is saturated (full width, or another digit
3248
+ // could only overflow the max).
3249
+ const full = buf.length >= maxDigits || num * 10 > max;
3250
+ if (full) {
3251
+ this.resetBuffer();
3252
+ this.focusSeg(index + 1);
3253
+ }
3254
+ }
3255
+ /** Set a segment, then rebuild + emit the composite date. */
3256
+ setSegment(key, value) {
3257
+ if (key === 'day')
3258
+ this.daySeg.set(value);
3259
+ else if (key === 'month')
3260
+ this.monthSeg.set(value);
3261
+ else
3262
+ this.yearSeg.set(value);
3263
+ this.recompute();
3264
+ }
3265
+ /**
3266
+ * Rebuild the `Date` from the three segments and emit it. Emits `null` unless
3267
+ * all three are set; clamps the day to the month length and the whole date to
3268
+ * `[min, max]`, writing any clamp back into the segments.
3269
+ */
3270
+ recompute() {
3271
+ const d = this.daySeg();
3272
+ const m = this.monthSeg();
3273
+ const y = this.yearSeg();
3274
+ if (d == null || m == null || y == null) {
3275
+ this.emit(null);
3276
+ return;
3277
+ }
3278
+ const len = daysInMonth(m, y);
3279
+ const clampedDay = Math.min(d, len);
3280
+ if (clampedDay !== d)
3281
+ this.daySeg.set(clampedDay);
3282
+ const raw = new Date(y, m - 1, clampedDay);
3283
+ const bounded = clampDate(startOfDay(raw), this.min(), this.max());
3284
+ if (!isSameDay(bounded, raw))
3285
+ this.split(bounded);
3286
+ this.emit(startOfDay(bounded));
3287
+ }
3288
+ emit(v) {
3289
+ this.value.set(v);
3290
+ this.onChange(v);
3291
+ }
3292
+ /** Populate the three segments from a `Date`. */
3293
+ split(date) {
3294
+ this.daySeg.set(date.getDate());
3295
+ this.monthSeg.set(date.getMonth() + 1);
3296
+ this.yearSeg.set(date.getFullYear());
3297
+ }
3298
+ resetBuffer() {
3299
+ this.buffer = '';
3300
+ this.bufferKey = null;
3301
+ }
3302
+ focusSeg(index) {
3303
+ const els = this.segEls();
3304
+ const el = els[Math.max(0, Math.min(els.length - 1, index))];
3305
+ el?.nativeElement.focus();
3306
+ this.resetBuffer();
3307
+ }
3308
+ pad(v, width, placeholder) {
3309
+ return v == null ? placeholder : String(v).padStart(width, '0');
3310
+ }
3311
+ // --- ControlValueAccessor -------------------------------------------------
3312
+ writeValue(value) {
3313
+ if (value instanceof Date && !Number.isNaN(value.getTime())) {
3314
+ this.split(value);
3315
+ }
3316
+ else {
3317
+ this.daySeg.set(null);
3318
+ this.monthSeg.set(null);
3319
+ this.yearSeg.set(null);
3320
+ }
3321
+ this.value.set(value ?? null);
3322
+ this.resetBuffer();
3323
+ }
3324
+ registerOnChange(fn) {
3325
+ this.onChange = fn;
3326
+ }
3327
+ registerOnTouched(fn) {
3328
+ this.onTouched = fn;
3329
+ }
3330
+ setDisabledState(isDisabled) {
3331
+ this.cvaDisabled.set(isDisabled);
3332
+ }
3333
+ // --- Validator ------------------------------------------------------------
3334
+ validatorChange = mkValidatorChange(() => {
3335
+ this.min();
3336
+ this.max();
3337
+ });
3338
+ /**
3339
+ * Reports `mkMinDate` / `mkMaxDate` against the `[min]` and `[max]` inputs.
3340
+ */
3341
+ validate(control) {
3342
+ const v = control.value;
3343
+ if (!(v instanceof Date) || Number.isNaN(v.getTime()))
3344
+ return null;
3345
+ const day = startOfDay(v);
3346
+ const min = this.min();
3347
+ if (min && isBefore(day, startOfDay(min)))
3348
+ return { mkMinDate: { min, actual: v } };
3349
+ const max = this.max();
3350
+ if (max && isAfter(day, startOfDay(max)))
3351
+ return { mkMaxDate: { max, actual: v } };
3352
+ return null;
3353
+ }
3354
+ registerOnValidatorChange(fn) {
3355
+ this.validatorChange.register(fn);
3356
+ }
3357
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkMiniDate, deps: [], target: i0.ɵɵFactoryTarget.Component });
3358
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkMiniDate, isStandalone: true, selector: "mk-mini-date", inputs: { min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, order: { classPropertyName: "order", publicName: "order", isSignal: true, isRequired: false, transformFunction: null }, yearMin: { classPropertyName: "yearMin", publicName: "yearMin", isSignal: true, isRequired: false, transformFunction: null }, yearMax: { classPropertyName: "yearMax", publicName: "yearMax", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { attributes: { "role": "group" }, properties: { "attr.aria-labelledby": "labelledBy()", "attr.aria-describedby": "describedBy()", "attr.aria-invalid": "isInvalid() || null", "attr.aria-required": "isRequired() || null", "class.mk-mini-date--sm": "effectiveSize() === 'sm'", "class.mk-mini-date--lg": "effectiveSize() === 'lg'", "class.mk-mini-date--invalid": "isInvalid()", "class.mk-mini-date--disabled": "isDisabled()" }, classAttribute: "mk-mini-date" }, providers: [
3359
+ {
3360
+ provide: NG_VALUE_ACCESSOR,
3361
+ useExisting: forwardRef(() => MkMiniDate),
3362
+ multi: true,
3363
+ },
3364
+ {
3365
+ provide: NG_VALIDATORS,
3366
+ useExisting: forwardRef(() => MkMiniDate),
3367
+ multi: true,
3368
+ },
3369
+ ], viewQueries: [{ propertyName: "segEls", predicate: ["seg"], descendants: true, isSignal: true }], exportAs: ["mkMiniDate"], ngImport: i0, template: "@for (part of parts(); track part.key; let last = $last) {\n <span\n #seg\n class=\"mk-mini-date__seg\"\n [class.mk-mini-date__seg--empty]=\"!part.filled\"\n [id]=\"segId + '-' + part.key\"\n role=\"spinbutton\"\n [attr.tabindex]=\"isDisabled() ? -1 : 0\"\n [attr.aria-label]=\"part.label\"\n [attr.aria-valuenow]=\"part.valueNow\"\n [attr.aria-valuemin]=\"part.valueMin\"\n [attr.aria-valuemax]=\"part.valueMax\"\n [attr.aria-valuetext]=\"part.valueText\"\n [attr.aria-disabled]=\"isDisabled() || null\"\n (keydown)=\"onSegKeydown(part.key, $index, $event)\"\n (focus)=\"onSegFocus(part.key)\"\n (blur)=\"onBlur()\"\n >{{ part.display }}</span>\n @if (!last) {\n <span class=\"mk-mini-date__sep\" aria-hidden=\"true\">/</span>\n }\n}\n", styles: [":host{display:inline-flex;align-items:center;gap:.125rem;height:var(--mk-control-height-md);padding:0 var(--mk-space-2);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-md);font-family:var(--mk-font-sans);font-size:var(--mk-font-size-md);color:var(--mk-text);transition:border-color var(--mk-duration-fast) var(--mk-ease-standard),box-shadow var(--mk-duration-fast) var(--mk-ease-standard)}:host(.mk-mini-date--sm){height:var(--mk-control-height-sm);border-radius:var(--mk-radius-sm);font-size:var(--mk-font-size-sm)}:host(.mk-mini-date--lg){height:var(--mk-control-height-lg);font-size:var(--mk-font-size-lg)}:host(:focus-within){border-color:var(--mk-primary);outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}:host(.mk-mini-date--invalid){border-color:var(--mk-danger)}:host(.mk-mini-date--disabled){background-color:var(--mk-surface-2);opacity:.7;pointer-events:none}.mk-mini-date__seg{padding:0 var(--mk-space-1);border-radius:var(--mk-radius-sm);font-variant-numeric:tabular-nums;text-align:center;cursor:default;outline:none;-webkit-user-select:none;user-select:none}.mk-mini-date__seg--empty{color:var(--mk-text-subtle)}.mk-mini-date__seg:focus-visible,.mk-mini-date__seg:focus{background-color:var(--mk-primary);color:var(--mk-primary-contrast)}.mk-mini-date__sep{color:var(--mk-text-muted);-webkit-user-select:none;user-select:none}@media(prefers-reduced-motion:reduce){:host{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3370
+ }
3371
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkMiniDate, decorators: [{
3372
+ type: Component,
3373
+ args: [{ selector: 'mk-mini-date', changeDetection: ChangeDetectionStrategy.OnPush, exportAs: 'mkMiniDate', host: {
3374
+ class: 'mk-mini-date',
3375
+ role: 'group',
3376
+ '[attr.aria-labelledby]': 'labelledBy()',
3377
+ '[attr.aria-describedby]': 'describedBy()',
3378
+ '[attr.aria-invalid]': 'isInvalid() || null',
3379
+ '[attr.aria-required]': 'isRequired() || null',
3380
+ '[class.mk-mini-date--sm]': "effectiveSize() === 'sm'",
3381
+ '[class.mk-mini-date--lg]': "effectiveSize() === 'lg'",
3382
+ '[class.mk-mini-date--invalid]': 'isInvalid()',
3383
+ '[class.mk-mini-date--disabled]': 'isDisabled()',
3384
+ }, providers: [
3385
+ {
3386
+ provide: NG_VALUE_ACCESSOR,
3387
+ useExisting: forwardRef(() => MkMiniDate),
3388
+ multi: true,
3389
+ },
3390
+ {
3391
+ provide: NG_VALIDATORS,
3392
+ useExisting: forwardRef(() => MkMiniDate),
3393
+ multi: true,
3394
+ },
3395
+ ], template: "@for (part of parts(); track part.key; let last = $last) {\n <span\n #seg\n class=\"mk-mini-date__seg\"\n [class.mk-mini-date__seg--empty]=\"!part.filled\"\n [id]=\"segId + '-' + part.key\"\n role=\"spinbutton\"\n [attr.tabindex]=\"isDisabled() ? -1 : 0\"\n [attr.aria-label]=\"part.label\"\n [attr.aria-valuenow]=\"part.valueNow\"\n [attr.aria-valuemin]=\"part.valueMin\"\n [attr.aria-valuemax]=\"part.valueMax\"\n [attr.aria-valuetext]=\"part.valueText\"\n [attr.aria-disabled]=\"isDisabled() || null\"\n (keydown)=\"onSegKeydown(part.key, $index, $event)\"\n (focus)=\"onSegFocus(part.key)\"\n (blur)=\"onBlur()\"\n >{{ part.display }}</span>\n @if (!last) {\n <span class=\"mk-mini-date__sep\" aria-hidden=\"true\">/</span>\n }\n}\n", styles: [":host{display:inline-flex;align-items:center;gap:.125rem;height:var(--mk-control-height-md);padding:0 var(--mk-space-2);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-md);font-family:var(--mk-font-sans);font-size:var(--mk-font-size-md);color:var(--mk-text);transition:border-color var(--mk-duration-fast) var(--mk-ease-standard),box-shadow var(--mk-duration-fast) var(--mk-ease-standard)}:host(.mk-mini-date--sm){height:var(--mk-control-height-sm);border-radius:var(--mk-radius-sm);font-size:var(--mk-font-size-sm)}:host(.mk-mini-date--lg){height:var(--mk-control-height-lg);font-size:var(--mk-font-size-lg)}:host(:focus-within){border-color:var(--mk-primary);outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}:host(.mk-mini-date--invalid){border-color:var(--mk-danger)}:host(.mk-mini-date--disabled){background-color:var(--mk-surface-2);opacity:.7;pointer-events:none}.mk-mini-date__seg{padding:0 var(--mk-space-1);border-radius:var(--mk-radius-sm);font-variant-numeric:tabular-nums;text-align:center;cursor:default;outline:none;-webkit-user-select:none;user-select:none}.mk-mini-date__seg--empty{color:var(--mk-text-subtle)}.mk-mini-date__seg:focus-visible,.mk-mini-date__seg:focus{background-color:var(--mk-primary);color:var(--mk-primary-contrast)}.mk-mini-date__sep{color:var(--mk-text-muted);-webkit-user-select:none;user-select:none}@media(prefers-reduced-motion:reduce){:host{transition:none}}\n"] }]
3396
+ }], propDecorators: { segEls: [{ type: i0.ViewChildren, args: ['seg', { isSignal: true }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], order: [{ type: i0.Input, args: [{ isSignal: true, alias: "order", required: false }] }], yearMin: [{ type: i0.Input, args: [{ isSignal: true, alias: "yearMin", required: false }] }], yearMax: [{ type: i0.Input, args: [{ isSignal: true, alias: "yearMax", required: false }] }] } });
3397
+
3398
+ /**
3399
+ * DATE & TIME components group barrel for @mk-kit/ui.
3400
+ * Re-exports every public component, the shared date helpers, and the
3401
+ * `MkDateRange` type.
3402
+ */
3403
+
3404
+ /**
3405
+ * Generated bundle index. Do not edit.
3406
+ */
3407
+
3408
+ export { MkCalendar, MkDatePicker, MkDateRangePicker, MkEventCalendar, MkMiniDate, MkMonthPicker, MkTimePicker, MkWeekPicker, addDays, addMonths, buildMonthMatrix, clampDate, endOfMonth, endOfWeek, formatDate, formatISODate, getISOWeek, getMonthNames, getWeekdayFullName, getWeekdayNames, isAfter, isBefore, isSameDay, isSameMonth, parseISODate, startOfDay, startOfMonth, startOfWeek };
3409
+ //# sourceMappingURL=mk-kit-ui-datetime.mjs.map