UNPKG

igniteui-angular

Version:

Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps

1,067 lines (1,063 loc) 146 kB
import * as i2 from 'igniteui-angular/core'; import { PickerInteractionMode, PickerHeaderOrientation, IgxPickerToggleComponent, IgxPickerClearComponent, DateRangePickerResourceStringsEN, CalendarDay, DateTimeUtil, IgxOverlayService, PlatformUtil, PickerCalendarOrientation, getCurrentResourceStrings, DatePickerResourceStringsEN, AutoPositionStrategy, AbsoluteScrollStrategy, isDate, isDateInRanges, DateRangeType, IgxPickerActionsDirective, clamp, parseDate, calendarRange } from 'igniteui-angular/core'; import * as i0 from '@angular/core'; import { inject, ElementRef, LOCALE_ID, EventEmitter, booleanAttribute, ViewChild, ContentChildren, Output, Input, Directive, ChangeDetectionStrategy, Component, HostListener, HostBinding, Injector, Renderer2, ChangeDetectorRef, ViewContainerRef, ContentChild, Pipe, TemplateRef, NgModule } from '@angular/core'; import { NgControl, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms'; import * as i4 from 'igniteui-angular/calendar'; import { IgxCalendarComponent, IgxCalendarHeaderTitleTemplateDirective, IgxCalendarHeaderTemplateDirective, IgxCalendarSubheaderTemplateDirective, CalendarSelection } from 'igniteui-angular/calendar'; import * as i3 from 'igniteui-angular/input-group'; import { IGX_INPUT_GROUP_TYPE, IgxInputGroupComponent, IgxPrefixDirective, IgxSuffixDirective, IgxInputState, IgxInputDirective, IgxReadOnlyInputDirective, IgxLabelDirective, IgxHintDirective, IgxInputGroupBase } from 'igniteui-angular/input-group'; import { Subject, merge, noop, fromEvent } from 'rxjs'; import { takeUntil, filter } from 'rxjs/operators'; import { IgxButtonDirective, IgxRippleDirective, IgxDividerDirective, IgxDateTimeEditorDirective, IgxTextSelectionDirective } from 'igniteui-angular/directives'; import { IgxIconComponent } from 'igniteui-angular/icon'; import { fadeOut, fadeIn } from 'igniteui-angular/animations'; import { getLocaleFirstDayOfWeek, NgTemplateOutlet } from '@angular/common'; import { IgxChipComponent } from 'igniteui-angular/chips'; class PickerBaseDirective { /** * @example * ```html * <igx-date-picker locale="jp"></igx-date-picker> * ``` */ /** * Gets the `locale` of the date-picker. * If not set, defaults to applciation's locale.. */ get locale() { return this._locale; } /** * Sets the `locale` of the date-picker. * Expects a valid BCP 47 language tag. */ set locale(value) { this._locale = value; // if value is invalid, set it back to _localeId try { getLocaleFirstDayOfWeek(this._locale); } catch (e) { this._locale = this._localeId; } } /** * Gets the start day of the week. * Can return a numeric or an enum representation of the week day. * If not set, defaults to the first day of the week for the application locale. */ get weekStart() { return this._weekStart ?? getLocaleFirstDayOfWeek(this._locale); } /** * Sets the start day of the week. * Can be assigned to a numeric value or to `WEEKDAYS` enum value. */ set weekStart(value) { this._weekStart = value; } /** * Determines how the picker's input will be styled. * * @remarks * Default is `box`. * * @example * ```html * <igx-date-picker [type]="'line'"></igx-date-picker> * ``` */ set type(val) { this._type = val; } get type() { return this._type || this._inputGroupType; } /** * Gets the picker's pop-up state. * * @example * ```typescript * const state = this.picker.collapsed; * ``` */ get collapsed() { return this._collapsed; } /** @hidden @internal */ get isDropdown() { return this.mode === PickerInteractionMode.DropDown; } /** * Returns if there's focus within the picker's element OR popup container * @hidden @internal */ get isFocused() { const document = this.element.nativeElement?.getRootNode(); if (!document?.activeElement) return false; return this.element.nativeElement.contains(document.activeElement) || !this.collapsed && this.toggleContainer.contains(document.activeElement); } constructor() { this.element = inject(ElementRef); this._localeId = inject(LOCALE_ID); this._inputGroupType = inject(IGX_INPUT_GROUP_TYPE, { optional: true }); /** * Sets the `placeholder` of the picker's input. * * @example * ```html * <igx-date-picker [placeholder]="'Choose your date'"></igx-date-picker> * ``` */ this.placeholder = ''; /** * Can be `dropdown` with editable input field or `dialog` with readonly input field. * * @remarks * Default mode is `dropdown` * * @example * ```html * <igx-date-picker mode="dialog"></igx-date-picker> * ``` */ this.mode = PickerInteractionMode.DropDown; /** * Gets/Sets the orientation of the `IgxDatePickerComponent` header. * * @example * ```html * <igx-date-picker headerOrientation="vertical"></igx-date-picker> * ``` */ this.headerOrientation = PickerHeaderOrientation.Horizontal; /** * Gets/Sets whether the header is hidden in dialog mode. * * @example * ```html * <igx-date-picker mode="dialog" [hideHeader]="true"></igx-date-picker> * ``` */ this.hideHeader = false; /** * Enables or disables the picker. * * @example * ```html * <igx-date-picker [disabled]="'true'"></igx-date-picker> * ``` */ this.disabled = false; /** * Emitted when the calendar has started opening, cancelable. * * @example * ```html * <igx-date-picker (opening)="handleOpening($event)"></igx-date-picker> * ``` */ this.opening = new EventEmitter(); /** * Emitted after the calendar has opened. * * @example * ```html * <igx-date-picker (opened)="handleOpened($event)"></igx-date-picker> * ``` */ this.opened = new EventEmitter(); /** * Emitted when the calendar has started closing, cancelable. * * @example * ```html * <igx-date-picker (closing)="handleClosing($event)"></igx-date-picker> * ``` */ this.closing = new EventEmitter(); /** * Emitted after the calendar has closed. * * @example * ```html * <igx-date-picker (closed)="handleClosed($event)"></igx-date-picker> * ``` */ this.closed = new EventEmitter(); this._collapsed = true; this._destroy$ = new Subject(); this.locale = this.locale || this._localeId; } /** @hidden @internal */ ngAfterViewInit() { this.subToIconsClicked(this.toggleComponents, () => this.toggle()); this.subToIconsClicked(this.clearComponents, () => this.clear()); } /** @hidden @internal */ ngAfterContentChecked() { if (this.inputGroup && this.prefixes?.length > 0) { this.inputGroup.prefixes = this.prefixes; } if (this.inputGroup && this.suffixes?.length > 0) { this.inputGroup.suffixes = this.suffixes; } } /** @hidden @internal */ ngOnDestroy() { this._destroy$.next(); this._destroy$.complete(); } /** Subscribes to the click events of toggle/clear icons in a query */ subToIconsClicked(components, handler) { const subscribeToClick = componentList => { componentList.forEach(component => { component.clicked .pipe(takeUntil(merge(componentList.changes, this._destroy$))) .subscribe(handler); }); }; subscribeToClick(components); components.changes.pipe(takeUntil(this._destroy$)) .subscribe(() => subscribeToClick(components)); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: PickerBaseDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "21.0.2", type: PickerBaseDirective, isStandalone: true, inputs: { inputFormat: "inputFormat", displayFormat: "displayFormat", placeholder: "placeholder", mode: "mode", headerOrientation: "headerOrientation", hideHeader: ["hideHeader", "hideHeader", booleanAttribute], overlaySettings: "overlaySettings", disabled: ["disabled", "disabled", booleanAttribute], locale: "locale", weekStart: "weekStart", outlet: "outlet", type: "type", tabIndex: "tabIndex" }, outputs: { opening: "opening", opened: "opened", closing: "closing", closed: "closed" }, queries: [{ propertyName: "toggleComponents", predicate: IgxPickerToggleComponent, descendants: true }, { propertyName: "clearComponents", predicate: IgxPickerClearComponent, descendants: true }, { propertyName: "prefixes", predicate: IgxPrefixDirective, descendants: true }, { propertyName: "suffixes", predicate: IgxSuffixDirective, descendants: true }], viewQueries: [{ propertyName: "inputGroup", first: true, predicate: IgxInputGroupComponent, descendants: true }], ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: PickerBaseDirective, decorators: [{ type: Directive }], ctorParameters: () => [], propDecorators: { inputFormat: [{ type: Input }], displayFormat: [{ type: Input }], placeholder: [{ type: Input }], mode: [{ type: Input }], headerOrientation: [{ type: Input }], hideHeader: [{ type: Input, args: [{ transform: booleanAttribute }] }], overlaySettings: [{ type: Input }], disabled: [{ type: Input, args: [{ transform: booleanAttribute }] }], locale: [{ type: Input }], weekStart: [{ type: Input }], outlet: [{ type: Input }], type: [{ type: Input }], tabIndex: [{ type: Input }], opening: [{ type: Output }], opened: [{ type: Output }], closing: [{ type: Output }], closed: [{ type: Output }], toggleComponents: [{ type: ContentChildren, args: [IgxPickerToggleComponent, { descendants: true }] }], clearComponents: [{ type: ContentChildren, args: [IgxPickerClearComponent, { descendants: true }] }], prefixes: [{ type: ContentChildren, args: [IgxPrefixDirective, { descendants: true }] }], suffixes: [{ type: ContentChildren, args: [IgxSuffixDirective, { descendants: true }] }], inputGroup: [{ type: ViewChild, args: [IgxInputGroupComponent] }] } }); class IgxPredefinedRangesAreaComponent { constructor() { this.usePredefinedRanges = false; this.customRanges = []; this.resourceStrings = DateRangePickerResourceStringsEN; this.rangeSelect = new EventEmitter(); this.trackByLabel = (i, r) => r.label; } get ranges() { const base = this.usePredefinedRanges ? this.getPredefinedRanges() : []; return [...base, ...(this.customRanges ?? [])]; } onSelect(range) { this.rangeSelect.emit(range); } getLabel(rs, shortKey, prefixedKey, fallback) { return rs?.[shortKey] ?? rs?.[prefixedKey] ?? fallback; } getPredefinedRanges() { const today = CalendarDay.today; const rs = this.resourceStrings ?? {}; const labels = { last7Days: this.getLabel(rs, 'last7Days', 'igx_date_range_picker_last7Days', 'Last 7 Days'), currentMonth: this.getLabel(rs, 'currentMonth', 'igx_date_range_picker_currentMonth', 'Current Month'), last30Days: this.getLabel(rs, 'last30Days', 'igx_date_range_picker_last30Days', 'Last 30 Days'), yearToDate: this.getLabel(rs, 'yearToDate', 'igx_date_range_picker_yearToDate', 'Year to Date') }; const startOfMonth = new Date(today.native.getFullYear(), today.native.getMonth(), 1); const endOfMonth = new Date(today.native.getFullYear(), today.native.getMonth() + 1, 0); const startOfYear = new Date(today.native.getFullYear(), 0, 1); const predefinedRanges = [ { key: 'last7Days', get: () => ({ start: today.add('day', -7).native, end: today.native }) }, { key: 'currentMonth', get: () => ({ start: startOfMonth, end: endOfMonth }) }, { key: 'last30Days', get: () => ({ start: today.add('day', -29).native, end: today.native }) }, { key: 'yearToDate', get: () => ({ start: startOfYear, end: today.native }) }, ]; return predefinedRanges.map(range => ({ label: labels[range.key], dateRange: range.get() })); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxPredefinedRangesAreaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.2", type: IgxPredefinedRangesAreaComponent, isStandalone: true, selector: "igx-predefined-ranges-area", inputs: { usePredefinedRanges: "usePredefinedRanges", customRanges: "customRanges", resourceStrings: "resourceStrings" }, outputs: { rangeSelect: "rangeSelect" }, ngImport: i0, template: "<div class=\"igx-predefined-ranges\" role=\"group\" aria-label=\"Predefined ranges\">\n @for (r of ranges; track r.label) {\n <igx-chip (click)=\"onSelect(r.dateRange)\">\n {{ r.label }}\n </igx-chip>\n }\n</div>\n", styles: [":host{display:block}.igx-predefined-ranges{display:flex;flex-wrap:wrap;gap:.5rem;padding:.5rem .75rem}\n"], dependencies: [{ kind: "component", type: IgxChipComponent, selector: "igx-chip", inputs: ["variant", "id", "tabIndex", "data", "draggable", "animateOnRelease", "hideBaseOnDrag", "removable", "removeIcon", "selectable", "selectIcon", "class", "disabled", "selected", "color", "resourceStrings"], outputs: ["selectedChange", "moveStart", "moveEnd", "remove", "chipClick", "selectedChanging", "selectedChanged", "keyDown", "dragEnter", "dragLeave", "dragOver", "dragDrop"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxPredefinedRangesAreaComponent, decorators: [{ type: Component, args: [{ selector: 'igx-predefined-ranges-area', standalone: true, imports: [IgxChipComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"igx-predefined-ranges\" role=\"group\" aria-label=\"Predefined ranges\">\n @for (r of ranges; track r.label) {\n <igx-chip (click)=\"onSelect(r.dateRange)\">\n {{ r.label }}\n </igx-chip>\n }\n</div>\n", styles: [":host{display:block}.igx-predefined-ranges{display:flex;flex-wrap:wrap;gap:.5rem;padding:.5rem .75rem}\n"] }] }], propDecorators: { usePredefinedRanges: [{ type: Input }], customRanges: [{ type: Input }], resourceStrings: [{ type: Input }], rangeSelect: [{ type: Output }] } }); /** @hidden */ class IgxCalendarContainerComponent { constructor() { this.calendarClose = new EventEmitter(); this.calendarCancel = new EventEmitter(); this.todaySelection = new EventEmitter(); this.rangeSelected = new EventEmitter(); this.styleClass = 'igx-date-picker'; this.usePredefinedRanges = false; this.customRanges = []; this.vertical = false; this.mode = PickerInteractionMode.DropDown; } get dropdownCSS() { return this.mode === PickerInteractionMode.DropDown; } onEscape(event) { event.preventDefault(); this.calendarClose.emit(); } get isReadonly() { return this.mode === PickerInteractionMode.Dialog; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxCalendarContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.2", type: IgxCalendarContainerComponent, isStandalone: true, selector: "igx-calendar-container", outputs: { calendarClose: "calendarClose", calendarCancel: "calendarCancel", todaySelection: "todaySelection", rangeSelected: "rangeSelected" }, host: { listeners: { "keydown.alt.arrowup": "onEscape($event)" }, properties: { "class.igx-date-picker": "this.styleClass", "class.igx-date-picker--dropdown": "this.dropdownCSS" } }, viewQueries: [{ propertyName: "calendar", first: true, predicate: IgxCalendarComponent, descendants: true, static: true }], ngImport: i0, template: "<ng-template #defaultPickerActions>\n @if (closeButtonLabel || cancelButtonLabel || todayButtonLabel) {\n <div class=\"igx-date-picker__buttons\">\n @if (cancelButtonLabel) {\n <button\n #cancelButton type=\"button\"\n igxButton=\"flat\" igxRipple\n (click)=\"calendarCancel.emit({ owner: this})\"\n >\n {{ cancelButtonLabel }}\n </button>\n }\n @if (closeButtonLabel) {\n <button\n #closeButton\n type=\"button\"\n igxButton=\"flat\"\n igxRipple\n (click)=\"calendarClose.emit({ owner: this})\"\n >\n {{ closeButtonLabel }}\n </button>\n }\n @if (todayButtonLabel) {\n <button\n #todayButton\n type=\"button\"\n igxButton=\"flat\"\n igxRipple\n (click)=\"todaySelection.emit({ owner: this })\"\n >\n {{ todayButtonLabel }}\n </button>\n }\n </div>\n }\n</ng-template>\n\n<igx-calendar></igx-calendar>\n @if( usePredefinedRanges || (customRanges?.length || 0) > 0 ){\n <igx-predefined-ranges-area\n [usePredefinedRanges]=\"usePredefinedRanges\"\n [customRanges]=\"customRanges\"\n [resourceStrings]=\"resourceStrings\"\n (rangeSelect)=\"rangeSelected.emit($event)\">\n </igx-predefined-ranges-area>\n }\n@if (pickerActions?.template || (closeButtonLabel || todayButtonLabel)) {\n <igx-divider></igx-divider>\n}\n@if (pickerActions?.template || (closeButtonLabel || cancelButtonLabel || todayButtonLabel)) {\n <div class=\"igx-date-picker__actions\">\n <ng-container\n *ngTemplateOutlet=\"\n pickerActions?.template || defaultPickerActions;\n context: { $implicit: calendar }\n \"\n >\n </ng-container>\n </div>\n}\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "directive", type: IgxButtonDirective, selector: "[igxButton]", inputs: ["selected", "igxButton", "igxLabel"], outputs: ["buttonSelected"] }, { kind: "directive", type: IgxRippleDirective, selector: "[igxRipple]", inputs: ["igxRippleTarget", "igxRipple", "igxRippleDuration", "igxRippleCentered", "igxRippleDisabled"] }, { kind: "component", type: IgxCalendarComponent, selector: "igx-calendar", inputs: ["id", "hasHeader", "vertical", "orientation", "headerOrientation", "monthsViewNumber", "showWeekNumbers"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: IgxDividerDirective, selector: "igx-divider", inputs: ["id", "role", "type", "middle", "vertical", "inset"] }, { kind: "component", type: IgxPredefinedRangesAreaComponent, selector: "igx-predefined-ranges-area", inputs: ["usePredefinedRanges", "customRanges", "resourceStrings"], outputs: ["rangeSelect"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxCalendarContainerComponent, decorators: [{ type: Component, args: [{ selector: 'igx-calendar-container', imports: [ IgxButtonDirective, IgxRippleDirective, IgxCalendarComponent, NgTemplateOutlet, IgxDividerDirective, IgxPredefinedRangesAreaComponent ], template: "<ng-template #defaultPickerActions>\n @if (closeButtonLabel || cancelButtonLabel || todayButtonLabel) {\n <div class=\"igx-date-picker__buttons\">\n @if (cancelButtonLabel) {\n <button\n #cancelButton type=\"button\"\n igxButton=\"flat\" igxRipple\n (click)=\"calendarCancel.emit({ owner: this})\"\n >\n {{ cancelButtonLabel }}\n </button>\n }\n @if (closeButtonLabel) {\n <button\n #closeButton\n type=\"button\"\n igxButton=\"flat\"\n igxRipple\n (click)=\"calendarClose.emit({ owner: this})\"\n >\n {{ closeButtonLabel }}\n </button>\n }\n @if (todayButtonLabel) {\n <button\n #todayButton\n type=\"button\"\n igxButton=\"flat\"\n igxRipple\n (click)=\"todaySelection.emit({ owner: this })\"\n >\n {{ todayButtonLabel }}\n </button>\n }\n </div>\n }\n</ng-template>\n\n<igx-calendar></igx-calendar>\n @if( usePredefinedRanges || (customRanges?.length || 0) > 0 ){\n <igx-predefined-ranges-area\n [usePredefinedRanges]=\"usePredefinedRanges\"\n [customRanges]=\"customRanges\"\n [resourceStrings]=\"resourceStrings\"\n (rangeSelect)=\"rangeSelected.emit($event)\">\n </igx-predefined-ranges-area>\n }\n@if (pickerActions?.template || (closeButtonLabel || todayButtonLabel)) {\n <igx-divider></igx-divider>\n}\n@if (pickerActions?.template || (closeButtonLabel || cancelButtonLabel || todayButtonLabel)) {\n <div class=\"igx-date-picker__actions\">\n <ng-container\n *ngTemplateOutlet=\"\n pickerActions?.template || defaultPickerActions;\n context: { $implicit: calendar }\n \"\n >\n </ng-container>\n </div>\n}\n", styles: [":host{display:block}\n"] }] }], propDecorators: { calendar: [{ type: ViewChild, args: [IgxCalendarComponent, { static: true }] }], calendarClose: [{ type: Output }], calendarCancel: [{ type: Output }], todaySelection: [{ type: Output }], rangeSelected: [{ type: Output }], styleClass: [{ type: HostBinding, args: ['class.igx-date-picker'] }], dropdownCSS: [{ type: HostBinding, args: ['class.igx-date-picker--dropdown'] }], onEscape: [{ type: HostListener, args: ['keydown.alt.arrowup', ['$event']] }] } }); let NEXT_ID = 0; /** * Date Picker displays a popup calendar that lets users select a single date. * * @igxModule IgxDatePickerModule * @igxTheme igx-calendar-theme, igx-icon-theme * @igxGroup Scheduling * @igxKeywords datepicker, calendar, schedule, date * @example * ```html * <igx-date-picker [(ngModel)]="selectedDate"></igx-date-picker> * ``` */ class IgxDatePickerComponent extends PickerBaseDirective { /** * Gets/Sets the date which is shown in the calendar picker and is highlighted. * By default it is the current date, or the value of the picker, if set. */ get activeDate() { const today = new Date(new Date().setHours(0, 0, 0, 0)); const dateValue = DateTimeUtil.isValidDate(this._dateValue) ? new Date(this._dateValue.setHours(0, 0, 0, 0)) : null; return this._activeDate ?? dateValue ?? this._calendar?.activeDate ?? today; } set activeDate(value) { this._activeDate = value; } /** * Gets/Sets the disabled dates descriptors. * * @example * ```typescript * let disabledDates = this.datepicker.disabledDates; * this.datePicker.disabledDates = [ {type: DateRangeType.Weekends}, ...]; * ``` */ get disabledDates() { return this._disabledDates; } set disabledDates(value) { this._disabledDates = value; this._onValidatorChange(); } /** * Gets/Sets the special dates descriptors. * * @example * ```typescript * let specialDates = this.datepicker.specialDates; * this.datePicker.specialDates = [ {type: DateRangeType.Weekends}, ... ]; * ``` */ get specialDates() { return this._specialDates; } set specialDates(value) { this._specialDates = value; } //#endregion /** * Gets/Sets the selected date. * * @example * ```html * <igx-date-picker [value]="date"></igx-date-picker> * ``` */ get value() { return this._value; } set value(date) { this._value = date; this.setDateValue(date); if (this.dateTimeEditor.value !== date) { this.dateTimeEditor.value = this._dateValue; } this.valueChange.emit(this.dateValue); this._onChangeCallback(this.dateValue); } /** * The minimum value the picker will accept. * * @example * <igx-date-picker [minValue]="minDate"></igx-date-picker> */ set minValue(value) { this._minValue = value; this._onValidatorChange(); } get minValue() { return this._minValue; } /** * The maximum value the picker will accept. * * @example * <igx-date-picker [maxValue]="maxDate"></igx-date-picker> */ set maxValue(value) { this._maxValue = value; this._onValidatorChange(); } get maxValue() { return this._maxValue; } get dialogOverlaySettings() { return Object.assign({}, this._dialogOverlaySettings, this.overlaySettings); } get dropDownOverlaySettings() { return Object.assign({}, this._dropDownOverlaySettings, this.overlaySettings); } get inputGroupElement() { return this.inputGroup?.element.nativeElement; } get dateValue() { return this._dateValue; } get pickerFormatViews() { return Object.assign({}, this._defFormatViews, this.formatViews); } get pickerCalendarFormat() { return Object.assign({}, this._calendarFormat, this.calendarFormat); } constructor() { super(); this._overlayService = inject(IgxOverlayService); this._injector = inject(Injector); this._renderer = inject(Renderer2); this.platform = inject(PlatformUtil); this.cdr = inject(ChangeDetectorRef); /** * Gets/Sets the number of month views displayed. * * @remarks * Default value is `1`. * * @example * ```html * <igx-date-picker [displayMonthsCount]="2"></igx-date-picker> * ``` * @example * ```typescript * let monthViewsDisplayed = this.datePicker.displayMonthsCount; * ``` */ this.displayMonthsCount = 1; /** * Gets/Sets the orientation of the multiple months displayed in the picker's calendar's days view. * * @example * <igx-date-picker orientation="vertical"></igx-date-picker> */ this.orientation = PickerCalendarOrientation.Horizontal; /** * Specify if the currently spun date segment should loop over. * * @example * ```html * <igx-date-picker [spinLoop]="false"></igx-date-picker> * ``` */ this.spinLoop = true; /** * Gets/Sets the value of `id` attribute. * * @remarks If not provided it will be automatically generated. * @example * ```html * <igx-date-picker [id]="'igx-date-picker-3'" cancelButtonLabel="cancel" todayButtonLabel="today"></igx-date-picker> * ``` */ this.id = `igx-date-picker-${NEXT_ID++}`; /** @hidden @internal */ this.readOnly = false; /** * Emitted when the picker's value changes. * * @remarks * Used for `two-way` bindings. * * @example * ```html * <igx-date-picker [(value)]="date"></igx-date-picker> * ``` */ this.valueChange = new EventEmitter(); /** * Emitted when the user types/spins invalid date in the date-picker editor. * * @example * ```html * <igx-date-picker (validationFailed)="onValidationFailed($event)"></igx-date-picker> * ``` */ this.validationFailed = new EventEmitter(); /** @hidden @internal */ this.displayValue = { transform: (date) => this.formatter(date) }; this._resourceStrings = getCurrentResourceStrings(DatePickerResourceStringsEN); this._ngControl = null; this._specialDates = null; this._disabledDates = null; this._activeDate = null; this._overlaySubFilter = [ filter(x => x.id === this._overlayId), takeUntil(this._destroy$) ]; this._dropDownOverlaySettings = { target: this.inputGroupElement, closeOnOutsideClick: true, modal: false, closeOnEscape: true, scrollStrategy: new AbsoluteScrollStrategy(), positionStrategy: new AutoPositionStrategy({ openAnimation: fadeIn, closeAnimation: fadeOut }) }; this._dialogOverlaySettings = { closeOnOutsideClick: true, modal: true, closeOnEscape: true }; this._calendarFormat = { day: 'numeric', month: 'short', weekday: 'short', year: 'numeric' }; this._defFormatViews = { day: false, month: true, year: false }; this._onChangeCallback = noop; this._onTouchedCallback = noop; this._onValidatorChange = noop; this.onStatusChanged = () => { this.disabled = this._ngControl.disabled; this.updateValidity(); this.inputGroup.isRequired = this.required; }; this.locale = this.locale || this._localeId; } /** @hidden @internal */ get required() { if (this._ngControl && this._ngControl.control && this._ngControl.control.validator) { // Run the validation with empty object to check if required is enabled. const error = this._ngControl.control.validator({}); return error && error.required; } return false; } /** @hidden @internal */ get pickerResourceStrings() { return Object.assign({}, this._resourceStrings, this.resourceStrings); } get toggleContainer() { return this._calendarContainer; } /** @hidden @internal */ onKeyDown(event) { switch (event.key) { case this.platform.KEYMAP.ARROW_UP: if (event.altKey) { this.close(); } break; case this.platform.KEYMAP.ARROW_DOWN: if (event.altKey) { this.open(); } break; case this.platform.KEYMAP.SPACE: event.preventDefault(); this.open(); break; } } /** * Opens the picker's dropdown or dialog. * * @example * ```html * <igx-date-picker #picker></igx-date-picker> * * <button type="button" igxButton (click)="picker.open()">Open Dialog</button> * ``` */ open(settings) { if (!this.collapsed || this.disabled || this.readOnly) { return; } const overlaySettings = Object.assign({}, this.isDropdown ? this.dropDownOverlaySettings : this.dialogOverlaySettings, settings); if (this.isDropdown && this.inputGroupElement) { overlaySettings.target = this.inputGroupElement; } if (this.outlet) { overlaySettings.outlet = this.outlet; } this._overlayId = this._overlayService .attach(IgxCalendarContainerComponent, this.viewContainerRef, overlaySettings); this._overlayService.show(this._overlayId); } /** * Toggles the picker's dropdown or dialog * * @example * ```html * <igx-date-picker #picker></igx-date-picker> * * <button type="button" igxButton (click)="picker.toggle()">Toggle Dialog</button> * ``` */ toggle(settings) { if (this.collapsed) { this.open(settings); } else { this.close(); } } /** * Closes the picker's dropdown or dialog. * * @example * ```html * <igx-date-picker #picker></igx-date-picker> * * <button type="button" igxButton (click)="picker.close()">Close Dialog</button> * ``` */ close() { if (!this.collapsed) { this._overlayService.hide(this._overlayId); } } /** * Selects a date. * * @remarks Updates the value in the input field. * * @example * ```typescript * this.datePicker.select(date); * ``` * @param date passed date that has to be set to the calendar. */ select(value) { this.value = value; } /** * Selects today's date and closes the picker. * * @example * ```html * <igx-date-picker #picker></igx-date-picker> * * <button type="button" igxButton (click)="picker.selectToday()">Select Today</button> * ``` * */ selectToday() { const today = new Date(); today.setHours(0); today.setMinutes(0); today.setSeconds(0); today.setMilliseconds(0); this.select(today); this.close(); } /** * Clears the input field and the picker's value. * * @example * ```typescript * this.datePicker.clear(); * ``` */ clear() { if (!this.disabled || !this.readOnly) { this._calendar?.deselectDate(); this.dateTimeEditor.clear(); } } /** * Increment a specified `DatePart`. * * @param datePart The optional DatePart to increment. Defaults to Date. * @param delta The optional delta to increment by. Overrides `spinDelta`. * @example * ```typescript * this.datePicker.increment(DatePart.Date); * ``` */ increment(datePart, delta) { this.dateTimeEditor.increment(datePart, delta); } /** * Decrement a specified `DatePart` * * @param datePart The optional DatePart to decrement. Defaults to Date. * @param delta The optional delta to decrement by. Overrides `spinDelta`. * @example * ```typescript * this.datePicker.decrement(DatePart.Date); * ``` */ decrement(datePart, delta) { this.dateTimeEditor.decrement(datePart, delta); } //#region Control Value Accessor /** @hidden @internal */ writeValue(value) { this._value = value; this.setDateValue(value); if (this.dateTimeEditor.value !== value) { this.dateTimeEditor.value = this._dateValue; } } /** @hidden @internal */ registerOnChange(fn) { this._onChangeCallback = fn; } /** @hidden @internal */ registerOnTouched(fn) { this._onTouchedCallback = fn; } /** @hidden @internal */ setDisabledState(isDisabled) { this.disabled = isDisabled; } //#endregion //#region Validator /** @hidden @internal */ registerOnValidatorChange(fn) { this._onValidatorChange = fn; } /** @hidden @internal */ validate(control) { if (!control.value) { return null; } // InvalidDate handling if (isDate(control.value) && !DateTimeUtil.isValidDate(control.value)) { return { value: true }; } const errors = {}; const value = DateTimeUtil.isValidDate(control.value) ? control.value : DateTimeUtil.parseIsoDate(control.value); if (value && this.disabledDates && isDateInRanges(value, this.disabledDates)) { Object.assign(errors, { dateIsDisabled: true }); } Object.assign(errors, DateTimeUtil.validateMinMax(value, this.minValue, this.maxValue, false)); return Object.keys(errors).length > 0 ? errors : null; } //#endregion /** @hidden @internal */ ngOnInit() { this._ngControl = this._injector.get(NgControl, null); this.locale = this.locale || this._localeId; } /** @hidden @internal */ ngAfterViewInit() { super.ngAfterViewInit(); this.subscribeToClick(); this.subscribeToOverlayEvents(); this.subscribeToDateEditorEvents(); this._dropDownOverlaySettings.excludeFromOutsideClick = [this.inputGroup.element.nativeElement]; fromEvent(this.inputDirective.nativeElement, 'blur') .pipe(takeUntil(this._destroy$)) .subscribe(() => { if (this.collapsed) { this._onTouchedCallback(); this.updateValidity(); } }); if (this._ngControl) { this._statusChanges$ = this._ngControl.statusChanges.subscribe(this.onStatusChanged.bind(this)); if (this._ngControl.control.validator) { this.inputGroup.isRequired = this.required; this.cdr.detectChanges(); } } } /** @hidden @internal */ ngAfterViewChecked() { if (this.labelDirective) { this._renderer.setAttribute(this.inputDirective.nativeElement, 'aria-labelledby', this.labelDirective.id); } } /** @hidden @internal */ ngOnDestroy() { super.ngOnDestroy(); if (this._statusChanges$) { this._statusChanges$.unsubscribe(); } if (this._overlayId) { this._overlayService.detach(this._overlayId); delete this._overlayId; } } /** @hidden @internal */ getEditElement() { return this.inputDirective.nativeElement; } subscribeToClick() { fromEvent(this.getEditElement(), 'click') .pipe(takeUntil(this._destroy$)) .subscribe(() => { if (!this.isDropdown) { this.toggle(); } }); } setDateValue(value) { if (isDate(value) && isNaN(value.getTime())) { this._dateValue = value; return; } this._dateValue = DateTimeUtil.isValidDate(value) ? value : DateTimeUtil.parseIsoDate(value); if (this._calendar) { this._calendar.selectDate(this._dateValue); this._calendar.activeDate = this.activeDate; this._calendar.viewDate = this.activeDate; this.cdr.detectChanges(); } } updateValidity() { // B.P. 18 May 2021: IgxDatePicker does not reset its state upon resetForm #9526 if (this._ngControl && !this.disabled && this.isTouchedOrDirty) { if (this.hasValidators && this.inputGroup.isFocused) { this.inputDirective.valid = this._ngControl.valid ? IgxInputState.VALID : IgxInputState.INVALID; } else { this.inputDirective.valid = this._ngControl.valid ? IgxInputState.INITIAL : IgxInputState.INVALID; } } else { this.inputDirective.valid = IgxInputState.INITIAL; } } get isTouchedOrDirty() { return (this._ngControl.control.touched || this._ngControl.control.dirty); } get hasValidators() { return (!!this._ngControl.control.validator || !!this._ngControl.control.asyncValidator); } handleSelection(date) { if (this.dateValue && DateTimeUtil.isValidDate(this.dateValue)) { date.setHours(this.dateValue.getHours()); date.setMinutes(this.dateValue.getMinutes()); date.setSeconds(this.dateValue.getSeconds()); date.setMilliseconds(this.dateValue.getMilliseconds()); } this.value = date; if (this._calendar) { this._calendar.activeDate = this.activeDate; this._calendar.viewDate = this.activeDate; } this.close(); } subscribeToDateEditorEvents() { this.dateTimeEditor.valueChange.pipe(takeUntil(this._destroy$)).subscribe(val => { this.value = val; }); this.dateTimeEditor.validationFailed.pipe(takeUntil(this._destroy$)).subscribe((event) => { this.validationFailed.emit({ owner: this, prevValue: event.oldValue, currentValue: this.value }); }); } subscribeToOverlayEvents() { this._overlayService.opening.pipe(...this._overlaySubFilter).subscribe((e) => { const args = { owner: this, event: e.event, cancel: e.cancel }; this.opening.emit(args); e.cancel = args.cancel; if (args.cancel) { this._overlayService.detach(this._overlayId); return; } this._initializeCalendarContainer(e.componentRef.instance); this._calendarContainer = e.componentRef.location.nativeElement; this._collapsed = false; }); this._overlayService.opened.pipe(...this._overlaySubFilter).subscribe(() => { this.opened.emit({ owner: this }); this._calendar.wrapper?.nativeElement?.focus(); }); this._overlayService.closing.pipe(...this._overlaySubFilter).subscribe((e) => { const args = { owner: this, event: e.event, cancel: e.cancel }; this.closing.emit(args); e.cancel = args.cancel; if (args.cancel) { return; } // do not focus the input if clicking outside in dropdown mode const outsideEvent = args.event && args.event.key !== this.platform.KEYMAP.ESCAPE; if (this.getEditElement() && !(outsideEvent && this.isDropdown)) { this.inputDirective.focus(); } else { this._onTouchedCallback(); this.updateValidity(); } }); this._overlayService.closed.pipe(...this._overlaySubFilter).subscribe(() => { this.closed.emit({ owner: this }); this._overlayService.detach(this._overlayId); this._collapsed = true; this._overlayId = null; this._calendar = null; this._calendarContainer = undefined; }); } getMinMaxDates() { const minValue = DateTimeUtil.isValidDate(this.minValue) ? this.minValue : DateTimeUtil.parseIsoDate(this.minValue); const maxValue = DateTimeUtil.isValidDate(this.maxValue) ? this.maxValue : DateTimeUtil.parseIsoDate(this.maxValue); return { minValue, maxValue }; } setDisabledDates() { this._calendar.disabledDates = this.disabledDates ? [...this.disabledDates] : []; const { minValue, maxValue } = this.getMinMaxDates(); if (minValue) { this._calendar.disabledDates.push({ type: DateRangeType.Before, dateRange: [minValue] }); } if (maxValue) { this._calendar.disabledDates.push({ type: DateRangeType.After, dateRange: [maxValue] }); } } _initializeCalendarContainer(componentInstance) { this._calendar = componentInstance.calendar; this._calendar.hasHeader = !this.isDropdown && !this.hideHeader; this._calendar.formatOptions = this.pickerCalendarFormat; this._calendar.formatViews = this.pickerFormatViews; this._calendar.locale = this.locale; this._calendar.weekStart = this.weekStart; this._calendar.specialDates = this.specialDates; this._calendar.headerTitleTemplate = this.headerTitleTemplate; this._calendar.headerTemplate = this.headerTemplate; this._calendar.subheaderTemplate = this.subheaderTemplate; this._calendar.headerOrientation = this.headerOrientation; this._calendar.hideOutsideDays = this.hideOutsideDays; this._calendar.monthsViewNumber = this.displayMonthsCount; this._calendar.showWeekNumbers = this.showWeekNumbers; this._calendar.orientation = this.orientation; this._calendar.selected.pipe(takeUntil(this._destroy$)).subscribe((ev) => this.handleSelection(ev)); this.setDisabledDates(); if (DateTimeUtil.isValidDate(this.dateValue)) { // calendar will throw if the picker's value is InvalidDate #9208 this._calendar.value = this.dateValue; } this._calendar.activeDate = this.activeDate; this._calendar.viewDate = this.activeDate; componentInstance.mode = this.mode; componentInstance.closeButtonLabel = this.cancelButtonLabel; componentInstance.todayButtonLabel = this.todayButtonLabel; componentInstance.pickerActions = this.pickerActions; componentInstance.calendarClose.pipe(takeUntil(this._destroy$)).subscribe(() => this.close()); componentInstance.todaySelection.pipe(takeUntil(this._destroy$)).subscribe(() => this.selectToday()); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: IgxDatePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.2", type: IgxDatePickerComponent, isStandalone: true, selector: "igx-date-picker", inputs: { hideOutsideDays: ["hideOutsideDays", "hideOutsideDays", booleanAttribute], displayMonthsCount: "displayMonthsCount", orientation: "orientation", showWeekNumbers: ["showWeekNumbers", "showWeekNumbers", booleanAttribute], activeDate: "activeDate", formatter: "formatter", todayButtonLabel: "todayButtonLabel", cancelButtonLabel: "cancelButtonLabel", spinLoop: ["spinLoop", "spinLoop", booleanAttribute], spinDelta: "spinDelta", outlet: "outlet", id: "id", formatViews: "formatViews", disabledDates: "disabledDates", specialDates: "specialDates", calendarFormat: "calendarFormat", value: "value", minValue: "minValue", maxValue: "maxValue", resourceStrings: "resourceStrings", readOnly: ["readOnly", "readOnly", booleanAttribute] }, outputs: { valueChange: "valueChange", validationFailed: "validationFailed" }, host: { listeners: { "keydown": "onKeyDown($event)" }, properties: { "attr.id": "this.id" } }, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: IgxDatePickerComponent, multi: true }, { provide: NG_VALIDATORS, useExisting: IgxDatePickerComponent, multi: true } ], queries: [{ propertyName: "label", first: true, predicate: IgxLabelDirective, descendants: true }, { propertyName: "headerTitleTemplate", first: true, predicate: IgxCalendarHeaderTitleTemplateDirective, descendants: true }, { propertyName: "headerTemplate", first: true, predicate: IgxCalendarHeaderTemplateDirective, descendants: true }, { propertyName: "subheaderTemplate", first: true, predicate: IgxCalendarSubheaderTemplateDirective, descendants: true }, { propertyName: "pickerActions", first: true, predicate: IgxPickerActionsDirective, descendants: true }], viewQueries: [{ propertyName: "dateTimeEditor", first: true, predicate: IgxDateTimeEditorDirective, descendants: true, static: true }, { propertyName: "viewContainerRef", first: true, predicate: IgxInputGroupComponent, descendants: true, read: ViewContainerRef }, { propertyName: "labelDirective", first: true, predicate: IgxLabelDirective, descendants: true }, { propertyName: "inputDirective", first: true, predicate: IgxInputDirective, descendants: true }], usesInheritance: true, ngImport: i0, template: "