UNPKG

ngx-hijri-datepicker

Version:
533 lines (479 loc) 21 kB
import { Component, HostListener, ElementRef, Input, Output, EventEmitter, forwardRef, OnInit, } from '@angular/core'; import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms'; import moment from 'moment-hijri'; @Component({ selector: 'ngx-hijri-datepicker', template: ` <div class="hijri-datepicker-container" [ngStyle]="{ width: width, }" > <!-- حقل الإدخال --> <div class="input-svg-wrapper" [ngStyle]="{ width: width, }" > <input type="text" [value]="displayedDate" (click)="toggleDatePicker()" readonly [placeholder]="placeholder" class="datepicker-input" [ngStyle]="{ width: width, borderColor: BorderColor, color: InputColor, backgroundColor: InputBackgroundColor }" /> <div class="iconClender" (click)="toggleDatePicker()" [ngStyle]="{ backgroundColor: IconBackgroundColor }" > <svg [attr.fill]="IconColor" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 610.398 610.398" class="datepicker-icon" > <g> <g> <path d="M159.567,0h-15.329c-1.956,0-3.811,0.411-5.608,0.995c-8.979,2.912-15.616,12.498-15.616,23.997v10.552v27.009v14.052 c0,2.611,0.435,5.078,1.066,7.44c2.702,10.146,10.653,17.552,20.158,17.552h15.329c11.724,0,21.224-11.188,21.224-24.992V62.553 V35.544V24.992C180.791,11.188,171.291,0,159.567,0z" /> <path d="M461.288,0h-15.329c-11.724,0-21.224,11.188-21.224,24.992v10.552v27.009v14.052c0,13.804,9.5,24.992,21.224,24.992 h15.329c11.724,0,21.224-11.188,21.224-24.992V62.553V35.544V24.992C482.507,11.188,473.007,0,461.288,0z" /> <path d="M539.586,62.553h-37.954v14.052c0,24.327-18.102,44.117-40.349,44.117h-15.329c-22.247,0-40.349-19.79-40.349-44.117 V62.553H199.916v14.052c0,24.327-18.102,44.117-40.349,44.117h-15.329c-22.248,0-40.349-19.79-40.349-44.117V62.553H70.818 c-21.066,0-38.15,16.017-38.15,35.764v476.318c0,19.784,17.083,35.764,38.15,35.764h468.763c21.085,0,38.149-15.984,38.149-35.764 V98.322C577.735,78.575,560.671,62.553,539.586,62.553z M527.757,557.9l-446.502-0.172V173.717h446.502V557.9z" /> <path d="M353.017,266.258h117.428c10.193,0,18.437-10.179,18.437-22.759s-8.248-22.759-18.437-22.759H353.017 c-10.193,0-18.437,10.179-18.437,22.759C334.58,256.074,342.823,266.258,353.017,266.258z" /> <path d="M353.017,348.467h117.428c10.193,0,18.437-10.179,18.437-22.759c0-12.579-8.248-22.758-18.437-22.758H353.017 c-10.193,0-18.437,10.179-18.437,22.758C334.58,338.288,342.823,348.467,353.017,348.467z" /> <path d="M353.017,430.676h117.428c10.193,0,18.437-10.18,18.437-22.759s-8.248-22.759-18.437-22.759H353.017 c-10.193,0-18.437,10.18-18.437,22.759S342.823,430.676,353.017,430.676z" /> <path d="M353.017,512.89h117.428c10.193,0,18.437-10.18,18.437-22.759c0-12.58-8.248-22.759-18.437-22.759H353.017 c-10.193,0-18.437,10.179-18.437,22.759C334.58,502.71,342.823,512.89,353.017,512.89z" /> <path d="M145.032,266.258H262.46c10.193,0,18.436-10.179,18.436-22.759s-8.248-22.759-18.436-22.759H145.032 c-10.194,0-18.437,10.179-18.437,22.759C126.596,256.074,134.838,266.258,145.032,266.258z" /> <path d="M145.032,348.467H262.46c10.193,0,18.436-10.179,18.436-22.759c0-12.579-8.248-22.758-18.436-22.758H145.032 c-10.194,0-18.437,10.179-18.437,22.758C126.596,338.288,134.838,348.467,145.032,348.467z" /> <path d="M145.032,430.676H262.46c10.193,0,18.436-10.18,18.436-22.759s-8.248-22.759-18.436-22.759H145.032 c-10.194,0-18.437,10.18-18.437,22.759S134.838,430.676,145.032,430.676z" /> <path d="M145.032,512.89H262.46c10.193,0,18.436-10.18,18.436-22.759c0-12.58-8.248-22.759-18.436-22.759H145.032 c-10.194,0-18.437,10.179-18.437,22.759C126.596,502.71,134.838,512.89,145.032,512.89z" /> </g> </g> </svg> </div> </div> <!-- التقويم --> <div class="datepicker-popup" [class.open]="showDatePicker" [class.closed]="!showDatePicker" [ngStyle]="{ borderColor: BorderColor }" > <!-- عنوان الشهر وأزرار التنقل --> <div class="header"> <a (click)="prevMonth()">&lt;</a> <span (click)="toggleYearPopup()" [ngStyle]="{ color: DayColor }">{{ currentMonthName }}</span> <a (click)="nextMonth()">&gt;</a> </div> <!-- Popup لاختيار السنة --> <div class="year-popup" *ngIf="showYearPopup"> <div class="year-list"> <div *ngFor="let year of hijriYears" (click)="selectYear(year)"> {{ year }} </div> </div> </div> <!-- أسماء أيام الأسبوع --> <div class="weekdays"> <div [ngStyle]="{ color: DatepickerPopupHeaderColor }" *ngFor="let day of dayNamesMin" class="weekday">{{ day }}</div> </div> <!-- عرض أيام الشهر --> <div class="days"> <div *ngFor="let day of daysInCurrentMonth" class="day" [class.selected]="selectedDate && day?.isSame(selectedDate, 'day')" (click)="selectDate(day)" > <span *ngIf="day">{{ day.iDate() }}</span> </div> </div> <!-- زر لتحديد اليوم الحالي --> <div class="today-button"> <a (click)="selectToday()" >{{todayBtn}}</a> </div> </div> </div> `, styles: ` .year-popup { position: absolute; top: 40px; left: 0; width: 100%; background: white; border: 1px solid var(--primary-color, #000); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); z-index: 1000; max-height: 200px; overflow-y: auto; } .year-list div { padding: 10px; text-align: center; cursor: pointer; transition: background-color 0.3s; } .year-list div:hover { background-color: var(--primary-color, #007bff); color: white; } .hijri-datepicker-container { position: relative; display: inline-block; width: 250px; } .input-svg-wrapper { display: flex; align-items: center; /* لمحاذاة المحتوى عموديًا */ width: 100%; /* نفس عرض الحاوية */ } .iconClender { background-color: var(--primary-color, #000); /* استخدام لون افتراضي */ padding: 14.5px; border-top-left-radius: 4px; border-bottom-left-radius: 4px; display: flex; justify-content: center; align-items: center; cursor: pointer; } .datepicker-input { flex: 1; /* يأخذ الإدخال كامل العرض المتبقي */ padding: 8px; font-size: 14px; box-sizing: border-box; border: 1px solid var(--primary-color, #000); /* استخدام لون افتراضي */ border-top-right-radius: 4px; border-bottom-right-radius: 4px; transition: border-color 0.3s ease-in-out; } .datepicker-input:focus { outline: none; /* إزالة الإطار الافتراضي */ border-color: var(--primary-color, #000); /* استخدام لون افتراضي */ box-shadow: 0 0 4px var(--primary-color, #000); /* تأثير خفيف عند التركيز */ } .datepicker-icon { width: 15px; /* يمكن ضبط الحجم حسب الحاجة */ height: 15px; /* نفس ارتفاع الإدخال */ cursor: pointer; } .datepicker-popup { position: absolute; top: 100%; left: 0; z-index: 1000; background: #fff; border: 1px solid var(--primary-color, #000); /* استخدام لون افتراضي */ box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); padding: 10px; width: 100%; opacity: 0; transform: scaleY(0); transform-origin: top; transition: all 0.3s ease-in-out; } .datepicker-popup.open { opacity: 1; transform: scaleY(1); } .datepicker-popup.closed { opacity: 0; transform: scaleY(0); } .header { display: flex; justify-content: space-between; margin-bottom: 10px; font-weight: bold; color: var(--primary-color, #000); /* استخدام لون افتراضي */ } .header a { cursor: pointer; } .weekdays, .days { display: grid; grid-template-columns: repeat(7, 1fr); text-align: center; } .weekday { font-weight: bold; margin-bottom: 5px; font-size: 12px; color: var(--primary-color, #000); /* استخدام لون افتراضي */ } .day { padding: 5px; cursor: pointer; text-align: center; border-radius: 4px; transition: background-color 0.3s, color 0.3s; } .day:hover { background-color: var(--primary-color, #000); /* استخدام لون افتراضي */ color: #fff; } .day.selected { background-color: var(--primary-color, #000); /* استخدام لون افتراضي */ color: white; font-weight: bold; } .today-button { text-align: left; margin-top: 10px; border: none; } .today-button a { cursor: pointer; padding: 5px 10px; color: #000; } .today-button a:hover { background-color: var(--primary-color, #000); /* اللون الافتراضي */ border-radius: 50px; color: white; } `, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NgxHijriDatepickerComponent), multi: true, }, ], }) export class NgxHijriDatepickerComponent { // Input properties to allow customization by the user // المدخلات لتخصيص المكون من قبل المستخدم @Input() value: string | null = null; // The initial value / القيمة الأولية @Output() valueChange: EventEmitter<string> = new EventEmitter<string>(); // Emits value when it changes / إرسال القيمة عند التغيير @Input() placeholder: string = 'اختر تاريخ هجري'; // Placeholder text / النص الافتراضي للحقل @Input() width: string = '250px'; // Width of the input field / عرض الحقل @Input() height: string = '40px'; // Height of the input field / ارتفاع الحقل @Input() InputColor: string | null = null; // Input text color / لون النص في الحقل @Input() InputBackgroundColor: string | null= null; // Input background color / لون خلفية الحقل @Input() IconColor: string | null= null; // Icon color / لون الأيقونة @Input() IconBackgroundColor: string| null = null; // Icon background color / لون خلفية الأيقونة @Input() DayColor: string| null= null; // Day color in calendar / لون الأيام في التقويم @Input() BorderColor: string | null= null; // Border color for input and popup / لون الحدود @Input() DatepickerPopupHeaderColor: string | null= null; // Header color in the popup / لون الرأس في نافذة التقويم @Input() displayFormat: string = 'iYYYY/iM/iD'; // تنسيق التاريخ الافتراضي @Input() storageFormat: string | null = null; // التنسيق المستخدم للتخزين (افتراضيًا يكون null) @Input() locale: 'en' | 'ar-SA' = 'ar-SA'; // خيار لاختيار اللغة (افتراضي: عربي) @Input() storageLocale: 'en' | 'ar-SA' = 'en'; // لغة التخزين selectedDate: moment.Moment | null = null; // Currently selected date / التاريخ المختار showDatePicker = false; // To control visibility of the datepicker popup / التحكم في إظهار التقويم showYearPopup: boolean = false; // إضافة الخاصية هنا currentViewDate: moment.Moment = moment().locale(this.locale);; // Currently displayed month in the popup / الشهر الحالي المعروض todayBtn:string = 'اليوم' onChange = (value: string) => {}; // Placeholder for Reactive Forms' change event / واجهة للتعامل مع التغييرات في Reactive Forms onTouched = () => {}; // Placeholder for Reactive Forms' touch event / واجهة للتعامل مع لمسة الحقل في Reactive Forms // أسماء الأيام (اختصار) // Short names for days of the week // Getter لتحديد أسماء الأيام بناءً على اللغة get dayNamesMin(): string[] { return this.locale === 'ar-SA' ? ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'] // عربي : ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']; // إنجليزي } constructor(private elementRef: ElementRef) {} // **Initial setup on component load** // **إعداد مبدئي عند تحميل المكون** ngOnInit() { moment.locale(this.locale); // ضبط locale بناءً على اللغة المختارة if (this.value) { this.selectedDate = moment(this.value, this.displayFormat).locale(this.locale);; // Parse the initial value / تحويل القيمة الأولية لتاريخ this.currentViewDate = this.selectedDate.clone(); // Set the view date to the selected date / عرض الشهر الخاص بالتاريخ المختار } if(this.locale == 'en') this.todayBtn = 'Today' } // إذا لم يتم تمرير `storageFormat` من المستخدم، نستخدم `displayFormat` كقيمة افتراضية get resolvedStorageFormat(): string { return this.storageFormat || this.displayFormat; } // **Property: Display the current month's name in Hijri format** // **خاصية: عرض اسم الشهر الحالي بالتنسيق الهجري** get currentMonthName(): string { return this.currentViewDate.locale(this.locale).format('iMMMM iYYYY'); // Format the current month / صيغة اسم الشهر الحالي } // **Property: Calculate and return days of the current month** // **خاصية: حساب وإرجاع أيام الشهر الحالي** get daysInCurrentMonth(): any[] { const startOfMonth = this.currentViewDate.clone().startOf('iMonth'); // Start of the month / بداية الشهر const endOfMonth = this.currentViewDate.clone().endOf('iMonth'); // End of the month / نهاية الشهر const days = []; const startDayOfWeek = startOfMonth.day(); // The first day of the month in the week / اليوم الأول من الشهر // Add empty slots for previous month's days // إضافة فراغات للأيام التي تسبق الشهر الحالي for (let i = 0; i < startDayOfWeek; i++) { days.push(null); } // Add actual days of the current month // إضافة الأيام الفعلية للشهر الحالي const totalDays = endOfMonth.iDate(); for (let d = 1; d <= totalDays; d++) { days.push(startOfMonth.clone().iDate(d).locale(this.locale)); } return days; } // **Method: Navigate to the previous month** // **طريقة: الانتقال إلى الشهر السابق** prevMonth() { this.currentViewDate = this.currentViewDate.clone().subtract(1, 'iMonth').locale(this.locale);; } // **Method: Navigate to the next month** // **طريقة: الانتقال إلى الشهر التالي** nextMonth() { this.currentViewDate = this.currentViewDate.clone().add(1, 'iMonth').locale(this.locale);; } // **Method: Select a specific date** // **طريقة: اختيار تاريخ معين** selectDate(day: moment.Moment | null) { if (day) { this.selectedDate = day.locale(this.locale); const storedValue = this.selectedDate.locale(this.storageLocale).format(this.resolvedStorageFormat); // القيمة لتخزينها في قاعدة البيانات const displayValue = this.selectedDate.locale(this.locale).format(this.displayFormat); // القيمة لعرضها this.onChange(storedValue); // إرسال القيمة المخزنة إلى النموذج this.valueChange.emit(storedValue); // إرسال القيمة المخزنة this.showDatePicker = false; // إغلاق نافذة التقويم } } // **Method: Toggle visibility of the datepicker** // **طريقة: فتح/إغلاق التقويم** toggleDatePicker() { this.currentViewDate = this.selectedDate ? this.selectedDate.clone().locale(this.locale) : moment().locale(this.locale); // Set to current date if no date is selected / عرض الشهر الحالي إذا لم يتم اختيار تاريخ this.showDatePicker = !this.showDatePicker; // Toggle visibility / تبديل الحالة } // **Method: Close the popup when clicking outside** // **طريقة: إغلاق التقويم عند النقر خارج المكون** @HostListener('document:click', ['$event.target']) onDocumentClick(targetElement: HTMLElement) { const clickedInside = this.elementRef.nativeElement.contains(targetElement); // Check if click is inside / التحقق إذا كان النقر داخل المكون if (!clickedInside) { this.showDatePicker = false; // Close the popup / إغلاق نافذة التقويم } } // **Property: Display the selected date in the input field** // **خاصية: عرض التاريخ المختار في الحقل** get displayedDate(): string { return this.selectedDate ? this.selectedDate.locale(this.locale).format(this.displayFormat) : ''; // Display formatted date or empty string / عرض التاريخ أو تركه فارغاً } // **Method: Select today's date** // **طريقة: اختيار تاريخ اليوم** selectToday() { this.selectedDate = moment().locale(this.locale); // Set the date to today / تعيين التاريخ إلى اليوم const storedValue = this.selectedDate.locale(this.storageLocale).format(this.resolvedStorageFormat); const displayValue = this.selectedDate.locale(this.storageLocale).format(this.displayFormat); this.onChange(storedValue); // Emit the value for Reactive Forms / إرسال القيمة إلى Reactive Forms this.valueChange.emit(storedValue); // Emit the value for standalone usage / إرسال القيمة عند الاستخدام الفردي this.showDatePicker = false; // Close the popup / إغلاق نافذة التقويم } // **ControlValueAccessor: Write a new value** // **واجهة: تعيين قيمة جديدة** writeValue(value: string): void { if (value) { this.selectedDate = moment(value, this.resolvedStorageFormat).locale(this.locale); // تحويل القيمة إلى لحظة بناءً على التنسيق this.currentViewDate = this.selectedDate.clone(); // Set the current view date / تعيين عرض الشهر } else { this.selectedDate = null; // Clear the selected date / مسح التاريخ المختار } } // **ControlValueAccessor: Register a change callback** // **واجهة: تسجيل دالة تغيير القيمة** registerOnChange(fn: any): void { this.onChange = fn; // Store the callback function / تخزين دالة التغيير } // **ControlValueAccessor: Register a touched callback** // **واجهة: تسجيل دالة اللمس** registerOnTouched(fn: any): void { this.onTouched = fn; // Store the callback function / تخزين دالة اللمس } toggleYearPopup() { this.showYearPopup = !this.showYearPopup; } selectYear(year: number) { this.currentViewDate = moment(this.currentViewDate).iYear(year); // تحديث باستخدام moment مباشرة this.selectedDate = this.currentViewDate.clone(); // تحديث التاريخ المختار أيضًا this.showYearPopup = false; this.onChange(this.selectedDate.format('iYYYY/iM/iD')); // إرسال التغيير إلى الـ formControl } get hijriYears(): number[] { const currentYear = moment().iYear(); const startYear = currentYear - 50; const endYear = currentYear + 50; const years = []; for (let i = startYear; i <= endYear; i++) { years.push(i); } return years; } }