UNPKG

@qeydar/datepicker

Version:

A comprehensive Date and Time Picker for Angular with Jalali calendar support

1,025 lines 154 kB
import { Component, forwardRef, Input, ViewChild, Output, EventEmitter, Inject, ViewChildren, ChangeDetectionStrategy, ContentChildren } from '@angular/core'; import { NG_VALUE_ACCESSOR, ReactiveFormsModule, FormsModule } from '@angular/forms'; import { slideMotion } from './utils/animation/slide'; import { DatePickerPopupComponent } from './date-picker-popup/date-picker-popup.component'; import { CdkOverlayOrigin, OverlayModule } from '@angular/cdk/overlay'; import { DATE_PICKER_POSITION_MAP, DEFAULT_DATE_PICKER_POSITIONS, NzConnectedOverlayDirective } from './utils/overlay/overlay'; import { DOCUMENT, NgIf, NgTemplateOutlet } from '@angular/common'; import { DestroyService, QeydarDatePickerService } from './date-picker.service'; import { fromEvent, takeUntil } from 'rxjs'; import { DateMaskDirective } from './utils/input-mask.directive'; import { CustomTemplate } from './utils/template.directive'; import * as i0 from "@angular/core"; import * as i1 from "@angular/forms"; import * as i2 from "./date-picker.service"; import * as i3 from "./date-adapter"; import * as i4 from "@angular/cdk/overlay"; export class DatePickerComponent { constructor(fb, elementRef, renderer, cdref, dpService, destroy$, ngZone, jalali, gregorian, doc) { this.fb = fb; this.elementRef = elementRef; this.renderer = renderer; this.cdref = cdref; this.dpService = dpService; this.destroy$ = destroy$; this.ngZone = ngZone; this.jalali = jalali; this.gregorian = gregorian; // ========== Input Properties ========== this.rtl = false; this.mode = 'day'; this.isRange = false; this.calendarType = 'gregorian'; this.cssClass = ''; this.footerDescription = ''; this.placement = 'bottomRight'; this.disabled = false; this.isInline = false; this.showSidebar = true; this.showToday = false; this.valueFormat = 'gregorian'; this.disableInputMask = false; this.disabledDates = []; this.allowEmpty = false; this.readOnly = false; this.readOnlyInput = false; // ========== Output Properties ========== this.onFocus = new EventEmitter(); this.onBlur = new EventEmitter(); this.onChangeValue = new EventEmitter(); this.onOpenChange = new EventEmitter(); this.overlayPositions = [...DEFAULT_DATE_PICKER_POSITIONS]; this.currentPositionX = 'start'; this.currentPositionY = 'bottom'; this.isOpen = false; this.selectedDate = null; this.selectedStartDate = null; this.selectedEndDate = null; this.activeInput = ''; this.hideStateHelper = false; this.isInternalChange = false; this.lastEmittedValue = null; this.showTimePicker = false; this.timeDisplayFormat = 'HH:mm'; this._format = 'yyyy/MM/dd'; // ========== ControlValueAccessor Implementation ========== this.onChange = () => { }; this.onTouch = () => { }; this.initializeComponent(doc); } set minDate(date) { if (date) { this._minDate = date; } } ; get minDate() { return this._minDate; } set maxDate(date) { if (date) { this._maxDate = date; } } ; get maxDate() { return this._maxDate; } set format(value) { this._format = value; this.showTimePicker = this.hasTimeComponent(value); this.timeDisplayFormat = this.extractTimeFormat(value); } get format() { return this._format; } get valueAdapter() { return this.valueFormat == 'jalali' ? this.jalali : this.gregorian; } // ========== Lifecycle Hooks ========== ngOnInit() { this.initialize(); document.addEventListener('click', this.documentClickListener); } ngOnChanges(changes) { this.handleChanges(changes); } ngAfterViewInit() { this.setupAfterViewInit(); this._minDate = this.valueAdapter?.parse(this._minDate, this.extractDateFormat(this.format)); this._maxDate = this.valueAdapter?.parse(this._maxDate, this.extractDateFormat(this.format)); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); document.removeEventListener('click', this.documentClickListener); } // ========== Initialization Methods ========== initializeComponent(doc) { this.origin = new CdkOverlayOrigin(this.elementRef); this.document = doc; this.form = this.fb.group({ dateInput: [''], startDateInput: [''], endDateInput: [''] }); this.documentClickListener = this.handleDocumentClick.bind(this); this.lang = this.calendarType === 'jalali' ? this.dpService.locale_fa : this.dpService.locale_en; this.dpService.locale = this.lang; } initialize() { this.setDateAdapter(); this.setupFormControls(); } setupAfterViewInit() { this.setupActiveInputSubscription(); this.setupMouseDownEventHandler(); } // ========== Date Adapter Methods ========== setDateAdapter() { this.dateAdapter = this.calendarType === 'jalali' ? this.jalali : this.gregorian; } // ========== Form Control Methods ========== setupFormControls() { if (this.isRange) { this.form.get('startDateInput')?.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(value => this.onInputChange(value, 'start')); this.form.get('endDateInput')?.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(value => this.onInputChange(value, 'end')); } else { this.form.get('dateInput')?.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(value => this.onInputChange(value)); } } // ========== Event Handlers ========== handleChanges(changes) { if (changes['calendarType']) { this.setDateAdapter(); this.updateInputValue(); this.lang = this.calendarType === 'jalali' ? this.dpService.locale_fa : this.dpService.locale_en; this.dpService.locale = this.lang; } if (changes['minDate'] || changes['maxDate']) { this._minDate = this.valueAdapter?.parse(this._minDate, this.extractDateFormat(this.format)); this._maxDate = this.valueAdapter?.parse(this._maxDate, this.extractDateFormat(this.format)); this.form.updateValueAndValidity(); } if (changes['mode'] || changes['isRange']) { this.setupFormControls(); } if (changes['placement']) { this.setPlacement(this.placement); } if (changes['lang']) { this.lang = changes['lang'].currentValue; this.dpService.locale = this.lang; } if (changes['mode'] && !changes['format']) { this.format = this.getFormatForMode(); } if (changes['isRange'] && this.isRange == false) { this.origin = new CdkOverlayOrigin(this.elementRef); } if (changes['valueFormat']) { this.emitValueIfChanged(); } } handleDocumentClick(event) { if (!this.elementRef.nativeElement.contains(event.target)) { if (this.isOpen) { this.close(); this.onInputBlur(this.activeInput, event); } this.hideStateHelper = false; } } // ========== Input Handling Methods ========== onInputChange(value, inputType) { if (!this.isInternalChange) { if (this.isRange) { this.handleRangeInputChange(value, inputType); } else { this.handleSingleInputChange(value); } this.updateDatePickerPopup(); } } handleRangeInputChange(value, inputType) { const date = this.dateAdapter.parse(value, this.format); if (date) { if (inputType === 'start') { this.selectedStartDate = this.clampDate(date); } else if (inputType === 'end') { this.selectedEndDate = this.clampDate(date); } this.emitValueIfChanged(); } } handleSingleInputChange(value) { const date = this.dateAdapter.parse(value, this.format); if (date) { this.selectedDate = this.clampDate(date); this.emitValueIfChanged(); } } // ========== Value Emission Methods ========== emitValueIfChanged() { const newValue = this.prepareValueForEmission(); if (newValue && JSON.stringify(newValue) !== JSON.stringify(this.lastEmittedValue)) { this.lastEmittedValue = newValue; this.onChange(newValue); this.onChangeValue.emit(newValue); } } prepareValueForEmission() { if (this.isRange) { if (this.selectedStartDate && this.selectedEndDate) { return { start: this.convertDateToFormat(this.selectedStartDate, this.calendarType), end: this.convertDateToFormat(this.selectedEndDate, this.calendarType) }; } } else if (this.selectedDate) { return this.convertDateToFormat(this.selectedDate, this.calendarType); } return null; } // ========== Date Selection Methods ========== onDateSelected(date) { const clampedDate = this.clampDate(date); if (this.isRange) { this.handleRangeDateSelection(clampedDate); } else { this.handleSingleDateSelection(clampedDate); } this.hideStateHelper = true; this.updateDatePickerPopup(); this.focus(); } handleRangeDateSelection(date) { if (!this.selectedStartDate || (this.selectedStartDate && this.selectedEndDate) || this.dateAdapter.isBefore(date, this.selectedStartDate)) { this.selectedStartDate = date; this.selectedEndDate = null; this.form.get('startDateInput')?.setValue(this.dateAdapter.format(date, this.format), { emitEvent: false }); this.form.get('endDateInput')?.setValue('', { emitEvent: false }); } else { this.selectedEndDate = date; this.form.get('endDateInput')?.setValue(this.dateAdapter.format(date, this.format), { emitEvent: false }); this.emitValueIfChanged(); this.close(); } } handleSingleDateSelection(date) { this.selectedDate = date; const formattedDate = this.dateAdapter.format(date, this.format); this.form.get('dateInput')?.setValue(formattedDate, { emitEvent: false }); this.emitValueIfChanged(); this.close(); } // ========== Date Range Methods ========== onDateRangeSelected(dateRange) { this.hideStateHelper = true; this.selectedStartDate = this.clampDate(dateRange.start); const startFormatted = this.dateAdapter.format(this.selectedStartDate, this.format); this.form.get('startDateInput')?.setValue(startFormatted, { emitEvent: false }); if (dateRange.end) { this.selectedEndDate = this.clampDate(dateRange.end); const endFormatted = this.dateAdapter.format(this.selectedEndDate, this.format); this.form.get('endDateInput')?.setValue(endFormatted, { emitEvent: false }); this.emitValueIfChanged(); if (!this.hasTimeComponent(this.format)) this.close(); this.updateDatePickerPopup(); this.focus(); } } // ========== UI State Methods ========== close() { if (this.isInline) { return; } if (this.isOpen) { this.isOpen = false; this.onOpenChange.emit(false); this.cdref.markForCheck(); } } open() { if (this.isInline || this.isOpen || this.disabled || this.readOnly) { return; } this.isOpen = true; this.onOpenChange.emit(true); this.focus(); this.cdref.markForCheck(); } focus() { const activeInputElement = this.getInput(this.activeInput); if (this.document.activeElement !== activeInputElement) { activeInputElement?.focus(); const length = activeInputElement?.value?.length; activeInputElement?.setSelectionRange(length, length); } } // ========== UI Helper Methods ========== getInput(partType) { if (this.isInline) { return undefined; } return this.isRange ? partType === 'start' ? this.rangePickerInputs?.first.nativeElement : this.rangePickerInputs?.last.nativeElement : this.datePickerInput?.nativeElement; } getPlaceholder(inputType = null) { if (inputType === 'start') return this.lang.startDate; if (inputType === 'end') return this.lang.endDate; switch (this.mode) { case 'month': return this.lang.selectMonth; case 'year': return this.lang.selectYear; default: return this.lang.selectDate; } } // ========== Date Validation Methods ========== clampDate(date) { if (!date) return date; let adjustedDate = this.dateAdapter.clone(date); if (this.minDate && this.dateAdapter.isBefore(adjustedDate, this.minDate)) { return this.minDate; } if (this.maxDate && this.dateAdapter.isAfter(adjustedDate, this.maxDate)) { return this.maxDate; } if (this.isDateDisabled(adjustedDate)) { // Find the nearest enabled date adjustedDate = this.findNearestValidDate(adjustedDate); } // Preserve the original time if format includes time adjustedDate = this.clampDateTime(adjustedDate, date); return adjustedDate; } clampDateTime(adjustedDate, date) { if (this.hasTimeComponent(this.format)) { adjustedDate.setHours(date.getHours()); adjustedDate.setMinutes(date.getMinutes()); adjustedDate.setSeconds(date.getSeconds()); let { normalizedDate } = this.validateAndNormalizeTime(adjustedDate); adjustedDate = normalizedDate; } return adjustedDate; } findNearestValidDate(date) { let nextDate = this.dateAdapter.addDays(date, 1); let prevDate = this.dateAdapter.addDays(date, -1); while (this.isDateDisabled(nextDate) && this.isDateDisabled(prevDate)) { nextDate = this.dateAdapter.addDays(nextDate, 1); prevDate = this.dateAdapter.addDays(prevDate, -1); } // Return the first non-disabled date found if (!this.isDateDisabled(nextDate)) { date = nextDate; } else if (!this.isDateDisabled(prevDate)) { date = prevDate; } return date; } validateAndNormalizeTime(date) { if (!this.dateAdapter) { return { isValid: false, normalizedDate: null }; } let isValid = true; let normalizedDate = this.dateAdapter.clone(date); if (this.isTimeDisabled(normalizedDate)) { isValid = false; // Get start and end of the current day const startOfDay = this.dateAdapter.clone(date); startOfDay.setHours(0, 0, 0, 0); const endOfDay = this.dateAdapter.clone(date); endOfDay.setHours(23, 59, 59, 999); // Try to find nearest valid time within the same day const currentMinutes = date.getHours() * 60 + date.getMinutes(); const maxForwardMinutes = (24 * 60) - currentMinutes; let validTimeFound = false; // Check forward for (let i = 1; i <= maxForwardMinutes; i++) { const nextTime = this.dateAdapter.clone(date); nextTime.setHours(Math.floor((currentMinutes + i) / 60), (currentMinutes + i) % 60, 0); if (nextTime.getTime() <= endOfDay.getTime() && !this.isTimeDisabled(nextTime)) { normalizedDate = nextTime; validTimeFound = true; break; } } // Check backward if (!validTimeFound) for (let i = 1; i < currentMinutes; i++) { const prevTime = this.dateAdapter.clone(date); prevTime.setHours(Math.floor((currentMinutes - i) / 60), (currentMinutes - i) % 60, 0); if (prevTime.getTime() >= startOfDay.getTime() && !this.isTimeDisabled(prevTime)) { normalizedDate = prevTime; break; } } // If no valid time found in the current day, set to start of day if (this.isTimeDisabled(normalizedDate)) { normalizedDate = startOfDay; } } return { isValid: isValid, normalizedDate }; } parseDisabledDates() { return this.disabledDates.map(date => { if (date instanceof Date) { return this.dateAdapter.startOfDay(date); } const parsedDate = this.dateAdapter.parse(date, this.extractDateFormat(this.format)); return parsedDate || null; }).filter(date => date !== null); } isDateDisabled(date) { if (!date) return false; const dateToCheck = this.dateAdapter.startOfDay(date); // Check if date is in disabled dates array const parsedDisabledDates = this.parseDisabledDates(); const isDisabledDate = parsedDisabledDates.some(disabledDate => this.dateAdapter.isSameDay(dateToCheck, disabledDate)); // Check custom filter function if provided const isFilterDisabled = this.disabledDatesFilter ? this.disabledDatesFilter(dateToCheck) : false; return isDisabledDate || isFilterDisabled; } isTimeDisabled(date) { return this.disabledTimesFilter ? this.disabledTimesFilter(date) : false; } // ========== Date Validation Methods (continued) ========== dateFormatValidator(control) { const value = control.value; if (!value) return null; const format = this.getFormatForMode(); if (!this.dateAdapter.isValidFormat(value, format)) { return { invalidFormat: true }; } return null; } getFormatForMode() { switch (this.mode) { case 'year': return 'yyyy'; case 'month': return 'yyyy/MM'; default: return this.format; } } // ========== Overlay Position Methods ========== setPlacement(placement) { const position = DATE_PICKER_POSITION_MAP[placement]; this.overlayPositions = [position, ...DEFAULT_DATE_PICKER_POSITIONS]; this.currentPositionX = position.originX; this.currentPositionY = position.originY; } onPositionChange(position) { if (this.currentPositionX !== position.connectionPair.originX || this.currentPositionY !== position.connectionPair.originY) { this.currentPositionX = position.connectionPair.originX; this.currentPositionY = position.connectionPair.originY; this.cdref.markForCheck(); } } // ========== Input Event Handlers ========== onFocusout(event) { event.preventDefault(); this.onTouch(); if (!this.elementRef.nativeElement.contains(event.relatedTarget) && !this.datePickerPopup?.el.nativeElement.contains(event.relatedTarget)) { this.close(); } } onInputBlur(inputType, event) { const inputValue = this.getInputValue(inputType); if (typeof inputValue === 'string' && !this.isOpen) { const correctedValue = this.validateAndCorrectInput(inputValue); if (correctedValue !== inputValue) { if (inputValue) { this.handleCorrectedValue(inputType, correctedValue); } else if (!this.allowEmpty) { this.handleCorrectedValue(inputType, correctedValue); } else { this.selectedDate = null; this.onChange(inputValue); } } this.onBlur.emit({ input: inputType, event, value: correctedValue }); } } getInputValue(inputType) { if (this.isRange) { return inputType === 'start' ? this.form.get('startDateInput')?.value : this.form.get('endDateInput')?.value; } return this.form.get('dateInput')?.value; } validateAndCorrectInput(value) { let date = this.dateAdapter.parse(value, this.format); if (!date) { const today = this.dateAdapter.today(); date = this.clampDate(today); } else { date = this.clampDate(date); } return this.dateAdapter.format(date, this.format); } handleCorrectedValue(inputType, correctedValue) { this.isInternalChange = true; if (this.isRange) { this.handleRangeCorrectedValue(inputType, correctedValue); } else { this.handleSingleCorrectedValue(correctedValue); } this.isInternalChange = false; } handleRangeCorrectedValue(inputType, correctedValue) { if (inputType === 'start') { this.form.get('startDateInput')?.setValue(correctedValue); this.selectedStartDate = this.dateAdapter.parse(correctedValue, this.format); } else { this.form.get('endDateInput')?.setValue(correctedValue); this.selectedEndDate = this.dateAdapter.parse(correctedValue, this.format); } if (this.selectedStartDate && this.selectedEndDate) { this.onChange({ start: this.dateAdapter.format(this.selectedStartDate, this.format), end: this.dateAdapter.format(this.selectedEndDate, this.format) }); } if (this.datePickerPopup) { this.datePickerPopup.selectedStartDate = this.selectedStartDate; this.datePickerPopup.selectedEndDate = this.selectedEndDate; this.datePickerPopup.generateCalendar(); } } handleSingleCorrectedValue(correctedValue) { this.form.get('dateInput')?.setValue(correctedValue); this.selectedDate = this.dateAdapter.parse(correctedValue, this.format); this.onChange(this.selectedDate); if (this.datePickerPopup) { this.datePickerPopup.selectedDate = this.selectedDate; } } onFocusInput(inputType, event) { if (this.hideStateHelper == false) { this.toggleDatePicker(inputType, event); this.hideStateHelper = true; } else { this.hideStateHelper = false; } } toggleDatePicker(inputType, event) { this.onFocus.emit({ input: inputType, event }); this.activeInput = inputType; this.dpService.activeInput$.next(this.activeInput); this.open(); this.cdref.detectChanges(); } onInputKeydown(event) { if ((!event.shiftKey && event.key === 'Tab') || (!event.shiftKey && event.key === 'Enter')) { if (this.isRange) { return; } this.close(); } } // ========== Update Methods ========== updateInputValue() { if (this.isRange) { if (this.selectedStartDate) { this.form.get('startDateInput')?.setValue(this.dateAdapter.format(this.selectedStartDate, this.format)); } if (this.selectedEndDate) { this.form.get('endDateInput')?.setValue(this.dateAdapter.format(this.selectedEndDate, this.format)); } } else if (this.selectedDate) { this.form.get('dateInput')?.setValue(this.dateAdapter.format(this.selectedDate, this.format)); } } updateDatePickerPopup() { if (this.datePickerPopup) { if (this.isRange) { this.datePickerPopup.selectedStartDate = this.selectedStartDate; this.datePickerPopup.selectedEndDate = this.selectedEndDate; if (this.showTimePicker) { this.datePickerPopup.timePicker.updateFromDate(this.activeInput == 'start' ? this.selectedStartDate : this.selectedEndDate); this.datePickerPopup.timePicker.scrollToTime(); } } else { this.datePickerPopup.selectedDate = this.selectedDate; if (this.showTimePicker) { this.datePickerPopup.timePicker.updateFromDate(this.selectedDate); this.datePickerPopup.timePicker.scrollToTime(); } } this.datePickerPopup.generateCalendar(); this.cdref.detectChanges(); } } convertDateToFormat(date, fromType) { if (!date) return null; switch (this.valueFormat) { case 'date': return date; case 'jalali': return this.jalali.format(date, this.format); case 'gregorian': return this.gregorian.format(date, this.format); default: return this.dateAdapter.format(date, this.format); } } writeValue(value) { if (value) { this.isInternalChange = true; if (this.isRange && typeof value === 'object') { const startDate = this.parseIncomingValue(value.start); const endDate = this.parseIncomingValue(value.end); if (startDate) { this.selectedStartDate = startDate; this.form.get('startDateInput')?.setValue(this.dateAdapter.format(startDate, this.format), { emitEvent: false }); } if (endDate) { this.selectedEndDate = endDate; this.form.get('endDateInput')?.setValue(this.dateAdapter.format(endDate, this.format), { emitEvent: false }); } } else { const parsedDate = this.parseIncomingValue(value); if (parsedDate) { this.selectedDate = parsedDate; this.form.get('dateInput')?.setValue(this.dateAdapter.format(parsedDate, this.format), { emitEvent: false }); } } this.lastEmittedValue = value; this.isInternalChange = false; this.updateDatePickerPopup(); this.cdref.markForCheck(); } else { this.resetValues(); } } resetValues() { this.isInternalChange = true; this.selectedDate = null; this.selectedStartDate = null; this.selectedEndDate = null; this.form.get('dateInput')?.setValue('', { emitEvent: false }); this.form.get('startDateInput')?.setValue('', { emitEvent: false }); this.form.get('endDateInput')?.setValue('', { emitEvent: false }); this.lastEmittedValue = null; this.isInternalChange = false; this.updateDatePickerPopup(); } registerOnChange(fn) { this.onChange = fn; } registerOnTouched(fn) { this.onTouch = fn; } // ========== Setup Methods ========== setupActiveInputSubscription() { this.dpService.activeInput$ .pipe(takeUntil(this.destroy$)) .subscribe((active) => { this.activeInput = active; if (active) { if (!this.isOpen) this.origin = new CdkOverlayOrigin(this.activeInput == 'start' ? this.rangePickerInputs?.first : this.rangePickerInputs.last); this.focus(); } }); } setupMouseDownEventHandler() { this.ngZone.runOutsideAngular(() => fromEvent(this.elementRef.nativeElement, 'mousedown') .pipe(takeUntil(this.destroy$)) .subscribe((event) => { if (event.target.tagName.toLowerCase() !== 'input') { event.preventDefault(); } })); } parseDateValue(value) { if (value instanceof Date) { return value; } return this.dateAdapter.parse(value, this.format); } parseValueFromFormat(value, targetAdapter) { if (!value) return null; if (value instanceof Date) return value; return targetAdapter.parse(value, this.format); } parseIncomingValue(value) { if (!value) return null; if (value instanceof Date) return value; let parsedDate = null; // try with value adapter parsedDate = this.valueAdapter.parse(value, this.format); if (parsedDate) return parsedDate; return null; } // ========== Time Methods ========== hasTimeComponent(format) { return /[Hh]|[m]|[s]|[a]/g.test(format); } extractTimeFormat(format) { const timeMatch = format.match(/[Hh]{1,2}:mm(?::ss)?(?:\s*[aA])?/); return timeMatch ? timeMatch[0] : 'HH:mm'; } extractDateFormat(format) { const dateFormatMatch = format.match(/[yMd\/.-]+/); return dateFormatMatch ? dateFormatMatch[0] : ''; } } DatePickerComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: DatePickerComponent, deps: [{ token: i1.FormBuilder }, { token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i0.ChangeDetectorRef }, { token: i2.QeydarDatePickerService }, { token: i2.DestroyService }, { token: i0.NgZone }, { token: i3.JalaliDateAdapter }, { token: i3.GregorianDateAdapter }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Component }); DatePickerComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: DatePickerComponent, isStandalone: true, selector: "qeydar-date-picker", inputs: { rtl: "rtl", mode: "mode", isRange: "isRange", customLabels: "customLabels", calendarType: "calendarType", lang: "lang", cssClass: "cssClass", footerDescription: "footerDescription", rangeInputLabels: "rangeInputLabels", inputLabel: "inputLabel", placement: "placement", disabled: "disabled", isInline: "isInline", showSidebar: "showSidebar", showToday: "showToday", valueFormat: "valueFormat", disableInputMask: "disableInputMask", disabledDates: "disabledDates", disabledDatesFilter: "disabledDatesFilter", disabledTimesFilter: "disabledTimesFilter", allowEmpty: "allowEmpty", readOnly: "readOnly", readOnlyInput: "readOnlyInput", minDate: "minDate", maxDate: "maxDate", format: "format" }, outputs: { onFocus: "onFocus", onBlur: "onBlur", onChangeValue: "onChangeValue", onOpenChange: "onOpenChange" }, host: { properties: { "class.qeydar-datepicker": "true", "class.qeydar-datepicker-rtl": "rtl" } }, providers: [ DestroyService, QeydarDatePickerService, { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => DatePickerComponent), multi: true } ], queries: [{ propertyName: "templates", predicate: CustomTemplate }], viewQueries: [{ propertyName: "datePickerInput", first: true, predicate: ["datePickerInput"], descendants: true }, { propertyName: "datePickerPopup", first: true, predicate: DatePickerPopupComponent, descendants: true }, { propertyName: "rangePickerInputs", predicate: ["rangePickerInput"], descendants: true }], usesOnChanges: true, ngImport: i0, template: ` <div qeydarDatepickerStyles class="date-picker-wrapper" [class.date-picker-rtl]="rtl" [class.disabled]="disabled" [formGroup]="form"> <ng-container *ngIf="!isInline; else inlineMode"> <ng-container *ngIf="!isRange; else rangeMode"> <div class="input-container" [class.rtl]> <label for="dateInput" *ngIf="inputLabel">{{ inputLabel }}</label> <input #datePickerInput type="text" formControlName="dateInput" [qeydar-dateMask]="format" [disableInputMask]="disableInputMask" (click)="toggleDatePicker(null,$event)" (focus)="onFocusInput(null,$event)" (blur)="onInputBlur(null,$event)" (keydown)="onInputKeydown($event)" [class.focus]="isOpen" [placeholder]="getPlaceholder()" [readonly]="readOnly || readOnlyInput" [attr.disabled]="disabled? 'disabled':null" > <ng-container *ngTemplateOutlet="icon"></ng-container> </div> </ng-container> <ng-template #rangeMode> <div *ngIf="rangeInputLabels" class="range-input-labels"> <div class="start-label"> <label for="startDateInput">{{ rangeInputLabels.start }}</label> </div> <div class="end-label"> <label for="endDateInput">{{ rangeInputLabels.end }}</label> </div> </div> <div class="range-input-container"> <input #rangePickerInput type="text" formControlName="startDateInput" [qeydar-dateMask]="format" [disableInputMask]="disableInputMask" (click)="toggleDatePicker('start',$event)" (focus)="onFocusInput('start',$event)" (focusout)="onFocusout($event)" (blur)="onInputBlur('start',$event)" (keydown)="onInputKeydown($event)" [class.focus]="isOpen && activeInput === 'start'" [placeholder]="getPlaceholder('start')" [readonly]="readOnly || readOnlyInput" [attr.disabled]="disabled? 'disabled':null" > <span class="range-separator">→</span> <input #rangePickerInput type="text" formControlName="endDateInput" [qeydar-dateMask]="format" [disableInputMask]="disableInputMask" (click)="toggleDatePicker('end',$event)" (focus)="onFocusInput('end',$event)" (focusout)="onFocusout($event)" (blur)="onInputBlur('end',$event)" (keydown)="onInputKeydown($event)" [class.focus]="isOpen && activeInput === 'end'" [placeholder]="getPlaceholder('end')" [readonly]="readOnly || readOnlyInput" [attr.disabled]="disabled? 'disabled':null" > <ng-container *ngTemplateOutlet="icon"></ng-container> </div> </ng-template> <ng-template #icon> <button class="calendar-button" (click)="toggleDatePicker(null, $event)" tabindex="-1"> <svg xmlns="http://www.w3.org/2000/svg" width="18px" height="18px" viewBox="0 0 24 24" fill="#999"> <path fill-rule="evenodd" clip-rule="evenodd" d="M6 2C6 1.44772 6.44772 1 7 1C7.55228 1 8 1.44772 8 2V3H16V2C16 1.44772 16.4477 1 17 1C17.5523 1 18 1.44772 18 2V3H19C20.6569 3 22 4.34315 22 6V20C22 21.6569 20.6569 23 19 23H5C3.34315 23 2 21.6569 2 20V6C2 4.34315 3.34315 3 5 3H6V2ZM16 5V6C16 6.55228 16.4477 7 17 7C17.5523 7 18 6.55228 18 6V5H19C19.5523 5 20 5.44772 20 6V9H4V6C4 5.44772 4.44772 5 5 5H6V6C6 6.55228 6.44772 7 7 7C7.55228 7 8 6.55228 8 6V5H16ZM4 11V20C4 20.5523 4.44772 21 5 21H19C19.5523 21 20 20.5523 20 20V11H4Z" fill="#999"/> </svg> </button> </ng-template> </ng-container> <ng-template #inlineMode> <div class="dp-dropdown" [class.qeydar-picker-dropdown-rtl]="rtl" [class.qeydar-picker-dropdown-placement-bottomLeft]="currentPositionY === 'bottom' && currentPositionX === 'start'" [class.qeydar-picker-dropdown-placement-topLeft]="currentPositionY === 'top' && currentPositionX === 'start'" [class.qeydar-picker-dropdown-placement-bottomRight]="currentPositionY === 'bottom' && currentPositionX === 'end'" [class.qeydar-picker-dropdown-placement-topRight]="currentPositionY === 'top' && currentPositionX === 'end'" [class.qeydar-picker-dropdown-range]="isRange" > <qeydar-date-picker-popup [rtl]="rtl" [@slideMotion]="'enter'" [selectedDate]="selectedDate" [selectedStartDate]="selectedStartDate" [selectedEndDate]="selectedEndDate" [mode]="mode" [isRange]="isRange" [customLabels]="customLabels" [calendarType]="calendarType" [minDate]="minDate" [maxDate]="maxDate" [cssClass]="cssClass" [footerDescription]="footerDescription" [activeInput]="activeInput" [showSidebar]="showSidebar" [showToday]="showToday" [showTimePicker]="showTimePicker" [timeDisplayFormat]="timeDisplayFormat" [dateFormat]="extractDateFormat(format)" [disabledDates]="disabledDates" [disabledDatesFilter]="disabledDatesFilter" [disabledTimesFilter]="disabledTimesFilter" [templates]="templates" (dateSelected)="onDateSelected($event)" (dateRangeSelected)="onDateRangeSelected($event)" (closePicker)="close()" (clickInside)="focus()" tabindex="-1" [attr.disabled]="disabled? 'disabled':null" ></qeydar-date-picker-popup> </div> </ng-template> <ng-template cdkConnectedOverlay nzConnectedOverlay [cdkConnectedOverlayOrigin]="origin" [cdkConnectedOverlayOpen]="isOpen" [cdkConnectedOverlayPositions]="overlayPositions" [cdkConnectedOverlayTransformOriginOn]="'.qeydar-picker-wrapper'" [cdkConnectedOverlayHasBackdrop]="false" (positionChange)="onPositionChange($event)" (detach)="close()" > <div class="qeydar-picker-wrapper" [class.disabled]="disabled" [@slideMotion]="'enter'" style="position: relative;" (click)="$event.stopPropagation()" > <ng-container *ngTemplateOutlet="inlineMode"></ng-container> </div> </ng-template> </div> `, isInline: true, styles: [":host.qeydar-datepicker ::ng-deep{display:block;max-width:fit-content}.date-picker-wrapper{position:relative;max-width:fit-content}input{font-family:inherit;max-width:300px;padding:6px 10px;border:1px solid #d9d9d9;border-radius:4px;font-size:14px;outline:none;transition:all .3s}input:hover{border-color:#40a9ff}input.focus{border-color:#40a9ff;box-shadow:0 0 0 2px #1890ff33;outline:none}.range-input-container{display:flex;align-items:center;border:1px solid #d9d9d9;border-radius:4px;overflow:hidden}.range-input-container input{border:none;flex:1;width:50%;padding:6px 10px;border-radius:0}.range-input-container input.focus{border-bottom:1px solid;border-color:#40a9ff;box-shadow:none!important}.range-separator{padding:0 8px;color:#999}.calendar-button{background:none;border:none;padding:4px 4px 0;cursor:pointer;font-size:16px}.range-input-labels{display:flex;justify-content:space-between;gap:10px;color:#444;padding:0 5px 5px}.end-label{width:49%}.disabled{opacity:.8;pointer-events:none}.disabled .range-input-container{background:#f3f3f3}.input-container .calendar-button{position:absolute;right:0;bottom:5px}.date-picker-rtl .input-container .calendar-button{right:auto;left:0}.input-container{display:flex;flex-direction:column;gap:3px;color:#444}.input-container.rtl{direction:rtl}// rtl :dir(rtl) .range-separator{rotate:180deg}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i4.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: NzConnectedOverlayDirective, selector: "[cdkConnectedOverlay][nzConnectedOverlay]", inputs: ["nzArrowPointAtCenter"], exportAs: ["nzConnectedOverlay"] }, { kind: "directive", type: DateMaskDirective, selector: "[qeydar-dateMask]", inputs: ["qeydar-dateMask", "disableInputMask"] }, { kind: "component", type: DatePickerPopupComponent, selector: "qeydar-date-picker-popup", inputs: ["rtl", "selectedDate", "selectedStartDate", "selectedEndDate", "mode", "isRange", "customLabels", "calendarType", "minDate", "maxDate", "cssClass", "footerDescription", "activeInput", "showSidebar", "showToday", "showTimePicker", "timeDisplayFormat", "dateFormat", "disabledDates", "disabledDatesFilter", "disabledTimesFilter", "templates"], outputs: ["dateSelected", "dateRangeSelected", "closePicker", "clickInside"] }], animations: [slideMotion], changeDetection: i0.ChangeDetectionStrategy.OnPush }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: DatePickerComponent, decorators: [{ type: Component, args: [{ selector: 'qeydar-date-picker', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: ` <div qeydarDatepickerStyles class="date-picker-wrapper" [class.date-picker-rtl]="rtl" [class.disabled]="disabled" [formGroup]="form"> <ng-container *ngIf="!isInline; else inlineMode"> <ng-container *ngIf="!isRange; else rangeMode"> <div class="input-container" [class.rtl]> <label for="dateInput" *ngIf="inputLabel">{{ inputLabel }}</label> <input #datePickerInput type="text" formControlName="dateInput" [qeydar-dateMask]="format" [disableInputMask]="disableInputMask" (click)="toggleDatePicker(null,$event)" (focus)="onFocusInput(null,$event)" (blur)="onInputBlur(null,$event)" (keydown)="onInputKeydown($event)" [class.focus]="isOpen" [placeholder]="getPlaceholder()" [readonly]="readOnly || readOnlyInput" [attr.disabled]="disabled? 'disabled':null" > <ng-container *ngTemplateOutlet="icon"></ng-container> </div> </ng-container> <ng-template #rangeMode> <div *ngIf="rangeInputLabels" class="range-input-labels"> <div class="start-label"> <label for="startDateInput">{{ rangeInputLabels.start }}</label> </div> <div class="end-label"> <label for="endDateInput">{{ rangeInputLabels.end }}</label> </div> </div> <div class="range-input-container"> <input #rangePickerInput type="text" formControlName="startDateInput" [qeydar-dateMask]="format" [disableInputMask]="disableInputMask" (click)="toggleDatePicker('start',$event)" (focus)="onFocusInput('start',$event)" (focusout)="onFocusout($event)" (blur)="onInputBlur('start',$event)" (keydown)="onInputKeydown($event)" [class.focus]="isOpen && activeInput === 'start'" [placeholder]="getPlaceholder('start')" [readonly]="readOnly || readOnlyInput" [attr.disabled]="disabled? 'disabled':null" > <span class="range-separator">→</span> <input #rangePickerInput type="text" formControlName="endDateInput" [qeydar-dateMask]="format" [disableInputMask]="disableInputMask" (click)="toggleDatePicker('end',$event)" (focus)="onFocusInput('end',$event)" (focusout)="onFocusout($event)" (blur)="onInputBlur('end',$event)" (keydown)="onInputKeydown($event)" [class.focus]="isOpen && activeInput === 'end'" [placeholder]="getPlaceholder('end')" [readonly]="readOnly || readOnlyInput" [attr.disabled]="disabled? 'disabled':null" > <ng-container *ngTemplateOutlet="icon"></ng-container> </div> </ng-template> <ng-template #icon> <button class="calendar-button" (click)="toggleDatePicker(null, $event)" tabindex="-1"> <svg xmlns="http://www.w3.org/2000/svg" width="18px" height="18px" viewBox="0 0 24 24" fill="#999"> <path fill-rule="evenodd" clip-rule="evenodd" d="M6 2C6 1.44772 6.44772 1 7 1C7.55228 1 8 1.44772 8 2V3H16V2C16 1.44772 16.4477 1 17 1C17.5523 1 18 1.44772 18 2V3H19C20.6569 3 22 4.34315 22 6V20C22 21.6569 20.6569 23 19 23H5C3.34315 23 2 21.6569 2 20V6C2 4.34315 3.34315 3 5 3H6V2ZM16 5V6C16 6.55228 16.4477 7 17 7C17.5523 7 18 6.55228 18 6V5H19C19.5523 5 20 5.44772 20 6V9H4V6C4 5.44772 4.44772 5 5 5H6V6C6 6.55228 6.44772 7 7 7C7.55228 7 8 6.55228 8 6V5H16ZM4 11V20C4 20.5523 4.44772 21 5 21H19C19.5523 21 20 20.5523 20 20V11H4Z" fill="#999"/> </svg> </button> </ng-template> </ng-container> <ng-template #inlineMode> <div class="dp-dropdown" [class.qeydar-picker-dropdown-rtl]="rtl" [class.qeydar-picker-dropdown-placement-bottomLeft]="currentPositionY === 'bottom' && currentPositionX === 'start'" [class.qeydar-picker-dropdown-placement-topLeft]="currentPositionY === 'top' && currentPositionX === 'start'" [class.qeydar-picker-dropdown-placement-bottomRight]="currentPositionY === 'bottom' && currentPositionX === 'end'" [class.qeydar-picker-dropdown-placement-topRight]="currentPositionY === 'top' && currentPositionX === 'end'" [class.qeydar-picker-dropdown-range]="isRange" > <qeydar-date-picker-popup [rtl]="rtl" [@slideMotion]="'enter'" [selectedDate]="selectedDate" [selectedStartDate]="selectedStartDate" [selectedEndDate]="selectedEndDate"