@netwin/angular-datetime-picker
Version:
Angular Date Time Picker
1,139 lines (1,128 loc) • 188 kB
JavaScript
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