UNPKG

@netwin/angular-datetime-picker

Version:
1,139 lines (1,128 loc) 188 kB
import { Platform, PlatformModule } from '@angular/cdk/platform'; import * as i0 from '@angular/core'; import { InjectionToken, inject, LOCALE_ID, Injectable, NgModule, ElementRef, NgZone, output, Input, ChangeDetectionStrategy, Component, ChangeDetectorRef, ViewChild, viewChild, booleanAttribute, numberAttribute, Directive, input, computed, forwardRef } from '@angular/core'; import { Subject, Subscription, take as take$1, debounceTime, map, filter } from 'rxjs'; import { take } from 'rxjs/operators'; import { ENTER, PAGE_DOWN, PAGE_UP, END, HOME, DOWN_ARROW, UP_ARROW, RIGHT_ARROW, LEFT_ARROW, SPACE } from '@angular/cdk/keycodes'; import { getLocaleFirstDayOfWeek } from '@angular/common'; import * as i1 from '@angular/cdk/a11y'; import { A11yModule } from '@angular/cdk/a11y'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { coerceNumberProperty } from '@angular/cdk/coercion'; /** * date-time-adapter.class */ /** InjectionToken for date time picker that can be used to override default locale code. */ const OWL_DATE_TIME_LOCALE = new InjectionToken('OWL_DATE_TIME_LOCALE', { providedIn: 'root', factory: OWL_DATE_TIME_LOCALE_FACTORY }); /** @docs-private */ function OWL_DATE_TIME_LOCALE_FACTORY() { return inject(LOCALE_ID); } /** Provider for OWL_DATE_TIME_LOCALE injection token. */ const OWL_DATE_TIME_LOCALE_PROVIDER = { provide: OWL_DATE_TIME_LOCALE, useExisting: LOCALE_ID }; class DateTimeAdapter { /** The locale to use for all dates. */ locale; /** A stream that emits when the locale changes. */ _localeChanges = new Subject(); localeChanges = this._localeChanges.asObservable(); /** total milliseconds in a day. */ millisecondsInDay = 86400000; /** total milliseconds in a minute. */ milliseondsInMinute = 60000; /** * Compare two given dates * 1 if the first date is after the second, * -1 if the first date is before the second * 0 if dates are equal. */ compare(first, second) { if (!this.isValid(first) || !this.isValid(second)) { throw Error('JSNativeDate: Cannot compare invalid dates.'); } const dateFirst = this.clone(first); const dateSecond = this.clone(second); const diff = this.getTime(dateFirst) - this.getTime(dateSecond); if (diff < 0) { return -1; } else if (diff > 0) { return 1; } else { // Return 0 if diff is 0; return NaN if diff is NaN return diff; } } /** * Check if two given dates are in the same year * 1 if the first date's year is after the second, * -1 if the first date's year is before the second * 0 if two given dates are in the same year */ compareYear(first, second) { if (!this.isValid(first) || !this.isValid(second)) { throw Error('JSNativeDate: Cannot compare invalid dates.'); } const yearLeft = this.getYear(first); const yearRight = this.getYear(second); const diff = yearLeft - yearRight; if (diff < 0) { return -1; } else if (diff > 0) { return 1; } else { return 0; } } /** * Attempts to deserialize a value to a valid date object. This is different from parsing in that * deserialize should only accept non-ambiguous, locale-independent formats (e.g. a ISO 8601 * string). The default implementation does not allow any deserialization, it simply checks that * the given value is already a valid date object or null. The `<mat-datepicker>` will call this * method on all of it's `@Input()` properties that accept dates. It is therefore possible to * support passing values from your backend directly to these properties by overriding this method * to also deserialize the format used by your backend. */ deserialize(value) { if (value == null || (this.isDateInstance(value) && this.isValid(value))) { return value; } return this.invalid(); } /** * Sets the locale used for all dates. */ setLocale(locale) { this.locale = locale; this._localeChanges.next(locale); } /** * Get the locale used for all dates. */ getLocale() { return this.locale; } /** * Clamp the given date between min and max dates. */ clampDate(date, min, max) { if (min && this.compare(date, min) < 0) { return min; } if (max && this.compare(date, max) > 0) { return max; } return date; } } /** * date-time-format.class */ /** InjectionToken for date time picker that can be used to override default format. */ const OWL_DATE_TIME_FORMATS = new InjectionToken('OWL_DATE_TIME_FORMATS'); /** Creates an array and fills it with values. */ function range(length, valueFunction) { return new Array(length).fill(null).map((_, index) => valueFunction(index)); } /** Whether the browser supports the Intl API. */ const SUPPORTS_INTL_API = typeof Intl !== 'undefined'; /** The default month names to use if Intl API is not available. */ const DEFAULT_MONTH_NAMES = { long: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ], short: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], narrow: ['J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D'] }; /** The default day of the week names to use if Intl API is not available. */ const DEFAULT_DAY_OF_WEEK_NAMES = { long: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], short: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], narrow: ['S', 'M', 'T', 'W', 'T', 'F', 'S'] }; /** The default date names to use if Intl API is not available. */ const DEFAULT_DATE_NAMES = range(31, (i) => String(i + 1)); /** * Creates a date with the given year, month, date, hour, minute and second. Does not allow over/under-flow of the * month and date. */ function createDate(year, month, date, hours = 0, minutes = 0, seconds = 0) { if (month < 0 || month > 11) { throw Error(`Invalid month index "${month}". Month index has to be between 0 and 11.`); } if (date < 1) { throw Error(`Invalid date "${date}". Date has to be greater than 0.`); } if (hours < 0 || hours > 23) { throw Error(`Invalid hours "${hours}". Hours has to be between 0 and 23.`); } if (minutes < 0 || minutes > 59) { throw Error(`Invalid minutes "${minutes}". Minutes has to between 0 and 59.`); } if (seconds < 0 || seconds > 59) { throw Error(`Invalid seconds "${seconds}". Seconds has to be between 0 and 59.`); } const result = createDateWithOverflow(year, month, date, hours, minutes, seconds); // Check that the date wasn't above the upper bound for the month, causing the month to overflow // For example, createDate(2017, 1, 31) would try to create a date 2017/02/31 which is invalid if (result.getMonth() !== month) { throw Error(`Invalid date "${date}" for month with index "${month}".`); } return result; } /** * Gets the number of days in the month of the given date. */ function getNumDaysInMonth(date) { const lastDateOfMonth = createDateWithOverflow(date.getFullYear(), date.getMonth() + 1, 0); return lastDateOfMonth.getDate(); } /** * Creates a date but allows the month and date to overflow. */ function createDateWithOverflow(year, month, date, hours = 0, minutes = 0, seconds = 0) { const result = new Date(year, month, date, hours, minutes, seconds); if (year >= 0 && year < 100) { result.setFullYear(result.getFullYear() - 1900); } return result; } /** * native-date-time-adapter.class */ /** * Matches strings that have the form of a valid RFC 3339 string * (https://tools.ietf.org/html/rfc3339). Note that the string may not actually be a valid date * because the regex will match strings an with out of bounds month, date, etc. */ const ISO_8601_REGEX = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|(?:[+-]\d{2}:\d{2}))?)?$/; class NativeDateTimeAdapter extends DateTimeAdapter { owlDateTimeLocale = inject(OWL_DATE_TIME_LOCALE, { optional: true }); platform = inject(Platform); /** Whether to clamp the date between 1 and 9999 to avoid IE and Edge errors. */ _clampDate; /** * Whether to use `timeZone: 'utc'` with `Intl.DateTimeFormat` when formatting dates. * Without this `Intl.DateTimeFormat` sometimes chooses the wrong timeZone, which can throw off * the result. (e.g. in the en-US locale `new Date(1800, 7, 14).toLocaleDateString()` * will produce `'8/13/1800'`. */ useUtcForDisplay; constructor() { super(); super.setLocale(this.owlDateTimeLocale); // IE does its own time zone correction, so we disable this on IE. this.useUtcForDisplay = !this.platform.TRIDENT; this._clampDate = this.platform.TRIDENT || this.platform.EDGE; } getYear(date) { return date.getFullYear(); } getMonth(date) { return date.getMonth(); } getDay(date) { return date.getDay(); } getDate(date) { return date.getDate(); } getHours(date) { return date.getHours(); } getMinutes(date) { return date.getMinutes(); } getSeconds(date) { return date.getSeconds(); } getTime(date) { return date.getTime(); } getNumDaysInMonth(date) { return getNumDaysInMonth(date); } differenceInCalendarDays(dateLeft, dateRight) { if (this.isValid(dateLeft) && this.isValid(dateRight)) { const dateLeftStartOfDay = this.createDate(this.getYear(dateLeft), this.getMonth(dateLeft), this.getDate(dateLeft)); const dateRightStartOfDay = this.createDate(this.getYear(dateRight), this.getMonth(dateRight), this.getDate(dateRight)); const timeStampLeft = this.getTime(dateLeftStartOfDay) - dateLeftStartOfDay.getTimezoneOffset() * this.milliseondsInMinute; const timeStampRight = this.getTime(dateRightStartOfDay) - dateRightStartOfDay.getTimezoneOffset() * this.milliseondsInMinute; return Math.round((timeStampLeft - timeStampRight) / this.millisecondsInDay); } else { return null; } } getYearName(date) { if (SUPPORTS_INTL_API) { const dtf = new Intl.DateTimeFormat(this.getLocale(), { year: 'numeric', timeZone: 'utc' }); return this.stripDirectionalityCharacters(this._format(dtf, date)); } return String(this.getYear(date)); } getMonthNames(style) { if (SUPPORTS_INTL_API) { const dtf = new Intl.DateTimeFormat(this.getLocale(), { month: style, timeZone: 'utc' }); return range(12, (i) => this.stripDirectionalityCharacters(this._format(dtf, new Date(2017, i, 1)))); } return DEFAULT_MONTH_NAMES[style]; } getDayOfWeekNames(style) { if (SUPPORTS_INTL_API) { const dtf = new Intl.DateTimeFormat(this.getLocale(), { weekday: style, timeZone: 'utc' }); return range(7, (i) => this.stripDirectionalityCharacters(this._format(dtf, new Date(2017, 0, i + 1)))); } return DEFAULT_DAY_OF_WEEK_NAMES[style]; } getDateNames() { if (SUPPORTS_INTL_API) { const dtf = new Intl.DateTimeFormat(this.getLocale(), { day: 'numeric', timeZone: 'utc' }); return range(31, (i) => this.stripDirectionalityCharacters(this._format(dtf, new Date(2017, 0, i + 1)))); } return DEFAULT_DATE_NAMES; } toIso8601(date) { return date.toISOString(); } isEqual(dateLeft, dateRight) { if (this.isValid(dateLeft) && this.isValid(dateRight)) { return dateLeft.getTime() === dateRight.getTime(); } return false; } isSameDay(dateLeft, dateRight) { if (this.isValid(dateLeft) && this.isValid(dateRight)) { const dateLeftStartOfDay = this.clone(dateLeft); const dateRightStartOfDay = this.clone(dateRight); dateLeftStartOfDay.setHours(0, 0, 0, 0); dateRightStartOfDay.setHours(0, 0, 0, 0); return dateLeftStartOfDay.getTime() === dateRightStartOfDay.getTime(); } return false; } isValid(date) { return date && !isNaN(date.getTime()); } invalid() { return new Date(NaN); } isDateInstance(obj) { return obj instanceof Date; } addCalendarYears(date, amount) { return this.addCalendarMonths(date, amount * 12); } addCalendarMonths(date, amount) { const result = this.clone(date); amount = Number(amount); const desiredMonth = result.getMonth() + amount; const dateWithDesiredMonth = new Date(0); dateWithDesiredMonth.setFullYear(result.getFullYear(), desiredMonth, 1); dateWithDesiredMonth.setHours(0, 0, 0, 0); const daysInMonth = this.getNumDaysInMonth(dateWithDesiredMonth); // Set the last day of the new month // if the original date was the last day of the longer month result.setMonth(desiredMonth, Math.min(daysInMonth, result.getDate())); return result; } addCalendarDays(date, amount) { const result = this.clone(date); amount = Number(amount); result.setDate(result.getDate() + amount); return result; } setHours(date, amount) { const result = this.clone(date); result.setHours(amount); return result; } setMinutes(date, amount) { const result = this.clone(date); result.setMinutes(amount); return result; } setSeconds(date, amount) { const result = this.clone(date); result.setSeconds(amount); return result; } createDate(year, month, date, hours = 0, minutes = 0, seconds = 0) { return createDate(year, month, date, hours, minutes, seconds); } clone(date) { return this.createDate(this.getYear(date), this.getMonth(date), this.getDate(date), this.getHours(date), this.getMinutes(date), this.getSeconds(date)); } now() { return new Date(); } format(date, displayFormat) { if (!this.isValid(date)) { throw Error('JSNativeDate: Cannot format invalid date.'); } if (SUPPORTS_INTL_API) { if (this._clampDate && (date.getFullYear() < 1 || date.getFullYear() > 9999)) { date = this.clone(date); date.setFullYear(Math.max(1, Math.min(9999, date.getFullYear()))); } displayFormat = { ...displayFormat, timeZone: 'utc' }; const dtf = new Intl.DateTimeFormat(this.getLocale(), displayFormat); return this.stripDirectionalityCharacters(this._format(dtf, date)); } return this.stripDirectionalityCharacters(date.toDateString()); } parse(value) { // There is no way using the native JS Date to set the parse format or locale if (typeof value === 'number') { return new Date(value); } return value ? new Date(Date.parse(value)) : null; } /** * Returns the given value if given a valid Date or null. Deserializes valid ISO 8601 strings * (https://www.ietf.org/rfc/rfc3339.txt) into valid Dates and empty string into null. Returns an * invalid date for all other values. */ deserialize(value) { if (typeof value === 'string') { if (!value) { return null; } // The `Date` constructor accepts formats other than ISO 8601, so we need to make sure the // string is the right format first. if (ISO_8601_REGEX.test(value)) { const date = new Date(value); if (this.isValid(date)) { return date; } } } return super.deserialize(value); } /** * Strip out unicode LTR and RTL characters. Edge and IE insert these into formatted dates while * other browsers do not. We remove them to make output consistent and because they interfere with * date parsing. */ stripDirectionalityCharacters(str) { return str.replace(/[\u200e\u200f]/g, ''); } /** * When converting Date object to string, javascript built-in functions may return wrong * results because it applies its internal DST rules. The DST rules around the world change * very frequently, and the current valid rule is not always valid in previous years though. * We work around this problem building a new Date object which has its internal UTC * representation with the local date and time. */ _format(dtf, date) { const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds())); return dtf.format(d); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: NativeDateTimeAdapter, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: NativeDateTimeAdapter }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: NativeDateTimeAdapter, decorators: [{ type: Injectable }], ctorParameters: () => [] }); const OWL_NATIVE_DATE_TIME_FORMATS = { fullPickerInput: { year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric' }, datePickerInput: { year: 'numeric', month: 'numeric', day: 'numeric' }, timePickerInput: { hour: 'numeric', minute: 'numeric' }, monthYearLabel: { year: 'numeric', month: 'short' }, dateA11yLabel: { year: 'numeric', month: 'long', day: 'numeric' }, monthYearA11yLabel: { year: 'numeric', month: 'long' } }; /** * native-date-time.module */ class NativeDateTimeModule { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: NativeDateTimeModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.1.2", ngImport: i0, type: NativeDateTimeModule, imports: [PlatformModule] }); static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: NativeDateTimeModule, providers: [{ provide: DateTimeAdapter, useClass: NativeDateTimeAdapter }], imports: [PlatformModule] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: NativeDateTimeModule, decorators: [{ type: NgModule, args: [{ imports: [PlatformModule], providers: [{ provide: DateTimeAdapter, useClass: NativeDateTimeAdapter }] }] }] }); class OwlNativeDateTimeModule { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: OwlNativeDateTimeModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.1.2", ngImport: i0, type: OwlNativeDateTimeModule, imports: [NativeDateTimeModule] }); static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: OwlNativeDateTimeModule, providers: [ { provide: OWL_DATE_TIME_FORMATS, useValue: OWL_NATIVE_DATE_TIME_FORMATS } ], imports: [NativeDateTimeModule] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: OwlNativeDateTimeModule, decorators: [{ type: NgModule, args: [{ imports: [NativeDateTimeModule], providers: [ { provide: OWL_DATE_TIME_FORMATS, useValue: OWL_NATIVE_DATE_TIME_FORMATS } ] }] }] }); class CalendarCell { value; displayValue; ariaLabel; enabled; out; cellClass; constructor(value, displayValue, ariaLabel, enabled, out = false, cellClass = '') { this.value = value; this.displayValue = displayValue; this.ariaLabel = ariaLabel; this.enabled = enabled; this.out = out; this.cellClass = cellClass; } } class OwlCalendarBodyComponent { elmRef = inject(ElementRef); ngZone = inject(NgZone); /** * The cell number of the active cell in the table. */ activeCell = 0; /** * The cells to display in the table. */ rows; /** * The number of columns in the table. */ numCols = 7; /** * The ratio (width / height) to use for the cells in the table. */ cellRatio = 1; /** * The value in the table that corresponds to today. */ todayValue; /** * The value in the table that is currently selected. */ selectedValues; /** * Current picker select mode */ selectMode; /** * Emit when a calendar cell is selected */ selectCell = output(); handleSelect(cell) { this.selectCell.emit(cell); } get isInSingleMode() { return this.selectMode === 'single'; } get isInRangeMode() { return this.selectMode === 'range' || this.selectMode === 'rangeFrom' || this.selectMode === 'rangeTo'; } isActiveCell(rowIndex, colIndex) { const cellNumber = rowIndex * this.numCols + colIndex; return cellNumber === this.activeCell; } /** * Check if the cell is selected */ isSelected(value) { if (!this.selectedValues || this.selectedValues.length === 0) { return false; } if (this.isInSingleMode) { return value === this.selectedValues[0]; } if (this.isInRangeMode) { const fromValue = this.selectedValues[0]; const toValue = this.selectedValues[1]; return value === fromValue || value === toValue; } return false; } /** * Check if the cell in the range */ isInRange(value) { if (this.isInRangeMode) { const fromValue = this.selectedValues[0]; const toValue = this.selectedValues[1]; if (fromValue !== null && toValue !== null) { return value >= fromValue && value <= toValue; } else { return value === fromValue || value === toValue; } } return false; } /** * Check if the cell is the range from */ isRangeFrom(value) { if (this.isInRangeMode) { const fromValue = this.selectedValues[0]; return fromValue !== null && value === fromValue; } return false; } /** * Check if the cell is the range to */ isRangeTo(value) { if (this.isInRangeMode) { const toValue = this.selectedValues[1]; return toValue !== null && value === toValue; } return false; } /** * Focus to a active cell */ focusActiveCell() { this.ngZone.runOutsideAngular(() => { this.ngZone.onStable .asObservable() .pipe(take(1)) .subscribe(() => { this.elmRef.nativeElement.querySelector('.owl-dt-calendar-cell-active').focus(); }); }); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: OwlCalendarBodyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.2", type: OwlCalendarBodyComponent, isStandalone: true, selector: "[owl-date-time-calendar-body]", inputs: { activeCell: "activeCell", rows: "rows", numCols: "numCols", cellRatio: "cellRatio", todayValue: "todayValue", selectedValues: "selectedValues", selectMode: "selectMode" }, outputs: { selectCell: "selectCell" }, host: { classAttribute: "owl-dt-calendar-body" }, exportAs: ["owlDateTimeCalendarBody"], ngImport: i0, template: "@for (row of rows; track rowIndex; let rowIndex = $index) {\n <tr role=\"row\">\n @for (item of row; track colIndex; let colIndex = $index) {\n <td\n [attr.aria-current]=\"item.value === todayValue ? 'date' : null\"\n [attr.aria-disabled]=\"!item.enabled || null\"\n [attr.aria-label]=\"item.ariaLabel\"\n [attr.aria-selected]=\"isSelected(item.value)\"\n [class]=\"item.cellClass\"\n [class.owl-dt-calendar-cell-active]=\"isActiveCell(rowIndex, colIndex)\"\n [class.owl-dt-calendar-cell-disabled]=\"!item.enabled\"\n [class.owl-dt-calendar-cell-in-range]=\"isInRange(item.value)\"\n [class.owl-dt-calendar-cell-range-from]=\"isRangeFrom(item.value)\"\n [class.owl-dt-calendar-cell-range-to]=\"isRangeTo(item.value)\"\n [style.paddingBottom.%]=\"(50 * cellRatio) / numCols\"\n [style.paddingTop.%]=\"(50 * cellRatio) / numCols\"\n [style.width.%]=\"100 / numCols\"\n [tabindex]=\"isActiveCell(rowIndex, colIndex) ? 0 : -1\"\n (click)=\"handleSelect(item)\"\n class=\"owl-dt-calendar-cell\">\n <span\n [class.owl-dt-calendar-cell-out]=\"item.out\"\n [class.owl-dt-calendar-cell-selected]=\"isSelected(item.value)\"\n [class.owl-dt-calendar-cell-today]=\"item.value === todayValue\"\n class=\"owl-dt-calendar-cell-content\">\n {{ item.displayValue }}\n </span>\n </td>\n }\n </tr>\n}\n", changeDetection: i0.ChangeDetectionStrategy.OnPush }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: OwlCalendarBodyComponent, decorators: [{ type: Component, args: [{ selector: '[owl-date-time-calendar-body]', exportAs: 'owlDateTimeCalendarBody', host: { 'class': 'owl-dt-calendar-body' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "@for (row of rows; track rowIndex; let rowIndex = $index) {\n <tr role=\"row\">\n @for (item of row; track colIndex; let colIndex = $index) {\n <td\n [attr.aria-current]=\"item.value === todayValue ? 'date' : null\"\n [attr.aria-disabled]=\"!item.enabled || null\"\n [attr.aria-label]=\"item.ariaLabel\"\n [attr.aria-selected]=\"isSelected(item.value)\"\n [class]=\"item.cellClass\"\n [class.owl-dt-calendar-cell-active]=\"isActiveCell(rowIndex, colIndex)\"\n [class.owl-dt-calendar-cell-disabled]=\"!item.enabled\"\n [class.owl-dt-calendar-cell-in-range]=\"isInRange(item.value)\"\n [class.owl-dt-calendar-cell-range-from]=\"isRangeFrom(item.value)\"\n [class.owl-dt-calendar-cell-range-to]=\"isRangeTo(item.value)\"\n [style.paddingBottom.%]=\"(50 * cellRatio) / numCols\"\n [style.paddingTop.%]=\"(50 * cellRatio) / numCols\"\n [style.width.%]=\"100 / numCols\"\n [tabindex]=\"isActiveCell(rowIndex, colIndex) ? 0 : -1\"\n (click)=\"handleSelect(item)\"\n class=\"owl-dt-calendar-cell\">\n <span\n [class.owl-dt-calendar-cell-out]=\"item.out\"\n [class.owl-dt-calendar-cell-selected]=\"isSelected(item.value)\"\n [class.owl-dt-calendar-cell-today]=\"item.value === todayValue\"\n class=\"owl-dt-calendar-cell-content\">\n {{ item.displayValue }}\n </span>\n </td>\n }\n </tr>\n}\n" }] }], propDecorators: { activeCell: [{ type: Input }], rows: [{ type: Input }], numCols: [{ type: Input }], cellRatio: [{ type: Input }], todayValue: [{ type: Input }], selectedValues: [{ type: Input }], selectMode: [{ type: Input }] } }); const DAYS_PER_WEEK = 7; const WEEKS_PER_VIEW = 6; class OwlMonthViewComponent { cdRef = inject(ChangeDetectorRef); dateTimeAdapter = inject((DateTimeAdapter), { optional: true }); dateTimeFormats = inject(OWL_DATE_TIME_FORMATS, { optional: true }); /** * Whether to hide dates in other months at the start or end of the current month. */ hideOtherMonths = false; isDefaultFirstDayOfWeek = true; /** * Define the first day of a week * Sunday: 0 - Saturday: 6 */ _firstDayOfWeek; get firstDayOfWeek() { return this._firstDayOfWeek; } set firstDayOfWeek(val) { if (val >= 0 && val <= 6 && val !== this._firstDayOfWeek) { this._firstDayOfWeek = val; this.isDefaultFirstDayOfWeek = false; if (this.initiated) { this.generateWeekDays(); this.generateCalendar(); this.cdRef.markForCheck(); } } } /** * The select mode of the picker; */ _selectMode = 'single'; get selectMode() { return this._selectMode; } set selectMode(val) { this._selectMode = val; if (this.initiated) { this.generateCalendar(); this.cdRef.markForCheck(); } } /** The currently selected date. */ _selected; get selected() { return this._selected; } set selected(value) { const oldSelected = this._selected; value = this.dateTimeAdapter.deserialize(value); this._selected = this.getValidDate(value); if (!this.dateTimeAdapter.isSameDay(oldSelected, this._selected)) { this.setSelectedDates(); } } _selecteds = []; get selecteds() { return this._selecteds; } set selecteds(values) { this._selecteds = values.map((v) => { v = this.dateTimeAdapter.deserialize(v); return this.getValidDate(v); }); this.setSelectedDates(); } _pickerMoment; get pickerMoment() { return this._pickerMoment; } set pickerMoment(value) { const oldMoment = this._pickerMoment; value = this.dateTimeAdapter.deserialize(value); this._pickerMoment = this.getValidDate(value) || this.dateTimeAdapter.now(); this.firstDateOfMonth = this.dateTimeAdapter.createDate(this.dateTimeAdapter.getYear(this._pickerMoment), this.dateTimeAdapter.getMonth(this._pickerMoment), 1); if (!this.isSameMonth(oldMoment, this._pickerMoment) && this.initiated) { this.generateCalendar(); } } /** * A function used to filter which dates are selectable */ _dateFilter; get dateFilter() { return this._dateFilter; } set dateFilter(filter) { this._dateFilter = filter; if (this.initiated) { this.generateCalendar(); this.cdRef.markForCheck(); } } /** The minimum selectable date. */ _minDate; get minDate() { return this._minDate; } set minDate(value) { value = this.dateTimeAdapter.deserialize(value); this._minDate = this.getValidDate(value); if (this.initiated) { this.generateCalendar(); this.cdRef.markForCheck(); } } /** The maximum selectable date. */ _maxDate; get maxDate() { return this._maxDate; } set maxDate(value) { value = this.dateTimeAdapter.deserialize(value); this._maxDate = this.getValidDate(value); if (this.initiated) { this.generateCalendar(); this.cdRef.markForCheck(); } } _weekdays; get weekdays() { return this._weekdays; } _days; get days() { return this._days; } get activeCell() { if (this.pickerMoment) { return this.dateTimeAdapter.getDate(this.pickerMoment) + this.firstRowOffset - 1; } return undefined; } get isInSingleMode() { return this.selectMode === 'single'; } get isInRangeMode() { return this.selectMode === 'range' || this.selectMode === 'rangeFrom' || this.selectMode === 'rangeTo'; } firstDateOfMonth; localeSub = Subscription.EMPTY; initiated = false; dateNames; /** * The date of the month that today falls on. */ todayDate; /** * An array to hold all selectedDates' value * the value is the day number in current month */ selectedDates = []; // the index of cell that contains the first date of the month firstRowOffset; /** * Callback to invoke when a new date is selected */ selectedChange = output(); /** * Callback to invoke when any date is selected. */ userSelection = output(); /** Emits when any date is activated. */ pickerMomentChange = output(); /** The body of calendar table */ calendarBodyElm; ngOnInit() { this.updateFirstDayOfWeek(this.dateTimeAdapter.getLocale()); this.generateWeekDays(); this.localeSub = this.dateTimeAdapter.localeChanges.subscribe((locale) => { this.updateFirstDayOfWeek(locale); this.generateWeekDays(); this.generateCalendar(); this.cdRef.markForCheck(); }); } ngAfterContentInit() { this.generateCalendar(); this.initiated = true; } ngOnDestroy() { this.localeSub.unsubscribe(); } /** * Handle a calendarCell selected */ selectCalendarCell(cell) { // Cases in which the date would not be selected // 1, the calendar cell is NOT enabled (is NOT valid) // 2, the selected date is NOT in current picker's month and the hideOtherMonths is enabled if (!cell.enabled || (this.hideOtherMonths && cell.out)) { return; } this.selectDate(cell.value); } /** * Handle a new date selected */ selectDate(date) { const daysDiff = date - 1; const selected = this.dateTimeAdapter.addCalendarDays(this.firstDateOfMonth, daysDiff); this.selectedChange.emit(selected); this.userSelection.emit(); } /** * Handle keydown event on calendar body */ handleCalendarKeydown(event) { let moment; switch (event.keyCode) { // minus 1 day case LEFT_ARROW: moment = this.dateTimeAdapter.addCalendarDays(this.pickerMoment, -1); this.pickerMomentChange.emit(moment); break; // add 1 day case RIGHT_ARROW: moment = this.dateTimeAdapter.addCalendarDays(this.pickerMoment, 1); this.pickerMomentChange.emit(moment); break; // minus 1 week case UP_ARROW: moment = this.dateTimeAdapter.addCalendarDays(this.pickerMoment, -7); this.pickerMomentChange.emit(moment); break; // add 1 week case DOWN_ARROW: moment = this.dateTimeAdapter.addCalendarDays(this.pickerMoment, 7); this.pickerMomentChange.emit(moment); break; // move to first day of current month case HOME: moment = this.dateTimeAdapter.addCalendarDays(this.pickerMoment, 1 - this.dateTimeAdapter.getDate(this.pickerMoment)); this.pickerMomentChange.emit(moment); break; // move to last day of current month case END: moment = this.dateTimeAdapter.addCalendarDays(this.pickerMoment, this.dateTimeAdapter.getNumDaysInMonth(this.pickerMoment) - this.dateTimeAdapter.getDate(this.pickerMoment)); this.pickerMomentChange.emit(moment); break; // minus 1 month (or 1 year) case PAGE_UP: moment = event.altKey ? this.dateTimeAdapter.addCalendarYears(this.pickerMoment, -1) : this.dateTimeAdapter.addCalendarMonths(this.pickerMoment, -1); this.pickerMomentChange.emit(moment); break; // add 1 month (or 1 year) case PAGE_DOWN: moment = event.altKey ? this.dateTimeAdapter.addCalendarYears(this.pickerMoment, 1) : this.dateTimeAdapter.addCalendarMonths(this.pickerMoment, 1); this.pickerMomentChange.emit(moment); break; // select the pickerMoment case ENTER: if (!this.dateFilter || this.dateFilter(this.pickerMoment)) { this.selectDate(this.dateTimeAdapter.getDate(this.pickerMoment)); } break; default: return; } this.focusActiveCell(); event.preventDefault(); } /** * Generate the calendar weekdays array */ generateWeekDays() { const longWeekdays = this.dateTimeAdapter.getDayOfWeekNames('long'); const shortWeekdays = this.dateTimeAdapter.getDayOfWeekNames('short'); const narrowWeekdays = this.dateTimeAdapter.getDayOfWeekNames('narrow'); const firstDayOfWeek = this.firstDayOfWeek; const weekdays = longWeekdays.map((long, i) => { return { long, short: shortWeekdays[i], narrow: narrowWeekdays[i] }; }); this._weekdays = weekdays.slice(firstDayOfWeek).concat(weekdays.slice(0, firstDayOfWeek)); this.dateNames = this.dateTimeAdapter.getDateNames(); return; } /** * Generate the calendar days array */ generateCalendar() { if (!this.pickerMoment) { return; } this.todayDate = null; // the first weekday of the month const startWeekdayOfMonth = this.dateTimeAdapter.getDay(this.firstDateOfMonth); const firstDayOfWeek = this.firstDayOfWeek; // the amount of days from the first date of the month // if it is < 0, it means the date is in previous month let daysDiff = 0 - ((startWeekdayOfMonth + (DAYS_PER_WEEK - firstDayOfWeek)) % DAYS_PER_WEEK); // the index of cell that contains the first date of the month this.firstRowOffset = Math.abs(daysDiff); this._days = []; for (let i = 0; i < WEEKS_PER_VIEW; i++) { const week = []; for (let j = 0; j < DAYS_PER_WEEK; j++) { const date = this.dateTimeAdapter.addCalendarDays(this.firstDateOfMonth, daysDiff); const dateCell = this.createDateCell(date, daysDiff); // check if the date is today if (this.dateTimeAdapter.isSameDay(this.dateTimeAdapter.now(), date)) { this.todayDate = daysDiff + 1; } week.push(dateCell); daysDiff += 1; } this._days.push(week); } this.setSelectedDates(); } updateFirstDayOfWeek(locale) { if (this.isDefaultFirstDayOfWeek) { try { this._firstDayOfWeek = getLocaleFirstDayOfWeek(locale); } catch { this._firstDayOfWeek = 0; } } } /** * Creates CalendarCell for days. */ createDateCell(date, daysDiff) { // total days of the month const daysInMonth = this.dateTimeAdapter.getNumDaysInMonth(this.pickerMoment); const dateNum = this.dateTimeAdapter.getDate(date); // const dateName = this.dateNames[dateNum - 1]; const dateName = dateNum.toString(); const ariaLabel = this.dateTimeAdapter.format(date, this.dateTimeFormats.dateA11yLabel); // check if the date if selectable const enabled = this.isDateEnabled(date); // check if date is not in current month const dayValue = daysDiff + 1; const out = dayValue < 1 || dayValue > daysInMonth; const cellClass = `owl-dt-day-${this.dateTimeAdapter.getDay(date)}`; return new CalendarCell(dayValue, dateName, ariaLabel, enabled, out, cellClass); } /** * Check if the date is valid */ isDateEnabled(date) { return (!!date && (!this.dateFilter || this.dateFilter(date)) && (!this.minDate || this.dateTimeAdapter.compare(date, this.minDate) >= 0) && (!this.maxDate || this.dateTimeAdapter.compare(date, this.maxDate) <= 0)); } /** * Get a valid date object */ getValidDate(obj) { return this.dateTimeAdapter.isDateInstance(obj) && this.dateTimeAdapter.isValid(obj) ? obj : null; } /** * Check if the give dates are none-null and in the same month */ isSameMonth(dateLeft, dateRight) { return !!(dateLeft && dateRight && this.dateTimeAdapter.isValid(dateLeft) && this.dateTimeAdapter.isValid(dateRight) && this.dateTimeAdapter.getYear(dateLeft) === this.dateTimeAdapter.getYear(dateRight) && this.dateTimeAdapter.getMonth(dateLeft) === this.dateTimeAdapter.getMonth(dateRight)); } /** * Set the selectedDates value. * In single mode, it has only one value which represent the selected date * In range mode, it would has two values, one for the fromValue and the other for the toValue */ setSelectedDates() { this.selectedDates = []; if (!this.firstDateOfMonth) { return; } if (this.isInSingleMode && this.selected) { const dayDiff = this.dateTimeAdapter.differenceInCalendarDays(this.selected, this.firstDateOfMonth); this.selectedDates[0] = dayDiff + 1; return; } if (this.isInRangeMode && this.selecteds) { this.selectedDates = this.selecteds.map((selected) => { if (this.dateTimeAdapter.isValid(selected)) { const dayDiff = this.dateTimeAdapter.differenceInCalendarDays(selected, this.firstDateOfMonth); return dayDiff + 1; } else { return null; } }); } } focusActiveCell() { this.calendarBodyElm.focusActiveCell(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: OwlMonthViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.2", type: OwlMonthViewComponent, isStandalone: true, selector: "owl-date-time-month-view", inputs: { hideOtherMonths: "hideOtherMonths", firstDayOfWeek: "firstDayOfWeek", selectMode: "selectMode", selected: "selected", selecteds: "selecteds", pickerMoment: "pickerMoment", dateFilter: "dateFilter", minDate: "minDate", maxDate: "maxDate" }, outputs: { selectedChange: "selectedChange", userSelection: "userSelection", pickerMomentChange: "pickerMomentChange" }, host: { classAttribute: "owl-dt-calendar-view" }, viewQueries: [{ propertyName: "calendarBodyElm", first: true, predicate: OwlCalendarBodyComponent, descendants: true, static: true }], exportAs: ["owlYearView"], ngImport: i0, template: "<table\n [class.owl-dt-calendar-only-current-month]=\"hideOtherMonths\"\n class=\"owl-dt-calendar-table owl-dt-calendar-month-table\">\n <thead class=\"owl-dt-calendar-header\">\n <tr class=\"owl-dt-weekdays\">\n @for (weekday of weekdays; track weekday.short) {\n <th\n [attr.aria-label]=\"weekday.long\"\n class=\"owl-dt-weekday\"\n scope=\"col\">\n <span>{{ weekday.short }}</span>\n </th>\n }\n </tr>\n <tr>\n <th\n aria-hidden=\"true\"\n class=\"owl-dt-calendar-table-divider\"\n colspan=\"7\"></th>\n </tr>\n </thead>\n <tbody\n [activeCell]=\"activeCell\"\n [rows]=\"days\"\n [selectMode]=\"selectMode\"\n [selectedValues]=\"selectedDates\"\n [todayValue]=\"todayDate\"\n (keydown)=\"handleCalendarKeydown($event)\"\n (selectCell)=\"selectCalendarCell($event)\"\n owl-date-time-calendar-body\n role=\"grid\"></tbody>\n</table>\n", dependencies: [{ kind: "component", type: OwlCalendarBodyComponent, selector: "[owl-date-time-calendar-body]", inputs: ["activeCell", "rows", "numCols", "cellRatio", "todayValue", "selectedValues", "selectMode"], outputs: ["selectCell"], exportAs: ["owlDateTimeCalendarBody"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: OwlMonthViewComponent, decorators: [{ type: Component, args: [{ selector: 'owl-date-time-month-view', exportAs: 'owlYearView', imports: [OwlCalendarBodyComponent], host: { 'class': 'owl-dt-calendar-view' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<table\n [class.owl-dt-calendar-only-current-month]=\"hideOtherMonths\"\n class=\"owl-dt-calendar-table owl-dt-calendar-month-table\">\n <thead class=\"owl-dt-calendar-header\">\n <tr class=\"owl-dt-weekdays\">\n @for (weekday of weekdays; track weekday.short) {\n <th\n [attr.aria-label]=\"weekday.long\"\n class=\"owl-dt-weekday\"\n scope=\"col\">\n <span>{{ weekday.short }}</span>\n </th>\n }\n </tr>\n <tr>\n <th\n aria-hidden=\"true\"\n class=\"owl-dt-calendar-table-divider\"\n colspan=\"7\"></th>\n </tr>\n </thead>\n <tbody\n [activeCell]=\"activeCell\"\n [rows]=\"days\"\n [selectMode]=\"selectMode\"\n [selectedValues]=\"selectedDates\"\n [todayValue]=\"todayDate\"\n (keydown)=\"handleCalendarKeydown($event)\"\n (selectCell)=\"selectCalendarCell($event)\"\n owl-date-time-calendar-body\n role=\"grid\"></tbody>\n</table>\n" }] }], propDecorators: { hideOtherMonths: [{ type: Input }], firstDayOfWeek: [{ type: Input }], selectMode: [{ type: Input }], selected: [{ type: Input }], selecteds: [{ type: Input }], pickerMoment: [{ type: Input }], dateFilter: [{ type: Input }], minDate: [{ type: Input }], maxDate: [{ type: Input }], calendarBodyElm: [{ type: ViewChild, args: [OwlCalendarBodyComponent, { static: true }] }] } }); class OwlDateTimeIntl { /** * Stream that emits whenever the labels here are changed. Use this to notify * components if the labels have changed after initialization. */ changes = new Subject(); /** A label for the up second button (used by screen readers). */ upSecondLabel = 'Add a second'; /** A label for the down second button (used by screen readers). */ downSecondLabel = 'Minus a second'; /** A label for the up minute button (used by screen readers). */ upMinuteLabel = 'Add a minute'; /** A label for the down minute button (used by screen readers). */ downMinuteLabel = 'Minus a minute'; /** A label for the up hour button (used by screen readers). */ upHourLabel = 'Add a hour'; /** A label for the down hour button (used by screen readers). */ downHourLabel = 'Minus a hour'; /** A label for the previous month button (used by screen readers). */ prevMonthLabel = 'Previous month'; /** A label for the next month button (used by screen readers). */ nextMonthLabel = 'Next month'; /** A label for the previous year button (used by screen readers). */ prevYearLabel = 'Previous year'; /** A label for the next year button (used by screen readers). */ nextYearLabel = 'Next year'; /** A label for the previous multi-year button (used by screen readers). */ prevMultiYearLabel = 'Previous 21 years'; /** A label for the next multi-year button (used by screen readers). */ nextMultiYearLabel = 'Next 21 years'; /** A label for the 'switch to month view' button (used by screen readers). */ switchToMonthViewLabel = 'Change to month view'; /** A label for the 'switch to year view' button (used by screen readers). */ switchToMultiYearViewLabel = 'Choose month and year'; /** A label for the range 'from' in picker info */ rangeFromLabel = 'From'; /** A label for the range 'to' in picker info */ rangeToLabel = 'To'; /** A label for the hour12 button (AM) */ hour12AMLabel = 'AM'; /** A label for the hour12 button (PM) */ hour12PMLabel = 'PM'; /** A label for the today button */ todayButtonLabel = 'Today'; static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: OwlDateTimeIntl, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDec