UNPKG

@qeydar/datepicker

Version:

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

995 lines (993 loc) 136 kB
import { Component, Input, Output, EventEmitter, ViewChild, HostListener, ChangeDetectionStrategy } from '@angular/core'; import { TimePickerComponent } from '../time-picker/time-picker.component'; import { takeUntil } from 'rxjs'; import { NgFor, NgIf, NgTemplateOutlet } from '@angular/common'; import * as i0 from "@angular/core"; import * as i1 from "../date-picker.service"; import * as i2 from "../date-adapter"; export class DatePickerPopupComponent { constructor(el, cdr, dpService, jalali, gregorian, destroy$) { this.el = el; this.cdr = cdr; this.dpService = dpService; this.jalali = jalali; this.gregorian = gregorian; this.destroy$ = destroy$; // ========== Input Properties ========== this.rtl = false; this.selectedDate = null; this.selectedStartDate = null; this.selectedEndDate = null; this.mode = 'day'; this.isRange = false; this.customLabels = []; this.calendarType = 'gregorian'; this.minDate = null; this.maxDate = null; this.cssClass = ''; this.footerDescription = ''; this.activeInput = null; this.showSidebar = true; this.showTimePicker = false; this.timeDisplayFormat = 'HH:mm'; this.disabledDates = []; // ========== Output Properties ========== this.dateSelected = new EventEmitter(); this.dateRangeSelected = new EventEmitter(); this.closePicker = new EventEmitter(); this.clickInside = new EventEmitter(); this.weekDays = []; this.periods = []; this.days = []; this.selectedPeriod = ''; this.tempEndDate = null; this.monthListNum = Array.from({ length: 12 }, (_, i) => i + 1); this.yearList = []; this.yearRanges = []; this.viewMode = 'days'; this.timeoutId = null; cdr.markForCheck(); } // ========== Getters ========== get getDate() { return this.selectedDate || this.selectedStartDate || this.selectedEndDate || new Date(); } // ========== Lifecycle Hooks ========== ngOnInit() { this.initializeComponent(); } ngOnChanges(changes) { this.handleChanges(changes); } ngAfterViewInit() { this.scrollToSelectedItem(); this.setTimePickerDate(); this.templates.forEach((item) => { switch (item.getType()) { case 'day': this.dayTemplate = item.template; break; case 'month': this.monthTemplate = item.template; break; case 'quarter': this.quarterTemplate = item.template; break; case 'year': this.yearTemplate = item.template; break; } }); this.cdr.markForCheck(); } ngOnDestroy() { if (this.timeoutId != null) { clearTimeout(this.timeoutId); } } // ========== Initialization Methods ========== initializeComponent() { this.setDateAdapter(); this.setInitialDate(); this.generateCalendar(); this.weekDays = this.dateAdapter.getDayOfWeekNames('short'); if (this.mode === 'year') { this.showYearSelector(); } this.initLabels(); } initLabels() { const today = this.dateAdapter.today(); if (this.customLabels?.length) { this.periods = this.customLabels; } else if (this.isRange) { this.generateDefaultPeriods(today); } } generateDefaultPeriods(today) { this.periods = [ { label: this.lang.lastDay, value: [this.dateAdapter.addDays(today, -1), today] }, { label: this.lang.lastWeek, value: [this.dateAdapter.addDays(today, -7), today], arrow: true }, { label: this.lang.lastMonth, value: [this.dateAdapter.addMonths(today, -1), today] }, { label: this.lang.custom, value: 'custom' } ]; } // ========== Date Adapter Methods ========== setDateAdapter() { this.dateAdapter = this.calendarType === 'jalali' ? this.jalali : this.gregorian; this.lang = this.dpService.locale; } // ========== Calendar Generation Methods ========== generateCalendar() { const firstDayOfMonth = this.dateAdapter.startOfMonth(this.currentDate); const startDate = this.dateAdapter.startOfWeek(firstDayOfMonth); this.days = Array.from({ length: 42 }, (_, i) => this.dateAdapter.addDays(startDate, i)); } // ========== View Mode Management ========== setViewMode() { switch (this.mode) { case 'day': this.viewMode = 'days'; break; case 'month': this.viewMode = 'months'; this.generateYearList(15); break; case 'year': this.viewMode = 'years'; break; } } showMonthSelector() { this.viewMode = 'months'; this.generateYearList(15); this.scrollToSelectedItem(this.dateAdapter.getYear(this.getDate)); this.cdr.markForCheck(); } showYearSelector() { this.viewMode = 'years'; this.generateYearRanges(); this.generateYearList(); this.scrollToSelectedItem(); this.cdr.markForCheck(); } // ========== Time Selection Methods ========== onTimeChange(time) { const timeDate = time instanceof Date ? time : new Date(time); if (!this.isRange) { this.updateSingleDateTime(timeDate); } else { this.updateRangeDateTime(timeDate); } } updateSingleDateTime(timeDate) { if (!this.selectedDate) { this.selectedDate = this.dateAdapter.today(); } const updatedDate = this.applyTimeToDate(this.selectedDate, timeDate); this.selectedDate = updatedDate; } updateRangeDateTime(timeDate) { if (this.activeInput === 'start' && this.selectedStartDate) { const updatedDate = this.applyTimeToDate(this.selectedStartDate, timeDate); this.selectedStartDate = updatedDate; this.dateRangeSelected.emit({ start: this.selectedStartDate, end: null }); } else if (this.activeInput === 'end' && this.selectedEndDate) { const updatedDate = this.applyTimeToDate(this.selectedEndDate, timeDate); this.selectedEndDate = updatedDate; clearTimeout(this.timeoutId); this.timeoutId = setTimeout(() => { this.dateRangeSelected.emit({ start: this.selectedStartDate, end: this.selectedEndDate }); }, 300); } } applyTimeToDate(date, timeDate) { let updatedDate = this.dateAdapter.setHours(date, timeDate.getHours()); updatedDate = this.dateAdapter.setMinutes(updatedDate, timeDate.getMinutes()); updatedDate = this.dateAdapter.setSeconds(updatedDate, timeDate.getSeconds()); return updatedDate; } setTimePickerDate(date) { if (this.showTimePicker) { if (this.isRange) { this.dpService.activeInput$.asObservable() .pipe(takeUntil(this.destroy$)) .subscribe(active => { if (active == 'start') { this.timePicker.updateFromDate(this.selectedStartDate); } else { this.timePicker.updateFromDate(this.selectedEndDate); } this.timePicker.scrollToTime(); }); } else { this.timePicker.updateFromDate(date || this.selectedDate); this.timePicker.scrollToTime(); } } } // ========== Date Selection Methods ========== selectDate(date) { if (this.isDateDisabled(date)) return; if (this.showTimePicker) { const existingDate = this.isRange ? (this.activeInput === 'start' ? this.selectedStartDate : this.selectedEndDate) : this.selectedDate; if (existingDate) { date = this.applyTimeToDate(date, existingDate); } } else { date = this.applyTimeToDate(date, new Date()); } if (this.isRange) { this.handleRangeSelection(date); } else { this.handleSingleSelection(date); } this.currentDate = date; this.cdr.markForCheck(); } handleRangeSelection(date) { const prevStartDate = this.selectedStartDate; const prevEndDate = this.selectedEndDate; if (!this.selectedStartDate || (this.selectedStartDate && this.selectedEndDate) || this.dateAdapter.isBefore(date, this.selectedStartDate)) { this.selectedStartDate = date; this.selectedEndDate = null; if (!this.showTimePicker) { this.activeInput = 'end'; this.dpService.activeInput$.next('end'); } this.dateRangeSelected.emit({ start: this.selectedStartDate, end: null }); } else { if (this.showTimePicker) { this.activeInput = 'end'; this.dpService.activeInput$.next('end'); } this.selectedEndDate = date; this.dateRangeSelected.emit({ start: this.selectedStartDate, end: this.selectedEndDate }); } if (prevStartDate !== this.selectedStartDate || prevEndDate !== this.selectedEndDate) this.cdr.markForCheck(); } handleSingleSelection(date) { this.selectedDate = date; if (!this.showTimePicker) this.dateSelected.emit(date); } selectMonth(month, closeAfterSelection = false) { if (this.isMonthDisabled(month)) return; this.currentDate = this.dateAdapter.createDate(this.dateAdapter.getYear(this.currentDate), month - 1, 1); if (this.isRange && this.mode === 'month') { this.handleRangeSelection(this.currentDate); return; } if (this.mode === 'month' || closeAfterSelection) { this.selectedDate = this.currentDate; this.dateSelected.emit(this.currentDate); this.closeDatePicker(); } else { this.viewMode = 'days'; this.generateCalendar(); this.cdr.detectChanges(); } this.scrollToSelectedItem(month); } selectYear(year, sideSelector = false) { if (this.isYearDisabled(year)) return; this.currentDate = this.dateAdapter.createDate(year, this.dateAdapter.getMonth(this.currentDate), 1); if (this.isRange && this.mode === 'year') { this.handleRangeSelection(this.currentDate); return; } if (this.mode === 'year') { this.selectedDate = this.currentDate; this.dateSelected.emit(this.currentDate); this.closeDatePicker(); return; } if (sideSelector) { this.currentDate = this.dateAdapter.setYear(this.selectedDate, year); this.scrollToSelectedItem(year); } else { this.viewMode = 'months'; this.cdr.detectChanges(); } } // ========== Navigation Methods ========== goPrev() { if (this.viewMode === 'days') { this.prevMonth(); this.cdr.detectChanges(); return; } let id; if (this.viewMode === 'months') { this.currentDate = this.dateAdapter.addYears(this.currentDate, -1); id = this.dateAdapter.getYear(this.currentDate); } if (this.viewMode === 'years') { const yearStart = this.yearList[0] - 15; this.yearList = Array.from({ length: 15 }, (_, i) => yearStart + i); id = yearStart; } this.cdr.detectChanges(); this.scrollToSelectedItem(id); } goNext() { if (this.viewMode === 'days') { this.nextMonth(); this.cdr.detectChanges(); return; } let id; if (this.viewMode === 'months') { this.currentDate = this.dateAdapter.addYears(this.currentDate, 1); id = this.dateAdapter.getYear(this.currentDate); } if (this.viewMode === 'years') { const yearStart = this.yearList[14] + 1; this.yearList = Array.from({ length: 15 }, (_, i) => yearStart + i); id = yearStart; } this.cdr.detectChanges(); this.scrollToSelectedItem(id); } prevMonth() { if (this.isPrevMonthDisabled()) return; this.currentDate = this.dateAdapter.addMonths(this.currentDate, -1); this.generateCalendar(); this.scrollToSelectedItem(this.dateAdapter.getMonth(this.currentDate) + 1); } nextMonth() { if (this.isNextMonthDisabled()) return; this.currentDate = this.dateAdapter.addMonths(this.currentDate, 1); this.generateCalendar(); this.scrollToSelectedItem(this.dateAdapter.getMonth(this.currentDate) + 1); } // ========== State Check Methods ========== isSelected(date) { if (this.isRange) { return this.isRangeStart(date) || this.isRangeEnd(date); } return this.selectedDate && this.dateAdapter.isSameDay(date, this.selectedDate); } isRangeStart(date) { return this.isRange && this.selectedStartDate && this.dateAdapter.isSameDay(date, this.selectedStartDate); } isRangeEnd(date) { return this.isRange && this.selectedEndDate && this.dateAdapter.isSameDay(date, this.selectedEndDate); } isInRange(date) { return this.isRange && this.selectedStartDate && (this.selectedEndDate || this.tempEndDate) && this.dateAdapter.isAfter(date, this.selectedStartDate) && this.dateAdapter.isBefore(date, this.selectedEndDate || this.tempEndDate); } isToday(date) { return this.dateAdapter.isSameDay(date, this.dateAdapter.today()) && this.showToday; } isActiveMonth(month) { return this.dateAdapter.getMonth(this.currentDate) === month - 1; } isActiveYear(year) { return year === this.dateAdapter.getYear(this.currentDate); } isActiveYearRange(startYear) { return startYear === this.yearList[0]; } // ========== Disabled State Methods ========== isDateDisabled(date) { if ((this.minDate && this.dateAdapter.isBefore(date, this.minDate)) || (this.maxDate && this.dateAdapter.isAfter(date, this.maxDate))) { return true; } // Check if date is in disabled dates array const parsedDisabledDates = this.parseDisabledDates(); const isDisabledDate = parsedDisabledDates.some(disabledDate => this.dateAdapter.isSameDay(date, disabledDate)); // Check custom filter function if provided const isFilterDisabled = this.disabledDatesFilter ? this.disabledDatesFilter(date) : false; return isDisabledDate || isFilterDisabled; } isMonthDisabled(month) { const year = this.dateAdapter.getYear(this.currentDate); const startOfMonth = this.dateAdapter.createDate(year, month - 1, 1); // Check if all days in month are disabled const daysInMonth = this.dateAdapter.getDaysInMonth(startOfMonth); let allDaysDisabled = true; for (let day = 1; day <= daysInMonth; day++) { const date = this.dateAdapter.createDate(year, month - 1, day); if (!this.isDateDisabled(date)) { allDaysDisabled = false; break; } } return allDaysDisabled; } isYearDisabled(year) { if (this.minDate && this.dateAdapter.getYear(this.minDate) > year) return true; if (this.maxDate && this.dateAdapter.getYear(this.maxDate) < year) return true; // Check if all months in year are disabled const firstOfMonth = this.dateAdapter.createDate(year, 0, 1); let day = 1; for (let date = firstOfMonth; date.getFullYear() == firstOfMonth.getFullYear(); date = this.dateAdapter.addDays(firstOfMonth, day++)) { if (!this.isDateDisabled(date)) { return false; } } return true; } isYearRangeDisabled(yearRange) { if (this.minDate && this.dateAdapter.getYear(this.minDate) > yearRange.end) return true; if (this.maxDate && this.dateAdapter.getYear(this.maxDate) < yearRange.start) return true; // Check if all years in range are disabled for (let year = yearRange.start; year <= yearRange.end; year++) { if (!this.isYearDisabled(year)) { return false; } } return true; } isPrevMonthDisabled() { if (!this.minDate) return false; const minYear = this.dateAdapter.getYear(this.minDate); switch (this.viewMode) { case 'days': const prevMonth = this.dateAdapter.getMonth(this.currentDate) - 1; return this.dateAdapter.getMonth(this.minDate) > prevMonth; case 'months': const prevYear = this.dateAdapter.getYear(this.currentDate) - 1; return minYear > prevYear; case 'years': return minYear > this.yearList[this.yearList.length - 1]; default: return false; } } isNextMonthDisabled() { if (!this.maxDate) return false; const maxYear = this.dateAdapter.getYear(this.maxDate); switch (this.viewMode) { case 'days': const nextMonth = this.dateAdapter.getMonth(this.currentDate) + 1; return this.dateAdapter.getMonth(this.maxDate) < nextMonth; case 'months': const nextYear = this.dateAdapter.getYear(this.currentDate) + 1; return maxYear < nextYear; case 'years': return maxYear < this.yearList[0]; default: return false; } } parseDisabledDates() { return this.disabledDates.map(date => { if (date instanceof Date) { return this.dateAdapter.startOfDay(date); } const parsedDate = this.dateAdapter.parse(date, this.dateFormat); return parsedDate || null; }).filter(date => date !== null); } // ========== Event Handlers ========== onMouseEnter(date, event) { if (this.isRange && this.selectedStartDate && !this.selectedEndDate) { this.tempEndDate = date; } } onClickInside() { this.clickInside.emit(true); } // ========== Utility Methods ========== getMonthName(month) { return this.dateAdapter.getMonthNames('long')[month - 1]; } getCurrentMonthName() { return this.dateAdapter.getMonthNames('long')[this.dateAdapter.getMonth(this.currentDate)]; } getCurrentYear() { return this.dateAdapter.getYear(this.currentDate); } getWeekDays() { return this.weekDays; } isSameMonth(date1, date2) { return this.dateAdapter.isSameMonth(date1, date2); } closeDatePicker() { this.closePicker.emit(); } // ========== Year Management Methods ========== generateYearRanges(length = 15) { const yearCount = 15; const currentYear = this.dateAdapter.getYear(this.dateAdapter.today()); const startYear = currentYear - Math.floor(yearCount / 2) - (yearCount * Math.floor(length / 2)); this.yearRanges = []; for (let i = 0; i < length; i++) { const start = startYear + i * yearCount; this.yearRanges.push({ start, end: start + 14 }); } } generateYearList(length = 15) { const date = this.selectedDate || this.selectedEndDate || this.selectedStartDate || new Date(); const currentYear = this.dateAdapter.getYear(date); let start; if (this.viewMode === 'years') { const currentRange = this.yearRanges.find(range => range.start <= currentYear && range.end >= currentYear); start = currentRange ? currentRange.start : currentYear; } else { start = this.dateAdapter.getYear(date) - Math.round(length / 2); } this.yearList = Array.from({ length }, (_, i) => start + i); } selectYearRange(startYear) { this.yearList = Array.from({ length: 15 }, (_, i) => startYear + i); this.viewMode = 'years'; this.cdr.detectChanges(); this.scrollToSelectedItem(startYear); } // ========== Period Selection Methods ========== isActivePeriod(period) { const sameStart = this.dateAdapter.isEqual(this.dateAdapter.startOfDay(period.value[0]), this.dateAdapter.startOfDay(this.selectedStartDate)); const sameEnd = this.dateAdapter.isEqual(this.dateAdapter.startOfDay(period.value[1]), this.dateAdapter.startOfDay(this.selectedEndDate)); if (period.value === 'custom') { let isActiveOther = this.periods.find(p => p.arrow); return !isActiveOther; } ; period.arrow = sameStart && sameEnd; return sameStart && sameEnd; } selectPeriod(period) { this.selectedPeriod = period.value; if (period.value !== 'custom') { const [start, end] = period.value; this.dateRangeSelected.emit({ start, end }); } } onTodayClick() { this.currentDate = this.selectedDate = new Date(); this.generateCalendar(); this.selectDate(this.currentDate); this.setTimePickerDate(this.currentDate); this.cdr.detectChanges(); } onOkClick() { if (this.isRange) { this.dateRangeSelected.emit({ start: this.selectedStartDate, end: this.selectedEndDate }); this.closeDatePicker(); } else { this.dateSelected.emit(this.selectedDate); } } // ========== Scroll Management ========== scrollToSelectedItem(id = null) { if (!this.showSidebar) return; if (this.timeoutId != null) { clearTimeout(this.timeoutId); } const itemId = this.determineScrollItemId(id); if (!itemId || !this.itemSelector) return; this.timeoutId = setTimeout(() => { const selectedElement = this.itemSelector.nativeElement.querySelector(`#selector_${itemId}`); if (selectedElement) { selectedElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); } }, 0); } determineScrollItemId(id) { if (id != null) return id; if (!this.getDate) return null; switch (this.viewMode) { case 'days': return this.dateAdapter.getMonth(this.getDate) + 1; case 'months': return this.dateAdapter.getYear(this.getDate); case 'years': const currentYear = this.dateAdapter.getYear(this.getDate); const currentRange = this.yearRanges.find(range => range.start <= currentYear && range.end >= currentYear); return currentRange?.start || null; default: return null; } } // ========== State Management ========== handleChanges(changes) { if (changes['calendarType']) { this.setDateAdapter(); this.weekDays = this.dateAdapter.getDayOfWeekNames('short'); } if (changes['selectedDate'] || changes['selectedStartDate'] || changes['selectedEndDate'] || changes['mode'] || changes['calendarType']) { this.setInitialDate(); this.generateCalendar(); } if (changes['minDate'] || changes['maxDate']) { this.adjustCurrentDateToValidRange(); } } setInitialDate() { this.currentDate = this.determineInitialDate(); this.setViewMode(); this.adjustCurrentDateToValidRange(); } determineInitialDate() { if (this.isRange) { if (this.activeInput === 'start') { return this.selectedStartDate || this.dateAdapter.today(); } return this.selectedEndDate || this.selectedStartDate || this.dateAdapter.today(); } return this.selectedDate || this.dateAdapter.today(); } adjustCurrentDateToValidRange() { let adjustedDate = this.currentDate; if (this.minDate && this.dateAdapter.isBefore(adjustedDate, this.minDate)) { adjustedDate = this.minDate; } else if (this.maxDate && this.dateAdapter.isAfter(adjustedDate, this.maxDate)) { adjustedDate = this.maxDate; } if (!this.dateAdapter.isSameDay(this.currentDate, adjustedDate)) { this.currentDate = adjustedDate; this.generateCalendar(); } } } DatePickerPopupComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: DatePickerPopupComponent, deps: [{ token: i0.ElementRef }, { token: i0.ChangeDetectorRef }, { token: i1.QeydarDatePickerService }, { token: i2.JalaliDateAdapter }, { token: i2.GregorianDateAdapter }, { token: i1.DestroyService }], target: i0.ɵɵFactoryTarget.Component }); DatePickerPopupComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: DatePickerPopupComponent, isStandalone: true, selector: "qeydar-date-picker-popup", inputs: { rtl: "rtl", 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: "dateFormat", disabledDates: "disabledDates", disabledDatesFilter: "disabledDatesFilter", disabledTimesFilter: "disabledTimesFilter", templates: "templates" }, outputs: { dateSelected: "dateSelected", dateRangeSelected: "dateRangeSelected", closePicker: "closePicker", clickInside: "clickInside" }, host: { listeners: { "click": "onClickInside()" } }, viewQueries: [{ propertyName: "itemSelector", first: true, predicate: ["itemSelector"], descendants: true }, { propertyName: "timePicker", first: true, predicate: TimePickerComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: ` <div class="date-picker-popup" [class.rtl]="rtl" [class]="cssClass" tabindex="-1"> <div class="date-picker-content"> <ng-container *ngIf="showSidebar"> <div *ngIf="isRange" class="period-selector"> <button *ngFor="let period of periods" tabindex="-1" [class.active]="isActivePeriod(period)" (click)="selectPeriod(period)" > {{ period.label }} <span *ngIf="period.arrow" class="arrow">→</span> </button> </div> <div *ngIf="!isRange" class="side-selector" #itemSelector> <ng-container *ngIf="viewMode == 'days'"> <button *ngFor="let month of monthListNum" tabindex="-1" [id]="'selector_'+month" [class.active]="isActiveMonth(month)" [disabled]="isMonthDisabled(month)" (click)="selectMonth(month, false)"> {{ getMonthName(month) }} </button> </ng-container> <ng-container *ngIf="viewMode == 'months'"> <button *ngFor="let year of yearList" tabindex="-1" [id]="'selector_'+year" [class.active]="isActiveYear(year)" [disabled]="isYearDisabled(year)" (click)="selectYear(year, true)" > {{ year }} </button> </ng-container> <ng-container *ngIf="viewMode == 'years'"> <button tabindex="-1" *ngFor="let yearRange of yearRanges" [id]="'selector_'+yearRange.start" [class.active]="isActiveYearRange(yearRange.start)" [disabled]="isYearRangeDisabled(yearRange)" (click)="selectYearRange(yearRange.start)" > {{ yearRange.start }} - {{ yearRange.end }} </button> </ng-container> </div> </ng-container> <div class="calendar"> <div class="header"> <button class="qeydar-calendar-nav-left" (click)="goPrev()" [disabled]="isPrevMonthDisabled()" tabindex="-1"></button> <span class="month-year"> <span *ngIf="mode != 'year'" class="month-name" (click)="showMonthSelector()">{{ getCurrentMonthName() }}</span> <span class="year" (click)="showYearSelector()">{{ getCurrentYear() }}</span> </span> <button class="qeydar-calendar-nav-right" (click)="goNext()" [disabled]="isNextMonthDisabled()" tabindex="-1"></button> </div> <div *ngIf="viewMode == 'days'"> <div *ngIf="viewMode === 'days'" class="weekdays"> <span *ngFor="let day of getWeekDays()">{{ day }}</span> </div> <div *ngIf="viewMode === 'days'" class="days"> <button *ngFor="let day of days" tabindex="-1" [class.different-month]="!isSameMonth(day, currentDate)" [class.selected]="isSelected(day)" [class.in-range]="isInRange(day)" [class.range-start]="isRangeStart(day)" [class.range-end]="isRangeEnd(day)" [class.today]="isToday(day)" [class.disabled]="isDateDisabled(day)" [disabled]="isDateDisabled(day)" (click)="selectDate(day)" (mouseenter)="onMouseEnter(day,$event)" > <ng-container *ngIf="dayTemplate; else dayDefTemplate"> <ng-container *ngTemplateOutlet="$any(dayTemplate); context: { $implicit: day }"></ng-container> </ng-container> <ng-template #dayDefTemplate> {{ dateAdapter.getDate(day) }} </ng-template> </button> </div> </div> <div *ngIf="viewMode === 'months'" class="months"> <button *ngFor="let month of monthListNum" tabindex="-1" [class.selected]="month === dateAdapter.getMonth(currentDate) + 1" [disabled]="isMonthDisabled(month)" (click)="selectMonth(month,false)" > <ng-container *ngIf="monthTemplate; else monthDefTemplate"> <ng-container *ngTemplateOutlet="$any(monthTemplate); context: { $implicit: month }"></ng-container> </ng-container> <ng-template #monthDefTemplate> {{ getMonthName(month) }} </ng-template> </button> </div> <div *ngIf="viewMode === 'years' || mode == 'year'" class="years"> <button *ngFor="let year of yearList" tabindex="-1" [class.selected]="year === dateAdapter.getYear(currentDate)" [disabled]="isYearDisabled(year)" (click)="selectYear(year)" > <ng-container *ngIf="yearTemplate; else yearDefTemplate"> <ng-container *ngTemplateOutlet="$any(yearTemplate); context: { $implicit: year }"></ng-container> </ng-container> <ng-template #yearDefTemplate> {{ year }} </ng-template> </button> </div> </div> <!-- Time Picker Integration --> <div *ngIf="showTimePicker" class="time-picker-section"> <qeydar-time-picker #timePicker [rtl]="rtl" [dateAdapter]="dateAdapter" [valueType]="'date'" [displayFormat]="timeDisplayFormat" [inline]="true" [disabledTimesFilter]="disabledTimesFilter" [cssClass]="'embedded-time-picker'" [selectedDate]="selectedDate" (timeChange)="onTimeChange($event)" ></qeydar-time-picker> </div> </div> <div class="date-picker-footer" *ngIf="footerDescription || showTimePicker || showToday"> <div class="footer-description" *ngIf="footerDescription" [innerHtml]="footerDescription"> </div> <div class="footer-actions"> <button *ngIf="showTimePicker" class="footer-button ok" (click)="onOkClick()">{{ lang.ok }}</button> <button *ngIf="showToday" class="footer-button" (click)="onTodayClick()">{{ lang.today }}</button> </div> </div> </div> `, isInline: true, styles: [":host *{font-family:inherit;font-weight:400;box-sizing:border-box;padding:0;margin:0}:host.up .date-picker-popup{bottom:100%;margin-bottom:5px}:host.down .date-picker-popup{top:100%;margin-top:5px}.date-picker-popup{display:flex;flex-direction:column;border-radius:4px;box-shadow:0 2px 10px #0000001a;overflow:hidden;z-index:1000;width:fit-content;border:1px solid #ddd;background:white}.date-picker-content{display:flex;flex-direction:row;max-height:295px}.date-picker-footer{border-top:1px solid #f0f0f0;padding:10px;display:flex;flex-direction:column;justify-content:space-between}.side-selector,.period-selector{width:120px;border-inline-end:1px solid #f0f0f0}.side-selector button,.period-selector button{display:flex;justify-content:space-between;font-size:14px;width:100%;padding:10px;text-align:start;border:none;background:none;cursor:pointer;border-block-end:1px solid #f0f0f0;color:#555;transition:background-color .3s}.side-selector button:hover,.period-selector button:hover{background-color:#e6f7ff}.side-selector button.active,.period-selector button.active{background-color:#bfeaff;color:#0175e0;width:100%}.side-selector{overflow:auto;scrollbar-width:none;-ms-overflow-style:none}.side-selector::-webkit-scrollbar{display:none}.calendar{padding:10px 15px 15px;flex-grow:1;background:#FFF;width:280px;max-width:280px}.header{display:flex;justify-content:space-between;align-items:center;margin-bottom:5px;padding-bottom:2px;border-bottom:1px solid #f0f0f0}.header button{background:none;border:none;font-size:16px;cursor:pointer;padding:4px 5px}.header .month-name,.header .year{color:#47366c}.weekdays{display:grid;grid-template-columns:repeat(7,1fr);text-align:center;margin-bottom:5px;font-size:14px;color:#18396cb0}.weekdays span{font-weight:700}.days{display:grid;grid-template-columns:repeat(7,1fr);gap:2px}.days button{position:relative;aspect-ratio:1;border:none;background:none;cursor:pointer;border-radius:50%;font-size:14px;color:#555;transition:background-color .3s,color .3s}.days button:hover{background-color:#e6f7ff}.days button.different-month{color:#ccc}.days button.selected,.days button.range-end.in-range,.days button.range-start.in-range{background-color:#1890ff;color:#fff}.days button.in-range{background-color:#e6f7ff;color:#1890ff}.days button.today{border:2px solid #29b9ff}.days button.today span{position:absolute;bottom:-1rem;right:.6rem;padding:0;margin:0;font-size:36px;color:#9370db}.days button.disabled{color:#aaa;background:#fafafa;opacity:1}.days button.disabled:after{content:\"\";background:rgba(252,103,143,.6666666667);position:absolute;right:20%;top:45%;width:20px;height:1px;transform:rotate(145deg)}.month-year{display:flex;align-items:center;cursor:pointer;font-size:16px}.month-name,.year{margin:0 5px}.years,.months{display:grid;grid-template-columns:repeat(3,1fr)}.years button,.months button{padding:10px;border:none;background:none;cursor:pointer;font-size:14px;position:relative}.years button.selected,.months button.selected{background-color:#1890ff;color:#fff}.months{gap:1rem .3rem}.years{gap:.5rem .3rem!important}.footer-description{font-size:13px;color:#666}.footer-actions{display:flex;gap:10px;justify-content:end;flex-direction:row-reverse}.footer-button{background:none;border:none;color:#1890ff;cursor:pointer;font-size:14px;padding:2px 6px;transition:color .3s}.footer-button.ok{background:#1890ff;border-radius:1px;color:#fff}.footer-button.ok:hover{color:#eee}.footer-button:hover{color:#40a9ff}.qeydar-calendar-nav-right:before,.qeydar-calendar-nav-left:before{position:relative;content:\"\";display:inline-block;height:6px;width:6px;vertical-align:baseline;border-style:solid;border-color:#555;border-width:2px 2px 0 0}.qeydar-calendar-nav-left:before{transform:rotate(-135deg)!important}.qeydar-calendar-nav-right:before{transform:rotate(45deg)}button:disabled{opacity:.5;cursor:not-allowed}[tabindex=\"-1\"]:focus{outline:none!important}.rtl{direction:rtl}.rtl .arrow{rotate:180deg}.rtl .qeydar-calendar-nav-left:before{transform:rotate(45deg)!important}.rtl .qeydar-calendar-nav-right:before{transform:rotate(-135deg)}.rtl .calendar{direction:rtl}[dir=rtl] .arrow{rotate:180deg}.time-picker-section{border-inline-start:1px solid #f0f0f0}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: TimePickerComponent, selector: "qeydar-time-picker", inputs: ["placeholder", "rtl", "placement", "minTime", "maxTime", "lang", "valueType", "cssClass", "showIcon", "dateAdapter", "inline", "disableInputMask", "disabled", "disabledTimesFilter", "allowEmpty", "readOnly", "readOnlyInput", "displayFormat", "selectedDate"], outputs: ["timeChange", "openChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: DatePickerPopupComponent, decorators: [{ type: Component, args: [{ selector: 'qeydar-date-picker-popup', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [ NgIf, NgFor, NgTemplateOutlet, TimePickerComponent ], template: ` <div class="date-picker-popup" [class.rtl]="rtl" [class]="cssClass" tabindex="-1"> <div class="date-picker-content"> <ng-container *ngIf="showSidebar"> <div *ngIf="isRange" class="period-selector"> <button *ngFor="let period of periods" tabindex="-1" [class.active]="isActivePeriod(period)" (click)="selectPeriod(period)" > {{ period.label }} <span *ngIf="period.arrow" class="arrow">→</span> </button> </div> <div *ngIf="!isRange" class="side-selector" #itemSelector> <ng-container *ngIf="viewMode == 'days'"> <button *ngFor="let month of monthListNum" tabindex="-1" [id]="'selector_'+month" [class.active]="isActiveMonth(month)" [disabled]="isMonthDisabled(month)" (click)="selectMonth(month, false)"> {{ getMonthName(month) }} </button> </ng-container> <ng-container *ngIf="viewMode == 'months'"> <button *ngFor="let year of yearList" tabindex="-1" [id]="'selector_'+year" [class.active]="isActiveYear(year)" [disabled]="isYearDisabled(year)" (click)="selectYear(year, true)" > {{ year }} </button> </ng-container> <ng-container *ngIf="viewMode == 'years'"> <button tabindex="-1" *ngFor="let yearRange of yearRanges" [id]="'selector_'+yearRange.start" [class.active]="isActiveYearRange(yearRange.start)" [disabled]="isYearRangeDisabled(yearRange)" (click)="selectYearRange(yearRange.start)" > {{ yearRange.start }} - {{ yearRange.end }} </button> </ng-container> </div> </ng-container> <div class="calendar"> <div class="header"> <button class="qeydar-calendar-nav-left" (click)="goPrev()" [disabled]="isPrevMonthDisabled()" tabindex="-1"></button> <span class="month-year"> <span *ngIf="mode != 'year'" class="month-name" (click)="showMonthSelector()">{{ getCurrentMonthName() }}</span> <span class="year" (click)="showYearSelector()">{{ getCurrentYear() }}</span> </span> <button class="qeydar-calendar-nav-right" (click)="goNext()" [disabled]="isNextMonthDisabled()" tabindex="-1"></button> </div> <div *ngIf="viewMode == 'days'"> <div *ngIf="viewMode === 'days'" class="weekdays"> <span *ngFor="let day of getWeekDays()">{{ day }}</span> </div> <div *ngIf="viewMode === 'days'" class="days"> <button *ngFor="let day of days" tabindex="-1" [class.different-month]="!isSameMonth(day, currentDate)" [class.selected]="isSelected(day)" [class.in-range]="isInRange(day)" [class.range-start]="isRangeStart(day)" [class.range-end]="isRangeEnd(day)" [class.today]="isToday(day)" [class.disabled]="isDateDisabled(day)" [disabled]="isDateDisabled(day)" (click)="selectDate(day)" (mouseenter)="onMouseEnter(day,$event)" > <ng-container *ngIf="dayTemplate; else dayDefTemplate"> <ng-container *ngTemplateOutlet="$any(dayTemplate); context: { $implicit: day }"></ng-container> </ng-container> <ng-template #dayDefTemplate> {{ dateAdapter.getDate(day) }} </ng-template> </button> </div> </div> <div *ngIf="viewMode === 'months'" class="months"> <button *ngFor="let month of monthListNum" tabindex="-1" [class.selected]="month === dateAdapter.getMonth(currentDate) + 1" [disabled]="isMonthDisabled(month)" (click)="selectMonth(month,false)" > <ng-container *ngIf="monthTemplate; else monthDefTemplate"> <ng-container *ngTemplateOutlet="$any(monthTemplate); context: { $implicit: month }"></ng-container> </ng-container> <ng-template #monthDefTemplate> {{ getMonthName(month) }} </ng-template> </button> </div> <div *ngIf="viewMode === 'years' || mode == 'year'" class="years"> <button *ngFor="let year of yearList" tabindex="-1" [class.selected]="year === dateAdapter.getYear(currentDate)" [disabled]="isYearDisabled(year)" (click)="selectYear(year)" > <ng-container *ngIf="yearTemplate; else yearDefTemplate"> <ng-container *ngTemplateOutlet="$any(yearTemplate); context: { $implicit: year }"></ng-container> </ng-container> <ng-template #yearDefTemplate> {{ year }} </ng-template> </button> </div> </div> <!-- Time Picker Integration --> <div *ngIf="showTimePicker" class="time-picker-section"> <qeydar-time-picker #timePicker [rtl]="rtl" [dateAdapter]="dateAdapter" [valueType]="'date'" [displayFormat]="timeDisplayFormat" [inline]="true" [disabledTimesFilter]="disabledTimesFilter" [cssClass]="'embedded-time-picker'" [selectedDate]="selectedDate" (timeChange)="onTimeChange($event)" ></qeydar-time-picker> </div> </div> <div class="date-picker-footer" *ngIf="footerDescription || showTimePicker || showToday"> <div class="footer-description" *ngIf="footerDescription" [innerHtml]="footerDescription"> </div> <div class="footer-actions"> <button *ngIf="showTimePicker" class="footer-button ok" (click)="onOkClick()">{{ lang.ok }}</button> <button *ngIf="showToday" class="footer-button" (click)="onTodayClick()">{{ lang.today }}</button> </div> </div> </div> `, styles: [":host *{font-family:inherit;font-weight:400;box-sizing:border-box;padding:0;margin:0}:host.up .date-picker-popup{bottom:100%;margin-bottom:5px}:host.down .date-picker-popup{top:100%;margin-top:5px}.date-picker-popup{display:flex;flex-direction:column;border-radius:4px;box-shadow:0 2px 10px #0000001a;overflow:hidden;z-index:1000;width:fit-content;border:1px solid #ddd;background:white}.date-picker-content{display:flex;flex-direction:row;max-height:295px}.date-picker-footer{border-top:1px solid #f0f0f0;padding:10px;display:flex;flex-direction:column;justify-content:space-between}.side-selector,.period-selector{width:120px;border-inline-end:1px solid #f0f0f0}.side-selector button,.period-selector button{display:flex;justify-content:space-between;font-size:14px;width:100%;padding:10px;text-align:start;border:none;background:none;cursor:pointer;border-block-end:1px solid #f0f0f0;color:#555;transition:background-color .3s}.side-selector button:hover,.period-selector button:hover{background-color:#e6f7ff}.side-selector button.active,.period-selector button.active{background-color:#bfeaff;color:#0175e0;width:100%}.side-selector{overflow:auto;scrollbar-width:none;-ms-overflow-style:none}.side-selector::-webkit-scrollbar{display:none}.calendar{padding:10px 15px 15px;flex-grow:1;background:#FFF;width:280px;max-width:280px}.header{display:flex;justify-content:space-between;align-items:center;margin-bottom:5px;padding-bottom:2px;border-bottom:1px solid #f0f0f0}.header button{background:none;border:none;font-size:16px;cursor:pointer;padding:4px 5px}.header .month-name,.header .year{color:#47366c}.weekdays{display:grid;grid-template-columns:repeat(7,1fr);text-align:center;margin-bottom:5px;font-size:14px;color:#18396cb0}.weekdays span{font-weight:700}.days{display:grid;grid-template-columns:repeat(7,1fr);gap:2px}.days button{position:relative;aspect-ratio:1;border:none;background:none;cursor:pointer;border-radius:50%;font-size:14px;color:#555;transition:background-color .3s,color .3s}.days button:hover{background-color:#e6f7ff}.days button.different-month{color:#ccc}.days button.selected,.days button.range-end.in-range,.days button.range-start.in-range{background-color:#1890ff;color:#fff}.days button.in-range{background-color:#e6f7ff;color:#1890ff}.days button.today{border:2px solid #29b9ff}.days button.today span{position:absolute;bottom:-1rem;right:.6rem;padding:0;margin:0;font-size:36px;color:#9370db}.days button.disabled{color:#aaa;background:#fafafa;opacity:1}.days button.disabled:after{content:\"\";background:rgba(252,103,143,.6666666667);position:absolute;right:20%;top:45%;width:20px;height:1px;transform:rotate(145deg)}.month-year{display:flex;a