@nanahoshi/mona-ui 0.24.5 → 0.24.6
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.
- package/README.md +2 -0
- package/fesm2022/nanahoshi-mona-ui-calendar.mjs +196 -137
- package/fesm2022/nanahoshi-mona-ui-date-input.mjs +164 -3
- package/fesm2022/nanahoshi-mona-ui-date-picker.mjs +124 -140
- package/fesm2022/nanahoshi-mona-ui-datetime-picker.mjs +205 -202
- package/fesm2022/nanahoshi-mona-ui-filter.mjs +1 -1
- package/fesm2022/{nanahoshi-mona-ui-grid-grid-footer-row.component-BMCM0W8s.mjs → nanahoshi-mona-ui-grid-grid-footer-row.component-CD_ODHpU.mjs} +2 -2
- package/fesm2022/{nanahoshi-mona-ui-grid-grid-list.component-BSlV8Sua.mjs → nanahoshi-mona-ui-grid-grid-list.component-0jN7I1jo.mjs} +4 -4
- package/fesm2022/{nanahoshi-mona-ui-grid-grid-row.component-CTOdnH29.mjs → nanahoshi-mona-ui-grid-grid-row.component-BI_iE7pl.mjs} +3 -3
- package/fesm2022/{nanahoshi-mona-ui-grid-grid-virtual-list.component-C2kf5MwO.mjs → nanahoshi-mona-ui-grid-grid-virtual-list.component-Sa1aAtJ-.mjs} +4 -4
- package/fesm2022/{nanahoshi-mona-ui-grid-nanahoshi-mona-ui-grid-Dh42tUui.mjs → nanahoshi-mona-ui-grid-nanahoshi-mona-ui-grid-BvUM9nPt.mjs} +8 -8
- package/fesm2022/nanahoshi-mona-ui-grid.mjs +1 -1
- package/fesm2022/nanahoshi-mona-ui-numeric-text-box.mjs +195 -122
- package/fesm2022/nanahoshi-mona-ui-time-picker.mjs +149 -167
- package/fesm2022/nanahoshi-mona-ui-time-selector.mjs +182 -137
- package/package.json +2 -2
- package/types/nanahoshi-mona-ui-calendar.d.ts +30 -11
- package/types/nanahoshi-mona-ui-date-input.d.ts +74 -3
- package/types/nanahoshi-mona-ui-date-picker.d.ts +61 -33
- package/types/nanahoshi-mona-ui-datetime-picker.d.ts +79 -38
- package/types/nanahoshi-mona-ui-numeric-text-box.d.ts +57 -16
- package/types/nanahoshi-mona-ui-time-picker.d.ts +72 -42
- package/types/nanahoshi-mona-ui-time-selector.d.ts +78 -46
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { NgTemplateOutlet } from '@angular/common';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { inject, TemplateRef, Directive, computed, input, output, DestroyRef, ElementRef, signal, contentChild, linkedSignal, model, afterNextRender, effect, untracked, Component } from '@angular/core';
|
|
3
|
+
import { inject, TemplateRef, Directive, computed, input, output, DestroyRef, ElementRef, signal, contentChild, linkedSignal, booleanAttribute, model, afterNextRender, effect, untracked, Component } from '@angular/core';
|
|
4
4
|
import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
5
5
|
import { LucideChevronLeft, LucideChevronRight } from '@lucide/angular';
|
|
6
|
-
import {
|
|
6
|
+
import { Dictionary, lastOrDefault, range, index, select } from '@mirei/ts-collections';
|
|
7
7
|
import { ButtonDirective } from '@nanahoshi/mona-ui/button';
|
|
8
8
|
import { PreventableEvent, TakeFirstPipe } from '@nanahoshi/mona-ui/common';
|
|
9
9
|
import { CalendarService } from '@nanahoshi/mona-ui/date-input';
|
|
@@ -11,7 +11,7 @@ import { themeRaisedBackdropClasses, createElementControlId, rxTimeout } from '@
|
|
|
11
11
|
import { DateTime } from 'luxon';
|
|
12
12
|
import { skip, fromEvent } from 'rxjs';
|
|
13
13
|
import { twMerge } from 'tailwind-merge';
|
|
14
|
-
import { formatNumber, MonaI18nService, injectComponentDirection, gregorianDateTime,
|
|
14
|
+
import { formatNumber, MonaI18nService, formatGregorianDateToLocaleString, injectComponentDirection, gregorianDateTime, getLocaleFirstDayOfWeek, gregorianDateTimeFromObject } from '@nanahoshi/mona-ui/i18n';
|
|
15
15
|
import { cva } from 'class-variance-authority';
|
|
16
16
|
|
|
17
17
|
// Generated from i18n/catalogs/en-US.messages.json by scripts/generate-locale-catalogs.ts. Do not edit.
|
|
@@ -212,10 +212,10 @@ const calendarDecadeViewCellThemeVariants = cva(`
|
|
|
212
212
|
}
|
|
213
213
|
});
|
|
214
214
|
|
|
215
|
+
/** One year of the decade view: a grid cell. */
|
|
215
216
|
class DecadeYearDirective {
|
|
216
217
|
constructor() {
|
|
217
218
|
this.#i18n = inject(MonaI18nService);
|
|
218
|
-
this.messages = this.#i18n.componentMessages("calendar", CALENDAR_DEFAULT_MESSAGES);
|
|
219
219
|
this.ariaLabel = computed(() => this.messages().yearCellLabel(this.year()), /* @ts-ignore */
|
|
220
220
|
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
221
221
|
this.baseClass = computed(() => {
|
|
@@ -233,6 +233,7 @@ class DecadeYearDirective {
|
|
|
233
233
|
return this.year() === DateTime.now().year;
|
|
234
234
|
}, /* @ts-ignore */
|
|
235
235
|
...(ngDevMode ? [{ debugName: "isCurrent" }] : /* istanbul ignore next */ []));
|
|
236
|
+
this.messages = this.#i18n.componentMessages("calendar", CALENDAR_DEFAULT_MESSAGES);
|
|
236
237
|
this.selected = computed(() => {
|
|
237
238
|
const selectedDate = this.selectedDate();
|
|
238
239
|
if (!selectedDate) {
|
|
@@ -241,6 +242,9 @@ class DecadeYearDirective {
|
|
|
241
242
|
return this.year() === DateTime.fromJSDate(selectedDate).year;
|
|
242
243
|
}, /* @ts-ignore */
|
|
243
244
|
...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
|
|
245
|
+
/** Whether the whole calendar is disabled. */
|
|
246
|
+
this.disabled = input(false, /* @ts-ignore */
|
|
247
|
+
...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
244
248
|
this.navigatedDate = input.required(/* @ts-ignore */
|
|
245
249
|
...(ngDevMode ? [{ debugName: "navigatedDate" }] : /* istanbul ignore next */ []));
|
|
246
250
|
this.rounded = input.required(/* @ts-ignore */
|
|
@@ -252,22 +256,23 @@ class DecadeYearDirective {
|
|
|
252
256
|
}
|
|
253
257
|
#i18n;
|
|
254
258
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: DecadeYearDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
255
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: DecadeYearDirective, isStandalone: true, selector: "[monaDecadeYear]", inputs: { navigatedDate: { classPropertyName: "navigatedDate", publicName: "navigatedDate", isSignal: true, isRequired: true, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: true, transformFunction: null }, selectedDate: { classPropertyName: "selectedDate", publicName: "selectedDate", isSignal: true, isRequired: false, transformFunction: null }, year: { classPropertyName: "year", publicName: "year", isSignal: true, isRequired: true, transformFunction: null } }, host: { attributes: { "role": "gridcell" }, properties: { "attr.tabindex": "focused() ? 0 : -1", "attr.aria-current": "isCurrent() ? 'true' : null", "attr.aria-label": "ariaLabel()", "attr.aria-selected": "selected() ? 'true' : null", "class": "baseClass()" } }, ngImport: i0 }); }
|
|
259
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: DecadeYearDirective, isStandalone: true, selector: "[monaDecadeYear]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, navigatedDate: { classPropertyName: "navigatedDate", publicName: "navigatedDate", isSignal: true, isRequired: true, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: true, transformFunction: null }, selectedDate: { classPropertyName: "selectedDate", publicName: "selectedDate", isSignal: true, isRequired: false, transformFunction: null }, year: { classPropertyName: "year", publicName: "year", isSignal: true, isRequired: true, transformFunction: null } }, host: { attributes: { "role": "gridcell" }, properties: { "attr.tabindex": "focused() && !disabled() ? 0 : -1", "attr.aria-current": "isCurrent() ? 'true' : null", "attr.aria-disabled": "disabled() ? 'true' : null", "attr.aria-label": "ariaLabel()", "attr.aria-selected": "selected() ? 'true' : null", "class": "baseClass()" } }, ngImport: i0 }); }
|
|
256
260
|
}
|
|
257
261
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: DecadeYearDirective, decorators: [{
|
|
258
262
|
type: Directive,
|
|
259
263
|
args: [{
|
|
260
264
|
selector: "[monaDecadeYear]",
|
|
261
265
|
host: {
|
|
262
|
-
"[attr.tabindex]": "focused() ? 0 : -1",
|
|
266
|
+
"[attr.tabindex]": "focused() && !disabled() ? 0 : -1",
|
|
263
267
|
"[attr.aria-current]": "isCurrent() ? 'true' : null",
|
|
268
|
+
"[attr.aria-disabled]": "disabled() ? 'true' : null",
|
|
264
269
|
"[attr.aria-label]": "ariaLabel()",
|
|
265
270
|
"[attr.aria-selected]": "selected() ? 'true' : null",
|
|
266
271
|
"[class]": "baseClass()",
|
|
267
272
|
role: "gridcell"
|
|
268
273
|
}
|
|
269
274
|
}]
|
|
270
|
-
}], propDecorators: { navigatedDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "navigatedDate", required: true }] }], rounded: [{ type: i0.Input, args: [{ isSignal: true, alias: "rounded", required: true }] }], selectedDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedDate", required: false }] }], year: [{ type: i0.Input, args: [{ isSignal: true, alias: "year", required: true }] }] } });
|
|
275
|
+
}], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], navigatedDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "navigatedDate", required: true }] }], rounded: [{ type: i0.Input, args: [{ isSignal: true, alias: "rounded", required: true }] }], selectedDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedDate", required: false }] }], year: [{ type: i0.Input, args: [{ isSignal: true, alias: "year", required: true }] }] } });
|
|
271
276
|
|
|
272
277
|
const compareDates = (date1, date2, operator) => {
|
|
273
278
|
if (!date1 || !date2) {
|
|
@@ -309,9 +314,25 @@ const compareDates = (date1, date2, operator) => {
|
|
|
309
314
|
}
|
|
310
315
|
return operator === "==";
|
|
311
316
|
};
|
|
317
|
+
/** A key that is equal for two dates on the same local calendar day, whatever their time. */
|
|
318
|
+
const toDayKey = (date) => `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
|
|
312
319
|
|
|
320
|
+
/**
|
|
321
|
+
* One day of the month view: a grid cell named by its full date ("Tuesday, November 4, 2026"). The calendar passes
|
|
322
|
+
* the selection and the range preview as sets of day keys, so each cell answers in constant time.
|
|
323
|
+
*/
|
|
313
324
|
class MonthDayDirective {
|
|
314
325
|
constructor() {
|
|
326
|
+
this.#i18n = inject(MonaI18nService);
|
|
327
|
+
this.#key = computed(() => toDayKey(this.entry().key), /* @ts-ignore */
|
|
328
|
+
...(ngDevMode ? [{ debugName: "#key" }] : /* istanbul ignore next */ []));
|
|
329
|
+
this.ariaLabel = computed(() => formatGregorianDateToLocaleString(this.entry().key, this.#i18n.localeId(), {
|
|
330
|
+
weekday: "long",
|
|
331
|
+
year: "numeric",
|
|
332
|
+
month: "long",
|
|
333
|
+
day: "numeric"
|
|
334
|
+
}), /* @ts-ignore */
|
|
335
|
+
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
315
336
|
this.baseClass = computed(() => {
|
|
316
337
|
const disabled = this.dayDisabled();
|
|
317
338
|
const focused = this.focused();
|
|
@@ -331,33 +352,11 @@ class MonthDayDirective {
|
|
|
331
352
|
});
|
|
332
353
|
}, /* @ts-ignore */
|
|
333
354
|
...(ngDevMode ? [{ debugName: "baseClass" }] : /* istanbul ignore next */ []));
|
|
334
|
-
this.dayDisabled = computed(() =>
|
|
335
|
-
const disabled = this.disabled();
|
|
336
|
-
const entry = this.entry();
|
|
337
|
-
const disabledDates = this.disabledDates();
|
|
338
|
-
const max = this.max();
|
|
339
|
-
const min = this.min();
|
|
340
|
-
let isDisabledByInput = false;
|
|
341
|
-
if (typeof disabledDates === "function") {
|
|
342
|
-
isDisabledByInput = disabledDates(entry.key);
|
|
343
|
-
}
|
|
344
|
-
else if (disabledDates) {
|
|
345
|
-
isDisabledByInput = any(disabledDates, d => compareDates(entry.key, d, "=="));
|
|
346
|
-
}
|
|
347
|
-
return disabled || isDisabledByInput || compareDates(entry.key, max, ">") || compareDates(entry.key, min, "<");
|
|
348
|
-
}, /* @ts-ignore */
|
|
355
|
+
this.dayDisabled = computed(() => this.disabled() || this.isDayUnavailable()(this.entry().key), /* @ts-ignore */
|
|
349
356
|
...(ngDevMode ? [{ debugName: "dayDisabled" }] : /* istanbul ignore next */ []));
|
|
350
|
-
this.focused = computed(() =>
|
|
351
|
-
const entry = this.entry();
|
|
352
|
-
const navigatedDate = this.navigatedDate();
|
|
353
|
-
return compareDates(entry.key, navigatedDate, "==");
|
|
354
|
-
}, /* @ts-ignore */
|
|
357
|
+
this.focused = computed(() => compareDates(this.entry().key, this.navigatedDate(), "=="), /* @ts-ignore */
|
|
355
358
|
...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
|
|
356
|
-
this.isToday = computed(() =>
|
|
357
|
-
const entry = this.entry();
|
|
358
|
-
const today = new Date();
|
|
359
|
-
return compareDates(entry.key, today, "==");
|
|
360
|
-
}, /* @ts-ignore */
|
|
359
|
+
this.isToday = computed(() => compareDates(this.entry().key, this.today(), "=="), /* @ts-ignore */
|
|
361
360
|
...(ngDevMode ? [{ debugName: "isToday" }] : /* istanbul ignore next */ []));
|
|
362
361
|
this.outside = computed(() => {
|
|
363
362
|
const entry = this.entry();
|
|
@@ -366,43 +365,35 @@ class MonthDayDirective {
|
|
|
366
365
|
return compareDates(entry.key, monthStart, "<") || compareDates(entry.key, monthEnd, ">");
|
|
367
366
|
}, /* @ts-ignore */
|
|
368
367
|
...(ngDevMode ? [{ debugName: "outside" }] : /* istanbul ignore next */ []));
|
|
369
|
-
this.rangePreviewed = computed(() =>
|
|
370
|
-
const entry = this.entry();
|
|
371
|
-
return !this.selected() && exactly(this.rangePreview(), 1, d => compareDates(entry.key, d, "=="));
|
|
372
|
-
}, /* @ts-ignore */
|
|
368
|
+
this.rangePreviewed = computed(() => !this.selected() && this.rangePreviewKeys().has(this.#key()), /* @ts-ignore */
|
|
373
369
|
...(ngDevMode ? [{ debugName: "rangePreviewed" }] : /* istanbul ignore next */ []));
|
|
374
|
-
this.selected = computed(() =>
|
|
375
|
-
const entry = this.entry();
|
|
376
|
-
const value = this.value();
|
|
377
|
-
if (Array.isArray(value)) {
|
|
378
|
-
return exactly(value, 1, d => compareDates(entry.key, d, "=="));
|
|
379
|
-
}
|
|
380
|
-
return compareDates(entry.key, value, "==");
|
|
381
|
-
}, /* @ts-ignore */
|
|
370
|
+
this.selected = computed(() => this.selectedKeys().has(this.#key()), /* @ts-ignore */
|
|
382
371
|
...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
|
|
383
372
|
this.dayPointerEnter = output();
|
|
384
373
|
this.daySelect = output();
|
|
374
|
+
/** Whether the whole calendar is disabled. */
|
|
385
375
|
this.disabled = input.required(/* @ts-ignore */
|
|
386
376
|
...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
387
|
-
this.disabledDates = input.required(/* @ts-ignore */
|
|
388
|
-
...(ngDevMode ? [{ debugName: "disabledDates" }] : /* istanbul ignore next */ []));
|
|
389
377
|
this.entry = input.required(/* @ts-ignore */
|
|
390
378
|
...(ngDevMode ? [{ debugName: "entry" }] : /* istanbul ignore next */ []));
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
|
|
379
|
+
/** Whether a day can't be picked: out of range or in `disabledDates`. */
|
|
380
|
+
this.isDayUnavailable = input.required(/* @ts-ignore */
|
|
381
|
+
...(ngDevMode ? [{ debugName: "isDayUnavailable" }] : /* istanbul ignore next */ []));
|
|
395
382
|
this.monthBounds = input.required(/* @ts-ignore */
|
|
396
383
|
...(ngDevMode ? [{ debugName: "monthBounds" }] : /* istanbul ignore next */ []));
|
|
397
384
|
this.navigatedDate = input.required(/* @ts-ignore */
|
|
398
385
|
...(ngDevMode ? [{ debugName: "navigatedDate" }] : /* istanbul ignore next */ []));
|
|
399
|
-
this.
|
|
400
|
-
...(ngDevMode ? [{ debugName: "
|
|
386
|
+
this.rangePreviewKeys = input.required(/* @ts-ignore */
|
|
387
|
+
...(ngDevMode ? [{ debugName: "rangePreviewKeys" }] : /* istanbul ignore next */ []));
|
|
401
388
|
this.rounded = input.required(/* @ts-ignore */
|
|
402
389
|
...(ngDevMode ? [{ debugName: "rounded" }] : /* istanbul ignore next */ []));
|
|
403
|
-
this.
|
|
404
|
-
...(ngDevMode ? [{ debugName: "
|
|
390
|
+
this.selectedKeys = input.required(/* @ts-ignore */
|
|
391
|
+
...(ngDevMode ? [{ debugName: "selectedKeys" }] : /* istanbul ignore next */ []));
|
|
392
|
+
this.today = input.required(/* @ts-ignore */
|
|
393
|
+
...(ngDevMode ? [{ debugName: "today" }] : /* istanbul ignore next */ []));
|
|
405
394
|
}
|
|
395
|
+
#i18n;
|
|
396
|
+
#key;
|
|
406
397
|
onClick(event) {
|
|
407
398
|
if (this.dayDisabled()) {
|
|
408
399
|
return;
|
|
@@ -417,31 +408,34 @@ class MonthDayDirective {
|
|
|
417
408
|
this.dayPointerEnter.emit(this.entry().key);
|
|
418
409
|
}
|
|
419
410
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: MonthDayDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
420
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: MonthDayDirective, isStandalone: true, selector: "[monaMonthDay]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: true, transformFunction: null },
|
|
411
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: MonthDayDirective, isStandalone: true, selector: "[monaMonthDay]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: true, transformFunction: null }, entry: { classPropertyName: "entry", publicName: "entry", isSignal: true, isRequired: true, transformFunction: null }, isDayUnavailable: { classPropertyName: "isDayUnavailable", publicName: "isDayUnavailable", isSignal: true, isRequired: true, transformFunction: null }, monthBounds: { classPropertyName: "monthBounds", publicName: "monthBounds", isSignal: true, isRequired: true, transformFunction: null }, navigatedDate: { classPropertyName: "navigatedDate", publicName: "navigatedDate", isSignal: true, isRequired: true, transformFunction: null }, rangePreviewKeys: { classPropertyName: "rangePreviewKeys", publicName: "rangePreviewKeys", isSignal: true, isRequired: true, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: true, transformFunction: null }, selectedKeys: { classPropertyName: "selectedKeys", publicName: "selectedKeys", isSignal: true, isRequired: true, transformFunction: null }, today: { classPropertyName: "today", publicName: "today", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { dayPointerEnter: "dayPointerEnter", daySelect: "daySelect" }, host: { attributes: { "role": "gridcell" }, listeners: { "click": "onClick($event)", "pointerenter": "onPointerEnter()" }, properties: { "attr.tabindex": "focused() && !disabled() ? 0 : -1", "attr.aria-current": "isToday() ? 'date' : null", "attr.aria-disabled": "dayDisabled() ? 'true' : null", "attr.aria-label": "ariaLabel()", "attr.aria-selected": "selected() ? 'true' : null", "attr.data-range-preview": "rangePreviewed() ? 'true' : null", "class": "baseClass()" } }, ngImport: i0 }); }
|
|
421
412
|
}
|
|
422
413
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: MonthDayDirective, decorators: [{
|
|
423
414
|
type: Directive,
|
|
424
415
|
args: [{
|
|
425
416
|
selector: "[monaMonthDay]",
|
|
426
417
|
host: {
|
|
427
|
-
"[attr.tabindex]": "focused() ? 0 : -1",
|
|
428
|
-
"[attr.aria-pressed]": "selected() ? 'true' : 'false'",
|
|
418
|
+
"[attr.tabindex]": "focused() && !disabled() ? 0 : -1",
|
|
429
419
|
"[attr.aria-current]": "isToday() ? 'date' : null",
|
|
430
420
|
"[attr.aria-disabled]": "dayDisabled() ? 'true' : null",
|
|
421
|
+
"[attr.aria-label]": "ariaLabel()",
|
|
422
|
+
"[attr.aria-selected]": "selected() ? 'true' : null",
|
|
431
423
|
"[attr.data-range-preview]": "rangePreviewed() ? 'true' : null",
|
|
432
424
|
"[class]": "baseClass()",
|
|
433
425
|
"(click)": "onClick($event)",
|
|
434
426
|
"(pointerenter)": "onPointerEnter()",
|
|
435
|
-
role: "
|
|
427
|
+
role: "gridcell"
|
|
436
428
|
}
|
|
437
429
|
}]
|
|
438
|
-
}], propDecorators: { dayPointerEnter: [{ type: i0.Output, args: ["dayPointerEnter"] }], daySelect: [{ type: i0.Output, args: ["daySelect"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: true }] }],
|
|
430
|
+
}], propDecorators: { dayPointerEnter: [{ type: i0.Output, args: ["dayPointerEnter"] }], daySelect: [{ type: i0.Output, args: ["daySelect"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: true }] }], entry: [{ type: i0.Input, args: [{ isSignal: true, alias: "entry", required: true }] }], isDayUnavailable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDayUnavailable", required: true }] }], monthBounds: [{ type: i0.Input, args: [{ isSignal: true, alias: "monthBounds", required: true }] }], navigatedDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "navigatedDate", required: true }] }], rangePreviewKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangePreviewKeys", required: true }] }], rounded: [{ type: i0.Input, args: [{ isSignal: true, alias: "rounded", required: true }] }], selectedKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedKeys", required: true }] }], today: [{ type: i0.Input, args: [{ isSignal: true, alias: "today", required: true }] }] } });
|
|
439
431
|
|
|
432
|
+
/** One month of the year view: a grid cell named by the long month and the year ("November 2026"). */
|
|
440
433
|
class YearMonthDirective {
|
|
441
434
|
constructor() {
|
|
435
|
+
this.#i18n = inject(MonaI18nService);
|
|
442
436
|
this.ariaLabel = computed(() => {
|
|
443
|
-
const
|
|
444
|
-
return
|
|
437
|
+
const date = new Date(this.navigatedDate().getFullYear(), this.month() - 1, 1);
|
|
438
|
+
return formatGregorianDateToLocaleString(date, this.#i18n.localeId(), { year: "numeric", month: "long" });
|
|
445
439
|
}, /* @ts-ignore */
|
|
446
440
|
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
447
441
|
this.baseClass = computed(() => {
|
|
@@ -471,10 +465,11 @@ class YearMonthDirective {
|
|
|
471
465
|
return this.month() === selDate.month && navDate.year === selDate.year;
|
|
472
466
|
}, /* @ts-ignore */
|
|
473
467
|
...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
|
|
468
|
+
/** Whether the whole calendar is disabled. */
|
|
469
|
+
this.disabled = input(false, /* @ts-ignore */
|
|
470
|
+
...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
474
471
|
this.month = input.required(/* @ts-ignore */
|
|
475
472
|
...(ngDevMode ? [{ debugName: "month" }] : /* istanbul ignore next */ []));
|
|
476
|
-
this.monthName = input.required(/* @ts-ignore */
|
|
477
|
-
...(ngDevMode ? [{ debugName: "monthName" }] : /* istanbul ignore next */ []));
|
|
478
473
|
this.navigatedDate = input.required(/* @ts-ignore */
|
|
479
474
|
...(ngDevMode ? [{ debugName: "navigatedDate" }] : /* istanbul ignore next */ []));
|
|
480
475
|
this.rounded = input.required(/* @ts-ignore */
|
|
@@ -482,24 +477,34 @@ class YearMonthDirective {
|
|
|
482
477
|
this.selectedDate = input(/* @ts-ignore */
|
|
483
478
|
...(ngDevMode ? [undefined, { debugName: "selectedDate" }] : /* istanbul ignore next */ []));
|
|
484
479
|
}
|
|
480
|
+
#i18n;
|
|
485
481
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: YearMonthDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
486
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: YearMonthDirective, isStandalone: true, selector: "[monaYearMonth]", inputs: {
|
|
482
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: YearMonthDirective, isStandalone: true, selector: "[monaYearMonth]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, month: { classPropertyName: "month", publicName: "month", isSignal: true, isRequired: true, transformFunction: null }, navigatedDate: { classPropertyName: "navigatedDate", publicName: "navigatedDate", isSignal: true, isRequired: true, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: true, transformFunction: null }, selectedDate: { classPropertyName: "selectedDate", publicName: "selectedDate", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "gridcell" }, properties: { "attr.tabindex": "focused() && !disabled() ? 0 : -1", "attr.aria-current": "isCurrent() ? 'date' : null", "attr.aria-disabled": "disabled() ? 'true' : null", "attr.aria-label": "ariaLabel()", "attr.aria-selected": "selected() ? 'true' : null", "class": "baseClass()" } }, ngImport: i0 }); }
|
|
487
483
|
}
|
|
488
484
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: YearMonthDirective, decorators: [{
|
|
489
485
|
type: Directive,
|
|
490
486
|
args: [{
|
|
491
487
|
selector: "[monaYearMonth]",
|
|
492
488
|
host: {
|
|
493
|
-
"[attr.tabindex]": "focused() ? 0 : -1",
|
|
489
|
+
"[attr.tabindex]": "focused() && !disabled() ? 0 : -1",
|
|
494
490
|
"[attr.aria-current]": "isCurrent() ? 'date' : null",
|
|
491
|
+
"[attr.aria-disabled]": "disabled() ? 'true' : null",
|
|
495
492
|
"[attr.aria-label]": "ariaLabel()",
|
|
496
493
|
"[attr.aria-selected]": "selected() ? 'true' : null",
|
|
497
494
|
"[class]": "baseClass()",
|
|
498
495
|
role: "gridcell"
|
|
499
496
|
}
|
|
500
497
|
}]
|
|
501
|
-
}], propDecorators: {
|
|
498
|
+
}], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], month: [{ type: i0.Input, args: [{ isSignal: true, alias: "month", required: true }] }], navigatedDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "navigatedDate", required: true }] }], rounded: [{ type: i0.Input, args: [{ isSignal: true, alias: "rounded", required: true }] }], selectedDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedDate", required: false }] }] } });
|
|
502
499
|
|
|
500
|
+
/** Midnight at the start of the local day `date` falls on. */
|
|
501
|
+
function startOfDay(date) {
|
|
502
|
+
return DateTime.fromJSDate(date).startOf("day").toJSDate();
|
|
503
|
+
}
|
|
504
|
+
/** Whole calendar days from `start` to `end`, whatever their times and across DST changes. */
|
|
505
|
+
function calendarDaysBetween(start, end) {
|
|
506
|
+
return Math.round(end.startOf("day").diff(start.startOf("day"), "days").days);
|
|
507
|
+
}
|
|
503
508
|
const FIRST_DAY_OF_WEEK_TO_ISO = {
|
|
504
509
|
monday: 1,
|
|
505
510
|
tuesday: 2,
|
|
@@ -638,6 +643,25 @@ class CalendarComponent {
|
|
|
638
643
|
...(ngDevMode ? [{ debugName: "headerYear" }] : /* istanbul ignore next */ []));
|
|
639
644
|
this.invalidState = computed(() => this.touched() && (this.invalid() || (this.required() && this.isEmptyValue())), /* @ts-ignore */
|
|
640
645
|
...(ngDevMode ? [{ debugName: "invalidState" }] : /* istanbul ignore next */ []));
|
|
646
|
+
/**
|
|
647
|
+
* Whether a day can't be picked: outside `minDate`/`maxDate` or in `disabledDates`. An iterable of disabled
|
|
648
|
+
* dates is turned into a set of day keys once, so each of the 42 cells answers in constant time.
|
|
649
|
+
*/
|
|
650
|
+
this.isDayUnavailable = computed(() => {
|
|
651
|
+
const disabledDates = this.disabledDates();
|
|
652
|
+
const min = this.minDate();
|
|
653
|
+
const max = this.maxDate();
|
|
654
|
+
const isOutOfRange = (date) => compareDates(date, max, ">") || compareDates(date, min, "<");
|
|
655
|
+
if (typeof disabledDates === "function") {
|
|
656
|
+
return date => isOutOfRange(date) || disabledDates(date);
|
|
657
|
+
}
|
|
658
|
+
const keys = new Set();
|
|
659
|
+
for (const disabledDate of disabledDates ?? []) {
|
|
660
|
+
keys.add(toDayKey(disabledDate));
|
|
661
|
+
}
|
|
662
|
+
return date => isOutOfRange(date) || keys.has(toDayKey(date));
|
|
663
|
+
}, /* @ts-ignore */
|
|
664
|
+
...(ngDevMode ? [{ debugName: "isDayUnavailable" }] : /* istanbul ignore next */ []));
|
|
641
665
|
this.messages = this.#i18n.componentMessages("calendar", CALENDAR_DEFAULT_MESSAGES);
|
|
642
666
|
this.monthBounds = computed(() => {
|
|
643
667
|
const navigatedDate = this.navigatedDate();
|
|
@@ -675,7 +699,8 @@ class CalendarComponent {
|
|
|
675
699
|
.toImmutableSet();
|
|
676
700
|
}, /* @ts-ignore */
|
|
677
701
|
...(ngDevMode ? [{ debugName: "months" }] : /* istanbul ignore next */ []));
|
|
678
|
-
|
|
702
|
+
// Midnight, so a date picked with the keyboard before any value exists carries no clock time.
|
|
703
|
+
this.navigatedDate = signal(startOfDay(new Date()), /* @ts-ignore */
|
|
679
704
|
...(ngDevMode ? [{ debugName: "navigatedDate" }] : /* istanbul ignore next */ []));
|
|
680
705
|
this.nextButtonLabel = computed(() => {
|
|
681
706
|
const view = this.calendarView();
|
|
@@ -718,6 +743,8 @@ class CalendarComponent {
|
|
|
718
743
|
return this.getInclusiveDateRange(pendingRangeStart, this.rangePreviewDate() ?? this.navigatedDate());
|
|
719
744
|
}, /* @ts-ignore */
|
|
720
745
|
...(ngDevMode ? [{ debugName: "rangePreviewDates" }] : /* istanbul ignore next */ []));
|
|
746
|
+
this.rangePreviewKeys = computed(() => new Set(this.rangePreviewDates().map(toDayKey)), /* @ts-ignore */
|
|
747
|
+
...(ngDevMode ? [{ debugName: "rangePreviewKeys" }] : /* istanbul ignore next */ []));
|
|
721
748
|
this.selectedDates = linkedSignal(() => {
|
|
722
749
|
const v = this.value();
|
|
723
750
|
const selection = this.selection();
|
|
@@ -729,7 +756,7 @@ class CalendarComponent {
|
|
|
729
756
|
const date2 = DateTime.fromJSDate(v[v.length - 1]);
|
|
730
757
|
const startDate = date1 <= date2 ? date1 : date2;
|
|
731
758
|
const endDate = date1 <= date2 ? date2 : date1;
|
|
732
|
-
const totalDays =
|
|
759
|
+
const totalDays = calendarDaysBetween(startDate, endDate) + 1;
|
|
733
760
|
return range(0, totalDays)
|
|
734
761
|
.select(i => startDate.plus({ days: i }).toJSDate())
|
|
735
762
|
.toArray();
|
|
@@ -737,10 +764,14 @@ class CalendarComponent {
|
|
|
737
764
|
return Array.isArray(v) ? v : [v];
|
|
738
765
|
}, /* @ts-ignore */
|
|
739
766
|
...(ngDevMode ? [{ debugName: "selectedDates" }] : /* istanbul ignore next */ []));
|
|
740
|
-
this.
|
|
767
|
+
this.selectedKeys = computed(() => new Set(this.selectedDates().map(toDayKey)), /* @ts-ignore */
|
|
768
|
+
...(ngDevMode ? [{ debugName: "selectedKeys" }] : /* istanbul ignore next */ []));
|
|
769
|
+
/** Today at midnight. Refreshed when focus enters the calendar, so a page left open overnight moves on. */
|
|
770
|
+
this.today = signal(startOfDay(new Date()), /* @ts-ignore */
|
|
771
|
+
...(ngDevMode ? [{ debugName: "today" }] : /* istanbul ignore next */ []));
|
|
741
772
|
this.todayButtonLabel = computed(() => {
|
|
742
773
|
const locale = this.#i18n.localeId();
|
|
743
|
-
const todayStr = formatGregorianDateToLocaleString(
|
|
774
|
+
const todayStr = formatGregorianDateToLocaleString(this.today(), locale, {
|
|
744
775
|
year: "numeric",
|
|
745
776
|
month: "long",
|
|
746
777
|
day: "numeric"
|
|
@@ -748,6 +779,8 @@ class CalendarComponent {
|
|
|
748
779
|
return this.messages().goToToday(todayStr);
|
|
749
780
|
}, /* @ts-ignore */
|
|
750
781
|
...(ngDevMode ? [{ debugName: "todayButtonLabel" }] : /* istanbul ignore next */ []));
|
|
782
|
+
this.todayUnavailable = computed(() => this.isDayUnavailable()(this.today()), /* @ts-ignore */
|
|
783
|
+
...(ngDevMode ? [{ debugName: "todayUnavailable" }] : /* istanbul ignore next */ []));
|
|
751
784
|
this.viewSwitchButtonLabel = computed(() => {
|
|
752
785
|
const view = this.calendarView();
|
|
753
786
|
const locale = this.#i18n.localeId();
|
|
@@ -795,8 +828,7 @@ class CalendarComponent {
|
|
|
795
828
|
/**
|
|
796
829
|
* @description Sets the disabled state of the calendar.
|
|
797
830
|
*/
|
|
798
|
-
this.disabled =
|
|
799
|
-
...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
831
|
+
this.disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
800
832
|
/**
|
|
801
833
|
* @description Sets the disabled dates of the calendar.
|
|
802
834
|
* Accepts either an iterable of Date objects or a predicate function.
|
|
@@ -809,13 +841,18 @@ class CalendarComponent {
|
|
|
809
841
|
*/
|
|
810
842
|
this.firstDay = input(null, /* @ts-ignore */
|
|
811
843
|
...(ngDevMode ? [{ debugName: "firstDay" }] : /* istanbul ignore next */ []));
|
|
844
|
+
/**
|
|
845
|
+
* @description Moves focus to the active day when the calendar first renders. An inline calendar on a page
|
|
846
|
+
* should leave focus where it is; a calendar that opens in a popup sets this so focus moves into it.
|
|
847
|
+
* @default false
|
|
848
|
+
*/
|
|
849
|
+
this.focusOnMount = input(false, { ...(ngDevMode ? { debugName: "focusOnMount" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
812
850
|
/**
|
|
813
851
|
* @description Marks the calendar as invalid. When bound to a signal form field via `[formField]`,
|
|
814
852
|
* this is written by the `FormField` directive.
|
|
815
853
|
* @default false
|
|
816
854
|
*/
|
|
817
|
-
this.invalid = input(false, /*
|
|
818
|
-
...(ngDevMode ? [{ debugName: "invalid" }] : /* istanbul ignore next */ []));
|
|
855
|
+
this.invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
819
856
|
/**
|
|
820
857
|
* @description Sets the maximum date that can be selected.
|
|
821
858
|
*/
|
|
@@ -830,15 +867,13 @@ class CalendarComponent {
|
|
|
830
867
|
* @description Sets the readonly state of the calendar.
|
|
831
868
|
* @default false
|
|
832
869
|
*/
|
|
833
|
-
this.readonly = input(false, /*
|
|
834
|
-
...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
|
|
870
|
+
this.readonly = input(false, { ...(ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
835
871
|
/**
|
|
836
872
|
* @description Sets whether the calendar is required. When bound to a signal form field via `[formField]`,
|
|
837
873
|
* this is written by the `FormField` directive.
|
|
838
874
|
* @default false
|
|
839
875
|
*/
|
|
840
|
-
this.required = input(false, /*
|
|
841
|
-
...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
|
|
876
|
+
this.required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
842
877
|
/**
|
|
843
878
|
* @description Sets the border radius of the calendar.
|
|
844
879
|
* @default "medium"
|
|
@@ -852,8 +887,8 @@ class CalendarComponent {
|
|
|
852
887
|
this.selection = input("single", /* @ts-ignore */
|
|
853
888
|
...(ngDevMode ? [{ debugName: "selection" }] : /* istanbul ignore next */ []));
|
|
854
889
|
/**
|
|
855
|
-
* @description Emitted when the calendar
|
|
856
|
-
* The `FormField` directive listens to this to mark the field as touched.
|
|
890
|
+
* @description Emitted when focus leaves the calendar, as a native input is touched on blur. Moving between
|
|
891
|
+
* its buttons and days doesn't count. The `FormField` directive listens to this to mark the field as touched.
|
|
857
892
|
*/
|
|
858
893
|
this.touch = output();
|
|
859
894
|
/**
|
|
@@ -861,14 +896,14 @@ class CalendarComponent {
|
|
|
861
896
|
* this is written by the `FormField` directive.
|
|
862
897
|
* @default false
|
|
863
898
|
*/
|
|
864
|
-
this.touched = input(false, /*
|
|
865
|
-
...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
|
|
899
|
+
this.touched = input(false, { ...(ngDevMode ? { debugName: "touched" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
866
900
|
/**
|
|
867
901
|
* @description Adds custom CSS classes to the calendar host element.
|
|
868
902
|
*/
|
|
869
903
|
this.userClass = input("", { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: "class" });
|
|
870
904
|
/**
|
|
871
|
-
* @description The current value of the calendar
|
|
905
|
+
* @description The current value of the calendar: a `Date` in single selection, the selected dates in multiple
|
|
906
|
+
* selection, and `[start, end]` in range selection. A range is written once both ends are picked.
|
|
872
907
|
* @default null
|
|
873
908
|
*/
|
|
874
909
|
this.value = model(null, /* @ts-ignore */
|
|
@@ -877,33 +912,36 @@ class CalendarComponent {
|
|
|
877
912
|
* @description Enables the display of week numbers in the calendar.
|
|
878
913
|
* @default false
|
|
879
914
|
*/
|
|
880
|
-
this.weekNumber = input(false, /*
|
|
881
|
-
...(ngDevMode ? [{ debugName: "weekNumber" }] : /* istanbul ignore next */ []));
|
|
915
|
+
this.weekNumber = input(false, { ...(ngDevMode ? { debugName: "weekNumber" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
882
916
|
afterNextRender({
|
|
883
917
|
read: () => {
|
|
884
918
|
this.setupKeyboardNavigation();
|
|
885
|
-
this.
|
|
919
|
+
if (this.focusOnMount()) {
|
|
920
|
+
this.focusActiveCell();
|
|
921
|
+
}
|
|
886
922
|
}
|
|
887
923
|
});
|
|
888
924
|
effect(() => {
|
|
889
925
|
const min = this.minDate();
|
|
890
926
|
const max = this.maxDate();
|
|
891
|
-
this.disabledDates();
|
|
892
927
|
untracked(() => {
|
|
928
|
+
// Keep the active day within the range. A day disabled through disabledDates can stay active: it is
|
|
929
|
+
// focusable and announced as unavailable, so the keyboard can move past it.
|
|
893
930
|
const navigationDate = this.navigatedDate();
|
|
894
|
-
if (
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
931
|
+
if (compareDates(navigationDate, min, "<") && min) {
|
|
932
|
+
this.navigatedDate.set(min);
|
|
933
|
+
}
|
|
934
|
+
else if (compareDates(navigationDate, max, ">") && max) {
|
|
935
|
+
this.navigatedDate.set(max);
|
|
899
936
|
}
|
|
900
937
|
});
|
|
901
938
|
});
|
|
902
939
|
effect(() => {
|
|
903
940
|
const v = this.value();
|
|
904
941
|
untracked(() => {
|
|
905
|
-
|
|
906
|
-
|
|
942
|
+
// Show the last selected date. Clearing the selection leaves the view where it is.
|
|
943
|
+
const lastDate = Array.isArray(v) ? lastOrDefault(v) : v;
|
|
944
|
+
if (lastDate) {
|
|
907
945
|
this.navigatedDate.set(lastDate);
|
|
908
946
|
}
|
|
909
947
|
});
|
|
@@ -957,6 +995,17 @@ class CalendarComponent {
|
|
|
957
995
|
}
|
|
958
996
|
this.rangePreviewDate.set(date);
|
|
959
997
|
}
|
|
998
|
+
onFocusOut(event) {
|
|
999
|
+
// Removing a focused calendar from the page fires focusout after it is destroyed.
|
|
1000
|
+
if (this.#destroyRef.destroyed) {
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
const nextTarget = event.relatedTarget;
|
|
1004
|
+
if (nextTarget instanceof Node && this.#hostElementRef.nativeElement.contains(nextTarget)) {
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
this.touch.emit();
|
|
1008
|
+
}
|
|
960
1009
|
onMonthClick(month) {
|
|
961
1010
|
this.navigatedDate.set(DateTime.fromJSDate(this.navigatedDate()).set({ month }).toJSDate());
|
|
962
1011
|
this.calendarView.set("month");
|
|
@@ -982,18 +1031,19 @@ class CalendarComponent {
|
|
|
982
1031
|
this.navigatedDate.set(direction === "prev" ? date.minus(unit).toJSDate() : date.plus(unit).toJSDate());
|
|
983
1032
|
}
|
|
984
1033
|
onTodayButtonClick() {
|
|
985
|
-
|
|
1034
|
+
this.refreshToday();
|
|
1035
|
+
if (this.disabled() || this.readonly() || this.todayUnavailable()) {
|
|
986
1036
|
return;
|
|
987
1037
|
}
|
|
988
|
-
|
|
989
|
-
const
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1038
|
+
// Today is picked as a click on its day would pick it, so the same rules apply.
|
|
1039
|
+
const today = this.today();
|
|
1040
|
+
if (this.selection() === "range") {
|
|
1041
|
+
this.handleRangeSelection(today);
|
|
1042
|
+
this.navigatedDate.set(today);
|
|
1043
|
+
}
|
|
1044
|
+
else {
|
|
1045
|
+
this.handleSingleSelection(today);
|
|
1046
|
+
}
|
|
997
1047
|
this.calendarView.set("month");
|
|
998
1048
|
this.commitUserSelection();
|
|
999
1049
|
}
|
|
@@ -1004,13 +1054,22 @@ class CalendarComponent {
|
|
|
1004
1054
|
this.navigatedDate.set(DateTime.fromJSDate(this.navigatedDate()).set({ year }).toJSDate());
|
|
1005
1055
|
this.calendarView.set("year");
|
|
1006
1056
|
}
|
|
1057
|
+
refreshToday() {
|
|
1058
|
+
const today = startOfDay(new Date());
|
|
1059
|
+
if (!compareDates(today, this.today(), "==")) {
|
|
1060
|
+
this.today.set(today);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1007
1063
|
clearPendingRange() {
|
|
1008
1064
|
this.pendingRangeStart.set(null);
|
|
1009
1065
|
this.rangePreviewDate.set(null);
|
|
1010
1066
|
}
|
|
1011
1067
|
commitUserSelection() {
|
|
1068
|
+
// A range is written once both ends are picked; the first click only starts it.
|
|
1069
|
+
if (this.selection() === "range" && this.pendingRangeStart() != null) {
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1012
1072
|
this.value.set(this.#outputValue());
|
|
1013
|
-
this.touch.emit();
|
|
1014
1073
|
}
|
|
1015
1074
|
extendSelection(offset) {
|
|
1016
1075
|
const currentDate = this.navigatedDate();
|
|
@@ -1048,7 +1107,7 @@ class CalendarComponent {
|
|
|
1048
1107
|
const lastSelectedDate = lastOrDefault(this.selectedDates()) ?? this.navigatedDate();
|
|
1049
1108
|
const startDate = lastSelectedDate < date ? lastSelectedDate : date;
|
|
1050
1109
|
const endDate = this.navigatedDate() > date ? this.navigatedDate() : date;
|
|
1051
|
-
const totalDays = DateTime.fromJSDate(
|
|
1110
|
+
const totalDays = calendarDaysBetween(DateTime.fromJSDate(startDate), DateTime.fromJSDate(endDate)) + 1;
|
|
1052
1111
|
return range(0, totalDays)
|
|
1053
1112
|
.select(i => DateTime.fromJSDate(startDate).plus({ days: i }).toJSDate())
|
|
1054
1113
|
.toArray();
|
|
@@ -1058,7 +1117,7 @@ class CalendarComponent {
|
|
|
1058
1117
|
const dateTime2 = DateTime.fromJSDate(date2);
|
|
1059
1118
|
const startDate = dateTime1 <= dateTime2 ? dateTime1 : dateTime2;
|
|
1060
1119
|
const endDate = dateTime1 <= dateTime2 ? dateTime2 : dateTime1;
|
|
1061
|
-
const totalDays =
|
|
1120
|
+
const totalDays = calendarDaysBetween(startDate, endDate) + 1;
|
|
1062
1121
|
return range(0, totalDays)
|
|
1063
1122
|
.select(i => startDate.plus({ days: i }).toJSDate())
|
|
1064
1123
|
.toArray();
|
|
@@ -1072,6 +1131,10 @@ class CalendarComponent {
|
|
|
1072
1131
|
if (preventableEvent.isDefaultPrevented()) {
|
|
1073
1132
|
return;
|
|
1074
1133
|
}
|
|
1134
|
+
// Keys pressed on the header buttons belong to the buttons: the arrows don't move the active day.
|
|
1135
|
+
if (event.target.closest("button")) {
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1075
1138
|
const view = this.calendarView();
|
|
1076
1139
|
const isCtrlOrCmd = event.ctrlKey || event.metaKey;
|
|
1077
1140
|
const isShift = event.shiftKey;
|
|
@@ -1127,10 +1190,6 @@ class CalendarComponent {
|
|
|
1127
1190
|
break;
|
|
1128
1191
|
case "Enter":
|
|
1129
1192
|
case " ": {
|
|
1130
|
-
const target = event.target;
|
|
1131
|
-
if (target.tagName === "BUTTON" || target.closest("button")) {
|
|
1132
|
-
return;
|
|
1133
|
-
}
|
|
1134
1193
|
event.preventDefault();
|
|
1135
1194
|
const previousView = this.calendarView();
|
|
1136
1195
|
if (this.readonly() && previousView === "month") {
|
|
@@ -1164,12 +1223,13 @@ class CalendarComponent {
|
|
|
1164
1223
|
break;
|
|
1165
1224
|
case "PageUp":
|
|
1166
1225
|
event.preventDefault();
|
|
1167
|
-
|
|
1226
|
+
// Shift+PageUp moves a year in the month view, as in the WAI-ARIA date picker pattern.
|
|
1227
|
+
this.navigateByPeriod(isShift && view === "month" ? -12 : -1, view);
|
|
1168
1228
|
shouldFocusCell = true;
|
|
1169
1229
|
break;
|
|
1170
1230
|
case "PageDown":
|
|
1171
1231
|
event.preventDefault();
|
|
1172
|
-
this.navigateByPeriod(1, view);
|
|
1232
|
+
this.navigateByPeriod(isShift && view === "month" ? 12 : 1, view);
|
|
1173
1233
|
shouldFocusCell = true;
|
|
1174
1234
|
break;
|
|
1175
1235
|
case "t":
|
|
@@ -1200,10 +1260,11 @@ class CalendarComponent {
|
|
|
1200
1260
|
const date1 = DateTime.fromJSDate(date);
|
|
1201
1261
|
const valueList = this.getDateArray(value);
|
|
1202
1262
|
const selectedDates = select(valueList, e => DateTime.fromJSDate(e));
|
|
1203
|
-
|
|
1263
|
+
// Compare by day: a selected value carries a time, the clicked day is midnight.
|
|
1264
|
+
const alreadySelected = selectedDates.any(e => e.hasSame(date1, "day"));
|
|
1204
1265
|
if (alreadySelected) {
|
|
1205
1266
|
const newDates = selectedDates
|
|
1206
|
-
.where(e => !e.
|
|
1267
|
+
.where(e => !e.hasSame(date1, "day"))
|
|
1207
1268
|
.select(e => e.toJSDate())
|
|
1208
1269
|
.toArray();
|
|
1209
1270
|
this.setCurrentDate(newDates);
|
|
@@ -1288,22 +1349,15 @@ class CalendarComponent {
|
|
|
1288
1349
|
return isRtl ? "prev" : "next";
|
|
1289
1350
|
}
|
|
1290
1351
|
isDateDisabled(date) {
|
|
1291
|
-
|
|
1292
|
-
const max = this.maxDate();
|
|
1293
|
-
const min = this.minDate();
|
|
1294
|
-
let isDisabledByInput = false;
|
|
1295
|
-
if (typeof disabledDates === "function") {
|
|
1296
|
-
isDisabledByInput = disabledDates(date);
|
|
1297
|
-
}
|
|
1298
|
-
else if (disabledDates) {
|
|
1299
|
-
isDisabledByInput = any(disabledDates, d => compareDates(date, d, "=="));
|
|
1300
|
-
}
|
|
1301
|
-
return isDisabledByInput || compareDates(date, max, ">") || compareDates(date, min, "<");
|
|
1352
|
+
return this.isDayUnavailable()(date);
|
|
1302
1353
|
}
|
|
1303
1354
|
isEmptyValue() {
|
|
1304
1355
|
const value = this.value();
|
|
1305
1356
|
return value == null || (Array.isArray(value) && value.length === 0);
|
|
1306
1357
|
}
|
|
1358
|
+
isOutOfRange(date) {
|
|
1359
|
+
return compareDates(date, this.maxDate(), ">") || compareDates(date, this.minDate(), "<");
|
|
1360
|
+
}
|
|
1307
1361
|
navigateByDaysOrCells(offset, view) {
|
|
1308
1362
|
const currentDate = this.navigatedDate();
|
|
1309
1363
|
const dateTime = DateTime.fromJSDate(currentDate);
|
|
@@ -1319,7 +1373,8 @@ class CalendarComponent {
|
|
|
1319
1373
|
newDate = dateTime.plus({ years: offset }).toJSDate();
|
|
1320
1374
|
break;
|
|
1321
1375
|
}
|
|
1322
|
-
|
|
1376
|
+
// A disabled day can be focused (it is announced as unavailable); only the range bounds stop the arrows.
|
|
1377
|
+
if (view === "month" && this.isOutOfRange(newDate)) {
|
|
1323
1378
|
return;
|
|
1324
1379
|
}
|
|
1325
1380
|
this.rangePreviewDate.set(null);
|
|
@@ -1371,7 +1426,8 @@ class CalendarComponent {
|
|
|
1371
1426
|
newDate = dateTime.plus({ years: offset * 4 }).toJSDate();
|
|
1372
1427
|
break;
|
|
1373
1428
|
}
|
|
1374
|
-
|
|
1429
|
+
// A disabled day can be focused (it is announced as unavailable); only the range bounds stop the arrows.
|
|
1430
|
+
if (view === "month" && this.isOutOfRange(newDate)) {
|
|
1375
1431
|
return;
|
|
1376
1432
|
}
|
|
1377
1433
|
this.rangePreviewDate.set(null);
|
|
@@ -1447,7 +1503,8 @@ class CalendarComponent {
|
|
|
1447
1503
|
this.navigatedDate.set(newDate);
|
|
1448
1504
|
}
|
|
1449
1505
|
navigateToToday() {
|
|
1450
|
-
|
|
1506
|
+
this.refreshToday();
|
|
1507
|
+
const today = this.today();
|
|
1451
1508
|
if (this.isDateDisabled(today)) {
|
|
1452
1509
|
return;
|
|
1453
1510
|
}
|
|
@@ -1536,7 +1593,7 @@ class CalendarComponent {
|
|
|
1536
1593
|
}
|
|
1537
1594
|
}
|
|
1538
1595
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: CalendarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1539
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: CalendarComponent, isStandalone: true, selector: "mona-calendar", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, disabledDates: { classPropertyName: "disabledDates", publicName: "disabledDates", isSignal: true, isRequired: false, transformFunction: null }, firstDay: { classPropertyName: "firstDay", publicName: "firstDay", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: false, transformFunction: null }, selection: { classPropertyName: "selection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, weekNumber: { classPropertyName: "weekNumber", publicName: "weekNumber", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange", touch: "touch", value: "valueChange" }, host: { attributes: { "role": "application" }, properties: { "attr.aria-label": "ariaLabel()", "attr.aria-invalid": "invalidState() ? 'true' : null", "attr.data-invalid": "invalidState() || null", "attr.data-required": "required() || null", "attr.tabindex": "disabled() ? -1 : 0", "class": "baseClass()" } }, queries: [{ propertyName: "decadeCellTemplate", first: true, predicate: CalendarDecadeCellTemplateDirective, descendants: true, isSignal: true }, { propertyName: "monthCellTemplate", first: true, predicate: CalendarMonthCellTemplateDirective, descendants: true, isSignal: true }, { propertyName: "yearCellTemplate", first: true, predicate: CalendarYearCellTemplateDirective, descendants: true, isSignal: true }], ngImport: i0, template: "<div [class]=\"headerClass()\">\n <button\n monaButton\n look=\"ghost\"\n [rounded]=\"rounded()\"\n [size]=\"'small'\"\n (click)=\"onTodayButtonClick()\"\n [attr.aria-label]=\"todayButtonLabel()\">\n <span>{{ messages().today }}</span>\n </button>\n @switch (calendarView()) {\n @case (\"month\") {\n <button\n monaButton\n look=\"ghost\"\n [rounded]=\"rounded()\"\n (click)=\"onViewChangeClick('year')\"\n [attr.aria-label]=\"viewSwitchButtonLabel()\">\n <span class=\"text-primary font-semibold\" [id]=\"gridId + '-heading'\">{{\n headerMonthYear()\n }}</span>\n </button>\n }\n @case (\"year\") {\n <button\n monaButton\n look=\"ghost\"\n [rounded]=\"rounded()\"\n (click)=\"onViewChangeClick('decade')\"\n [attr.aria-label]=\"viewSwitchButtonLabel()\">\n <span class=\"text-primary font-semibold\" [id]=\"gridId + '-heading'\">{{\n headerYear()\n }}</span>\n </button>\n }\n @case (\"decade\") {\n <button monaButton look=\"ghost\" [rounded]=\"rounded()\" [attr.aria-label]=\"viewSwitchButtonLabel()\">\n <span class=\"text-primary font-semibold\" [id]=\"gridId + '-heading'\"\n >{{ formatInteger(decadeStart()) }} - {{ formatInteger(decadeEnd()) }}</span\n >\n </button>\n }\n }\n <button\n monaButton\n look=\"ghost\"\n [rounded]=\"rounded()\"\n [iconOnly]=\"true\"\n [size]=\"'small'\"\n (click)=\"onNavigationClick('prev')\"\n [attr.aria-label]=\"prevButtonLabel()\">\n <svg lucideChevronLeft class=\"rtl:rotate-180\"></svg>\n </button>\n <button\n monaButton\n look=\"ghost\"\n [rounded]=\"rounded()\"\n [iconOnly]=\"true\"\n [size]=\"'small'\"\n (click)=\"onNavigationClick('next')\"\n [attr.aria-label]=\"nextButtonLabel()\">\n <svg lucideChevronRight class=\"rtl:rotate-180\"></svg>\n </button>\n</div>\n\n<div aria-live=\"polite\" aria-atomic=\"true\" class=\"sr-only\">{{ ariaLabel() }}</div>\n\n@if (calendarView() === \"month\") {\n @let columnCount = weekNumber() ? 8 : 7;\n <div [class]=\"monthGridHeaderClass()\" [style.grid-template-columns]=\"`repeat(${columnCount}, 32px)`\">\n @if (weekNumber()) {\n <div></div>\n }\n @for (day of weekdays(); track day.full) {\n <div>\n <span aria-hidden=\"true\">{{ day.display }}</span>\n <span class=\"sr-only\">{{ day.full }}</span>\n </div>\n }\n </div>\n <div\n [class]=\"monthGridClass()\"\n [attr.aria-labelledby]=\"gridId + '-heading'\"\n (pointerleave)=\"onMonthGridPointerLeave()\"\n [style.grid-template-columns]=\"`repeat(${columnCount}, 32px)`\">\n @for (row of monthDictChunked(); track $index) {\n @if (weekNumber()) {\n @let firstEntry = row | monaTakeFirst;\n @if (firstEntry) {\n @let week = getWeekNumber(firstEntry.key);\n <div>{{ formatInteger(week) }}</div>\n }\n }\n @for (entry of row; track entry.key) {\n <div\n (dayPointerEnter)=\"onDayPointerEnter($event)\"\n (daySelect)=\"onDayClick($event.date, $event.event)\"\n monaMonthDay\n [disabled]=\"disabled()\"\n [disabledDates]=\"disabledDates()\"\n [min]=\"minDate()\"\n [max]=\"maxDate()\"\n [monthBounds]=\"[monthBounds().start, monthBounds().end]\"\n [navigatedDate]=\"navigatedDate()\"\n [rangePreview]=\"rangePreviewDates()\"\n [rounded]=\"rounded()\"\n [value]=\"selectedDates()\"\n [entry]=\"entry\">\n @if (monthCellTemplate()) {\n <ng-container\n [ngTemplateOutlet]=\"monthCellTemplate()?.templateRef\"\n [ngTemplateOutletContext]=\"{ $implicit: entry.value, date: entry.key }\"></ng-container>\n } @else {\n <span>{{ formatInteger(entry.value) }}</span>\n }\n </div>\n }\n }\n </div>\n} @else if (calendarView() === \"year\") {\n <div [class]=\"yearGridClass()\" [attr.aria-labelledby]=\"gridId + '-heading'\">\n @for (row of months(); track $index) {\n @for (monthItem of row; track monthItem) {\n <div\n (click)=\"onMonthClick(monthItem[0])\"\n monaYearMonth\n [month]=\"monthItem[0]\"\n [monthName]=\"monthItem[1]\"\n [navigatedDate]=\"navigatedDate()\"\n [rounded]=\"rounded()\"\n [selectedDate]=\"selectedDates()[0]\">\n @if (yearCellTemplate()) {\n <ng-container\n [ngTemplateOutlet]=\"yearCellTemplate()?.templateRef\"\n [ngTemplateOutletContext]=\"{ $implicit: monthItem[0], text: monthItem[1] }\">\n </ng-container>\n } @else {\n <span>{{ monthItem[1] }}</span>\n }\n </div>\n }\n }\n </div>\n} @else if (calendarView() === \"decade\") {\n <div [class]=\"decadeGridClass()\" [attr.aria-labelledby]=\"gridId + '-heading'\">\n @for (row of decadeYears(); track $index) {\n @for (year of row; track year) {\n <div\n (click)=\"onYearClick(year)\"\n monaDecadeYear\n [navigatedDate]=\"navigatedDate()\"\n [rounded]=\"rounded()\"\n [year]=\"year\"\n [selectedDate]=\"selectedDates()[0]\">\n @if (decadeCellTemplate()) {\n <ng-container\n [ngTemplateOutlet]=\"decadeCellTemplate()?.templateRef\"\n [ngTemplateOutletContext]=\"{ $implicit: year }\">\n </ng-container>\n } @else {\n <span>{{ formatInteger(year) }}</span>\n }\n </div>\n }\n }\n </div>\n}\n", dependencies: [{ kind: "directive", type: ButtonDirective, selector: "button[monaButton]", inputs: ["aria-haspopup", "disabled", "iconOnly", "loading", "look", "rounded", "selected", "size", "tabindex", "toggleable", "type", "class"], outputs: ["lookChange", "selectedChange"] }, { kind: "directive", type: MonthDayDirective, selector: "[monaMonthDay]", inputs: ["disabled", "disabledDates", "entry", "max", "min", "monthBounds", "navigatedDate", "rangePreview", "rounded", "value"], outputs: ["dayPointerEnter", "daySelect"] }, { kind: "directive", type: DecadeYearDirective, selector: "[monaDecadeYear]", inputs: ["navigatedDate", "rounded", "selectedDate", "year"] }, { kind: "directive", type: YearMonthDirective, selector: "[monaYearMonth]", inputs: ["month", "monthName", "navigatedDate", "rounded", "selectedDate"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: LucideChevronLeft, selector: "svg[lucideChevronLeft]" }, { kind: "component", type: LucideChevronRight, selector: "svg[lucideChevronRight]" }, { kind: "pipe", type: TakeFirstPipe, name: "monaTakeFirst" }] }); }
|
|
1596
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: CalendarComponent, isStandalone: true, selector: "mona-calendar", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, disabledDates: { classPropertyName: "disabledDates", publicName: "disabledDates", isSignal: true, isRequired: false, transformFunction: null }, firstDay: { classPropertyName: "firstDay", publicName: "firstDay", isSignal: true, isRequired: false, transformFunction: null }, focusOnMount: { classPropertyName: "focusOnMount", publicName: "focusOnMount", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: false, transformFunction: null }, selection: { classPropertyName: "selection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, weekNumber: { classPropertyName: "weekNumber", publicName: "weekNumber", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { touch: "touch", value: "valueChange" }, host: { listeners: { "focusin": "refreshToday()", "focusout": "onFocusOut($event)" }, properties: { "attr.aria-label": "null", "attr.data-disabled": "disabled() || null", "attr.data-invalid": "invalidState() || null", "attr.data-required": "required() || null", "class": "baseClass()" } }, queries: [{ propertyName: "decadeCellTemplate", first: true, predicate: CalendarDecadeCellTemplateDirective, descendants: true, isSignal: true }, { propertyName: "monthCellTemplate", first: true, predicate: CalendarMonthCellTemplateDirective, descendants: true, isSignal: true }, { propertyName: "yearCellTemplate", first: true, predicate: CalendarYearCellTemplateDirective, descendants: true, isSignal: true }], ngImport: i0, template: "<div [class]=\"headerClass()\">\n <button\n monaButton\n look=\"ghost\"\n [rounded]=\"rounded()\"\n [size]=\"'small'\"\n [disabled]=\"disabled() || todayUnavailable()\"\n (click)=\"onTodayButtonClick()\"\n [attr.aria-label]=\"todayButtonLabel()\">\n <span>{{ messages().today }}</span>\n </button>\n @switch (calendarView()) {\n @case (\"month\") {\n <button\n monaButton\n look=\"ghost\"\n [rounded]=\"rounded()\"\n [disabled]=\"disabled()\"\n (click)=\"onViewChangeClick('year')\"\n [attr.aria-label]=\"viewSwitchButtonLabel()\">\n <span class=\"text-primary font-semibold\" [id]=\"gridId + '-heading'\">{{\n headerMonthYear()\n }}</span>\n </button>\n }\n @case (\"year\") {\n <button\n monaButton\n look=\"ghost\"\n [rounded]=\"rounded()\"\n [disabled]=\"disabled()\"\n (click)=\"onViewChangeClick('decade')\"\n [attr.aria-label]=\"viewSwitchButtonLabel()\">\n <span class=\"text-primary font-semibold\" [id]=\"gridId + '-heading'\">{{\n headerYear()\n }}</span>\n </button>\n }\n @case (\"decade\") {\n <!-- The decade is the top view, so its heading isn't a button. It names the grid with the locale's\n wording of the range, and shows the digits. -->\n <span\n class=\"text-primary inline-flex h-9 items-center px-4 text-sm font-semibold\"\n [id]=\"gridId + '-heading'\"\n ><span aria-hidden=\"true\">{{ formatInteger(decadeStart()) }} - {{ formatInteger(decadeEnd()) }}</span\n ><span class=\"sr-only\">{{ viewSwitchButtonLabel() }}</span></span\n >\n }\n }\n <button\n monaButton\n look=\"ghost\"\n [rounded]=\"rounded()\"\n [iconOnly]=\"true\"\n [size]=\"'small'\"\n [disabled]=\"disabled()\"\n (click)=\"onNavigationClick('prev')\"\n [attr.aria-label]=\"prevButtonLabel()\">\n <svg lucideChevronLeft class=\"rtl:rotate-180\"></svg>\n </button>\n <button\n monaButton\n look=\"ghost\"\n [rounded]=\"rounded()\"\n [iconOnly]=\"true\"\n [size]=\"'small'\"\n [disabled]=\"disabled()\"\n (click)=\"onNavigationClick('next')\"\n [attr.aria-label]=\"nextButtonLabel()\">\n <svg lucideChevronRight class=\"rtl:rotate-180\"></svg>\n </button>\n</div>\n\n<div aria-live=\"polite\" aria-atomic=\"true\" class=\"sr-only\">{{ ariaLabel() }}</div>\n\n@if (calendarView() === \"month\") {\n @let columnCount = weekNumber() ? 8 : 7;\n <div\n role=\"grid\"\n class=\"flex flex-col gap-0.5\"\n [attr.aria-labelledby]=\"gridId + '-heading'\"\n [attr.aria-disabled]=\"disabled() || null\"\n [attr.aria-invalid]=\"invalidState() ? 'true' : null\"\n [attr.aria-readonly]=\"readonly() || null\"\n [attr.aria-required]=\"required() || null\"\n (pointerleave)=\"onMonthGridPointerLeave()\">\n <div role=\"row\" [class]=\"monthGridHeaderClass()\" [style.grid-template-columns]=\"`repeat(${columnCount}, 32px)`\">\n @if (weekNumber()) {\n <!-- Week numbers are visual only: every day is already named by its full date. -->\n <div aria-hidden=\"true\"></div>\n }\n @for (day of weekdays(); track day.full) {\n <div role=\"columnheader\">\n <span aria-hidden=\"true\">{{ day.display }}</span>\n <span class=\"sr-only\">{{ day.full }}</span>\n </div>\n }\n </div>\n @for (row of monthDictChunked(); track $index) {\n <div role=\"row\" [class]=\"monthGridClass()\" [style.grid-template-columns]=\"`repeat(${columnCount}, 32px)`\">\n @if (weekNumber()) {\n @let firstEntry = row | monaTakeFirst;\n @if (firstEntry) {\n @let week = getWeekNumber(firstEntry.key);\n <div aria-hidden=\"true\">{{ formatInteger(week) }}</div>\n }\n }\n <!-- Track by time: the month is rebuilt with new Date objects on every navigation. -->\n @for (entry of row; track entry.key.getTime()) {\n <div\n (dayPointerEnter)=\"onDayPointerEnter($event)\"\n (daySelect)=\"onDayClick($event.date, $event.event)\"\n monaMonthDay\n [disabled]=\"disabled()\"\n [isDayUnavailable]=\"isDayUnavailable()\"\n [monthBounds]=\"[monthBounds().start, monthBounds().end]\"\n [navigatedDate]=\"navigatedDate()\"\n [rangePreviewKeys]=\"rangePreviewKeys()\"\n [rounded]=\"rounded()\"\n [selectedKeys]=\"selectedKeys()\"\n [today]=\"today()\"\n [entry]=\"entry\">\n @if (monthCellTemplate()) {\n <ng-container\n [ngTemplateOutlet]=\"monthCellTemplate()?.templateRef\"\n [ngTemplateOutletContext]=\"{ $implicit: entry.value, date: entry.key }\"></ng-container>\n } @else {\n <span>{{ formatInteger(entry.value) }}</span>\n }\n </div>\n }\n </div>\n }\n </div>\n} @else if (calendarView() === \"year\") {\n <div role=\"grid\" class=\"flex flex-col gap-0.5\" [attr.aria-labelledby]=\"gridId + '-heading'\">\n @for (row of months(); track $index) {\n <div role=\"row\" [class]=\"yearGridClass()\">\n @for (monthItem of row; track monthItem) {\n <div\n (click)=\"!disabled() && onMonthClick(monthItem[0])\"\n monaYearMonth\n [disabled]=\"disabled()\"\n [month]=\"monthItem[0]\"\n [navigatedDate]=\"navigatedDate()\"\n [rounded]=\"rounded()\"\n [selectedDate]=\"selectedDates()[0]\">\n @if (yearCellTemplate()) {\n <ng-container\n [ngTemplateOutlet]=\"yearCellTemplate()?.templateRef\"\n [ngTemplateOutletContext]=\"{ $implicit: monthItem[0], text: monthItem[1] }\">\n </ng-container>\n } @else {\n <span>{{ monthItem[1] }}</span>\n }\n </div>\n }\n </div>\n }\n </div>\n} @else if (calendarView() === \"decade\") {\n <div role=\"grid\" class=\"flex flex-col gap-0.5\" [attr.aria-labelledby]=\"gridId + '-heading'\">\n @for (row of decadeYears(); track $index) {\n <div role=\"row\" [class]=\"decadeGridClass()\">\n @for (year of row; track year) {\n <div\n (click)=\"!disabled() && onYearClick(year)\"\n monaDecadeYear\n [disabled]=\"disabled()\"\n [navigatedDate]=\"navigatedDate()\"\n [rounded]=\"rounded()\"\n [year]=\"year\"\n [selectedDate]=\"selectedDates()[0]\">\n @if (decadeCellTemplate()) {\n <ng-container\n [ngTemplateOutlet]=\"decadeCellTemplate()?.templateRef\"\n [ngTemplateOutletContext]=\"{ $implicit: year }\">\n </ng-container>\n } @else {\n <span>{{ formatInteger(year) }}</span>\n }\n </div>\n }\n </div>\n }\n </div>\n}\n", dependencies: [{ kind: "directive", type: ButtonDirective, selector: "button[monaButton]", inputs: ["aria-haspopup", "disabled", "iconOnly", "loading", "look", "rounded", "selected", "size", "tabindex", "toggleable", "type", "class"], outputs: ["lookChange", "selectedChange"] }, { kind: "directive", type: MonthDayDirective, selector: "[monaMonthDay]", inputs: ["disabled", "entry", "isDayUnavailable", "monthBounds", "navigatedDate", "rangePreviewKeys", "rounded", "selectedKeys", "today"], outputs: ["dayPointerEnter", "daySelect"] }, { kind: "directive", type: DecadeYearDirective, selector: "[monaDecadeYear]", inputs: ["disabled", "navigatedDate", "rounded", "selectedDate", "year"] }, { kind: "directive", type: YearMonthDirective, selector: "[monaYearMonth]", inputs: ["disabled", "month", "navigatedDate", "rounded", "selectedDate"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: LucideChevronLeft, selector: "svg[lucideChevronLeft]" }, { kind: "component", type: LucideChevronRight, selector: "svg[lucideChevronRight]" }, { kind: "pipe", type: TakeFirstPipe, name: "monaTakeFirst" }] }); }
|
|
1540
1597
|
}
|
|
1541
1598
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: CalendarComponent, decorators: [{
|
|
1542
1599
|
type: Component,
|
|
@@ -1550,15 +1607,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
|
|
|
1550
1607
|
LucideChevronLeft,
|
|
1551
1608
|
LucideChevronRight
|
|
1552
1609
|
], host: {
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
"[attr.aria-
|
|
1610
|
+
// The host is a plain container: the grid inside it is the widget, and focus moves through its cells. A
|
|
1611
|
+
// static aria-label written on <mona-calendar> would otherwise stay on the role-less host.
|
|
1612
|
+
"[attr.aria-label]": "null",
|
|
1613
|
+
"[attr.data-disabled]": "disabled() || null",
|
|
1556
1614
|
"[attr.data-invalid]": "invalidState() || null",
|
|
1557
1615
|
"[attr.data-required]": "required() || null",
|
|
1558
|
-
"[
|
|
1559
|
-
"
|
|
1560
|
-
|
|
1561
|
-
|
|
1616
|
+
"[class]": "baseClass()",
|
|
1617
|
+
"(focusin)": "refreshToday()",
|
|
1618
|
+
"(focusout)": "onFocusOut($event)"
|
|
1619
|
+
}, template: "<div [class]=\"headerClass()\">\n <button\n monaButton\n look=\"ghost\"\n [rounded]=\"rounded()\"\n [size]=\"'small'\"\n [disabled]=\"disabled() || todayUnavailable()\"\n (click)=\"onTodayButtonClick()\"\n [attr.aria-label]=\"todayButtonLabel()\">\n <span>{{ messages().today }}</span>\n </button>\n @switch (calendarView()) {\n @case (\"month\") {\n <button\n monaButton\n look=\"ghost\"\n [rounded]=\"rounded()\"\n [disabled]=\"disabled()\"\n (click)=\"onViewChangeClick('year')\"\n [attr.aria-label]=\"viewSwitchButtonLabel()\">\n <span class=\"text-primary font-semibold\" [id]=\"gridId + '-heading'\">{{\n headerMonthYear()\n }}</span>\n </button>\n }\n @case (\"year\") {\n <button\n monaButton\n look=\"ghost\"\n [rounded]=\"rounded()\"\n [disabled]=\"disabled()\"\n (click)=\"onViewChangeClick('decade')\"\n [attr.aria-label]=\"viewSwitchButtonLabel()\">\n <span class=\"text-primary font-semibold\" [id]=\"gridId + '-heading'\">{{\n headerYear()\n }}</span>\n </button>\n }\n @case (\"decade\") {\n <!-- The decade is the top view, so its heading isn't a button. It names the grid with the locale's\n wording of the range, and shows the digits. -->\n <span\n class=\"text-primary inline-flex h-9 items-center px-4 text-sm font-semibold\"\n [id]=\"gridId + '-heading'\"\n ><span aria-hidden=\"true\">{{ formatInteger(decadeStart()) }} - {{ formatInteger(decadeEnd()) }}</span\n ><span class=\"sr-only\">{{ viewSwitchButtonLabel() }}</span></span\n >\n }\n }\n <button\n monaButton\n look=\"ghost\"\n [rounded]=\"rounded()\"\n [iconOnly]=\"true\"\n [size]=\"'small'\"\n [disabled]=\"disabled()\"\n (click)=\"onNavigationClick('prev')\"\n [attr.aria-label]=\"prevButtonLabel()\">\n <svg lucideChevronLeft class=\"rtl:rotate-180\"></svg>\n </button>\n <button\n monaButton\n look=\"ghost\"\n [rounded]=\"rounded()\"\n [iconOnly]=\"true\"\n [size]=\"'small'\"\n [disabled]=\"disabled()\"\n (click)=\"onNavigationClick('next')\"\n [attr.aria-label]=\"nextButtonLabel()\">\n <svg lucideChevronRight class=\"rtl:rotate-180\"></svg>\n </button>\n</div>\n\n<div aria-live=\"polite\" aria-atomic=\"true\" class=\"sr-only\">{{ ariaLabel() }}</div>\n\n@if (calendarView() === \"month\") {\n @let columnCount = weekNumber() ? 8 : 7;\n <div\n role=\"grid\"\n class=\"flex flex-col gap-0.5\"\n [attr.aria-labelledby]=\"gridId + '-heading'\"\n [attr.aria-disabled]=\"disabled() || null\"\n [attr.aria-invalid]=\"invalidState() ? 'true' : null\"\n [attr.aria-readonly]=\"readonly() || null\"\n [attr.aria-required]=\"required() || null\"\n (pointerleave)=\"onMonthGridPointerLeave()\">\n <div role=\"row\" [class]=\"monthGridHeaderClass()\" [style.grid-template-columns]=\"`repeat(${columnCount}, 32px)`\">\n @if (weekNumber()) {\n <!-- Week numbers are visual only: every day is already named by its full date. -->\n <div aria-hidden=\"true\"></div>\n }\n @for (day of weekdays(); track day.full) {\n <div role=\"columnheader\">\n <span aria-hidden=\"true\">{{ day.display }}</span>\n <span class=\"sr-only\">{{ day.full }}</span>\n </div>\n }\n </div>\n @for (row of monthDictChunked(); track $index) {\n <div role=\"row\" [class]=\"monthGridClass()\" [style.grid-template-columns]=\"`repeat(${columnCount}, 32px)`\">\n @if (weekNumber()) {\n @let firstEntry = row | monaTakeFirst;\n @if (firstEntry) {\n @let week = getWeekNumber(firstEntry.key);\n <div aria-hidden=\"true\">{{ formatInteger(week) }}</div>\n }\n }\n <!-- Track by time: the month is rebuilt with new Date objects on every navigation. -->\n @for (entry of row; track entry.key.getTime()) {\n <div\n (dayPointerEnter)=\"onDayPointerEnter($event)\"\n (daySelect)=\"onDayClick($event.date, $event.event)\"\n monaMonthDay\n [disabled]=\"disabled()\"\n [isDayUnavailable]=\"isDayUnavailable()\"\n [monthBounds]=\"[monthBounds().start, monthBounds().end]\"\n [navigatedDate]=\"navigatedDate()\"\n [rangePreviewKeys]=\"rangePreviewKeys()\"\n [rounded]=\"rounded()\"\n [selectedKeys]=\"selectedKeys()\"\n [today]=\"today()\"\n [entry]=\"entry\">\n @if (monthCellTemplate()) {\n <ng-container\n [ngTemplateOutlet]=\"monthCellTemplate()?.templateRef\"\n [ngTemplateOutletContext]=\"{ $implicit: entry.value, date: entry.key }\"></ng-container>\n } @else {\n <span>{{ formatInteger(entry.value) }}</span>\n }\n </div>\n }\n </div>\n }\n </div>\n} @else if (calendarView() === \"year\") {\n <div role=\"grid\" class=\"flex flex-col gap-0.5\" [attr.aria-labelledby]=\"gridId + '-heading'\">\n @for (row of months(); track $index) {\n <div role=\"row\" [class]=\"yearGridClass()\">\n @for (monthItem of row; track monthItem) {\n <div\n (click)=\"!disabled() && onMonthClick(monthItem[0])\"\n monaYearMonth\n [disabled]=\"disabled()\"\n [month]=\"monthItem[0]\"\n [navigatedDate]=\"navigatedDate()\"\n [rounded]=\"rounded()\"\n [selectedDate]=\"selectedDates()[0]\">\n @if (yearCellTemplate()) {\n <ng-container\n [ngTemplateOutlet]=\"yearCellTemplate()?.templateRef\"\n [ngTemplateOutletContext]=\"{ $implicit: monthItem[0], text: monthItem[1] }\">\n </ng-container>\n } @else {\n <span>{{ monthItem[1] }}</span>\n }\n </div>\n }\n </div>\n }\n </div>\n} @else if (calendarView() === \"decade\") {\n <div role=\"grid\" class=\"flex flex-col gap-0.5\" [attr.aria-labelledby]=\"gridId + '-heading'\">\n @for (row of decadeYears(); track $index) {\n <div role=\"row\" [class]=\"decadeGridClass()\">\n @for (year of row; track year) {\n <div\n (click)=\"!disabled() && onYearClick(year)\"\n monaDecadeYear\n [disabled]=\"disabled()\"\n [navigatedDate]=\"navigatedDate()\"\n [rounded]=\"rounded()\"\n [year]=\"year\"\n [selectedDate]=\"selectedDates()[0]\">\n @if (decadeCellTemplate()) {\n <ng-container\n [ngTemplateOutlet]=\"decadeCellTemplate()?.templateRef\"\n [ngTemplateOutletContext]=\"{ $implicit: year }\">\n </ng-container>\n } @else {\n <span>{{ formatInteger(year) }}</span>\n }\n </div>\n }\n </div>\n }\n </div>\n}\n" }]
|
|
1620
|
+
}], ctorParameters: () => [], propDecorators: { decadeCellTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => CalendarDecadeCellTemplateDirective), { isSignal: true }] }], monthCellTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => CalendarMonthCellTemplateDirective), { isSignal: true }] }], yearCellTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => CalendarYearCellTemplateDirective), { isSignal: true }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], disabledDates: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabledDates", required: false }] }], firstDay: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstDay", required: false }] }], focusOnMount: [{ type: i0.Input, args: [{ isSignal: true, alias: "focusOnMount", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], maxDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDate", required: false }] }], minDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDate", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], rounded: [{ type: i0.Input, args: [{ isSignal: true, alias: "rounded", required: false }] }], selection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }], touch: [{ type: i0.Output, args: ["touch"] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], weekNumber: [{ type: i0.Input, args: [{ isSignal: true, alias: "weekNumber", required: false }] }] } });
|
|
1562
1621
|
|
|
1563
1622
|
/*
|
|
1564
1623
|
* Public API Surface of @nanahoshi/mona-ui/calendar
|