UNPKG

@ng-matero/extensions

Version:
714 lines (707 loc) 239 kB
import { A11yModule } from '@angular/cdk/a11y'; import * as i1$1 from '@angular/cdk/overlay'; import { Overlay, OverlayConfig, OverlayModule } from '@angular/cdk/overlay'; import { ComponentPortal, CdkPortalOutlet, TemplatePortal, PortalModule } from '@angular/cdk/portal'; import { DOCUMENT, CommonModule } from '@angular/common'; import * as i0 from '@angular/core'; import { EventEmitter, booleanAttribute, Component, ViewEncapsulation, ChangeDetectionStrategy, Inject, Input, Output, Optional, Injectable, Directive, ElementRef, ViewChild, inject, Injector, afterNextRender, InjectionToken, forwardRef, Attribute, ContentChild, TemplateRef, NgModule } from '@angular/core'; import { MatButton, MatIconButton, MatButtonModule } from '@angular/material/button'; import { UP_ARROW, DOWN_ARROW, ENTER, PAGE_DOWN, PAGE_UP, END, HOME, RIGHT_ARROW, LEFT_ARROW, ESCAPE, hasModifierKey } from '@angular/cdk/keycodes'; import * as i1 from '@ng-matero/extensions/core'; import { MTX_DATETIME_FORMATS } from '@ng-matero/extensions/core'; import { normalizePassiveListenerOptions, _getFocusedElementPierceShadowDom } from '@angular/cdk/platform'; import { trigger, transition, animate, keyframes, style, state } from '@angular/animations'; import { coerceNumberProperty, coerceStringArray } from '@angular/cdk/coercion'; import { Subject, Subscription, merge, of } from 'rxjs'; import { take, filter } from 'rxjs/operators'; import * as i3 from '@angular/cdk/bidi'; import { NG_VALUE_ACCESSOR, NG_VALIDATORS, Validators } from '@angular/forms'; import { MAT_INPUT_VALUE_ACCESSOR } from '@angular/material/input'; import * as i2 from '@angular/material/form-field'; var MtxDatetimepickerFilterType; (function (MtxDatetimepickerFilterType) { MtxDatetimepickerFilterType[MtxDatetimepickerFilterType["DATE"] = 0] = "DATE"; MtxDatetimepickerFilterType[MtxDatetimepickerFilterType["HOUR"] = 1] = "HOUR"; MtxDatetimepickerFilterType[MtxDatetimepickerFilterType["MINUTE"] = 2] = "MINUTE"; })(MtxDatetimepickerFilterType || (MtxDatetimepickerFilterType = {})); const activeEventOptions = normalizePassiveListenerOptions({ passive: false }); const CLOCK_RADIUS = 50; const CLOCK_INNER_RADIUS = 27.5; const CLOCK_OUTER_RADIUS = 41.25; const CLOCK_TICK_RADIUS = 7.0833; /** * A clock that is used as part of the datetimepicker. * @docs-private */ class MtxClock { constructor(_elementRef, _adapter, _changeDetectorRef, _document) { this._elementRef = _elementRef; this._adapter = _adapter; this._changeDetectorRef = _changeDetectorRef; this._document = _document; /** Step over minutes. */ this.interval = 1; /** Whether the clock uses 12 hour format. */ this.twelvehour = false; /** Whether the time is now in AM or PM. */ this.AMPM = 'AM'; /** Emits when the currently selected date changes. */ this.selectedChange = new EventEmitter(); /** Emits when any date is activated. */ this.activeDateChange = new EventEmitter(); /** Emits when any date is selected. */ this._userSelection = new EventEmitter(); /** Whether the clock is in hour view. */ this._hourView = true; this._hours = []; this._minutes = []; this._timeChanged = false; /** Called when the user has put their pointer down on the clock. */ this._pointerDown = (event) => { this._timeChanged = false; this.setTime(event); this._bindGlobalEvents(event); }; /** * Called when the user has moved their pointer after * starting to drag. Bound on the document level. */ this._pointerMove = (event) => { if (event.cancelable) { event.preventDefault(); } this.setTime(event); }; /** Called when the user has lifted their pointer. Bound on the document level. */ this._pointerUp = (event) => { if (event.cancelable) { event.preventDefault(); } this._removeGlobalEvents(); if (this._timeChanged) { this.selectedChange.emit(this.activeDate); if (!this._hourView) { this._userSelection.emit(); } } }; } /** * The date to display in this clock view. */ get activeDate() { return this._activeDate; } set activeDate(value) { const oldActiveDate = this._activeDate; this._activeDate = this._adapter.clampDate(value, this.minDate, this.maxDate); if (!this._adapter.sameMinute(oldActiveDate, this._activeDate)) { this._init(); } } /** The currently selected date. */ get selected() { return this._selected; } set selected(value) { this._selected = this._adapter.getValidDateOrNull(this._adapter.deserialize(value)); if (this._selected) { this.activeDate = this._selected; } } /** The minimum selectable date. */ get minDate() { return this._minDate; } set minDate(value) { this._minDate = this._adapter.getValidDateOrNull(this._adapter.deserialize(value)); } /** The maximum selectable date. */ get maxDate() { return this._maxDate; } set maxDate(value) { this._maxDate = this._adapter.getValidDateOrNull(this._adapter.deserialize(value)); } /** Whether the clock should be started in hour or minute view. */ set startView(value) { this._hourView = value !== 'minute'; } get _hand() { const hour = this._adapter.getHour(this.activeDate); this._selectedHour = hour; this._selectedMinute = this._adapter.getMinute(this.activeDate); let deg = 0; let radius = CLOCK_OUTER_RADIUS; if (this._hourView) { const outer = this._selectedHour > 0 && this._selectedHour < 13; radius = outer ? CLOCK_OUTER_RADIUS : CLOCK_INNER_RADIUS; if (this.twelvehour) { radius = CLOCK_OUTER_RADIUS; } deg = Math.round(this._selectedHour * (360 / (24 / 2))); } else { deg = Math.round(this._selectedMinute * (360 / 60)); } return { height: `${radius}%`, marginTop: `${50 - radius}%`, transform: `rotate(${deg}deg)`, }; } ngAfterContentInit() { this.activeDate = this._activeDate || this._adapter.today(); this._init(); } ngOnDestroy() { this._removeGlobalEvents(); } ngOnChanges() { this._init(); } /** Binds our global move and end events. */ _bindGlobalEvents(triggerEvent) { // Note that we bind the events to the `document`, because it allows us to capture // drag cancel events where the user's pointer is outside the browser window. const document = this._document; const isTouch = isTouchEvent(triggerEvent); const moveEventName = isTouch ? 'touchmove' : 'mousemove'; const endEventName = isTouch ? 'touchend' : 'mouseup'; document.addEventListener(moveEventName, this._pointerMove, activeEventOptions); document.addEventListener(endEventName, this._pointerUp, activeEventOptions); if (isTouch) { document.addEventListener('touchcancel', this._pointerUp, activeEventOptions); } } /** Removes any global event listeners that we may have added. */ _removeGlobalEvents() { const document = this._document; document.removeEventListener('mousemove', this._pointerMove, activeEventOptions); document.removeEventListener('mouseup', this._pointerUp, activeEventOptions); document.removeEventListener('touchmove', this._pointerMove, activeEventOptions); document.removeEventListener('touchend', this._pointerUp, activeEventOptions); document.removeEventListener('touchcancel', this._pointerUp, activeEventOptions); } /** Initializes this clock view. */ _init() { this._hours.length = 0; this._minutes.length = 0; const hourNames = this._adapter.getHourNames(); const minuteNames = this._adapter.getMinuteNames(); if (this.twelvehour) { const hours = []; for (let i = 0; i < hourNames.length; i++) { const radian = (i / 6) * Math.PI; const radius = CLOCK_OUTER_RADIUS; const hour = i; const date = this._adapter.createDatetime(this._adapter.getYear(this.activeDate), this._adapter.getMonth(this.activeDate), this._adapter.getDate(this.activeDate), hour, 0); // Check if the date is enabled, no need to respect the minute setting here const enabled = (!this.minDate || this._adapter.compareDatetime(date, this.minDate, false) >= 0) && (!this.maxDate || this._adapter.compareDatetime(date, this.maxDate, false) <= 0) && (!this.dateFilter || this.dateFilter(date, MtxDatetimepickerFilterType.HOUR)); // display value for twelvehour clock should be from 1-12 not including 0 and not above 12 hours.push({ value: i, displayValue: i % 12 === 0 ? '12' : hourNames[i % 12], enabled, top: CLOCK_RADIUS - Math.cos(radian) * radius - CLOCK_TICK_RADIUS, left: CLOCK_RADIUS + Math.sin(radian) * radius - CLOCK_TICK_RADIUS, }); } // filter out AM or PM hours based on AMPM if (this.AMPM === 'AM') { this._hours = hours.filter(x => x.value < 12); } else { this._hours = hours.filter(x => x.value >= 12); } } else { for (let i = 0; i < hourNames.length; i++) { const radian = (i / 6) * Math.PI; const outer = i > 0 && i < 13; const radius = outer ? CLOCK_OUTER_RADIUS : CLOCK_INNER_RADIUS; const date = this._adapter.createDatetime(this._adapter.getYear(this.activeDate), this._adapter.getMonth(this.activeDate), this._adapter.getDate(this.activeDate), i, 0); // Check if the date is enabled, no need to respect the minute setting here const enabled = (!this.minDate || this._adapter.compareDatetime(date, this.minDate, false) >= 0) && (!this.maxDate || this._adapter.compareDatetime(date, this.maxDate, false) <= 0) && (!this.dateFilter || this.dateFilter(date, MtxDatetimepickerFilterType.HOUR)); this._hours.push({ value: i, displayValue: i === 0 ? '00' : hourNames[i], enabled, top: CLOCK_RADIUS - Math.cos(radian) * radius - CLOCK_TICK_RADIUS, left: CLOCK_RADIUS + Math.sin(radian) * radius - CLOCK_TICK_RADIUS, fontSize: i > 0 && i < 13 ? '' : '80%', }); } } for (let i = 0; i < minuteNames.length; i += 5) { const radian = (i / 30) * Math.PI; const date = this._adapter.createDatetime(this._adapter.getYear(this.activeDate), this._adapter.getMonth(this.activeDate), this._adapter.getDate(this.activeDate), this._adapter.getHour(this.activeDate), i); const enabled = (!this.minDate || this._adapter.compareDatetime(date, this.minDate) >= 0) && (!this.maxDate || this._adapter.compareDatetime(date, this.maxDate) <= 0) && (!this.dateFilter || this.dateFilter(date, MtxDatetimepickerFilterType.MINUTE)); this._minutes.push({ value: i, displayValue: i === 0 ? '00' : minuteNames[i], enabled, top: CLOCK_RADIUS - Math.cos(radian) * CLOCK_OUTER_RADIUS - CLOCK_TICK_RADIUS, left: CLOCK_RADIUS + Math.sin(radian) * CLOCK_OUTER_RADIUS - CLOCK_TICK_RADIUS, }); } } /** * Set Time * @param event */ setTime(event) { const trigger = this._elementRef.nativeElement; const triggerRect = trigger.getBoundingClientRect(); const width = trigger.offsetWidth; const height = trigger.offsetHeight; const { pageX, pageY } = getPointerPositionOnPage(event); const x = width / 2 - (pageX - triggerRect.left - window.pageXOffset); const y = height / 2 - (pageY - triggerRect.top - window.pageYOffset); let radian = Math.atan2(-x, y); const unit = Math.PI / (this._hourView ? 6 : this.interval ? 30 / this.interval : 30); const z = Math.sqrt(x * x + y * y); const outer = this._hourView && z > (width * (CLOCK_OUTER_RADIUS / 100) + width * (CLOCK_INNER_RADIUS / 100)) / 2; if (radian < 0) { radian = Math.PI * 2 + radian; } let value = Math.round(radian / unit); let date; if (this._hourView) { if (this.twelvehour) { if (this.AMPM === 'AM') { value = value === 0 ? 12 : value; } else { // if we chosen 12 in PM, the value should be 0 for 0:00, // else we can safely add 12 to the final value value = value === 12 ? 0 : value + 12; } } else { if (value === 12) { value = 0; } value = outer ? (value === 0 ? 12 : value) : value === 0 ? 0 : value + 12; } date = this._adapter.createDatetime(this._adapter.getYear(this.activeDate), this._adapter.getMonth(this.activeDate), this._adapter.getDate(this.activeDate), value, this._adapter.getMinute(this.activeDate)); } else { if (this.interval) { value *= this.interval; } if (value === 60) { value = 0; } date = this._adapter.createDatetime(this._adapter.getYear(this.activeDate), this._adapter.getMonth(this.activeDate), this._adapter.getDate(this.activeDate), this._adapter.getHour(this.activeDate), value); } // if there is a dateFilter, check if the date is allowed if it is not then do not set/emit new date // https://github.com/ng-matero/extensions/issues/244 if (this.dateFilter && !this.dateFilter(date, this._hourView ? MtxDatetimepickerFilterType.HOUR : MtxDatetimepickerFilterType.MINUTE)) { return; } this._timeChanged = true; this.activeDate = date; this._changeDetectorRef.markForCheck(); this.activeDateChange.emit(this.activeDate); } /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.0", ngImport: i0, type: MtxClock, deps: [{ token: i0.ElementRef }, { token: i1.DatetimeAdapter }, { token: i0.ChangeDetectorRef }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Component }); } /** @nocollapse */ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.0", type: MtxClock, isStandalone: true, selector: "mtx-clock", inputs: { dateFilter: "dateFilter", interval: "interval", twelvehour: ["twelvehour", "twelvehour", booleanAttribute], AMPM: "AMPM", activeDate: "activeDate", selected: "selected", minDate: "minDate", maxDate: "maxDate", startView: "startView" }, outputs: { selectedChange: "selectedChange", activeDateChange: "activeDateChange", _userSelection: "_userSelection" }, host: { attributes: { "role": "clock" }, listeners: { "mousedown": "_pointerDown($event)", "touchstart": "_pointerDown($event)" }, classAttribute: "mtx-clock" }, exportAs: ["mtxClock"], usesOnChanges: true, ngImport: i0, template: "<div class=\"mtx-clock-wrapper\">\n <div class=\"mtx-clock-center\"></div>\n <div class=\"mtx-clock-hand\" [style]=\"_hand\"></div>\n <div class=\"mtx-clock-hours\" [class.active]=\"_hourView\">\n @for (item of _hours; track item.value) {\n <div\n class=\"mtx-clock-cell\"\n [class.mtx-clock-cell-disabled]=\"!item.enabled\"\n [class.mtx-clock-cell-selected]=\"_selectedHour === item.value\"\n [style.fontSize]=\"item.fontSize\"\n [style.left]=\"item.left+'%'\"\n [style.top]=\"item.top+'%'\">{{ item.displayValue }}</div>\n }\n </div>\n <div class=\"mtx-clock-minutes\" [class.active]=\"!_hourView\">\n @for (item of _minutes; track item.value) {\n <div\n class=\"mtx-clock-cell\"\n [class.mtx-clock-cell-disabled]=\"!item.enabled\"\n [class.mtx-clock-cell-selected]=\"_selectedMinute === item.value\"\n [style.left]=\"item.left+'%'\"\n [style.top]=\"item.top+'%'\">{{ item.displayValue }}</div>\n }\n </div>\n</div>\n", styles: [".mtx-clock{position:relative;display:block;min-width:224px;margin:12px;box-sizing:border-box;-webkit-user-select:none;user-select:none;touch-action:none;font-size:var(--mtx-datetimepicker-clock-text-size, var(--mat-app-title-small-size))}.mtx-clock-wrapper{position:relative;width:100%;height:0;padding-top:100%;border-radius:50%;background-color:var(--mtx-datetimepicker-clock-dial-background-color, var(--mat-app-surface-container-highest))}.mtx-clock-center{position:absolute;top:50%;left:50%;width:3%;height:3%;margin:-1.5%;border-radius:50%;background-color:var(--mtx-datetimepicker-clock-hand-background-color, var(--mat-app-primary))}.mtx-clock-hand{position:absolute;inset:0;width:2px;margin:0 auto;transform-origin:bottom;background-color:var(--mtx-datetimepicker-clock-hand-background-color, var(--mat-app-primary))}.mtx-clock-hand:before{content:\"\";position:absolute;top:-4px;left:-3px;width:8px;height:8px;border-radius:50%;background-color:var(--mtx-datetimepicker-clock-hand-background-color, var(--mat-app-primary))}.mtx-clock-hours,.mtx-clock-minutes{position:absolute;top:0;left:0;width:100%;height:100%;opacity:0;visibility:hidden;transition:.35s;transform:scale(1.2)}.mtx-clock-hours.active,.mtx-clock-minutes.active{opacity:1;visibility:visible;transform:scale(1)}.mtx-clock-minutes{transform:scale(.8)}.mtx-clock-cell{position:absolute;display:flex;width:14.1666%;height:14.1666%;justify-content:center;box-sizing:border-box;border-radius:50%;align-items:center;cursor:pointer;color:var(--mtx-datetimepicker-clock-cell-text-color, var(--mat-app-on-surface))}.mtx-clock-cell.mtx-clock-cell-selected{color:#fff;background-color:var(--mtx-datetimepicker-clock-hand-background-color, var(--mat-app-primary))}.mtx-clock-cell:not(.mtx-clock-cell-selected,.mtx-clock-cell-disabled):hover{background-color:var(--mtx-datetimepicker-clock-cell-hover-state-background-color)}.mtx-clock-cell.mtx-clock-cell-disabled{pointer-events:none;color:var(--mtx-datetimepicker-clock-cell-disabled-state-text-color)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.0", ngImport: i0, type: MtxClock, decorators: [{ type: Component, args: [{ selector: 'mtx-clock', host: { 'role': 'clock', 'class': 'mtx-clock', '(mousedown)': '_pointerDown($event)', '(touchstart)': '_pointerDown($event)', }, exportAs: 'mtxClock', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "<div class=\"mtx-clock-wrapper\">\n <div class=\"mtx-clock-center\"></div>\n <div class=\"mtx-clock-hand\" [style]=\"_hand\"></div>\n <div class=\"mtx-clock-hours\" [class.active]=\"_hourView\">\n @for (item of _hours; track item.value) {\n <div\n class=\"mtx-clock-cell\"\n [class.mtx-clock-cell-disabled]=\"!item.enabled\"\n [class.mtx-clock-cell-selected]=\"_selectedHour === item.value\"\n [style.fontSize]=\"item.fontSize\"\n [style.left]=\"item.left+'%'\"\n [style.top]=\"item.top+'%'\">{{ item.displayValue }}</div>\n }\n </div>\n <div class=\"mtx-clock-minutes\" [class.active]=\"!_hourView\">\n @for (item of _minutes; track item.value) {\n <div\n class=\"mtx-clock-cell\"\n [class.mtx-clock-cell-disabled]=\"!item.enabled\"\n [class.mtx-clock-cell-selected]=\"_selectedMinute === item.value\"\n [style.left]=\"item.left+'%'\"\n [style.top]=\"item.top+'%'\">{{ item.displayValue }}</div>\n }\n </div>\n</div>\n", styles: [".mtx-clock{position:relative;display:block;min-width:224px;margin:12px;box-sizing:border-box;-webkit-user-select:none;user-select:none;touch-action:none;font-size:var(--mtx-datetimepicker-clock-text-size, var(--mat-app-title-small-size))}.mtx-clock-wrapper{position:relative;width:100%;height:0;padding-top:100%;border-radius:50%;background-color:var(--mtx-datetimepicker-clock-dial-background-color, var(--mat-app-surface-container-highest))}.mtx-clock-center{position:absolute;top:50%;left:50%;width:3%;height:3%;margin:-1.5%;border-radius:50%;background-color:var(--mtx-datetimepicker-clock-hand-background-color, var(--mat-app-primary))}.mtx-clock-hand{position:absolute;inset:0;width:2px;margin:0 auto;transform-origin:bottom;background-color:var(--mtx-datetimepicker-clock-hand-background-color, var(--mat-app-primary))}.mtx-clock-hand:before{content:\"\";position:absolute;top:-4px;left:-3px;width:8px;height:8px;border-radius:50%;background-color:var(--mtx-datetimepicker-clock-hand-background-color, var(--mat-app-primary))}.mtx-clock-hours,.mtx-clock-minutes{position:absolute;top:0;left:0;width:100%;height:100%;opacity:0;visibility:hidden;transition:.35s;transform:scale(1.2)}.mtx-clock-hours.active,.mtx-clock-minutes.active{opacity:1;visibility:visible;transform:scale(1)}.mtx-clock-minutes{transform:scale(.8)}.mtx-clock-cell{position:absolute;display:flex;width:14.1666%;height:14.1666%;justify-content:center;box-sizing:border-box;border-radius:50%;align-items:center;cursor:pointer;color:var(--mtx-datetimepicker-clock-cell-text-color, var(--mat-app-on-surface))}.mtx-clock-cell.mtx-clock-cell-selected{color:#fff;background-color:var(--mtx-datetimepicker-clock-hand-background-color, var(--mat-app-primary))}.mtx-clock-cell:not(.mtx-clock-cell-selected,.mtx-clock-cell-disabled):hover{background-color:var(--mtx-datetimepicker-clock-cell-hover-state-background-color)}.mtx-clock-cell.mtx-clock-cell-disabled{pointer-events:none;color:var(--mtx-datetimepicker-clock-cell-disabled-state-text-color)}\n"] }] }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.DatetimeAdapter }, { type: i0.ChangeDetectorRef }, { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT] }] }], propDecorators: { dateFilter: [{ type: Input }], interval: [{ type: Input }], twelvehour: [{ type: Input, args: [{ transform: booleanAttribute }] }], AMPM: [{ type: Input }], selectedChange: [{ type: Output }], activeDateChange: [{ type: Output }], _userSelection: [{ type: Output }], activeDate: [{ type: Input }], selected: [{ type: Input }], minDate: [{ type: Input }], maxDate: [{ type: Input }], startView: [{ type: Input }] } }); /** Returns whether an event is a touch event. */ function isTouchEvent(event) { // This function is called for every pixel that the user has dragged so we need it to be // as fast as possible. Since we only bind mouse events and touch events, we can assume // that if the event's name starts with `t`, it's a touch event. return event.type[0] === 't'; } /** Gets the coordinates of a touch or mouse event relative to the document. */ function getPointerPositionOnPage(event) { let point; if (isTouchEvent(event)) { // `touches` will be empty for start/end events so we have to fall back to `changedTouches`. point = event.touches[0] || event.changedTouches[0]; } else { point = event; } return point; } /** * Animations used by the Material datetimepicker. * @docs-private */ const mtxDatetimepickerAnimations = { /** Transforms the height of the datetimepicker's calendar. */ transformPanel: trigger('transformPanel', [ transition('void => enter-dropdown', animate('120ms cubic-bezier(0, 0, 0.2, 1)', keyframes([ style({ opacity: 0, transform: 'scale(1, 0.8)' }), style({ opacity: 1, transform: 'scale(1, 1)' }), ]))), transition('void => enter-dialog', animate('150ms cubic-bezier(0, 0, 0.2, 1)', keyframes([ style({ opacity: 0, transform: 'scale(0.7)' }), style({ transform: 'none', opacity: 1 }), ]))), transition('* => void', animate('100ms linear', style({ opacity: 0 }))), ]), /** Fades in the content of the calendar. */ fadeInCalendar: trigger('fadeInCalendar', [ state('void', style({ opacity: 0 })), state('enter', style({ opacity: 1 })), // TODO(crisbeto): this animation should be removed since it isn't quite on spec, but we // need to keep it until #12440 gets in, otherwise the exit animation will look glitchy. transition('void => *', animate('120ms 100ms cubic-bezier(0.55, 0, 0.55, 0.2)')), ]), slideCalendar: trigger('slideCalendar', [ transition('* => left', [ animate(180, keyframes([ style({ transform: 'translateX(100%)', offset: 0.5 }), style({ transform: 'translateX(-100%)', offset: 0.51 }), style({ transform: 'translateX(0)', offset: 1 }), ])), ]), transition('* => right', [ animate(180, keyframes([ style({ transform: 'translateX(-100%)', offset: 0.5 }), style({ transform: 'translateX(100%)', offset: 0.51 }), style({ transform: 'translateX(0)', offset: 1 }), ])), ]), ]), }; /** @docs-private */ function createMissingDateImplError(provider) { return Error(`MtxDatetimepicker: No provider found for ${provider}. You must add one of the following ` + `to your app config: provideNativeDatetimeAdapter, provideDateFnsDatetimeAdapter,` + `provideLuxonDatetimeAdapter, provideMomentDatetimeAdapter, or provide a ` + `custom implementation.`); } /** * An internal class that represents the data corresponding to a single calendar cell. * @docs-private */ class MtxCalendarCell { constructor(value, displayValue, ariaLabel, enabled) { this.value = value; this.displayValue = displayValue; this.ariaLabel = ariaLabel; this.enabled = enabled; } } /** * An internal component used to display calendar data in a table. * @docs-private */ class MtxCalendarBody { constructor() { /** The number of columns in the table. */ this.numCols = 7; /** Whether to allow selection of disabled cells. */ this.allowDisabledSelection = false; /** The cell number of the active cell in the table. */ this.activeCell = 0; /** Emits when a new value is selected. */ this.selectedValueChange = new EventEmitter(); } /** The number of blank cells to put at the beginning for the first row. */ get _firstRowOffset() { return this.rows && this.rows.length && this.rows[0].length ? this.numCols - this.rows[0].length : 0; } _cellClicked(cell) { if (!this.allowDisabledSelection && !cell.enabled) { return; } this.selectedValueChange.emit(cell.value); } _isActiveCell(rowIndex, colIndex) { let cellNumber = rowIndex * this.numCols + colIndex; // Account for the fact that the first row may not have as many cells. if (rowIndex) { cellNumber -= this._firstRowOffset; } return cellNumber === this.activeCell; } /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.0", ngImport: i0, type: MtxCalendarBody, deps: [], target: i0.ɵɵFactoryTarget.Component }); } /** @nocollapse */ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.0", type: MtxCalendarBody, isStandalone: true, selector: "[mtx-calendar-body]", inputs: { label: "label", rows: "rows", todayValue: "todayValue", selectedValue: "selectedValue", labelMinRequiredCells: "labelMinRequiredCells", numCols: "numCols", allowDisabledSelection: "allowDisabledSelection", activeCell: "activeCell" }, outputs: { selectedValueChange: "selectedValueChange" }, host: { classAttribute: "mtx-calendar-body" }, exportAs: ["mtxCalendarBody"], ngImport: i0, template: "<!--\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@if (_firstRowOffset < labelMinRequiredCells) {\n <tr aria-hidden=\"true\">\n <td class=\"mtx-calendar-body-label\" [attr.colspan]=\"numCols\">{{ label }}</td>\n </tr>\n}\n\n<!-- Create the first row separately so we can include a special spacer cell. -->\n@for (row of rows; track row; let rowIndex = $index) {\n <tr role=\"row\">\n <!--\n We mark this cell as aria-hidden so it doesn't get read out as one of the days in the week.\n -->\n @if (rowIndex === 0 && _firstRowOffset) {\n <td\n class=\"mtx-calendar-body-label\" [attr.colspan]=\"_firstRowOffset\" aria-hidden=\"true\">\n {{ _firstRowOffset >= labelMinRequiredCells ? label : '' }}\n </td>\n }\n @for (item of row; track item; let colIndex = $index) {\n <td\n role=\"gridcell\"\n class=\"mtx-calendar-body-cell\"\n [class.mtx-calendar-body-active]=\"_isActiveCell(rowIndex, colIndex)\"\n [class.mtx-calendar-body-disabled]=\"!item.enabled\"\n [tabindex]=\"_isActiveCell(rowIndex, colIndex) ? 0 : -1\"\n [attr.data-mat-row]=\"rowIndex\"\n [attr.data-mat-col]=\"colIndex\"\n [attr.aria-label]=\"item.ariaLabel\"\n [attr.aria-disabled]=\"!item.enabled || null\"\n (click)=\"_cellClicked(item)\">\n <div class=\"mtx-calendar-body-cell-content\"\n [class.mtx-calendar-body-selected]=\"selectedValue === item.value\"\n [class.mtx-calendar-body-today]=\"todayValue === item.value\"\n [attr.aria-selected]=\"selectedValue === item.value\">\n {{ item.displayValue }}\n </div>\n </td>\n }\n </tr>\n}\n", styles: [".mtx-calendar-body{min-width:224px}.mtx-calendar-body-today:not(.mtx-calendar-body-selected){border-color:var(--mtx-datetimepicker-calendar-date-today-outline-color, var(--mat-app-primary))}.mtx-calendar-body-label{height:0;line-height:0;text-align:left;padding:7.1428571429% 4.7142857143%;font-size:var(--mtx-datetimepicker-calendar-body-label-text-size, var(--mat-app-title-small-size));font-weight:var(--mtx-datetimepicker-calendar-body-label-text-weight, var(--mat-app-title-small-weight));color:var(--mtx-datetimepicker-calendar-body-label-text-color, var(--mat-app-on-surface))}[dir=rtl] .mtx-calendar-body-label{text-align:right}.mtx-calendar-body-cell{position:relative;width:14.2857142857%;height:0;line-height:0;padding:7.1428571429% 0;text-align:center;outline:none;cursor:pointer}.mtx-calendar-body-disabled{cursor:default;pointer-events:none}.mtx-calendar-body-disabled>.mtx-calendar-body-cell-content:not(.mtx-calendar-body-selected){color:var(--mtx-datetimepicker-calendar-date-disabled-state-text-color)}.mtx-calendar-body-disabled>.mtx-calendar-body-today:not(.mtx-calendar-body-selected){border-color:var(--mtx-datetimepicker-calendar-date-disabled-state-text-color)}.mtx-calendar-body-cell-content{position:absolute;top:5%;left:5%;display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:90%;height:90%;border:1px solid transparent;border-radius:999px;color:var(--mtx-datetimepicker-calendar-date-text-color, var(--mat-app-on-surface));border-color:var(--mtx-datetimepicker-calendar-date-outline-color)}.mtx-calendar-body-active>.mtx-calendar-body-cell-content:not(.mtx-calendar-body-selected){background-color:var(--mtx-datetimepicker-calendar-date-focus-state-background-color)}@media (hover: hover){.mtx-calendar-body-cell:not(.mtx-calendar-body-disabled):hover>.mtx-calendar-body-cell-content:not(.mtx-calendar-body-selected){background-color:var(--mtx-datetimepicker-calendar-date-hover-state-background-color)}}.mtx-calendar-body-selected{background-color:var(--mtx-datetimepicker-calendar-date-selected-state-background-color, var(--mat-app-primary));color:var(--mtx-datetimepicker-calendar-date-selected-state-text-color, var(--mat-app-on-primary))}.mtx-calendar-body-disabled>.mtx-calendar-body-selected{background-color:var(--mtx-datetimepicker-calendar-date-selected-disabled-state-background-color)}.mtx-calendar-body-selected.mtx-calendar-body-today{box-shadow:inset 0 0 0 1px var(--mtx-datetimepicker-calendar-date-today-selected-state-outline-color, var(--mat-app-primary))}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.0", ngImport: i0, type: MtxCalendarBody, decorators: [{ type: Component, args: [{ selector: '[mtx-calendar-body]', host: { class: 'mtx-calendar-body', }, exportAs: 'mtxCalendarBody', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "<!--\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@if (_firstRowOffset < labelMinRequiredCells) {\n <tr aria-hidden=\"true\">\n <td class=\"mtx-calendar-body-label\" [attr.colspan]=\"numCols\">{{ label }}</td>\n </tr>\n}\n\n<!-- Create the first row separately so we can include a special spacer cell. -->\n@for (row of rows; track row; let rowIndex = $index) {\n <tr role=\"row\">\n <!--\n We mark this cell as aria-hidden so it doesn't get read out as one of the days in the week.\n -->\n @if (rowIndex === 0 && _firstRowOffset) {\n <td\n class=\"mtx-calendar-body-label\" [attr.colspan]=\"_firstRowOffset\" aria-hidden=\"true\">\n {{ _firstRowOffset >= labelMinRequiredCells ? label : '' }}\n </td>\n }\n @for (item of row; track item; let colIndex = $index) {\n <td\n role=\"gridcell\"\n class=\"mtx-calendar-body-cell\"\n [class.mtx-calendar-body-active]=\"_isActiveCell(rowIndex, colIndex)\"\n [class.mtx-calendar-body-disabled]=\"!item.enabled\"\n [tabindex]=\"_isActiveCell(rowIndex, colIndex) ? 0 : -1\"\n [attr.data-mat-row]=\"rowIndex\"\n [attr.data-mat-col]=\"colIndex\"\n [attr.aria-label]=\"item.ariaLabel\"\n [attr.aria-disabled]=\"!item.enabled || null\"\n (click)=\"_cellClicked(item)\">\n <div class=\"mtx-calendar-body-cell-content\"\n [class.mtx-calendar-body-selected]=\"selectedValue === item.value\"\n [class.mtx-calendar-body-today]=\"todayValue === item.value\"\n [attr.aria-selected]=\"selectedValue === item.value\">\n {{ item.displayValue }}\n </div>\n </td>\n }\n </tr>\n}\n", styles: [".mtx-calendar-body{min-width:224px}.mtx-calendar-body-today:not(.mtx-calendar-body-selected){border-color:var(--mtx-datetimepicker-calendar-date-today-outline-color, var(--mat-app-primary))}.mtx-calendar-body-label{height:0;line-height:0;text-align:left;padding:7.1428571429% 4.7142857143%;font-size:var(--mtx-datetimepicker-calendar-body-label-text-size, var(--mat-app-title-small-size));font-weight:var(--mtx-datetimepicker-calendar-body-label-text-weight, var(--mat-app-title-small-weight));color:var(--mtx-datetimepicker-calendar-body-label-text-color, var(--mat-app-on-surface))}[dir=rtl] .mtx-calendar-body-label{text-align:right}.mtx-calendar-body-cell{position:relative;width:14.2857142857%;height:0;line-height:0;padding:7.1428571429% 0;text-align:center;outline:none;cursor:pointer}.mtx-calendar-body-disabled{cursor:default;pointer-events:none}.mtx-calendar-body-disabled>.mtx-calendar-body-cell-content:not(.mtx-calendar-body-selected){color:var(--mtx-datetimepicker-calendar-date-disabled-state-text-color)}.mtx-calendar-body-disabled>.mtx-calendar-body-today:not(.mtx-calendar-body-selected){border-color:var(--mtx-datetimepicker-calendar-date-disabled-state-text-color)}.mtx-calendar-body-cell-content{position:absolute;top:5%;left:5%;display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:90%;height:90%;border:1px solid transparent;border-radius:999px;color:var(--mtx-datetimepicker-calendar-date-text-color, var(--mat-app-on-surface));border-color:var(--mtx-datetimepicker-calendar-date-outline-color)}.mtx-calendar-body-active>.mtx-calendar-body-cell-content:not(.mtx-calendar-body-selected){background-color:var(--mtx-datetimepicker-calendar-date-focus-state-background-color)}@media (hover: hover){.mtx-calendar-body-cell:not(.mtx-calendar-body-disabled):hover>.mtx-calendar-body-cell-content:not(.mtx-calendar-body-selected){background-color:var(--mtx-datetimepicker-calendar-date-hover-state-background-color)}}.mtx-calendar-body-selected{background-color:var(--mtx-datetimepicker-calendar-date-selected-state-background-color, var(--mat-app-primary));color:var(--mtx-datetimepicker-calendar-date-selected-state-text-color, var(--mat-app-on-primary))}.mtx-calendar-body-disabled>.mtx-calendar-body-selected{background-color:var(--mtx-datetimepicker-calendar-date-selected-disabled-state-background-color)}.mtx-calendar-body-selected.mtx-calendar-body-today{box-shadow:inset 0 0 0 1px var(--mtx-datetimepicker-calendar-date-today-selected-state-outline-color, var(--mat-app-primary))}\n"] }] }], propDecorators: { label: [{ type: Input }], rows: [{ type: Input }], todayValue: [{ type: Input }], selectedValue: [{ type: Input }], labelMinRequiredCells: [{ type: Input }], numCols: [{ type: Input }], allowDisabledSelection: [{ type: Input }], activeCell: [{ type: Input }], selectedValueChange: [{ type: Output }] } }); const DAYS_PER_WEEK = 7; /** * An internal component used to display a single month in the datetimepicker. * @docs-private */ class MtxMonthView { constructor(_adapter, _dateFormats) { this._adapter = _adapter; this._dateFormats = _dateFormats; this.type = 'date'; /** Emits when a new date is selected. */ this.selectedChange = new EventEmitter(); /** Emits when any date is selected. */ this._userSelection = new EventEmitter(); if (!this._adapter) { throw createMissingDateImplError('DatetimeAdapter'); } if (!this._dateFormats) { throw createMissingDateImplError('MTX_DATETIME_FORMATS'); } const firstDayOfWeek = this._adapter.getFirstDayOfWeek(); const narrowWeekdays = this._adapter.getDayOfWeekNames('narrow'); const longWeekdays = this._adapter.getDayOfWeekNames('long'); // Rotate the labels for days of the week based on the configured first day of the week. const weekdays = longWeekdays.map((long, i) => { return { long, narrow: narrowWeekdays[i] }; }); this._weekdays = weekdays.slice(firstDayOfWeek).concat(weekdays.slice(0, firstDayOfWeek)); this._activeDate = this._adapter.today(); } /** * The date to display in this month view (everything other than the month and year is ignored). */ get activeDate() { return this._activeDate; } set activeDate(value) { const oldActiveDate = this._activeDate; this._activeDate = value || this._adapter.today(); if (oldActiveDate && this._activeDate && !this._adapter.sameMonthAndYear(oldActiveDate, this._activeDate)) { this._init(); if (this._adapter.isInNextMonth(oldActiveDate, this._activeDate)) { this.calendarState('right'); } else { this.calendarState('left'); } } } /** The currently selected date. */ get selected() { return this._selected; } set selected(value) { this._selected = value; this._selectedDate = this._getDateInCurrentMonth(this.selected); } ngAfterContentInit() { this._init(); } /** Handles when a new date is selected. */ _dateSelected(date) { const dateObject = this._adapter.createDatetime(this._adapter.getYear(this.activeDate), this._adapter.getMonth(this.activeDate), date, this._adapter.getHour(this.activeDate), this._adapter.getMinute(this.activeDate)); this.selectedChange.emit(dateObject); this._activeDate = dateObject; if (this.type === 'date') { this._userSelection.emit(); } } _calendarStateDone() { this._calendarState = ''; } /** Initializes this month view. */ _init() { this._selectedDate = this._getDateInCurrentMonth(this.selected); this._todayDate = this._getDateInCurrentMonth(this._adapter.today()); const firstOfMonth = this._adapter.createDatetime(this._adapter.getYear(this.activeDate), this._adapter.getMonth(this.activeDate), 1, this._adapter.getHour(this.activeDate), this._adapter.getMinute(this.activeDate)); this._firstWeekOffset = (DAYS_PER_WEEK + this._adapter.getDayOfWeek(firstOfMonth) - this._adapter.getFirstDayOfWeek()) % DAYS_PER_WEEK; this._createWeekCells(); } /** Creates MdCalendarCells for the dates in this month. */ _createWeekCells() { const daysInMonth = this._adapter.getNumDaysInMonth(this.activeDate); const dateNames = this._adapter.getDateNames(); this._weeks = [[]]; for (let i = 0, cell = this._firstWeekOffset; i < daysInMonth; i++, cell++) { if (cell === DAYS_PER_WEEK) { this._weeks.push([]); cell = 0; } const date = this._adapter.createDatetime(this._adapter.getYear(this.activeDate), this._adapter.getMonth(this.activeDate), i + 1, this._adapter.getHour(this.activeDate), this._adapter.getMinute(this.activeDate)); const enabled = !this.dateFilter || this.dateFilter(date); const ariaLabel = this._adapter.format(date, this._dateFormats.display.dateA11yLabel); this._weeks[this._weeks.length - 1].push(new MtxCalendarCell(i + 1, dateNames[i], ariaLabel, enabled)); } } /** * Gets the date in this month that the given Date falls on. * Returns null if the given Date is in another month. */ _getDateInCurrentMonth(date) { return this._adapter.sameMonthAndYear(date, this.activeDate) ? this._adapter.getDate(date) : null; } calendarState(direction) { this._calendarState = direction; } /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.0", ngImport: i0, type: MtxMonthView, deps: [{ token: i1.DatetimeAdapter, optional: true }, { token: MTX_DATETIME_FORMATS, optional: true }], target: i0.ɵɵFactoryTarget.Component }); } /** @nocollapse */ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.0", type: MtxMonthView, isStandalone: true, selector: "mtx-month-view", inputs: { type: "type", dateFilter: "dateFilter", activeDate: "activeDate", selected: "selected" }, outputs: { selectedChange: "selectedChange", _userSelection: "_userSelection" }, exportAs: ["mtxMonthView"], ngImport: i0, template: "<table class=\"mtx-calendar-table\" role=\"grid\">\n <thead class=\"mtx-calendar-table-header\">\n <tr>\n @for (day of _weekdays; track day) {\n <th [attr.aria-label]=\"day.long\">{{day.narrow}}</th>\n }\n </tr>\n </thead>\n <tbody mtx-calendar-body\n (@slideCalendar.done)=\"_calendarStateDone()\"\n [@slideCalendar]=\"_calendarState\"\n [rows]=\"_weeks\"\n [todayValue]=\"_todayDate!\"\n [activeCell]=\"_adapter.getDate(activeDate) - 1\"\n [selectedValue]=\"_selectedDate!\"\n (selectedValueChange)=\"_dateSelected($event)\"></tbody>\n</table>\n", dependencies: [{ kind: "component", type: MtxCalendarBody, selector: "[mtx-calendar-body]", inputs: ["label", "rows", "todayValue", "selectedValue", "labelMinRequiredCells", "numCols", "allowDisabledSelection", "activeCell"], outputs: ["selectedValueChange"], exportAs: ["mtxCalendarBody"] }], animations: [mtxDatetimepickerAnimations.slideCalendar], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.0", ngImport: i0, type: MtxMonthView, decorators: [{ type: Component, args: [{ selector: 'mtx-month-view', exportAs: 'mtxMonthView', animations: [mtxDatetimepickerAnimations.slideCalendar], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [MtxCalendarBody], template: "<table class=\"mtx-calendar-table\" role=\"grid\">\n <thead class=\"mtx-calendar-table-header\">\n <tr>\n @for (day of _weekdays; track day) {\n <th [attr.aria-label]=\"day.long\">{{day.narrow}}</th>\n }\n </tr>\n </thead>\n <tbody mtx-calendar-body\n (@slideCalendar.done)=\"_calendarStateDone()\"\n [@slideCalendar]=\"_calendarState\"\n [rows]=\"_weeks\"\n [todayValue]=\"_todayDate!\"\n [activeCell]=\"_adapter.getDate(activeDate) - 1\"\n [selectedValue]=\"_selectedDate!\"\n (selectedValueChange)=\"_dateSelected($event)\"></tbody>\n</table>\n" }] }], ctorParameters: () => [{ type: i1.DatetimeAdapter, decorators: [{ type: Optional }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MTX_DATETIME_FORMATS] }] }], propDecorators: { type: [{ type: Input }], dateFilter: [{ type: Input }], selectedChange: [{ type: Output }], _userSelection: [{ type: Output }], activeDate: [{ type: Input }], selected: [{ type: Input }] } }); const yearsPerPage = 24; const yearsPerRow = 4; /** * An internal component used to display multiple years in the datetimepicker. * @docs-private */ class MtxMultiYearView { constructor(_adapter, _dateFormats) { this._adapter = _adapter; this._dateFormats = _dateFormats; this.type = 'date'; /** Emits when a new month is selected. */ this.selectedChange = new EventEmitter(); /** Emits when any date is selected. */ this._userSelection = new EventEmitter(); if (!this._adapter) { throw createMissingDateImplError('DatetimeAdapter'); } if (!this._dateFormats) { throw createMissingDateImplError('MTX_DATETIME_FORMATS'); } this._activeDate = this._adapter.today(); } /** The date to display in this multi year view */ get activeDate() { return this._activeDate; } set activeDate(value) { const oldActiveDate = this._activeDate; this._activeDate = value || this._adapter.today(); if (oldActiveDate && this._activeDate && !isSameMultiYearView(this._adapter, oldActiveDate, this._activeDate, this.minDate, this.maxDate)) { this._init(); } } /** The currently selected date. */ get selected() { return this._selected; } set selected(value) { this._selected = value; this._selectedYear = this._selected && this._adapter.getYear(this._selected); } /** The minimum selectable date. */ get minDate() { return this._minDate; } set minDate(value) { this._minDate = this._getValidDateOrNull(this._adapter.deserialize(value)); } /** The maximum selectable date. */ get maxDate() { return this._maxDate; } set maxDate(value) { this._maxDate = this._getValidDateOrNull(this._adapter.deserialize(value)); } ngAfterContentInit() { this._init(); } /** Handles when a new year is selected. */ _yearSelected(year) { const month = this._adapter.getMonth(this.activeDate); const normalizedDate = this._adapter.createDatetime(year, month, 1, 0, 0); const da