@qeydar/datepicker
Version:
A comprehensive Date and Time Picker for Angular with Jalali calendar support
995 lines (993 loc) • 136 kB
JavaScript
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