UNPKG

axiros-datetime-picker

Version:
1,145 lines (1,137 loc) 257 kB
import * as i0 from '@angular/core'; import { Directive, Input, InjectionToken, Injectable, inject, LOCALE_ID, EventEmitter, Component, ChangeDetectionStrategy, Output, Optional, Inject, ViewChild, Pipe, TemplateRef, SkipSelf, forwardRef, NgModule } from '@angular/core'; import * as i1 from '@angular/common'; import { DOCUMENT, CommonModule } from '@angular/common'; import * as i1$1 from '@angular/cdk/a11y'; import { A11yModule } from '@angular/cdk/a11y'; import * as i1$2 from '@angular/cdk/overlay'; import { NoopScrollStrategy, Overlay, OverlayConfig, OverlayModule } from '@angular/cdk/overlay'; import { Subscription, of, merge, Subject, defer } from 'rxjs'; import * as i2 from '@angular/cdk/portal'; import { BasePortalOutlet, CdkPortalOutlet, ComponentPortal, PortalInjector, PortalModule } from '@angular/cdk/portal'; import { ENTER, PAGE_DOWN, PAGE_UP, END, HOME, DOWN_ARROW, UP_ARROW, RIGHT_ARROW, LEFT_ARROW, SPACE, ESCAPE } from '@angular/cdk/keycodes'; import { coerceNumberProperty, coerceBooleanProperty, coerceArray } from '@angular/cdk/coercion'; import { take, debounceTime, distinctUntilChanged, filter, startWith } from 'rxjs/operators'; import { trigger, state, style, transition, group, query, animateChild, animate, keyframes } from '@angular/animations'; import { NG_VALUE_ACCESSOR, NG_VALIDATORS, Validators } from '@angular/forms'; import * as i1$3 from '@angular/cdk/platform'; import { PlatformModule } from '@angular/cdk/platform'; class OwlDateTimeTriggerDirective { constructor(changeDetector) { this.changeDetector = changeDetector; this.stateChanges = Subscription.EMPTY; } get disabled() { return this._disabled === undefined ? this.dtPicker.disabled : !!this._disabled; } set disabled(value) { this._disabled = value; } get owlDTTriggerDisabledClass() { return this.disabled; } ngOnInit() { } ngOnChanges(changes) { if (changes.datepicker) { this.watchStateChanges(); } } ngAfterContentInit() { this.watchStateChanges(); } ngOnDestroy() { this.stateChanges.unsubscribe(); } handleClickOnHost(event) { if (this.dtPicker) { this.dtPicker.open(); event.stopPropagation(); } } watchStateChanges() { this.stateChanges.unsubscribe(); const inputDisabled = this.dtPicker && this.dtPicker.dtInput ? this.dtPicker.dtInput.disabledChange : of(); const pickerDisabled = this.dtPicker ? this.dtPicker.disabledChange : of(); this.stateChanges = merge([pickerDisabled, inputDisabled]) .subscribe(() => { this.changeDetector.markForCheck(); }); } } /** @nocollapse */ OwlDateTimeTriggerDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: OwlDateTimeTriggerDirective, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive }); /** @nocollapse */ OwlDateTimeTriggerDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "13.3.11", type: OwlDateTimeTriggerDirective, selector: "[owlDateTimeTrigger]", inputs: { dtPicker: ["owlDateTimeTrigger", "dtPicker"], disabled: "disabled" }, host: { listeners: { "click": "handleClickOnHost($event)" }, properties: { "class.owl-dt-trigger-disabled": "owlDTTriggerDisabledClass" } }, usesOnChanges: true, ngImport: i0 }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: OwlDateTimeTriggerDirective, decorators: [{ type: Directive, args: [{ selector: '[owlDateTimeTrigger]', host: { '(click)': 'handleClickOnHost($event)', '[class.owl-dt-trigger-disabled]': 'owlDTTriggerDisabledClass' } }] }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }]; }, propDecorators: { dtPicker: [{ type: Input, args: ['owlDateTimeTrigger'] }], disabled: [{ type: Input }] } }); /** * 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'); /** * date-time-picker-intl.service */ class OwlDateTimeIntl { constructor() { /** * Stream that emits whenever the labels here are changed. Use this to notify * components if the labels have changed after initialization. */ this.changes = new Subject(); /** A label for the up second button (used by screen readers). */ this.upSecondLabel = 'Add a second'; /** A label for the down second button (used by screen readers). */ this.downSecondLabel = 'Minus a second'; /** A label for the up minute button (used by screen readers). */ this.upMinuteLabel = 'Add a minute'; /** A label for the down minute button (used by screen readers). */ this.downMinuteLabel = 'Minus a minute'; /** A label for the up hour button (used by screen readers). */ this.upHourLabel = 'Add a hour'; /** A label for the down hour button (used by screen readers). */ this.downHourLabel = 'Minus a hour'; /** A label for the previous month button (used by screen readers). */ this.prevMonthLabel = 'Previous month'; /** A label for the next month button (used by screen readers). */ this.nextMonthLabel = 'Next month'; /** A label for the previous year button (used by screen readers). */ this.prevYearLabel = 'Previous year'; /** A label for the next year button (used by screen readers). */ this.nextYearLabel = 'Next year'; /** A label for the previous multi-year button (used by screen readers). */ this.prevMultiYearLabel = 'Previous 21 years'; /** A label for the next multi-year button (used by screen readers). */ this.nextMultiYearLabel = 'Next 21 years'; /** A label for the 'switch to month view' button (used by screen readers). */ this.switchToMonthViewLabel = 'Change to month view'; /** A label for the 'switch to year view' button (used by screen readers). */ this.switchToMultiYearViewLabel = 'Choose month and year'; /** A label for the cancel button */ this.cancelBtnLabel = 'Cancel'; /** A label for the set button */ this.setBtnLabel = 'Set'; /** A label for the range 'from' in picker info */ this.rangeFromLabel = 'From'; /** A label for the range 'to' in picker info */ this.rangeToLabel = 'To'; /** A label for the hour12 button (AM) */ this.hour12AMLabel = 'AM'; /** A label for the hour12 button (PM) */ this.hour12PMLabel = 'PM'; } } /** @nocollapse */ OwlDateTimeIntl.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: OwlDateTimeIntl, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); /** @nocollapse */ OwlDateTimeIntl.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: OwlDateTimeIntl, providedIn: 'root' }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: OwlDateTimeIntl, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); /** * 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 { constructor() { /** A stream that emits when the locale changes. */ this._localeChanges = new Subject(); /** total milliseconds in a day. */ this.millisecondsInDay = 86400000; /** total milliseconds in a minute. */ this.millisecondsInMinute = 60000; } get localeChanges() { return this._localeChanges; } /** * 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(); } /** * 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; } } /** * calendar-body.component */ class CalendarCell { 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 { constructor(elmRef, ngZone) { this.elmRef = elmRef; this.ngZone = ngZone; /** * The cell number of the active cell in the table. */ this.activeCell = 0; /** * The number of columns in the table. * */ this.numCols = 7; /** * The ratio (width / height) to use for the cells in the table. */ this.cellRatio = 1; /** * Emit when a calendar cell is selected * */ this.select = new EventEmitter(); } get owlDTCalendarBodyClass() { return true; } get isInSingleMode() { return this.selectMode === 'single'; } get isInRangeMode() { return (this.selectMode === 'range' || this.selectMode === 'rangeFrom' || this.selectMode === 'rangeTo'); } ngOnInit() { } selectCell(cell) { this.select.emit(cell); } 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; } } /** * 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; } } } /** * Check if the cell is the range from * */ isRangeFrom(value) { if (this.isInRangeMode) { const fromValue = this.selectedValues[0]; return fromValue !== null && value === fromValue; } } /** * Check if the cell is the range to * */ isRangeTo(value) { if (this.isInRangeMode) { const toValue = this.selectedValues[1]; return toValue !== null && value === toValue; } } /** * 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(); }); }); } } /** @nocollapse */ OwlCalendarBodyComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: OwlCalendarBodyComponent, deps: [{ token: i0.ElementRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); /** @nocollapse */ OwlCalendarBodyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.11", type: OwlCalendarBodyComponent, selector: "[owl-date-time-calendar-body]", inputs: { activeCell: "activeCell", rows: "rows", numCols: "numCols", cellRatio: "cellRatio", todayValue: "todayValue", selectedValues: "selectedValues", selectMode: "selectMode" }, outputs: { select: "select" }, host: { properties: { "class.owl-dt-calendar-body": "owlDTCalendarBodyClass" } }, exportAs: ["owlDateTimeCalendarBody"], ngImport: i0, template: "<tr *ngFor=\"let row of rows; let rowIndex = index\" role=\"row\">\n <td *ngFor=\"let item of row; let colIndex = index\"\n class=\"owl-dt-calendar-cell {{item.cellClass}}\"\n [tabindex]=\"isActiveCell(rowIndex, colIndex) ? 0 : -1\"\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 [attr.aria-label]=\"item.ariaLabel\"\n [attr.aria-disabled]=\"!item.enabled || null\"\n [style.width.%]=\"100 / numCols\"\n [style.paddingTop.%]=\"50 * cellRatio / numCols\"\n [style.paddingBottom.%]=\"50 * cellRatio / numCols\"\n (click)=\"selectCell(item)\">\n <span class=\"owl-dt-calendar-cell-content\"\n [ngClass]=\"{\n 'owl-dt-calendar-cell-out': item.out,\n 'owl-dt-calendar-cell-today': item.value === todayValue,\n 'owl-dt-calendar-cell-selected': isSelected(item.value)\n }\">\n {{item.displayValue}}\n </span>\n </td>\n</tr>\n", styles: [""], directives: [{ type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: OwlCalendarBodyComponent, decorators: [{ type: Component, args: [{ selector: '[owl-date-time-calendar-body]', exportAs: 'owlDateTimeCalendarBody', host: { '[class.owl-dt-calendar-body]': 'owlDTCalendarBodyClass' }, preserveWhitespaces: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "<tr *ngFor=\"let row of rows; let rowIndex = index\" role=\"row\">\n <td *ngFor=\"let item of row; let colIndex = index\"\n class=\"owl-dt-calendar-cell {{item.cellClass}}\"\n [tabindex]=\"isActiveCell(rowIndex, colIndex) ? 0 : -1\"\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 [attr.aria-label]=\"item.ariaLabel\"\n [attr.aria-disabled]=\"!item.enabled || null\"\n [style.width.%]=\"100 / numCols\"\n [style.paddingTop.%]=\"50 * cellRatio / numCols\"\n [style.paddingBottom.%]=\"50 * cellRatio / numCols\"\n (click)=\"selectCell(item)\">\n <span class=\"owl-dt-calendar-cell-content\"\n [ngClass]=\"{\n 'owl-dt-calendar-cell-out': item.out,\n 'owl-dt-calendar-cell-today': item.value === todayValue,\n 'owl-dt-calendar-cell-selected': isSelected(item.value)\n }\">\n {{item.displayValue}}\n </span>\n </td>\n</tr>\n", styles: [""] }] }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.NgZone }]; }, propDecorators: { activeCell: [{ type: Input }], rows: [{ type: Input }], numCols: [{ type: Input }], cellRatio: [{ type: Input }], todayValue: [{ type: Input }], selectedValues: [{ type: Input }], selectMode: [{ type: Input }], select: [{ type: Output }] } }); /** * calendar-month-view.component */ const DAYS_PER_WEEK = 7; const WEEKS_PER_VIEW = 6; class OwlMonthViewComponent { constructor(cdRef, dateTimeAdapter, dateTimeFormats) { this.cdRef = cdRef; this.dateTimeAdapter = dateTimeAdapter; this.dateTimeFormats = dateTimeFormats; /** * Whether to hide dates in other months at the start or end of the current month. * */ this.hideOtherMonths = false; /** * Define the first day of a week * Sunday: 0 ~ Saturday: 6 * */ this._firstDayOfWeek = 0; /** * The select mode of the picker; * */ this._selectMode = 'single'; this._selecteds = []; this.localeSub = Subscription.EMPTY; this.initiated = false; /** * An array to hold all selectedDates' value * the value is the day number in current month * */ this.selectedDates = []; /** * Callback to invoke when a new date is selected * */ this.selectedChange = new EventEmitter(); /** * Callback to invoke when any date is selected. * */ this.userSelection = new EventEmitter(); /** Emits when any date is activated. */ this.pickerMomentChange = new EventEmitter(); } get firstDayOfWeek() { return this._firstDayOfWeek; } set firstDayOfWeek(val) { val = coerceNumberProperty(val); if (val >= 0 && val <= 6 && val !== this._firstDayOfWeek) { this._firstDayOfWeek = val; if (this.initiated) { this.generateWeekDays(); this.generateCalendar(); this.cdRef.markForCheck(); } } } get selectMode() { return this._selectMode; } set selectMode(val) { this._selectMode = val; if (this.initiated) { this.generateCalendar(); this.cdRef.markForCheck(); } } 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(); } } get selecteds() { return this._selecteds; } set selecteds(values) { this._selecteds = values.map(v => { v = this.dateTimeAdapter.deserialize(v); return this.getValidDate(v); }); this.setSelectedDates(); } 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(); } } get dateFilter() { return this._dateFilter; } set dateFilter(filter) { this._dateFilter = filter; if (this.initiated) { this.generateCalendar(); this.cdRef.markForCheck(); } } 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(); } } 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(); } } get weekdays() { return this._weekdays; } get days() { return this._days; } get activeCell() { if (this.pickerMoment) { return (this.dateTimeAdapter.getDate(this.pickerMoment) + this.firstRowOffset - 1); } } get isInSingleMode() { return this.selectMode === 'single'; } get isInRangeMode() { return (this.selectMode === 'range' || this.selectMode === 'rangeFrom' || this.selectMode === 'rangeTo'); } get owlDTCalendarView() { return true; } ngOnInit() { this.generateWeekDays(); this.localeSub = this.dateTimeAdapter.localeChanges.subscribe(() => { 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(); } /** * 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(); } } /** @nocollapse */ OwlMonthViewComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: OwlMonthViewComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: DateTimeAdapter, optional: true }, { token: OWL_DATE_TIME_FORMATS, optional: true }], target: i0.ɵɵFactoryTarget.Component }); /** @nocollapse */ OwlMonthViewComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.11", type: OwlMonthViewComponent, 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: { properties: { "class.owl-dt-calendar-view": "owlDTCalendarView" } }, viewQueries: [{ propertyName: "calendarBodyElm", first: true, predicate: OwlCalendarBodyComponent, descendants: true, static: true }], exportAs: ["owlYearView"], ngImport: i0, template: "<table class=\"owl-dt-calendar-table owl-dt-calendar-month-table\"\n [class.owl-dt-calendar-only-current-month]=\"hideOtherMonths\">\n <thead class=\"owl-dt-calendar-header\">\n <tr class=\"owl-dt-weekdays\">\n <th *ngFor=\"let weekday of weekdays\"\n [attr.aria-label]=\"weekday.long\"\n class=\"owl-dt-weekday\" scope=\"col\">\n <span>{{weekday.short}}</span>\n </th>\n </tr>\n <tr>\n <th class=\"owl-dt-calendar-table-divider\" aria-hidden=\"true\" colspan=\"7\"></th>\n </tr>\n </thead>\n <tbody owl-date-time-calendar-body role=\"grid\"\n [rows]=\"days\" [todayValue]=\"todayDate\"\n [selectedValues]=\"selectedDates\"\n [selectMode]=\"selectMode\"\n [activeCell]=\"activeCell\"\n (keydown)=\"handleCalendarKeydown($event)\"\n (select)=\"selectCalendarCell($event)\">\n </tbody>\n</table>\n", styles: [""], components: [{ type: OwlCalendarBodyComponent, selector: "[owl-date-time-calendar-body]", inputs: ["activeCell", "rows", "numCols", "cellRatio", "todayValue", "selectedValues", "selectMode"], outputs: ["select"], exportAs: ["owlDateTimeCalendarBody"] }], directives: [{ type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.11", ngImport: i0, type: OwlMonthViewComponent, decorators: [{ type: Component, args: [{ selector: 'owl-date-time-month-view', exportAs: 'owlYearView', host: { '[class.owl-dt-calendar-view]': 'owlDTCalendarView' }, preserveWhitespaces: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "<table class=\"owl-dt-calendar-table owl-dt-calendar-month-table\"\n [class.owl-dt-calendar-only-current-month]=\"hideOtherMonths\">\n <thead class=\"owl-dt-calendar-header\">\n <tr class=\"owl-dt-weekdays\">\n <th *ngFor=\"let weekday of weekdays\"\n [attr.aria-label]=\"weekday.long\"\n class=\"owl-dt-weekday\" scope=\"col\">\n <span>{{weekday.short}}</span>\n </th>\n </tr>\n <tr>\n <th class=\"owl-dt-calendar-table-divider\" aria-hidden=\"true\" colspan=\"7\"></th>\n </tr>\n </thead>\n <tbody owl-date-time-calendar-body role=\"grid\"\n [rows]=\"days\" [todayValue]=\"todayDate\"\n [selectedValues]=\"selectedDates\"\n [selectMode]=\"selectMode\"\n [activeCell]=\"activeCell\"\n (keydown)=\"handleCalendarKeydown($event)\"\n (select)=\"selectCalendarCell($event)\">\n </tbody>\n</table>\n", styles: [""] }] }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }, { type: DateTimeAdapter, decorators: [{ type: Optional }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [OWL_DATE_TIME_FORMATS] }] }]; }, 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 }], selectedChange: [{ type: Output }], userSelection: [{ type: Output }], pickerMomentChange: [{ type: Output }], calendarBodyElm: [{ type: ViewChild, args: [OwlCalendarBodyComponent, { static: true }] }] } }); /** * calendar-year-view.component */ const MONTHS_PER_YEAR = 12; const MONTHS_PER_ROW = 3; class OwlYearViewComponent { constructor(cdRef, dateTimeAdapter, dateTimeFormats) { this.cdRef = cdRef; this.dateTimeAdapter = dateTimeAdapter; this.dateTimeFormats = dateTimeFormats; /** * The select mode of the picker; * */ this._selectMode = 'single'; this._selecteds = []; this.localeSub = Subscription.EMPTY; this.initiated = false; /** * An array to hold all selectedDates' month value * the value is the month number in current year * */ this.selectedMonths = []; /** * Callback to invoke when a new month is selected * */ this.change = new EventEmitter(); /** * Emits the selected year. This doesn't imply a change on the selected date * */ this.monthSelected = new EventEmitter(); /** Emits when any date is activated. */ this.pickerMomentChange = new EventEmitter(); /** Emits when use keyboard enter to select a calendar cell */ this.keyboardEnter = new EventEmitter(); this.monthNames = this.dateTimeAdapter.getMonthNames('short'); } get selectMode() { return this._selectMode; } set selectMode(val) { this._selectMode = val; if (this.initiated) { this.generateMonthList(); this.cdRef.markForCheck(); } } get selected() { return this._selected; } set selected(value) { value = this.dateTimeAdapter.deserialize(value); this._selected = this.getValidDate(value); this.setSelectedMonths(); } get selecteds() { return this._selecteds; } set selecteds(values) { this._selecteds = []; for (let i = 0; i < values.length; i++) { const value = this.dateTimeAdapter.deserialize(values[i]); this._selecteds.push(this.getValidDate(value)); } this.setSelectedMonths(); } 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(); if (!this.hasSameYear(oldMoment, this._pickerMoment) && this.initiated) { this.generateMonthList(); } } get dateFilter() { return this._dateFilter; } set dateFilter(filter) { this._dateFilter = filter; if (this.initiated) { this.generateMonthList(); } } get minDate() { return this._minDate; } set minDate(value) { value = this.dateTimeAdapter.deserialize(value); this._minDate = this.getValidDate(value); if (this.initiated) { this.generateMonthList(); } } get maxDate() { return this._maxDate; } set maxDate(value) { value = this.dateTimeAdapter.deserialize(value); this._maxDate = this.getValidDate(value); if (this.initiated) { this.generateMonthList(); } } get months() { return this._months; } get activeCell() { if (this._pickerMoment) { return this.dateTimeAdapter.getMonth(this._pickerMoment); } } get isInSingleMode() { return this.selectMode === 'single'; } get isInRangeMode() { return (this.selectMode === 'range' || this.selectMode === 'rangeFrom' || this.selectMode === 'rangeTo'); } get owlDTCalendarView() { return true; } ngOnInit() { this.localeSub = this.dateTimeAdapter.localeChanges.subscribe(() => { this.generateMonthList(); this.cdRef.markForCheck(); }); } ngAfterContentInit() { this.generateMonthList(); this.initiated = true; } ngOnDestroy() { this.localeSub.unsubscribe(); } /** * Handle a calendarCell selected */ selectCalendarCell(cell) { this.selectMonth(cell.value); } /** * Handle a new month selected */ selectMonth(month) { const firstDateOfMonth = this.dateTimeAdapter.createDate(this.dateTimeAdapter.getYear(this.pickerMoment), month, 1); this.monthSelected.emit(firstDateOfMonth); const daysInMonth = this.dateTimeAdapter.getNumDaysInMonth(firstDateOfMonth); const result = this.dateTimeAdapter.createDate(this.dateTimeAdapter.getYear(this.pickerMoment), month, Math.min(daysInMonth, this.dateTimeAdapter.getDate(this.pickerMoment)), this.dateTimeAdapter.getHours(this.pickerMoment), this.dateTimeAdapter.getMinutes(this.pickerMoment), this.dateTimeAdapter.getSeconds(this.pickerMoment)); this.change.emit(result); } /** * Handle keydown event on calendar body */ handleCalendarKeydown(event) { let moment; switch (event.keyCode) { // minus 1 month case LEFT_ARROW: moment = this.dateTimeAdapter.addCalendarMonths(this.pickerMoment, -1); this.pickerMomentChange.emit(moment); break; // add 1 month case RIGHT_ARROW: moment = this.dateTimeAdapter.addCalendarMonths(this.pickerMoment, 1); this.pickerMomentChange.emit(moment); break; // minus 3 months case UP_ARROW: moment = this.dateTimeAdapter.addCalendarMonths(this.pickerMoment, -3); this.pickerMomentChange.emit(moment); break; // add 3 months case DOWN_ARROW: moment = this.dateTimeAdapter.addCalendarMonths(this.pickerMoment, 3); this.pickerMomentChange.emit(moment); break; // move to first month of current year case HOME: moment = this.dateTimeAdapter.addCalendarMonths(this.pickerMoment, -this.dateTimeAdapter.getMonth(this.pickerMoment)); this.pickerMomentChange.emit(moment); break; // move to last month of current year case END: moment = this.dateTimeAdapter.addCalendarMonths(this.pickerMoment, 11 - this.dateTimeAdapter.getMonth(this.pickerMoment)); this.pickerMomentChange.emit(moment); break; // minus 1 year (or 10 year) case PAGE_UP: moment = this.dateTimeAdapter.addCalendarYears(this.pickerMoment, event.altKey ? -10 : -1); this.pickerMomentChange.emit(moment); break; // add 1 year (or 10 year) case PAGE_DOWN: moment = this.dateTimeAdapter.addCalendarYears(this.pickerMoment, event.altKey ? 10 : 1); this.pickerMomentChange.emit(moment); break; // Select current month case ENTER: this.selectMonth(this.dateTimeAdapter.getMonth(this.pickerMoment)); this.keyboardEnter.emit(); break; default: return; } this.focusActiveCell(); event.preventDefault(); } /** * Generate the calendar month list * */ generateMonthList() { if (!this.pickerMoment) { return; } this.setSelectedMonths(); this.todayMonth = this.getMonthInCurrentYear(this.dateTimeAdapter.now()); this._months = []; for (let i = 0; i < MONTHS_PER_YEAR / MONTHS_PER_ROW; i++) { const row = []; for (let j = 0; j < MONTHS_PER_ROW; j++) { const month = j + i * MONTHS_PER_ROW; const monthCell = this.createMonthCell(month); row.push(monthCell); } this._months.push(row); } return; } /** * Creates an CalendarCell for the given month. */ createMonthCell(month) { const startDateOfMonth = this.dateTimeAdapter.createDate(this.dateTimeAdapter.getYear(this.pickerMoment), month, 1); const ariaLabel = this.dateTimeAdapter.format(startDateOfMonth, this.dateTimeFormats.monthYearA11yLabel); const cellClass = 'owl-dt-month-' + month; return new CalendarCell(month, this.monthNames[month], ariaLabel, this.isMonthEnabled(month), false, cellClass); } /** * Check if the given month is enable */ isMonthEnabled(month) { const firstDateOfMonth = this.dateTimeAdapter.createDate(this.dateTimeAdapter.getYear(this.pickerMoment), month, 1); // If any date in the month is selectable, // we count the month as enable for (let date = firstDateOfMonth; this.dateTimeAdapter.getMonth(date) === month; date = this.dateTimeAdapter.addCalendarDays(date, 1)) { if (!!date && (!this.dateFilter || this.dateFilter(date)) && (!this.minDate || this.dateTimeAdapter.compare(date, this.minDate) >= 0) && (!this.maxDate || this.dateTimeAdapter.compare(date, this.maxDate) <= 0)) { return true; } } return false; } /** * Gets the month in this year that the given Date falls on. * Returns null if the given Date is in another year. */ getMonthInCurrentYear(date) { if (this.getValidDate(date) && this.getValidDate(this._pickerMoment)) { const result = this.dateTimeAdapter.compareYear(date, this._pickerMoment); // < 0 : the given date's year is before pickerMoment's year, we return -1 as selected month value. // > 0 : the given date's year is after pickerMoment's year, we return 12 as selected month value. // 0 : the give date's year is same as the pickerMoment's year, we return the actual month value. if (result < 0) { return -1; } else if (result > 0) { return 12; } else { return this.dateTimeAdapter.getMonth(date); } } else { return null; } } /** * Set the selectedMonths value * In single mode, it has only one value which represent the month the selected date in * In range mode, it would has two values, one for the month the fromValue in and the other for the month the toValue in * */ setSelectedMonths() { this.selectedMonths = []; if (this.isInSingleMode && this.selected) { this.selectedMonths[0] = this.getMonthInCurrentYear(this.selected); } if (this.isInRangeMode && this.selecteds) { this.selectedMonths[0] = this.getMonthInCurrentYear(this.selecteds[0]); this.selectedMonths[1] = this.getMonthInCurrentYear(this.selecteds[1]); } } /** * Check the given dates are in the same year */ hasSameYear(dateLeft, dateRight) { return !!(dateLeft && dateRight &&