UNPKG

mat-daterangepicker

Version:

Angular material date picker extension to support range selection.

1,035 lines (1,023 loc) 50.2 kB
import { __decorate, __param, __awaiter } from 'tslib'; import { EventEmitter, ReflectiveInjector, Optional, Inject, ChangeDetectorRef, Injector, Input, Output, Component, ViewEncapsulation, ChangeDetectionStrategy, ViewChild, ViewChildren, InjectionToken, NgZone, ViewContainerRef, forwardRef, ElementRef, Directive, ComponentFactoryResolver, NgModule } from '@angular/core'; import { DOCUMENT, CommonModule } from '@angular/common'; import { A11yModule } from '@angular/cdk/a11y'; import { Overlay, OverlayModule } from '@angular/cdk/overlay'; import { ComponentPortal, PortalModule } from '@angular/cdk/portal'; import { DateAdapter, MAT_DATE_FORMATS, MatNativeDateModule } from '@angular/material/core'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog, MatDialogModule } from '@angular/material/dialog'; import { MatCalendar, MatDatepickerIntl, MatDatepickerContent, matDatepickerAnimations, MatDatepicker, MAT_DATEPICKER_SCROLL_STRATEGY, MatDatepickerInput, MatDatepickerInputEvent, MatMonthView, MatCalendarBody, MatDatepickerToggle, MatDatepickerModule } from '@angular/material/datepicker'; import { BehaviorSubject, Subject, Subscription } from 'rxjs'; import { Directionality } from '@angular/cdk/bidi'; import { take } from 'rxjs/operators'; import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms'; import { MatFormField } from '@angular/material/form-field'; import { MAT_INPUT_VALUE_ACCESSOR } from '@angular/material/input'; /* RANGE: Most of the code is redundant and hera as inheritance boilerplate. The relevant code is marked with a comment. */ let MatDaterangeCalendar = class MatDaterangeCalendar extends MatCalendar { // THIS SECTION IS JUST TO PROVIDE MatCalendar as SgCalendar to MatCalendarHeader constructor(_intl, _dateAdapter, _dateFormats, changeDetectorRef, drInjector) { super(_intl, _dateAdapter, _dateFormats, changeDetectorRef); this.drInjector = drInjector; this.selectedRangeEndChange = new EventEmitter(); } ngAfterContentInit() { super.ngAfterContentInit(); const injector = ReflectiveInjector.resolveAndCreate([{ provide: MatCalendar, useValue: this }], this.drInjector); this._calendarHeaderPortal.injector = injector; const activeDate = this.startAt || this['_dateAdapter'].today(); this.activeDate = this.view === 0 ? activeDate : this.getSecondViewActiveDate(activeDate); // Assign to the private property since we don't want to move focus on init. // this._currentView = this.startView; } get selectedRangeEnd() { return this._selectedRangeEnd; } set selectedRangeEnd(value) { this._selectedRangeEnd = this._getValidDateOrNull(this._dateAdapter.deserialize(value)); } _dateSelectedRangeEnd(date) { if (!this._dateAdapter.sameDate(date, this.selectedRangeEnd)) { this.selectedRangeEndChange.emit(date); } } getSecondViewActiveDate(activeDate) { const date = this['_dateAdapter'].getDate(activeDate); const month = this['_dateAdapter'].getMonth(activeDate); const year = this['_dateAdapter'].getYear(activeDate); let y, m; if (month === 11) { m = 0; y = year + 1; } else { m = month + 1, y = year; } const D = this['_dateAdapter'].createDate(y, m, date); return D; } }; MatDaterangeCalendar.ctorParameters = () => [ { type: MatDatepickerIntl }, { type: DateAdapter, decorators: [{ type: Optional }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_DATE_FORMATS,] }] }, { type: ChangeDetectorRef }, { type: Injector } ]; __decorate([ Input() ], MatDaterangeCalendar.prototype, "view", void 0); __decorate([ Input() ], MatDaterangeCalendar.prototype, "setHoverCells", void 0); __decorate([ Input() ], MatDaterangeCalendar.prototype, "clearBody", void 0); __decorate([ Input() ], MatDaterangeCalendar.prototype, "range", void 0); __decorate([ Input() ], MatDaterangeCalendar.prototype, "selectedRangeEnd", null); __decorate([ Output() ], MatDaterangeCalendar.prototype, "selectedRangeEndChange", void 0); MatDaterangeCalendar = __decorate([ Component({ selector: 'mat-daterange-calendar', template: "<!-- \n\tRANGE: Most of the code is redundant and hera as inheritance boilerplate.\n\tThe relevant code is marked with a comment.\n\tADDED LOC ~3\n-->\n<ng-template [cdkPortalOutlet]=\"_calendarHeaderPortal\"></ng-template>\n\n<div class=\"mat-calendar-content\" [ngSwitch]=\"currentView\" cdkMonitorSubtreeFocus tabindex=\"-1\">\n\n\t<mat-daterange-month-view\n\t\t*ngSwitchCase=\"'month'\"\n\t\t[view]=\"view\"\n\t\t[activeDate]=\"activeDate\"\n\t\t[selected]=\"selected\"\n\t\t[dateFilter]=\"dateFilter\"\n\t\t[maxDate]=\"maxDate\"\n\t\t[minDate]=\"minDate\"\n\t\t(selectedChange)=\"_dateSelected($event)\"\n\t\t(_userSelection)=\"_userSelected()\"\n\n\t\t[range]=\"range\"\n\t\t[setHoverCells]=\"setHoverCells\"\n\t\t[clearBody]=\"clearBody\"\n\t\t[selectedRangeEnd]=\"selectedRangeEnd\"\n\t\t(selectedRangeEndChange)=\"_dateSelectedRangeEnd($event)\">\n\t\t<!-- THE 3 LINES ABOVE ARE THE ONLY ADDITION -->\n\t</mat-daterange-month-view>\n\n\t<mat-year-view\n\t\t*ngSwitchCase=\"'year'\"\n\t\t[activeDate]=\"activeDate\"\n\t\t[selected]=\"selected\"\n\t\t[dateFilter]=\"dateFilter\"\n\t\t[maxDate]=\"maxDate\"\n\t\t[minDate]=\"minDate\"\n\t\t(monthSelected)=\"_monthSelectedInYearView($event)\"\n\t\t(selectedChange)=\"_goToDateInView($event, 'month')\">\n\t</mat-year-view>\n\n\t<mat-multi-year-view\n\t\t*ngSwitchCase=\"'multi-year'\"\n\t\t[activeDate]=\"activeDate\"\n\t\t[selected]=\"selected\"\n\t\t[dateFilter]=\"dateFilter\"\n\t\t[maxDate]=\"maxDate\"\n\t\t[minDate]=\"minDate\"\n\t\t(yearSelected)=\"_yearSelectedInMultiYearView($event)\"\n\t\t(selectedChange)=\"_goToDateInView($event, 'year')\">\n\t</mat-multi-year-view>\n</div>\n\n", host: { 'class': 'mat-calendar', }, exportAs: 'matDaterangeCalendar', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush }), __param(1, Optional()), __param(2, Optional()), __param(2, Inject(MAT_DATE_FORMATS)) ], MatDaterangeCalendar); /* RANGE: Most of the code is redundant and hera as inheritance boilerplate. The relevant code is marked with a comment. */ let MatDaterangepickerContent = class MatDaterangepickerContent extends MatDatepickerContent { constructor() { super(...arguments); this._onApplyRange = new EventEmitter(); this.setHoverCells = new BehaviorSubject(null); this.clearBody = new BehaviorSubject(null); } get drDatepicker() { return this.datepicker; } ngOnInit() { } ngAfterViewInit() { } // THIS IS THE ONLY ADDITION onUserSelection(view) { this.drDatepicker._userSelection(); } applyRange() { if (this.drDatepicker.applyButton) { this.drDatepicker.applyRange(); } } clearRange() { this.drDatepicker.clearRange(); this.clearBody.next(true); this.datepicker._selected = null; this.drDatepicker._selectedRangeEnd = null; } setCustomRange(range) { if (!range.startDate || !range.endDate) return; this.datepicker._selected = range.startDate; this.drDatepicker._selectedRangeEnd = range.endDate; this.datepicker._selectedChanged.next(range.startDate); this.drDatepicker._selectedChangedRangeEnd.next(range.endDate); this.setActiveDate(range.startDate, range.endDate); } isSameRange(range) { if (!this.datepicker._selected || !this.drDatepicker._selectedRangeEnd || !range.startDate || !range.endDate) return; return this.drDatepicker._drDateAdapter.compareDate(range.startDate, this.datepicker._selected) == 0 && this.drDatepicker._drDateAdapter.compareDate(range.endDate, this.drDatepicker._selectedRangeEnd) == 0; } isSomeRange() { if (!this.datepicker._selected || !this.drDatepicker._selectedRangeEnd) return false; return this.drDatepicker.customRanges.some(range => this.isSameRange(range)); } getWidthDateRangePicker() { if (this.drDatepicker.dualView) { if (this.drDatepicker.showCustomRanges && this.drDatepicker.customRanges.length) { return '655px'; } return '600px'; } if (this.drDatepicker.showCustomRanges && this.drDatepicker.customRanges.length) { return '450px'; } } setActiveDate(from, to) { const fromMonth = this.drDatepicker._drDateAdapter.getMonth(from); const toMonth = this.drDatepicker._drDateAdapter.getMonth(to); const toYear = this.drDatepicker._drDateAdapter.getYear(to); const toDate = this.drDatepicker._drDateAdapter.getDate(to); let y, m; if (toMonth === 11) { m = 0; y = toYear + 1; } else { m = toMonth + 1, y = toYear; } const endDate = this.drDatepicker._drDateAdapter.createDate(y, m, 1); this.calendars.forEach((calendar, i) => { if (i === 0) { calendar.activeDate = from; } else { if (fromMonth !== toMonth) { // if start month & end month are differents calendar.activeDate = to; } else { calendar.activeDate = endDate; } } }); } }; __decorate([ ViewChild(MatDaterangeCalendar, { static: true }) ], MatDaterangepickerContent.prototype, "_calendar", void 0); __decorate([ ViewChildren(MatDaterangeCalendar) ], MatDaterangepickerContent.prototype, "calendars", void 0); MatDaterangepickerContent = __decorate([ Component({ selector: 'mat-daterangepicker-content', template: "<div>\n\t<!-- \n\t\tRANGE: Most of the code is redundant and hera as inheritance boilerplate.\n\t\tThe relevant code is marked with a comment.\n\t\tADDED LOC ~3\n\t-->\n\t<div class=\"mat-daterange-calendars\" [ngStyle]=\"{'width': getWidthDateRangePicker()}\">\n\t\t<div *ngIf=\"drDatepicker.showCustomRanges && drDatepicker.customRanges.length\" class=\"mat-daterange-custom-ranges\">\n\t\t\t<button *ngFor=\"let range of drDatepicker.customRanges\" mat-flat-button [color]=\"isSameRange(range) ? 'primary' : ''\" (click)=\"setCustomRange(range)\" class=\"mat-daterange-calendar-button-action\">{{range.name}}</button>\n\t\t\t<button mat-flat-button [color]=\"!isSomeRange() ? 'primary' : ''\" class=\"mat-daterange-custom-range mat-daterange-calendar-button-action\">Custom Range</button>\n\t\t</div>\n\t\t<mat-daterange-calendar *ngFor=\"let view of drDatepicker._getViews()\" #Calendar cdkTrapFocus\n\t\t\t\t\t[id]=\"datepicker.id\"\n\t\t\t\t\t[ngClass]=\"datepicker.panelClass\"\n\t\t\t\t\tclass=\"mat-daterange-calendar\"\n\t\t\t\t\t[view]=\"view\"\n\t\t\t\t\t[setHoverCells]=\"setHoverCells\"\n\t\t\t\t\t[clearBody]=\"clearBody\"\n\t\t\t\t\t\n\t\t\t\t\t[startAt]=\"datepicker.startAt\"\n\t\t\t\t\t[startView]=\"datepicker.startView\"\n\t\t\t\t\t[minDate]=\"datepicker._minDate\"\n\t\t\t\t\t[maxDate]=\"datepicker._maxDate\"\n\t\t\t\t\t[dateFilter]=\"datepicker._dateFilter\"\n\t\t\t\t\t[headerComponent]=\"datepicker.calendarHeaderComponent\"\n\t\t\t\t\t[selected]=\"datepicker._selected\"\n\t\t\t\t\t[@fadeInCalendar]=\"'enter'\"\n\t\t\t\t\t(selectedChange)=\"datepicker.select($event)\"\n\t\t\t\t\t(yearSelected)=\"datepicker._selectYear($event)\"\n\t\t\t\t\t(monthSelected)=\"datepicker._selectMonth($event)\"\n\n\t\t\t\t\t[range]=\"!!drDatepicker._datepickerInputRangeEnd\"\n\t\t\t\t\t[selectedRangeEnd]=\"drDatepicker._selectedRangeEnd\"\n\t\t\t\t\t(selectedRangeEndChange)=\"drDatepicker.selectRangeEnd($event)\"\n\t\t\t\t\t(_userSelection)=\"onUserSelection(view)\">\n\t\t\t\t\t<!-- \n\t\t\t\t\t\t\tTHE 3 LINES ABOVE ARE THE ONLY ADDITION\n\t\t\t\t\t\t\tThe _userSelection will pass logic handling to the datepicker component\n\t\t\t\t\t\t\tinstead of closing the dialog/popup, this is because range logic is handled there.\n\n\t\t\t\t\t\t\tThe other attributes are passing on the additional date (Range end) bindings\n\t\t\t\t\t\t\tand a flag that sets range on/off based on the input range.\n\t\t\t\t\t\t\tThis means that an *ngIf on that input by the user can toggle range on/off without\n\t\t\t\t\t\t\ta special flag.\n\t\t\t\t\t-->\n\n\t\t</mat-daterange-calendar>\n\t</div>\n\n\t<div *ngIf=\"drDatepicker.applyButton\" class=\"mat-daterange-calendar-actions\">\n\t\t<button mat-button color=\"primary\" (click)=\"clearRange()\">Clear</button>&nbsp;&nbsp;\n\t\t<button mat-flat-button color=\"primary\" (click)=\"applyRange()\">Apply</button>\n\t</div>\n</div>", host: { 'class': 'mat-datepicker-content', '[@transformPanel]': '"enter"', '[class.mat-datepicker-content-touch]': 'datepicker.touchUi', '[class.mat-datepicker-content-above]': '_isAbove', }, animations: [ matDatepickerAnimations.transformPanel, matDatepickerAnimations.fadeInCalendar, ], exportAs: 'matDaterangepickerContent', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, inputs: ['color'], styles: [".mat-daterange-calendar-body-in-range{background-color:rgba(63,81,181,.15)}.mat-daterange-calendar-body-in-range.mat-daterange-calendar-body-range-start{border-radius:100% 0 0 100%}.mat-daterange-calendar-body-in-range.mat-daterange-calendar-body-range-end{border-radius:0 100% 100% 0}.mat-daterange-calendars{display:flex}.mat-daterange-calendar{flex-grow:1}.mat-daterange-calendar-actions{padding:15px;text-align:right}.mat-daterange-custom-ranges{display:flex;flex-flow:column;width:130px;padding:8px}.mat-daterange-custom-ranges button{text-align:left;font-size:12px;line-height:32px;font-weight:400!important}.mat-daterange-calendar-button-action{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}"] }) ], MatDaterangepickerContent); const thisMonthRange = () => { const y = new Date().getFullYear(); const m = new Date().getMonth(); let yearFrom, monthFrom, yearTo, monthTo; yearFrom = y; monthFrom = m; yearTo = y; monthTo = m + 1; if (m === 11) { yearTo = y + 1; monthTo = 0; } return { name: 'This Month', startDate: new Date(yearFrom, monthFrom, 1), endDate: new Date(yearTo, monthTo, 0) }; }; const ɵ0 = thisMonthRange; const lastMonthRange = () => { const y = new Date().getFullYear(); const m = new Date().getMonth(); let yearFrom, monthFrom, yearTo, monthTo; yearFrom = y; monthFrom = m - 1; yearTo = y; monthTo = m; if (m === 0) { yearFrom = y - 1; monthFrom = 11; } return { name: 'Last Month', startDate: new Date(yearFrom, monthFrom, 1), endDate: new Date(yearTo, monthTo, 0) }; }; const ɵ1 = lastMonthRange; const lastThreeMonthsRange = () => { const y = new Date().getFullYear(); const m = new Date().getMonth(); let yearFrom, monthFrom, yearTo, monthTo; yearFrom = y; monthFrom = m - 3; // current month - number desired yearTo = y; monthTo = m; if (monthFrom < 0) { yearFrom = y - 1; monthFrom = 11 + 1 + monthFrom; } return { name: 'Last 3 Months', startDate: new Date(yearFrom, monthFrom, 1), endDate: new Date(yearTo, monthTo, 0) }; }; const ɵ2 = lastThreeMonthsRange; const DEFAULT_DATES_RANGES = [ { name: 'Today', startDate: new Date(), endDate: new Date() }, { name: 'Yesterday', startDate: new Date(new Date().setDate(new Date().getDate() - 1)), endDate: new Date(new Date().setDate(new Date().getDate() - 1)) }, { name: 'Last 7 Days', startDate: new Date(new Date().setDate(new Date().getDate() - 6)), endDate: new Date() }, { name: 'Last 30 Days', startDate: new Date(new Date().setDate(new Date().getDate() - 29)), endDate: new Date() }, thisMonthRange(), lastMonthRange(), lastThreeMonthsRange(), ]; /* RANGE: Most of the code is redundant and hera as inheritance boilerplate. The added/edited code adds logic for handling 1 or 2 inputs, i.e. range. When we have 2 inputs it's a range, otherwise its a single value date selection. The main logic is in `selectRangeEnd()` which handles range/non-range and inner range states. (this could probably be more orgenaized with proper states for clarity) Instead of "_userSelection" closing the popup/dialog from the calendar-content, the logic is moved here into _userSelection() which will close/keep based on range logic. */ const MAT_DEFAULT_DATES_RANGES = new InjectionToken('Custom Ranges'); const ɵ0$1 = []; let MatDaterangepicker = class MatDaterangepicker extends MatDatepicker { constructor(_drDialog, _drOverlay, _drNgZone, _drViewContainerRef, _drScrollStrategy, _drDateAdapter, _drDir, _drDocument, _customRanges) { super(_drDialog = Object.create(_drDialog), _drOverlay, _drNgZone, _drViewContainerRef, _drScrollStrategy, _drDateAdapter, _drDir, _drDocument); this._drOverlay = _drOverlay; this._drNgZone = _drNgZone; this._drViewContainerRef = _drViewContainerRef; this._drScrollStrategy = _drScrollStrategy; this._drDateAdapter = _drDateAdapter; this._drDir = _drDir; this._drDocument = _drDocument; this._customRanges = _customRanges; this.applyButton = true; this.dualView = true; this.showCustomRanges = false; this.apply = new EventEmitter(); this.customRanges = []; this._validSelectedRangeEnd = null; /** Emits new selected date when selected date changes. */ this._selectedChangedRangeEnd = new Subject(); this._clearRangeEnd = new Subject(); this._range = false; this._inputRangeEndSubscription = Subscription.EMPTY; /* This is a monkey patch workaround to support a new component for dialog/popup. Because everything is freaking private in material, why would somone use protected anyway. */ this._drDialog = _drDialog; this._drDialog.open = function (...args) { if (typeof args[0].createEmbeddedView !== 'function') { args[0] = MatDaterangepickerContent; } return MatDialog.prototype.open.apply(_drDialog, args); }; // Object.defineProperty(this, '_calendarPortal', { // get: function () { return this.__calendarPortal; }, // set: function (value) { // this.__calendarPortal = value; // if (value) { // value.component = MatDaterangepickerContent; // } // } // }) this['_openAsPopup'] = () => { const portal = new ComponentPortal(MatDaterangepickerContent, this._drViewContainerRef); this['_destroyPopup'](); this['_createPopup'](); this['_popupComponentRef'] = this['_popupRef'].attach(portal); this.forwardContentValues(this['_popupComponentRef'].instance); // Update the position once the calendar has rendered. this._drNgZone.onStable.asObservable().pipe(take(1)).subscribe(() => { this['_popupRef'].updatePosition(); }); }; } /** The currently selected date. */ get _selectedRangeEnd() { return this._validSelectedRangeEnd; } set _selectedRangeEnd(value) { this._validSelectedRangeEnd = value; } ngOnInit() { if (this.customRanges) { this._setCustomRanges(); } } ngAfterViewInit() { } forwardContentValues(instance) { instance.datepicker = this; instance.color = this.color; } ngOnDestroy() { super.ngOnDestroy(); this._inputRangeEndSubscription.unsubscribe(); } selectRangeEnd(date) { if (!this._range) { super.select(date); } else { if (!date) { if (this._selected && this._selectedRangeEnd) { super.select(null); this._selectRangeEnd(null); } else if (this._selected) { super.select(null); } else if (this._selectedRangeEnd) { this._selectRangeEnd(null); } } else if (!this._selected || this._selectedRangeEnd) { if (this._selectedRangeEnd) { this._selectRangeEnd(null); } super.select(date); } else { if (date < this._selected) { const swap = this._selected; super.select(date); this._selectRangeEnd(swap); } else if (date >= this._selected) { // notice date === this._selected is not skipped this._selectRangeEnd(date); } } } } _userSelection() { if (this.applyButton) return; if (!this._range || (this._selected && this._selectedRangeEnd)) { this.close(); } } applyRange() { this.close(); this.apply.emit(); } clearRange() { this._datepickerInput.value = null; this._clearRangeEnd.next(); } _getViews() { if (this.dualView) { return [0, 1]; } return [0]; } _registerInputRangeEnd(input) { if (this._datepickerInputRangeEnd) { throw Error('A MatDatepicker can only be associated with a single range end input.'); } this._datepickerInputRangeEnd = input; this._range = !!input; this._inputRangeEndSubscription.unsubscribe(); if (this._range) { this._inputRangeEndSubscription = this._datepickerInputRangeEnd._valueChange.subscribe((value) => this._selectedRangeEnd = value); } } _unregisterInputRangeEnd(input) { if (this._datepickerInputRangeEnd) { if (!input || input === this._datepickerInputRangeEnd) { this._datepickerInputRangeEnd = undefined; this._range = false; this._inputRangeEndSubscription.unsubscribe(); } } } _selectRangeEnd(date) { const oldValue = this._selectedRangeEnd; this._selectedRangeEnd = date; if (!this._drDateAdapter.sameDate(oldValue, this._selectedRangeEnd)) { this._selectedChangedRangeEnd.next(this._selectedRangeEnd); } } _setCustomRanges() { if (this._customRanges.length) { this.customRanges = this._customRanges; } else { this.customRanges = DEFAULT_DATES_RANGES.map(range => { return { name: range.name, startDate: range.startDate ? this._drDateAdapter.createDate(range.startDate.getFullYear(), range.startDate.getMonth(), range.startDate.getDate()) : null, endDate: range.endDate ? this._drDateAdapter.createDate(range.endDate.getFullYear(), range.endDate.getMonth(), range.endDate.getDate()) : null }; }); } } }; MatDaterangepicker.ctorParameters = () => [ { type: MatDialog }, { type: Overlay }, { type: NgZone }, { type: ViewContainerRef }, { type: undefined, decorators: [{ type: Inject, args: [MAT_DATEPICKER_SCROLL_STRATEGY,] }] }, { type: DateAdapter, decorators: [{ type: Optional }] }, { type: Directionality, decorators: [{ type: Optional }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [DOCUMENT,] }] }, { type: Array, decorators: [{ type: Inject, args: [MAT_DEFAULT_DATES_RANGES,] }] } ]; __decorate([ Input() ], MatDaterangepicker.prototype, "applyButton", void 0); __decorate([ Input() ], MatDaterangepicker.prototype, "dualView", void 0); __decorate([ Input() ], MatDaterangepicker.prototype, "showCustomRanges", void 0); __decorate([ Output() ], MatDaterangepicker.prototype, "apply", void 0); MatDaterangepicker = __decorate([ Component({ selector: 'mat-daterangepicker', template: '', exportAs: 'matDaterangepicker', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [ { provide: MAT_DEFAULT_DATES_RANGES, useValue: ɵ0$1 }, ] }), __param(4, Inject(MAT_DATEPICKER_SCROLL_STRATEGY)), __param(5, Optional()), __param(6, Optional()), __param(7, Optional()), __param(7, Inject(DOCUMENT)), __param(8, Inject(MAT_DEFAULT_DATES_RANGES)) ], MatDaterangepicker); /* RANGE: Most of the code is redundant and hera as inheritance boilerplate. The relevant code adds simple logic for a 2nd input, combined with minimal changes in the datepicker component, the work is done there so in native implementation this should have minimal changes, perhaps using dedicated input without min/max/filter etc... which are only set on the 1st input */ var MatDaterangepickerInputEnd_1; const SG_DATEPICKER_VALUE_ACCESSOR = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MatDaterangepickerInputEnd), multi: true }; const SG_DATEPICKER_VALIDATORS = { provide: NG_VALIDATORS, useExisting: forwardRef(() => MatDaterangepickerInputEnd), multi: true }; let MatDaterangepickerInputEnd = MatDaterangepickerInputEnd_1 = class MatDaterangepickerInputEnd extends MatDatepickerInput { constructor(_drElementRef, _drDateAdapter, _drDateFormats, _drFormField) { super(_drElementRef, _drDateAdapter, _drDateFormats, _drFormField); this._drElementRef = _drElementRef; this._drDateAdapter = _drDateAdapter; this._drDateFormats = _drDateFormats; this._drFormField = _drFormField; this._matDatepickerSubscription = Subscription.EMPTY; this._drCvaOnChange = () => { }; this._drDatepickerClearSubscription = Subscription.EMPTY; } set matDaterangepickerEnd(value) { if (this._datepicker !== value) { this.unregister(); this._datepicker = value; value._registerInputRangeEnd(this); } } get _matDaterangepickerEnd() { return this._datepicker; } ngAfterViewInit() { // Object.defineProperty(this, 'value', { // set: function (value: D | null) { // if(!this._matDaterangepickerEnd.applyButton){ // console.log('end '+value) // return; // } // value = this._dateAdapter.deserialize(value); // this._lastValueValid = !value || this._dateAdapter.isValid(value); // value = this._getValidDateOrNull(value); // const oldDate = this.value; // this['_assignValue'](value); // this['_formatValue'](value); // if (!this._dateAdapter.sameDate(oldDate, value)) { // this._valueChange.emit(value); // } // } // }) } ngAfterContentInit() { if (this._datepicker) { this._matDatepickerSubscription = this._matDaterangepickerEnd._selectedChangedRangeEnd.subscribe((selected) => { this.value = selected; this._drCvaOnChange(selected); this._onTouched(); this.dateInput.emit(new MatDatepickerInputEvent(this, this._drElementRef.nativeElement)); this.dateChange.emit(new MatDatepickerInputEvent(this, this._drElementRef.nativeElement)); }); //runs every clear range end this._drDatepickerClearSubscription = this._matDaterangepickerEnd._clearRangeEnd.subscribe(() => { this.value = null; this._drCvaOnChange(null); }); } } ngOnDestroy() { super.ngOnDestroy(); this.unregister(); this._matDatepickerSubscription.unsubscribe(); this._drDatepickerClearSubscription.unsubscribe(); } // Implemented as part of ControlValueAccessor. registerOnChange(fn) { this._drCvaOnChange = fn; super.registerOnChange(fn); } unregister() { if (this._matDaterangepickerEnd) { this._matDaterangepickerEnd._unregisterInputRangeEnd(this); } } }; MatDaterangepickerInputEnd.ctorParameters = () => [ { type: ElementRef }, { type: DateAdapter, decorators: [{ type: Optional }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_DATE_FORMATS,] }] }, { type: MatFormField, decorators: [{ type: Optional }] } ]; __decorate([ Input() ], MatDaterangepickerInputEnd.prototype, "matDaterangepickerEnd", null); MatDaterangepickerInputEnd = MatDaterangepickerInputEnd_1 = __decorate([ Directive({ selector: '[matDaterangepickerEnd]', providers: [ SG_DATEPICKER_VALUE_ACCESSOR, SG_DATEPICKER_VALIDATORS, { provide: MAT_INPUT_VALUE_ACCESSOR, useExisting: MatDaterangepickerInputEnd_1 }, ], host: { '[attr.aria-haspopup]': 'true', '[attr.aria-owns]': '(_datepicker?.opened && _datepicker.id) || null', '[attr.min]': 'min ? _dateAdapter.toIso8601(min) : null', '[attr.max]': 'max ? _dateAdapter.toIso8601(max) : null', '[disabled]': 'disabled', '(input)': '_onInput($event.target.value)', '(change)': '_onChange()', '(blur)': '_onBlur()', '(keydown)': '_onKeydown($event)', }, exportAs: 'MatDaterangepickerInputEnd', }), __param(1, Optional()), __param(2, Optional()), __param(2, Inject(MAT_DATE_FORMATS)), __param(3, Optional()) ], MatDaterangepickerInputEnd); /* RANGE: Most of the code is redundant and hera as inheritance boilerplate. The added/edited code adds logic for handling range / non range mode. Additionaly, in range mode it will apply logic to mark the dates within the active month that are within the range. There are 3 range states for a month view, set by the combination of "_rangable" and "_allInRange": - Not rangable (single value datepicker OR active month not in range) - _rangable = False - Rangable but not all (active month is partially in range) - _rangable = True - All In Range (active month is in range, start/end dates before/after this month) - _rangable = True & _allInRange = True */ let MatDaterangeMonthView = class MatDaterangeMonthView extends MatMonthView { constructor() { super(...arguments); this.selectedRangeEndChange = new EventEmitter(); this._createDate = (date) => { const selectedYear = this._dateAdapter.getYear(this.activeDate); const selectedMonth = this._dateAdapter.getMonth(this.activeDate); return new Date(selectedYear, selectedMonth, date); }; } get selectedRangeEnd() { return this._selectedRangeEnd; } set selectedRangeEnd(value) { this._selectedRangeEnd = this._getValidDateOrNull(this._dateAdapter.deserialize(value)); this._selectedRangeEndDate = this._getDateInCurrentMonth(this._selectedRangeEnd); } get _allInRange() { if (!this.range || !this.activeDate || !this.selectedRangeEnd || !this.selected) { return false; } else { const daysInMonth = this._dateAdapter.getNumDaysInMonth(this.activeDate); const startDate = this.createDate(1); const endDate = this.createDate(daysInMonth); return this._dateAdapter.compareDate(this.selected, startDate) < 0 && this._dateAdapter.compareDate(this.selectedRangeEnd, endDate) > 0; } } get _rangable() { if (!this.range || !this._weeks) { return false; } else { const daysInMonth = this._dateAdapter.getNumDaysInMonth(this.activeDate); const startDate = this.createDate(1); const endDate = this.createDate(daysInMonth); if (this.selectedRangeEnd < startDate) { return false; } if (this.selected > endDate) { return false; } return true; } } _init() { this._selectedRangeEndDate = this._getDateInCurrentMonth(this.selectedRangeEnd); super._init(); } _dateSelected(date) { const _super = Object.create(null, { _dateSelected: { get: () => super._dateSelected } }); return __awaiter(this, void 0, void 0, function* () { if (!this.range) { _super._dateSelected.call(this, date); } else { if (!this.selected || this.selectedRangeEnd) { if (this._selectedRangeEnd) { this._dateSelectedRangeEnd(null); } _super._dateSelected.call(this, date); } else { if (date === null) { this._dateSelectedRangeEnd(null); } else { const selectedDate = this.createDate(date); if (selectedDate < this.selected) { // const swap = this._selectedDate; const year = this._dateAdapter.getYear(this.selected); const month = this._dateAdapter.getMonth(this.selected); const swap = this._dateAdapter.getDate(this.selected); _super._dateSelected.call(this, date); this._dateSelectedRangeEnd(swap, year, month); } else { this._dateSelectedRangeEnd(date); } } } } }); } _dateSelectedRangeEnd(date, year, month) { if (date === null) { this.selectedRangeEndChange.emit(null); } else if (this._selectedRangeEndDate != date) { let selectedDate; if (year && month) { selectedDate = this._dateAdapter.createDate(year, month, date); } else { selectedDate = this.createDate(date); } this.selectedRangeEndChange.emit(selectedDate); this._userSelection.emit(); } } createDate(date) { const selectedYear = this._dateAdapter.getYear(this.activeDate); const selectedMonth = this._dateAdapter.getMonth(this.activeDate); return this._dateAdapter.createDate(selectedYear, selectedMonth, date); } getSelectedDate() { if (!this.selected) return null; const selectedYear = this._dateAdapter.getYear(this.selected); const selectedMonth = this._dateAdapter.getMonth(this.selected); const selectedDate = this._dateAdapter.getDate(this.selected); return new Date(selectedYear, selectedMonth, selectedDate); } getSelectedRangeEndDate() { if (!this.selectedRangeEnd) return null; const selectedYear = this._dateAdapter.getYear(this.selectedRangeEnd); const selectedMonth = this._dateAdapter.getMonth(this.selectedRangeEnd); const selectedDate = this._dateAdapter.getDate(this.selectedRangeEnd); return new Date(selectedYear, selectedMonth, selectedDate); } }; __decorate([ Input() ], MatDaterangeMonthView.prototype, "range", void 0); __decorate([ Input() ], MatDaterangeMonthView.prototype, "view", void 0); __decorate([ Input() ], MatDaterangeMonthView.prototype, "setHoverCells", void 0); __decorate([ Input() ], MatDaterangeMonthView.prototype, "clearBody", void 0); __decorate([ Input() ], MatDaterangeMonthView.prototype, "selectedRangeEnd", null); __decorate([ Output() ], MatDaterangeMonthView.prototype, "selectedRangeEndChange", void 0); MatDaterangeMonthView = __decorate([ Component({ selector: 'mat-daterange-month-view', template: "<!-- \n RANGE: Most of the code is redundant and hera as inheritance boilerplate.\n \n The 3 attributes in the middle are the additional bindings to the template.\n They notify the body:\n - Are we in range mode ([range])\n - Are all the dates in range ([allInRange])\n this happens when the start date is before the current month and the end is after it.\n - The value marking the end of the range ([selectedRangeEndValue])\n\n ADDED LOC ~3\n -->\n\n<table class=\"mat-calendar-table\">\n <thead class=\"mat-calendar-table-header\">\n <tr><th *ngFor=\"let day of _weekdays\" [attr.aria-label]=\"day.long\">{{day.narrow}}</th></tr>\n <tr><th class=\"mat-calendar-table-header-divider\" colspan=\"7\" aria-hidden=\"true\"></th></tr>\n </thead>\n <tbody mat-daterange-calendar-body\n [view]=\"view\"\n [label]=\"_monthLabel\"\n [rows]=\"_weeks\"\n [todayValue]=\"_todayDate\"\n [selectedValue]=\"_selectedDate\"\n\n [selectedRangeEndValue]=\"_selectedRangeEndDate\"\n [allInRange]=\"_allInRange\"\n [range]=\"_rangable\"\n [createDate]=\"_createDate\"\n [dateAdapter]=\"_dateAdapter\"\n [selected]=\"getSelectedDate()\"\n [selectedRangeEnd]=\"getSelectedRangeEndDate()\"\n [setHoverCells]=\"setHoverCells\"\n [clearBody]=\"clearBody\"\n [labelMinRequiredCells]=\"3\"\n [activeCell]=\"_dateAdapter.getDate(activeDate) - 1\"\n (selectedValueChange)=\"_dateSelected($event)\" \n (keydown)=\"_handleCalendarBodyKeydown($event)\">\n </tbody>\n</table>\n", exportAs: 'matMonthView', encapsulation: ViewEncapsulation.None, preserveWhitespaces: false, changeDetection: ChangeDetectionStrategy.OnPush }) ], MatDaterangeMonthView); /* RANGE: Most of the code is redundant and hera as inheritance boilerplate. The relevant code is marked with a comment. */ let MatDaterangeCalendarBody = class MatDaterangeCalendarBody extends MatCalendarBody { constructor() { super(...arguments); this.hoverSubscription = Subscription.EMPTY; this.clearBodySubscription = Subscription.EMPTY; } ngOnInit() { this.hoverSubscription = this.setHoverCells.subscribe((tmpDate) => __awaiter(this, void 0, void 0, function* () { if (!tmpDate || !this.selected || this.selectedRangeEnd || (this.selected && this.selectedRangeEnd)) return; this._doHoverCells(tmpDate); })); this.clearBodySubscription = this.clearBody.subscribe(clear => { if (clear) { this._doHoverCells(null, clear); } }); } _cellClass(date) { if (!this.selected || !this.selectedRangeEnd) return; const startDate = this.selected.getTime(); const endDate = this.selectedRangeEnd.getTime(); const cellDate = this.createDate(date).getTime(); return { 'mat-daterange-calendar-body-in-range': cellDate >= startDate && cellDate <= endDate && startDate !== endDate, 'mat-daterange-calendar-body-range-start': cellDate === startDate && startDate !== endDate, 'mat-daterange-calendar-body-range-end': cellDate === endDate && startDate !== endDate }; } _inRange(date) { // if (this.range) { // if (this.allInRange) { // return true; // } // if (this.selectedValue && !this.selectedRangeEndValue) { // return date >= this.selectedValue; // } // if (this.selectedRangeEndValue && !this.selectedValue) { // return date <= this.selectedRangeEndValue; // } // return date >= this.selectedValue && date <= this.selectedRangeEndValue; // } } _onHoverCell(tmpDate) { if (this.selectedRangeEndValue) return; this.setHoverCells.next(this.createDate(tmpDate)); } _doHoverCells(tmpDate, clear) { var _a; const startDate = (_a = this.selected) === null || _a === void 0 ? void 0 : _a.getTime(); const endTempDate = tmpDate === null || tmpDate === void 0 ? void 0 : tmpDate.getTime(); this.rows.map((row, r) => { const rowElm = this.rowsElms.toArray()[r]; let hasLabel = false; row.map((cell, c) => { let cellElm = rowElm.nativeElement.cells[hasLabel ? c + 1 : c]; if (cellElm.classList.contains('mat-calendar-body-label')) { hasLabel = true; cellElm = rowElm.nativeElement.cells[c + 1]; } cellElm.classList.remove('mat-daterange-calendar-body-in-range'); cellElm.classList.remove('mat-daterange-calendar-body-range-end'); cellElm.classList.remove('mat-daterange-calendar-body-range-start'); if (clear) return; const inRange = this._inRangeTemp(cell.value, startDate, endTempDate, cellElm); }); }); } _inRangeTemp(date, startDate, endTempDate, cellElm) { const cellDate = this.createDate(date).getTime(); if (endTempDate >= startDate) { if (endTempDate !== startDate) { if (cellDate === startDate) { cellElm.classList.add('mat-daterange-calendar-body-in-range'); cellElm.classList.add('mat-daterange-calendar-body-range-start'); } if (cellDate === endTempDate) { cellElm.classList.add('mat-daterange-calendar-body-in-range'); cellElm.classList.add('mat-daterange-calendar-body-range-end'); } } if (cellDate > startDate && cellDate < endTempDate) { cellElm.classList.add('mat-daterange-calendar-body-in-range'); return true; } return false; } else { if (endTempDate !== startDate) { if (cellDate === startDate) { cellElm.classList.add('mat-daterange-calendar-body-in-range'); cellElm.classList.add('mat-daterange-calendar-body-range-end'); } if (cellDate === endTempDate) { cellElm.classList.add('mat-daterange-calendar-body-in-range'); cellElm.classList.add('mat-daterange-calendar-body-range-start'); } } if (cellDate < startDate && cellDate > endTempDate) { cellElm.classList.add('mat-daterange-calendar-body-in-range'); return true; } return false; } } ngOnDestroy() { this.hoverSubscription.unsubscribe(); this.clearBodySubscription.unsubscribe(); } }; __decorate([ Input() ], MatDaterangeCalendarBody.prototype, "range", void 0); __decorate([ Input() ], MatDaterangeCalendarBody.prototype, "allInRange", void 0); __decorate([ Input() ], MatDaterangeCalendarBody.prototype, "selectedRangeEndValue", void 0); __decorate([ Input() ], MatDaterangeCalendarBody.prototype, "view", void 0); __decorate([ Input() ], MatDaterangeCalendarBody.prototype, "createDate", void 0); __decorate([ Input() ], MatDaterangeCalendarBody.prototype, "selected", void 0); __decorate([ Input() ], MatDaterangeCalendarBody.prototype, "selectedRangeEnd", void 0); __decorate([ Input() ], MatDaterangeCalendarBody.prototype, "setHoverCells", void 0); __decorate([ Input() ], MatDaterangeCalendarBody.prototype, "clearBody", void 0); __decorate([ Input() ], MatDaterangeCalendarBody.prototype, "dateAdapter", void 0); __decorate([ ViewChildren('Row') ], MatDaterangeCalendarBody.prototype, "rowsElms", void 0); MatDaterangeCalendarBody = __decorate([ Component({ selector: '[mat-daterange-calendar-body]', template: "<!-- \n RANGE: Most of the code is redundant and hera as inheritance boilerplate.\n The relevant code is all element attribute bindings with the word \"range\"\n and an editing to the binding of [class.mat-calendar-body-selected]\n\n ADDED LOC ~3\n -->\n\n<!--\n If there's not enough space in the first row, create a separate label row. We mark this row as\n aria-hidden because we don't want it to be read out as one of the weeks in the month.\n-->\n<tr *ngIf=\"_firstRowOffset < labelMinRequiredCells\" aria-hidden=\"true\">\n\t<td class=\"mat-calendar-body-label\"\n\t\t[attr.colspan]=\"numCols\"\n\t\t[style.paddingTop.%]=\"50 * cellAspectRatio / numCols\"\n\t\t[style.paddingBottom.%]=\"50 * cellAspectRatio / numCols\">\n\t\t{{label}}\n\t</td>\n</tr>\n\n<!-- Create the first row separately so we can include a special spacer cell. -->\n<tr *ngFor=\"let row of rows; let rowIndex = index\" #Row role=\"row\">\n\t<!--\n\t\tWe mark this cell as aria-hidden so it doesn't get read out as one of the days in the week.\n\t\tThe aspect ratio of the table cells is maintained by setting the top and bottom padding as a\n\t\tpercentage of the width (a variant of the trick described here:\n\t\thttps://www.w3schools.com/howto/howto_css_aspect_ratio.asp).\n\t-->\n\t<td *ngIf=\"rowIndex === 0 && _firstRowOffset\"\n\t\taria-hidden=\"true\"\n\t\tclass=\"mat-calendar-body-label\"\n\t\t[attr.colspan]=\"_firstRowOffset\"\n\t\t[style.paddingTop.%]=\"50 * cellAspectRatio / numCols\"\n\t\t[style.paddingBottom.%]=\"50 * cellAspectRatio / numCols\">\n\t\t{{_firstRowOffset >= labelMinRequiredCells ? label : ''}}\n\t</td>\n\t<td *ngFor=\"let item of row; let colIndex = index\"\n\t\t#Cell\n\t\trole=\"gridcell\"\n\t\tclass=\"mat-calendar-body-cell\"\n\t\t[tabindex]=\"_isActiveCell(rowIndex, colIndex) ? 0 : -1\"\n\t\t[class.mat-calendar-body-disabled]=\"!item.enabled\"\n\t\t[class.mat-calendar-body-active]=\"_isActiveCell(rowIndex, colIndex)\"\n\n\t\t[ngClass]=\"_cellClass(item.value)\"\n\n\t\t[attr.aria-label]=\"item.ariaLabel\"\n\t\t[attr.aria-disabled]=\"!item.enabled || null\"\n\t\t(click)=\"_cellClicked(item)\"\n\t\t(mouseover)=\"_onHoverCell(item.value)\"\n\t\t[style.width.%]=\"100 / numCols\"\n\t\t[style.paddingTop.%]=\"50 * cellAspectRatio / numCols\"\n\t\t[style.paddingBottom.%]=\"50 * cellAspectRatio / numCols\">\n\t\t<div class=\"mat-calendar-body-cell-content\"\n\t\t\t[class.mat-calendar-body-selected]=\"selectedValue === item.value || selectedRangeEndValue === item.value\"\n\t\t\t[class.mat-calendar-body-today]=\"todayValue === item.value\">\n\t\t{{item.displayValue}}\n\t\t</div>\n\t</td>\n</tr>\n", host: { 'class': 'mat-calendar-body', '[class.mat-daterange-calendar-body-range]': 'range', 'role': 'grid', 'attr.aria-readonly': 'true' }, exportAs: 'matCalendarBody', encapsulation: ViewEncapsulation.None, preserveWhitespaces: false, changeDetection: ChangeDetectionStrategy.OnPush }) ], MatDaterangeCalendarBody); let MatDaterangepickerModule = class MatDaterangepickerModule { constructor(cf, injector) { // this is a workaround to ensure CSS/HTML from mat datepicker is loaded, otherwise it is omitted. cf.resolveComponentFactory(MatDatepicker).create(injector); cf.resolveComponentFactory(MatDatepickerContent).create(injector); cf.resolveComponentFactory(MatCalendar).create(injector); cf.resolveComponentFactory(MatMonthView).create(injector); cf.resolveComponentFactory(MatCalendarBody).create(injector); cf.resolveComponentFactory(MatDatepickerToggle).create(injector); } }; MatDaterangepickerModule.ctorParameters = () => [ { type: ComponentFactoryResolver }, { type: Injector } ]; MatDaterangepickerModule = __decorate([ NgModule({ imports: [ CommonModule, MatButtonModule, MatDialogModule, A11yModule, PortalModule, OverlayModule, MatDatepickerModule, MatNativeDateModule ], declarations: [ MatDaterangepicker, MatDaterangepickerInputEnd, MatDaterangepickerContent, MatDaterangeCalendar, MatDaterangeMonthView, MatDaterangeCalendarBody ], exports: [ MatDaterangepicker, MatDaterangepickerInputEnd, MatDaterangepickerContent ], entryComponents: [ // MatDatepicker, // MatDatepickerToggle, // MatDatepickerContent, // MatCalendar, // MatMonthView, // MatCalendarBody, // MatCalendarHeader, MatDaterangepickerContent ] }) ], MatDaterangepickerModule); /* * Public API Surface of mat-daterangepicker */ /** * Generated bundle index. Do not edit. */ export { MAT_DEFAULT_DATES_RANGES, MatDaterangepicker, MatDaterangepickerContent, MatDaterangepickerInput