@dcsl/flex-ui 0.0.14 → 0.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/fesm2022/dcsl-flex-ui.mjs +612 -63
- package/fesm2022/dcsl-flex-ui.mjs.map +1 -1
- package/index.d.ts +47 -8
- package/package.json +4 -2
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, Renderer2, HostListener, ViewChild, Component, input, EventEmitter, effect, Output, ContentChild, Input, ContentChildren, output, model, signal } from '@angular/core';
|
|
2
|
+
import { inject, Renderer2, HostListener, ViewChild, Component, input, EventEmitter, effect, Output, ContentChild, Input, ContentChildren, output, model, signal, Injectable } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/common';
|
|
4
4
|
import { CommonModule } from '@angular/common';
|
|
5
5
|
import * as i2 from '@angular/forms';
|
|
6
6
|
import { FormsModule } from '@angular/forms';
|
|
7
7
|
import dayjs from 'dayjs';
|
|
8
|
+
import calendarSystems from '@calidy/dayjs-calendarsystems';
|
|
9
|
+
import PersianCalendarSystem from '@calidy/dayjs-calendarsystems/calendarSystems/PersianCalendarSystem';
|
|
10
|
+
import HijriCalendarSystem from '@calidy/dayjs-calendarsystems/calendarSystems/HijriCalendarSystem';
|
|
11
|
+
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
|
8
12
|
|
|
9
13
|
class FlexPanelComponent {
|
|
10
14
|
resizeObserver;
|
|
@@ -437,25 +441,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImpor
|
|
|
437
441
|
//
|
|
438
442
|
// ✅ Umm al-Qura Hijri calendar with Latin digits
|
|
439
443
|
//
|
|
440
|
-
const hijriFmt = new Intl.DateTimeFormat('en-u-ca-islamic-umalqura-nu-latn', {
|
|
444
|
+
const hijriFmt$1 = new Intl.DateTimeFormat('en-u-ca-islamic-umalqura-nu-latn', {
|
|
441
445
|
day: 'numeric',
|
|
442
446
|
month: 'numeric',
|
|
443
447
|
year: 'numeric'
|
|
444
448
|
});
|
|
445
|
-
const hijriMonthNamesEn = [
|
|
449
|
+
const hijriMonthNamesEn$1 = [
|
|
446
450
|
'Muharram', 'Safar', 'Rabi I', 'Rabi II',
|
|
447
451
|
'Jumada I', 'Jumada II', 'Rajab', 'Sha’ban',
|
|
448
452
|
'Ramadan', 'Shawwal', 'Dhu al-Qi’dah', 'Dhu al-Hijjah'
|
|
449
453
|
];
|
|
450
|
-
const hijriMonthNamesAr = [
|
|
454
|
+
const hijriMonthNamesAr$1 = [
|
|
451
455
|
'محرم', 'صفر', 'ربيع الأول', 'ربيع الآخر',
|
|
452
456
|
'جمادى الأولى', 'جمادى الآخرة', 'رجب', 'شعبان',
|
|
453
457
|
'رمضان', 'شوال', 'ذو القعدة', 'ذو الحجة'
|
|
454
458
|
];
|
|
455
|
-
const weekDaysEn = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
456
|
-
const weekDaysAr = ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'];
|
|
457
|
-
function hijriFromGregorian(g) {
|
|
458
|
-
const parts = hijriFmt.formatToParts(g);
|
|
459
|
+
const weekDaysEn$1 = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
460
|
+
const weekDaysAr$1 = ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'];
|
|
461
|
+
function hijriFromGregorian$1(g) {
|
|
462
|
+
const parts = hijriFmt$1.formatToParts(g);
|
|
459
463
|
//console.log(parts);
|
|
460
464
|
const map = {};
|
|
461
465
|
for (const p of parts)
|
|
@@ -463,26 +467,26 @@ function hijriFromGregorian(g) {
|
|
|
463
467
|
const n = (k) => parseInt(map[k] ?? '0', 10);
|
|
464
468
|
return { hy: n('year'), hm: n('month'), hd: n('day') };
|
|
465
469
|
}
|
|
466
|
-
function compareHijri(a, b) {
|
|
470
|
+
function compareHijri$1(a, b) {
|
|
467
471
|
if (a.hy !== b.hy)
|
|
468
472
|
return a.hy - b.hy;
|
|
469
473
|
if (a.hm !== b.hm)
|
|
470
474
|
return a.hm - b.hm;
|
|
471
475
|
return a.hd - b.hd;
|
|
472
476
|
}
|
|
473
|
-
function findGregorianForHijri(target) {
|
|
477
|
+
function findGregorianForHijri$1(target) {
|
|
474
478
|
let lo = new Date(1937, 0, 1).getTime();
|
|
475
479
|
let hi = new Date(2076, 11, 31).getTime();
|
|
476
480
|
const dayMs = 86400000;
|
|
477
481
|
while (lo <= hi) {
|
|
478
482
|
const mid = Math.floor((lo + hi) / 2);
|
|
479
|
-
const h = hijriFromGregorian(new Date(mid));
|
|
480
|
-
compareHijri(h, target) < 0 ? (lo = mid + dayMs) : (hi = mid - dayMs);
|
|
483
|
+
const h = hijriFromGregorian$1(new Date(mid));
|
|
484
|
+
compareHijri$1(h, target) < 0 ? (lo = mid + dayMs) : (hi = mid - dayMs);
|
|
481
485
|
}
|
|
482
486
|
let d = new Date(lo);
|
|
483
487
|
while (true) {
|
|
484
|
-
const h = hijriFromGregorian(d);
|
|
485
|
-
const cmp = compareHijri(h, target);
|
|
488
|
+
const h = hijriFromGregorian$1(d);
|
|
489
|
+
const cmp = compareHijri$1(h, target);
|
|
486
490
|
if (cmp === 0)
|
|
487
491
|
return d;
|
|
488
492
|
if (cmp > 0)
|
|
@@ -498,35 +502,35 @@ class HijriCalendarComponent {
|
|
|
498
502
|
minDate = null;
|
|
499
503
|
maxDate = null;
|
|
500
504
|
/** Month + weekday language: 'en' or 'ar' */
|
|
501
|
-
monthLang = '
|
|
505
|
+
monthLang = 'ar';
|
|
502
506
|
hy = signal(0, ...(ngDevMode ? [{ debugName: "hy" }] : []));
|
|
503
507
|
hm = signal(0, ...(ngDevMode ? [{ debugName: "hm" }] : []));
|
|
504
508
|
selectedMonth = 1;
|
|
505
509
|
selectedYear = 1447;
|
|
506
510
|
yearRange = [];
|
|
507
511
|
get hijriMonthsEn() {
|
|
508
|
-
return hijriMonthNamesEn;
|
|
512
|
+
return hijriMonthNamesEn$1;
|
|
509
513
|
}
|
|
510
514
|
get hijriMonthsAr() {
|
|
511
|
-
return hijriMonthNamesAr;
|
|
515
|
+
return hijriMonthNamesAr$1;
|
|
512
516
|
}
|
|
513
517
|
get hijriMonths() {
|
|
514
|
-
return this.monthLang === 'ar' ? hijriMonthNamesAr : hijriMonthNamesEn;
|
|
518
|
+
return this.monthLang === 'ar' ? hijriMonthNamesAr$1 : hijriMonthNamesEn$1;
|
|
515
519
|
}
|
|
516
520
|
get weekDays() {
|
|
517
|
-
return this.monthLang === 'ar' ? weekDaysAr : weekDaysEn;
|
|
521
|
+
return this.monthLang === 'ar' ? weekDaysAr$1 : weekDaysEn$1;
|
|
518
522
|
}
|
|
519
523
|
get weekDaysEn() {
|
|
520
|
-
return weekDaysEn;
|
|
524
|
+
return weekDaysEn$1;
|
|
521
525
|
}
|
|
522
526
|
get weekDaysAr() {
|
|
523
|
-
return weekDaysAr;
|
|
527
|
+
return weekDaysAr$1;
|
|
524
528
|
}
|
|
525
529
|
get currentMonthName() {
|
|
526
530
|
return this.hijriMonths[this.selectedMonth - 1];
|
|
527
531
|
}
|
|
528
532
|
constructor() {
|
|
529
|
-
const h = hijriFromGregorian(new Date());
|
|
533
|
+
const h = hijriFromGregorian$1(new Date());
|
|
530
534
|
this.hy.set(h.hy);
|
|
531
535
|
this.hm.set(h.hm);
|
|
532
536
|
this.selectedYear = h.hy;
|
|
@@ -537,7 +541,7 @@ class HijriCalendarComponent {
|
|
|
537
541
|
}
|
|
538
542
|
ngOnChanges() {
|
|
539
543
|
if (this.value) {
|
|
540
|
-
const h = hijriFromGregorian(this.value);
|
|
544
|
+
const h = hijriFromGregorian$1(this.value);
|
|
541
545
|
this.hy.set(h.hy);
|
|
542
546
|
this.hm.set(h.hm);
|
|
543
547
|
this.selectedYear = h.hy;
|
|
@@ -546,8 +550,8 @@ class HijriCalendarComponent {
|
|
|
546
550
|
this.buildYearRange();
|
|
547
551
|
}
|
|
548
552
|
buildYearRange() {
|
|
549
|
-
const minH = this.minDate ? hijriFromGregorian(this.minDate) : { hy: 1356, hm: 1, hd: 1 };
|
|
550
|
-
const maxH = this.maxDate ? hijriFromGregorian(this.maxDate) : { hy: 1499, hm: 12, hd: 30 };
|
|
553
|
+
const minH = this.minDate ? hijriFromGregorian$1(this.minDate) : { hy: 1356, hm: 1, hd: 1 };
|
|
554
|
+
const maxH = this.maxDate ? hijriFromGregorian$1(this.maxDate) : { hy: 1499, hm: 12, hd: 30 };
|
|
551
555
|
this.yearRange = [];
|
|
552
556
|
for (let y = minH.hy; y <= maxH.hy; y++) {
|
|
553
557
|
this.yearRange.push(y);
|
|
@@ -558,18 +562,18 @@ class HijriCalendarComponent {
|
|
|
558
562
|
this.hm.set(this.selectedMonth);
|
|
559
563
|
}
|
|
560
564
|
firstOfMonth() {
|
|
561
|
-
return findGregorianForHijri({ hy: this.hy(), hm: this.hm(), hd: 1 });
|
|
565
|
+
return findGregorianForHijri$1({ hy: this.hy(), hm: this.hm(), hd: 1 });
|
|
562
566
|
}
|
|
563
567
|
cells() {
|
|
564
568
|
const firstG = this.firstOfMonth();
|
|
565
|
-
const firstH = hijriFromGregorian(firstG);
|
|
569
|
+
const firstH = hijriFromGregorian$1(firstG);
|
|
566
570
|
const start = new Date(firstG);
|
|
567
571
|
start.setDate(firstG.getDate() - firstG.getDay());
|
|
568
572
|
const result = [];
|
|
569
573
|
for (let i = 0; i < 42; i++) {
|
|
570
574
|
const g = new Date(start);
|
|
571
575
|
g.setDate(start.getDate() + i);
|
|
572
|
-
const h = hijriFromGregorian(g);
|
|
576
|
+
const h = hijriFromGregorian$1(g);
|
|
573
577
|
let disabled = false;
|
|
574
578
|
if (this.minDate && g < this.stripTime(this.minDate))
|
|
575
579
|
disabled = true;
|
|
@@ -613,13 +617,13 @@ class HijriCalendarComponent {
|
|
|
613
617
|
canPrev() {
|
|
614
618
|
if (!this.minDate)
|
|
615
619
|
return true;
|
|
616
|
-
const minH = hijriFromGregorian(this.stripTime(this.minDate));
|
|
620
|
+
const minH = hijriFromGregorian$1(this.stripTime(this.minDate));
|
|
617
621
|
return this.hy() > minH.hy || (this.hy() === minH.hy && this.hm() > minH.hm);
|
|
618
622
|
}
|
|
619
623
|
canNext() {
|
|
620
624
|
if (!this.maxDate)
|
|
621
625
|
return true;
|
|
622
|
-
const maxH = hijriFromGregorian(this.stripTime(this.maxDate));
|
|
626
|
+
const maxH = hijriFromGregorian$1(this.stripTime(this.maxDate));
|
|
623
627
|
return this.hy() < maxH.hy || (this.hy() === maxH.hy && this.hm() < maxH.hm);
|
|
624
628
|
}
|
|
625
629
|
isToday(g) {
|
|
@@ -637,7 +641,7 @@ class HijriCalendarComponent {
|
|
|
637
641
|
select(c) {
|
|
638
642
|
if (c.disabled)
|
|
639
643
|
return;
|
|
640
|
-
console.log(c);
|
|
644
|
+
//console.log(c);
|
|
641
645
|
this.value = c.g;
|
|
642
646
|
this.valueChange.emit(this.value);
|
|
643
647
|
}
|
|
@@ -645,11 +649,11 @@ class HijriCalendarComponent {
|
|
|
645
649
|
return new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
|
646
650
|
}
|
|
647
651
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: HijriCalendarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
648
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: HijriCalendarComponent, isStandalone: true, selector: "app-hijri-calendar", inputs: { value: "value", minDate: "minDate", maxDate: "maxDate", monthLang: "monthLang" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"card border-0 p-0\">\r\n <div class=\"card-header p-1\">\r\n <div class=\"d-flex justify-content-between align-items-center\">\r\n <button class=\"btn btn-sm\" (click)=\"prevMonth()\" [disabled]=\"!canPrev()\">«</button>\r\n <div class=\"d-flex justify-content-center gap-1\">\r\n <select class=\"form-select form-select-sm w-auto\" [(ngModel)]=\"selectedMonth\"\r\n (change)=\"onMonthYearChange()\">\r\n @for (m of hijriMonths; track $index) {\r\n <option [value]=\"$index + 1\">{{ hijriMonthsAr[$index] }} ({{ hijriMonthsEn[$index] }})</option>\r\n }\r\n </select>\r\n <select class=\"form-select form-select-sm w-auto\" [(ngModel)]=\"selectedYear\"\r\n (change)=\"onMonthYearChange()\">\r\n @for (y of yearRange; track y) {\r\n <option [value]=\"y\">{{ y }}</option>\r\n }\r\n </select>\r\n </div>\r\n <button class=\"btn btn-sm\" (click)=\"nextMonth()\" [disabled]=\"!canNext()\">»</button>\r\n </div>\r\n </div>\r\n <div class=\"card-body p-1\">\r\n\r\n\r\n <!-- month/year pickers -->\r\n\r\n\r\n <!-- weekday labels -->\r\n <div class=\"grid\">\r\n @for (w of weekDays; track $index) {\r\n <div class=\"col fw-bold\">{{ w }}</div>\r\n }\r\n\r\n </div>\r\n\r\n <!-- calendar cells -->\r\n <div class=\"grid\">\r\n <button *ngFor=\"let c of cells()\" class=\"cell btn rounded-0\" [class.muted]=\"!c.inCurrentMonth\"\r\n [class.today]=\"isToday(c.g)\" [class.selected]=\"isSelected(c.g)\" [disabled]=\"c.disabled\"\r\n (click)=\"select(c)\">\r\n {{ c.h.hd }}\r\n </button>\r\n </div>\r\n </div>\r\n</div>", styles: [".grid{display:grid;grid-template-columns:repeat(7,1fr)}.cell{border:1px solid #ddd}.cell.muted{opacity:.5}.cell.selected{background-color:#0d6efd;color:#fff}.cell:disabled{background-color:#f8f9fa;color:#aaa;cursor:not-allowed}.col{text-align:center}.btn{font-size:90%;padding:.25rem}.btn:hover{background-color:azure}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
|
|
652
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: HijriCalendarComponent, isStandalone: true, selector: "app-hijri-calendar", inputs: { value: "value", minDate: "minDate", maxDate: "maxDate", monthLang: "monthLang" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"card border-0 p-0\">\r\n <div class=\"card-header p-1\">\r\n <div class=\"d-flex justify-content-between align-items-center\">\r\n <button class=\"btn btn-sm\" (click)=\"prevMonth()\" [disabled]=\"!canPrev()\">«</button>\r\n <div class=\"d-flex justify-content-center gap-1\">\r\n <select class=\"form-select form-select-sm w-auto\" [(ngModel)]=\"selectedMonth\"\r\n (change)=\"onMonthYearChange()\">\r\n @for (m of hijriMonths; track $index) {\r\n <option [value]=\"$index + 1\">{{ hijriMonthsAr[$index] }} ({{ hijriMonthsEn[$index] }})</option>\r\n }\r\n </select>\r\n <select class=\"form-select form-select-sm w-auto\" [(ngModel)]=\"selectedYear\"\r\n (change)=\"onMonthYearChange()\">\r\n @for (y of yearRange; track y) {\r\n <option [value]=\"y\">{{ y }}</option>\r\n }\r\n </select>\r\n </div>\r\n <button class=\"btn btn-sm\" (click)=\"nextMonth()\" [disabled]=\"!canNext()\">»</button>\r\n </div>\r\n </div>\r\n <div class=\"card-body p-1\">\r\n\r\n\r\n <!-- month/year pickers -->\r\n\r\n\r\n <!-- weekday labels -->\r\n <div class=\"grid\">\r\n @for (w of weekDays; track $index) {\r\n <div class=\"col fw-bold border pb-1\">{{ w }}</div>\r\n }\r\n\r\n </div>\r\n\r\n <!-- calendar cells -->\r\n <div class=\"grid\">\r\n <button *ngFor=\"let c of cells()\" class=\"cell btn rounded-0\" [class.muted]=\"!c.inCurrentMonth\"\r\n [class.today]=\"isToday(c.g)\" [class.selected]=\"isSelected(c.g)\" [disabled]=\"c.disabled\"\r\n (click)=\"select(c)\">\r\n {{ c.h.hd }}\r\n </button>\r\n </div>\r\n </div>\r\n</div>", styles: [".grid{display:grid;grid-template-columns:repeat(7,1fr)}.cell{border:1px solid #ddd}.cell.muted{opacity:.5}.cell.selected{background-color:#0d6efd;color:#fff}.cell:disabled{background-color:#f8f9fa;color:#aaa;cursor:not-allowed}.col{text-align:center}.btn{font-size:90%;padding:.25rem .1rem}.btn:hover{background-color:azure}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
|
|
649
653
|
}
|
|
650
654
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: HijriCalendarComponent, decorators: [{
|
|
651
655
|
type: Component,
|
|
652
|
-
args: [{ selector: 'app-hijri-calendar', standalone: true, imports: [CommonModule, FormsModule], template: "<div class=\"card border-0 p-0\">\r\n <div class=\"card-header p-1\">\r\n <div class=\"d-flex justify-content-between align-items-center\">\r\n <button class=\"btn btn-sm\" (click)=\"prevMonth()\" [disabled]=\"!canPrev()\">«</button>\r\n <div class=\"d-flex justify-content-center gap-1\">\r\n <select class=\"form-select form-select-sm w-auto\" [(ngModel)]=\"selectedMonth\"\r\n (change)=\"onMonthYearChange()\">\r\n @for (m of hijriMonths; track $index) {\r\n <option [value]=\"$index + 1\">{{ hijriMonthsAr[$index] }} ({{ hijriMonthsEn[$index] }})</option>\r\n }\r\n </select>\r\n <select class=\"form-select form-select-sm w-auto\" [(ngModel)]=\"selectedYear\"\r\n (change)=\"onMonthYearChange()\">\r\n @for (y of yearRange; track y) {\r\n <option [value]=\"y\">{{ y }}</option>\r\n }\r\n </select>\r\n </div>\r\n <button class=\"btn btn-sm\" (click)=\"nextMonth()\" [disabled]=\"!canNext()\">»</button>\r\n </div>\r\n </div>\r\n <div class=\"card-body p-1\">\r\n\r\n\r\n <!-- month/year pickers -->\r\n\r\n\r\n <!-- weekday labels -->\r\n <div class=\"grid\">\r\n @for (w of weekDays; track $index) {\r\n <div class=\"col fw-bold\">{{ w }}</div>\r\n }\r\n\r\n </div>\r\n\r\n <!-- calendar cells -->\r\n <div class=\"grid\">\r\n <button *ngFor=\"let c of cells()\" class=\"cell btn rounded-0\" [class.muted]=\"!c.inCurrentMonth\"\r\n [class.today]=\"isToday(c.g)\" [class.selected]=\"isSelected(c.g)\" [disabled]=\"c.disabled\"\r\n (click)=\"select(c)\">\r\n {{ c.h.hd }}\r\n </button>\r\n </div>\r\n </div>\r\n</div>", styles: [".grid{display:grid;grid-template-columns:repeat(7,1fr)}.cell{border:1px solid #ddd}.cell.muted{opacity:.5}.cell.selected{background-color:#0d6efd;color:#fff}.cell:disabled{background-color:#f8f9fa;color:#aaa;cursor:not-allowed}.col{text-align:center}.btn{font-size:90%;padding:.25rem}.btn:hover{background-color:azure}\n"] }]
|
|
656
|
+
args: [{ selector: 'app-hijri-calendar', standalone: true, imports: [CommonModule, FormsModule], template: "<div class=\"card border-0 p-0\">\r\n <div class=\"card-header p-1\">\r\n <div class=\"d-flex justify-content-between align-items-center\">\r\n <button class=\"btn btn-sm\" (click)=\"prevMonth()\" [disabled]=\"!canPrev()\">«</button>\r\n <div class=\"d-flex justify-content-center gap-1\">\r\n <select class=\"form-select form-select-sm w-auto\" [(ngModel)]=\"selectedMonth\"\r\n (change)=\"onMonthYearChange()\">\r\n @for (m of hijriMonths; track $index) {\r\n <option [value]=\"$index + 1\">{{ hijriMonthsAr[$index] }} ({{ hijriMonthsEn[$index] }})</option>\r\n }\r\n </select>\r\n <select class=\"form-select form-select-sm w-auto\" [(ngModel)]=\"selectedYear\"\r\n (change)=\"onMonthYearChange()\">\r\n @for (y of yearRange; track y) {\r\n <option [value]=\"y\">{{ y }}</option>\r\n }\r\n </select>\r\n </div>\r\n <button class=\"btn btn-sm\" (click)=\"nextMonth()\" [disabled]=\"!canNext()\">»</button>\r\n </div>\r\n </div>\r\n <div class=\"card-body p-1\">\r\n\r\n\r\n <!-- month/year pickers -->\r\n\r\n\r\n <!-- weekday labels -->\r\n <div class=\"grid\">\r\n @for (w of weekDays; track $index) {\r\n <div class=\"col fw-bold border pb-1\">{{ w }}</div>\r\n }\r\n\r\n </div>\r\n\r\n <!-- calendar cells -->\r\n <div class=\"grid\">\r\n <button *ngFor=\"let c of cells()\" class=\"cell btn rounded-0\" [class.muted]=\"!c.inCurrentMonth\"\r\n [class.today]=\"isToday(c.g)\" [class.selected]=\"isSelected(c.g)\" [disabled]=\"c.disabled\"\r\n (click)=\"select(c)\">\r\n {{ c.h.hd }}\r\n </button>\r\n </div>\r\n </div>\r\n</div>", styles: [".grid{display:grid;grid-template-columns:repeat(7,1fr)}.cell{border:1px solid #ddd}.cell.muted{opacity:.5}.cell.selected{background-color:#0d6efd;color:#fff}.cell:disabled{background-color:#f8f9fa;color:#aaa;cursor:not-allowed}.col{text-align:center}.btn{font-size:90%;padding:.25rem .1rem}.btn:hover{background-color:azure}\n"] }]
|
|
653
657
|
}], ctorParameters: () => [], propDecorators: { value: [{
|
|
654
658
|
type: Input
|
|
655
659
|
}], valueChange: [{
|
|
@@ -662,24 +666,90 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImpor
|
|
|
662
666
|
type: Input
|
|
663
667
|
}] } });
|
|
664
668
|
|
|
669
|
+
// Extend Day.js with calendar systems support
|
|
670
|
+
dayjs.extend(calendarSystems);
|
|
671
|
+
dayjs.extend(customParseFormat);
|
|
672
|
+
// Register the Persian calendar
|
|
673
|
+
dayjs.registerCalendarSystem('persian', new PersianCalendarSystem());
|
|
674
|
+
dayjs.registerCalendarSystem('islamic', new HijriCalendarSystem());
|
|
675
|
+
class DateServiceService {
|
|
676
|
+
log(date) {
|
|
677
|
+
//var dj = dayjs(date);
|
|
678
|
+
// // Convert current date to Persian calendar
|
|
679
|
+
// const persianDate = dj.toCalendarSystem('persian');
|
|
680
|
+
// console.log(persianDate.format('YYYY-MM-DD')); // Example: "1403-08-15"
|
|
681
|
+
// // Create a date from Persian calendar
|
|
682
|
+
// const nowruz = dayjs.fromCalendarSystem('persian', 1403, 1, 1);
|
|
683
|
+
// console.log(nowruz.format('YYYY-MM-DD')); // "2024-03-20"
|
|
684
|
+
// const hijriDate = dj.toCalendarSystem('islamic');
|
|
685
|
+
// console.log(hijriDate.format('YYYY-MM-DD')); // e.g., "1446-03-15"
|
|
686
|
+
//Step 1: User will input gregorian or islamic(hijri) date. for this we will use customParseFormat
|
|
687
|
+
//in the control we will specify the format using binding.
|
|
688
|
+
const d = dayjs("18-05-1447", "DD-MM-YYYY");
|
|
689
|
+
//we are writing this to parse the date , month, year only.
|
|
690
|
+
//then based on the calender type we will decide.
|
|
691
|
+
//but by default system will take gregorian
|
|
692
|
+
const customDate = dayjs.fromCalendarSystem('islamic', d.year(), d.month() + 1, d.date());
|
|
693
|
+
//here we are assuming the control want to handle islamic calender date, we will take it dynmically.
|
|
694
|
+
console.log("Prased Date in Gregorian String as per format ['DD-MM-YYYY']:", customDate.format('DD-MM-YYYY'));
|
|
695
|
+
//here it will show gregorian calender, because even if we parse using islamic calender, it will convert the date
|
|
696
|
+
//and keep it to gregorian
|
|
697
|
+
//to show islamic date, we have to tell the dayjs to use islamic calender
|
|
698
|
+
console.log("Hijri Date String: ", customDate.toCalendarSystem('islamic').format('YYYY-MM-DD'));
|
|
699
|
+
// console.log(d.format("DD/MM/YYYY"));
|
|
700
|
+
// console.log(d.date());
|
|
701
|
+
// console.log(d.month() + 1);
|
|
702
|
+
// console.log(d.year());
|
|
703
|
+
// console.log(d.toDate());
|
|
704
|
+
// const d2 = dayjs("09-11-2025", "DD-MM-YYYY");
|
|
705
|
+
// console.log(d2.format("DD/MM/YYYY"));
|
|
706
|
+
// console.log(d2.date());
|
|
707
|
+
// console.log(d2.month() + 1);
|
|
708
|
+
// console.log(d2.year());
|
|
709
|
+
// console.log(d2.toDate());
|
|
710
|
+
// // Create a Day.js instance from a specific calendar system
|
|
711
|
+
// const hijriCustomDate = dayjs.fromCalendarSystem('islamic', 1447, 5, 18);
|
|
712
|
+
// console.log(hijriCustomDate.toString());
|
|
713
|
+
// // The result is a standard Day.js instance in that calendar system
|
|
714
|
+
// console.log(hijriCustomDate.toCalendarSystem('islamic').format('YYYY-MM-DD')); // In Persian: "1403-01-01"
|
|
715
|
+
// // Convert to Gregorian to see the equivalent date
|
|
716
|
+
// const gregorian = hijriCustomDate.toCalendarSystem('gregory');
|
|
717
|
+
// console.log(gregorian.format('YYYY-MM-DD')); // "2024-03-20"
|
|
718
|
+
}
|
|
719
|
+
format(date, calendar, format) {
|
|
720
|
+
const d = dayjs(date);
|
|
721
|
+
return d.toCalendarSystem(calendar).format(format);
|
|
722
|
+
}
|
|
723
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: DateServiceService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
724
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: DateServiceService, providedIn: 'root' });
|
|
725
|
+
}
|
|
726
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: DateServiceService, decorators: [{
|
|
727
|
+
type: Injectable,
|
|
728
|
+
args: [{
|
|
729
|
+
providedIn: 'root'
|
|
730
|
+
}]
|
|
731
|
+
}] });
|
|
732
|
+
|
|
665
733
|
class HijriDatepickerComponent {
|
|
734
|
+
dateService;
|
|
666
735
|
value = model(...(ngDevMode ? [undefined, { debugName: "value" }] : []));
|
|
667
|
-
valueChange = output();
|
|
668
736
|
selectedDate;
|
|
669
737
|
showCalendar = false;
|
|
738
|
+
calender = input("gregory", ...(ngDevMode ? [{ debugName: "calender" }] : []));
|
|
670
739
|
enabled = input(true, ...(ngDevMode ? [{ debugName: "enabled" }] : []));
|
|
671
740
|
cssClass = input(...(ngDevMode ? [undefined, { debugName: "cssClass" }] : []));
|
|
672
|
-
format = input(...(ngDevMode ? [
|
|
741
|
+
format = input("DD/MM/YYYY", ...(ngDevMode ? [{ debugName: "format" }] : []));
|
|
673
742
|
inputDate = "";
|
|
674
743
|
isMobile = false;
|
|
675
|
-
constructor() {
|
|
744
|
+
constructor(dateService) {
|
|
745
|
+
this.dateService = dateService;
|
|
676
746
|
effect(() => {
|
|
677
747
|
const iso = this.value();
|
|
678
748
|
if (iso) {
|
|
679
749
|
const date = dayjs(iso);
|
|
680
750
|
if (date.isValid()) {
|
|
681
751
|
this.selectedDate = date.toDate();
|
|
682
|
-
this.inputDate = this.
|
|
752
|
+
this.inputDate = this.formatDate(this.selectedDate, this.calender(), this.format());
|
|
683
753
|
}
|
|
684
754
|
return;
|
|
685
755
|
}
|
|
@@ -691,7 +761,8 @@ class HijriDatepickerComponent {
|
|
|
691
761
|
this.showCalendar = !this.showCalendar;
|
|
692
762
|
}
|
|
693
763
|
onDateSelectedFromCalender(e) {
|
|
694
|
-
console.log("onDateSelectedFromCalender: ", e);
|
|
764
|
+
// console.log("onDateSelectedFromCalender: ", e);
|
|
765
|
+
this.dateService.log(e);
|
|
695
766
|
//var time = this.selectedDate ?? new Date();
|
|
696
767
|
// e.setHours(time.getHours());
|
|
697
768
|
// e.setMinutes(time.getMinutes());
|
|
@@ -714,23 +785,18 @@ class HijriDatepickerComponent {
|
|
|
714
785
|
/** Reusable update method */
|
|
715
786
|
updateDate(d) {
|
|
716
787
|
this.selectedDate = d;
|
|
717
|
-
this.inputDate = this.formatDateForInput(d);
|
|
788
|
+
//this.inputDate = this.formatDateForInput(d);
|
|
718
789
|
this.value.set(d);
|
|
719
|
-
this.valueChange.emit(d);
|
|
790
|
+
//this.valueChange.emit(d);
|
|
720
791
|
}
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
// return dayjs(date)
|
|
724
|
-
// .toCalendarSystem('islamic') // or 'islamic-umalqura'
|
|
725
|
-
// .locale(locale)
|
|
726
|
-
// .format(format);
|
|
727
|
-
return "";
|
|
728
|
-
}
|
|
729
|
-
formatDateForInput(date) {
|
|
730
|
-
const fmt = this.format()?.toUpperCase() ?? "DD/MM/YYYY";
|
|
731
|
-
//return dayjs(date).format(fmt);
|
|
732
|
-
return this.formatHijri(date, fmt);
|
|
792
|
+
formatDate(date, calender, format) {
|
|
793
|
+
return this.dateService.format(date, calender, format);
|
|
733
794
|
}
|
|
795
|
+
// private formatDateForInput(date: Date): string {
|
|
796
|
+
// const fmt = this.format()?.toUpperCase() ?? "DD/MM/YYYY";
|
|
797
|
+
// //return dayjs(date).format(fmt);
|
|
798
|
+
// return this.formatDate(date, fmt);
|
|
799
|
+
// }
|
|
734
800
|
parseInputDate(str) {
|
|
735
801
|
const fmt = this.format()?.toUpperCase() ?? "DD/MM/YYYY";
|
|
736
802
|
console.log(str, "-", fmt);
|
|
@@ -757,13 +823,13 @@ class HijriDatepickerComponent {
|
|
|
757
823
|
}
|
|
758
824
|
this.showCalendar = false;
|
|
759
825
|
}
|
|
760
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: HijriDatepickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
761
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: HijriDatepickerComponent, isStandalone: true, selector: "app-hijri-datepicker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, enabled: { classPropertyName: "enabled", publicName: "enabled", isSignal: true, isRequired: false, transformFunction: null }, cssClass: { classPropertyName: "cssClass", publicName: "cssClass", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange"
|
|
826
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: HijriDatepickerComponent, deps: [{ token: DateServiceService }], target: i0.ɵɵFactoryTarget.Component });
|
|
827
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: HijriDatepickerComponent, isStandalone: true, selector: "app-hijri-datepicker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, calender: { classPropertyName: "calender", publicName: "calender", isSignal: true, isRequired: false, transformFunction: null }, enabled: { classPropertyName: "enabled", publicName: "enabled", isSignal: true, isRequired: false, transformFunction: null }, cssClass: { classPropertyName: "cssClass", publicName: "cssClass", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, viewQueries: [{ propertyName: "inputDiv", first: true, predicate: ["inputDiv"], descendants: true }, { propertyName: "modalDiv", first: true, predicate: ["modalDiv"], descendants: true }, { propertyName: "popupDiv", first: true, predicate: ["popupDiv"], descendants: true }], ngImport: i0, template: "<div class=\"input-group flex-nowrap\" #inputDiv>\r\n <input type=\"text\" class=\"form-control\" [(ngModel)]=\"inputDate\" (blur)=\"onManualDateEntered()\"\r\n [disabled]=\"!enabled()\" [placeholder]=\"format()?.toUpperCase()\" (keyup.enter)=\"onManualDateEntered()\">\r\n\r\n <button class=\"btn btn-outline-secondary\" type=\"button\" (click)=\"toggleCalendar()\" [disabled]=\"!enabled()\">\r\n <i class=\"bi bi-calendar3\"></i>\r\n </button>\r\n</div>\r\n\r\n@if(showCalendar){\r\n@if(isMobile){\r\n<div class=\"modal modal-backdrop\" tabindex=\"-1\" role=\"dialog\" style=\"display: block;\" #modalDiv>\r\n <div class=\"modal-dialog modal-dialog-scrollable\" role=\"document\">\r\n <div class=\"modal-content\">\r\n <div class=\"modal-header\">\r\n <button type=\"button\" class=\"btn btn-danger close ms-auto btn-sm\" (click)=\"showCalendar = false\">\r\n <i class=\"bi bi-x\"></i>\r\n </button>\r\n </div>\r\n <div class=\"modal-body\">\r\n\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n}\r\n@else{\r\n<div class=\"dropdown-menu show\" #popupDiv>\r\n <app-hijri-calendar (valueChange)=\"onDateSelectedFromCalender($event)\"></app-hijri-calendar>\r\n</div>\r\n}\r\n}", styles: [".dropdown-menu{--bs-dropdown-padding-y: 0}.card-body{padding:0!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: HijriCalendarComponent, selector: "app-hijri-calendar", inputs: ["value", "minDate", "maxDate", "monthLang"], outputs: ["valueChange"] }] });
|
|
762
828
|
}
|
|
763
829
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: HijriDatepickerComponent, decorators: [{
|
|
764
830
|
type: Component,
|
|
765
831
|
args: [{ selector: 'app-hijri-datepicker', imports: [CommonModule, FormsModule, HijriCalendarComponent], template: "<div class=\"input-group flex-nowrap\" #inputDiv>\r\n <input type=\"text\" class=\"form-control\" [(ngModel)]=\"inputDate\" (blur)=\"onManualDateEntered()\"\r\n [disabled]=\"!enabled()\" [placeholder]=\"format()?.toUpperCase()\" (keyup.enter)=\"onManualDateEntered()\">\r\n\r\n <button class=\"btn btn-outline-secondary\" type=\"button\" (click)=\"toggleCalendar()\" [disabled]=\"!enabled()\">\r\n <i class=\"bi bi-calendar3\"></i>\r\n </button>\r\n</div>\r\n\r\n@if(showCalendar){\r\n@if(isMobile){\r\n<div class=\"modal modal-backdrop\" tabindex=\"-1\" role=\"dialog\" style=\"display: block;\" #modalDiv>\r\n <div class=\"modal-dialog modal-dialog-scrollable\" role=\"document\">\r\n <div class=\"modal-content\">\r\n <div class=\"modal-header\">\r\n <button type=\"button\" class=\"btn btn-danger close ms-auto btn-sm\" (click)=\"showCalendar = false\">\r\n <i class=\"bi bi-x\"></i>\r\n </button>\r\n </div>\r\n <div class=\"modal-body\">\r\n\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n}\r\n@else{\r\n<div class=\"dropdown-menu show\" #popupDiv>\r\n <app-hijri-calendar (valueChange)=\"onDateSelectedFromCalender($event)\"></app-hijri-calendar>\r\n</div>\r\n}\r\n}", styles: [".dropdown-menu{--bs-dropdown-padding-y: 0}.card-body{padding:0!important}\n"] }]
|
|
766
|
-
}], ctorParameters: () => [], propDecorators: { inputDiv: [{
|
|
832
|
+
}], ctorParameters: () => [{ type: DateServiceService }], propDecorators: { inputDiv: [{
|
|
767
833
|
type: ViewChild,
|
|
768
834
|
args: ["inputDiv"]
|
|
769
835
|
}], modalDiv: [{
|
|
@@ -780,7 +846,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImpor
|
|
|
780
846
|
class FlexContainerComponent {
|
|
781
847
|
renderer;
|
|
782
848
|
contentHost;
|
|
783
|
-
resizeHandler;
|
|
849
|
+
//private resizeHandler!: () => void;
|
|
784
850
|
// Allow consumer to provide templates
|
|
785
851
|
headerTpl;
|
|
786
852
|
footerTpl;
|
|
@@ -790,13 +856,16 @@ class FlexContainerComponent {
|
|
|
790
856
|
}
|
|
791
857
|
ngAfterViewInit() {
|
|
792
858
|
this.resizeContent();
|
|
793
|
-
this.resizeHandler = this.resizeContent.bind(this);
|
|
794
|
-
window.addEventListener('resize', this.resizeHandler);
|
|
859
|
+
//this.resizeHandler = this.resizeContent.bind(this);
|
|
860
|
+
//window.addEventListener('resize', this.resizeHandler);
|
|
795
861
|
}
|
|
796
862
|
ngOnDestroy() {
|
|
797
|
-
if (this.resizeHandler) {
|
|
798
|
-
|
|
799
|
-
}
|
|
863
|
+
// if (this.resizeHandler) {
|
|
864
|
+
// window.removeEventListener('resize', this.resizeHandler);
|
|
865
|
+
// }
|
|
866
|
+
}
|
|
867
|
+
onWindowResize() {
|
|
868
|
+
this.resizeContent();
|
|
800
869
|
}
|
|
801
870
|
resizeContent() {
|
|
802
871
|
const parent = this.contentHost.nativeElement.parentElement;
|
|
@@ -807,9 +876,10 @@ class FlexContainerComponent {
|
|
|
807
876
|
const footerHeight = this.contentHost.nativeElement.nextElementSibling?.clientHeight || 0;
|
|
808
877
|
const available = parentHeight - headerHeight - footerHeight;
|
|
809
878
|
this.renderer.setStyle(this.contentHost.nativeElement, 'height', `${available}px`);
|
|
879
|
+
this.renderer.setStyle(this.contentHost.nativeElement, 'width', `${parent.clientWidth}px`);
|
|
810
880
|
}
|
|
811
881
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: FlexContainerComponent, deps: [{ token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component });
|
|
812
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: FlexContainerComponent, isStandalone: true, selector: "flex-container", queries: [{ propertyName: "headerTpl", first: true, predicate: ["header"], descendants: true }, { propertyName: "footerTpl", first: true, predicate: ["footer"], descendants: true }, { propertyName: "contentTpl", first: true, predicate: ["content"], descendants: true }], viewQueries: [{ propertyName: "contentHost", first: true, predicate: ["contentHost"], descendants: true }], ngImport: i0, template: "<div class=\"flex-container\">\r\n @if(headerTpl){\r\n <div class=\"header\">\r\n <ng-container *ngTemplateOutlet=\"headerTpl\"></ng-container>\r\n </div>\r\n }\r\n\r\n\r\n <div #contentHost class=\"content\">\r\n <ng-container *ngTemplateOutlet=\"contentTpl\"></ng-container>\r\n </div>\r\n @if(footerTpl){\r\n <div class=\"footer\">\r\n <ng-container *ngTemplateOutlet=\"footerTpl\"></ng-container>\r\n </div>\r\n }\r\n\r\n</div>", styles: [".flex-container{display:flex;flex-direction:column;height:100%}.header,.footer{flex:0 0 auto}.content{flex:1 1 auto;min-height:0;overflow:hidden}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
|
|
882
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: FlexContainerComponent, isStandalone: true, selector: "flex-container", host: { listeners: { "window:resize": "onWindowResize()" } }, queries: [{ propertyName: "headerTpl", first: true, predicate: ["header"], descendants: true }, { propertyName: "footerTpl", first: true, predicate: ["footer"], descendants: true }, { propertyName: "contentTpl", first: true, predicate: ["content"], descendants: true }], viewQueries: [{ propertyName: "contentHost", first: true, predicate: ["contentHost"], descendants: true }], ngImport: i0, template: "<div class=\"flex-container\">\r\n @if(headerTpl){\r\n <div class=\"header\">\r\n <ng-container *ngTemplateOutlet=\"headerTpl\"></ng-container>\r\n </div>\r\n }\r\n\r\n\r\n <div #contentHost class=\"content\">\r\n <ng-container *ngTemplateOutlet=\"contentTpl\"></ng-container>\r\n </div>\r\n @if(footerTpl){\r\n <div class=\"footer\">\r\n <ng-container *ngTemplateOutlet=\"footerTpl\"></ng-container>\r\n </div>\r\n }\r\n\r\n</div>", styles: [".flex-container{display:flex;flex-direction:column;height:100%}.header,.footer{flex:0 0 auto}.content{flex:1 1 auto;min-height:0;overflow:hidden}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
|
|
813
883
|
}
|
|
814
884
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: FlexContainerComponent, decorators: [{
|
|
815
885
|
type: Component,
|
|
@@ -826,6 +896,485 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImpor
|
|
|
826
896
|
}], contentTpl: [{
|
|
827
897
|
type: ContentChild,
|
|
828
898
|
args: ['content']
|
|
899
|
+
}], onWindowResize: [{
|
|
900
|
+
type: HostListener,
|
|
901
|
+
args: ['window:resize']
|
|
902
|
+
}] } });
|
|
903
|
+
|
|
904
|
+
// ✅ Intl formatters
|
|
905
|
+
const hijriFmt = new Intl.DateTimeFormat('en-u-ca-islamic-umalqura-nu-latn', {
|
|
906
|
+
day: 'numeric',
|
|
907
|
+
month: 'numeric',
|
|
908
|
+
year: 'numeric'
|
|
909
|
+
});
|
|
910
|
+
const hijriMonthNamesEn = [
|
|
911
|
+
'Muharram', 'Safar', 'Rabi I', 'Rabi II',
|
|
912
|
+
'Jumada I', 'Jumada II', 'Rajab', 'Sha’ban',
|
|
913
|
+
'Ramadan', 'Shawwal', 'Dhu al-Qi’dah', 'Dhu al-Hijjah'
|
|
914
|
+
];
|
|
915
|
+
const hijriMonthNamesAr = [
|
|
916
|
+
'محرم', 'صفر', 'ربيع الأول', 'ربيع الآخر',
|
|
917
|
+
'جمادى الأولى', 'جمادى الآخرة', 'رجب', 'شعبان',
|
|
918
|
+
'رمضان', 'شوال', 'ذو القعدة', 'ذو الحجة'
|
|
919
|
+
];
|
|
920
|
+
const gregMonthNamesEn = [
|
|
921
|
+
'January', 'February', 'March', 'April', 'May', 'June',
|
|
922
|
+
'July', 'August', 'September', 'October', 'November', 'December'
|
|
923
|
+
];
|
|
924
|
+
const gregMonthNamesAr = [
|
|
925
|
+
'يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو',
|
|
926
|
+
'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'
|
|
927
|
+
];
|
|
928
|
+
const weekDaysEn = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
|
|
929
|
+
const weekDaysAr = ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'];
|
|
930
|
+
function hijriFromGregorian(g) {
|
|
931
|
+
const parts = hijriFmt.formatToParts(g);
|
|
932
|
+
const map = {};
|
|
933
|
+
for (const p of parts)
|
|
934
|
+
map[p.type] = p.value;
|
|
935
|
+
const n = (k) => parseInt(map[k] ?? '0', 10);
|
|
936
|
+
return { hy: n('year'), hm: n('month'), hd: n('day') };
|
|
937
|
+
}
|
|
938
|
+
function compareHijri(a, b) {
|
|
939
|
+
if (a.hy !== b.hy)
|
|
940
|
+
return a.hy - b.hy;
|
|
941
|
+
if (a.hm !== b.hm)
|
|
942
|
+
return a.hm - b.hm;
|
|
943
|
+
return a.hd - b.hd;
|
|
944
|
+
}
|
|
945
|
+
function findGregorianForHijri(target) {
|
|
946
|
+
let lo = new Date(1937, 0, 1).getTime();
|
|
947
|
+
let hi = new Date(2076, 11, 31).getTime();
|
|
948
|
+
const dayMs = 86400000;
|
|
949
|
+
while (lo <= hi) {
|
|
950
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
951
|
+
const h = hijriFromGregorian(new Date(mid));
|
|
952
|
+
compareHijri(h, target) < 0 ? (lo = mid + dayMs) : (hi = mid - dayMs);
|
|
953
|
+
}
|
|
954
|
+
let d = new Date(lo);
|
|
955
|
+
while (true) {
|
|
956
|
+
const h = hijriFromGregorian(d);
|
|
957
|
+
const cmp = compareHijri(h, target);
|
|
958
|
+
if (cmp === 0)
|
|
959
|
+
return d;
|
|
960
|
+
if (cmp > 0)
|
|
961
|
+
throw new Error('Hijri date out of supported range');
|
|
962
|
+
d = new Date(d.getTime() + dayMs);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
function stripTime(d) {
|
|
966
|
+
return new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
|
967
|
+
}
|
|
968
|
+
class FlexDualCalendarComponent {
|
|
969
|
+
/** Two-way bound selected date (Gregorian) */
|
|
970
|
+
value;
|
|
971
|
+
valueChange = new EventEmitter();
|
|
972
|
+
/** Optional min/max (Gregorian) */
|
|
973
|
+
minDate;
|
|
974
|
+
maxDate;
|
|
975
|
+
/** Month/weekday language */
|
|
976
|
+
monthLang = 'en';
|
|
977
|
+
/** Start mode, default Hijri */
|
|
978
|
+
startMode = 'gregory';
|
|
979
|
+
/** Current mode (hijri | gregory) */
|
|
980
|
+
mode = signal('gregory', ...(ngDevMode ? [{ debugName: "mode" }] : []));
|
|
981
|
+
/** Hijri view state */
|
|
982
|
+
hy = signal(0, ...(ngDevMode ? [{ debugName: "hy" }] : []));
|
|
983
|
+
hm = signal(0, ...(ngDevMode ? [{ debugName: "hm" }] : []));
|
|
984
|
+
/** Gregorian view state */
|
|
985
|
+
gy = signal(0, ...(ngDevMode ? [{ debugName: "gy" }] : []));
|
|
986
|
+
gm = signal(0, ...(ngDevMode ? [{ debugName: "gm" }] : [])); // 1..12
|
|
987
|
+
/** selectors */
|
|
988
|
+
selectedMonth = 1;
|
|
989
|
+
selectedYear = 1447;
|
|
990
|
+
yearRange = [];
|
|
991
|
+
// ---- getters for UI ----
|
|
992
|
+
get hijriMonths() {
|
|
993
|
+
return this.monthLang === 'ar' ? hijriMonthNamesAr : hijriMonthNamesEn;
|
|
994
|
+
}
|
|
995
|
+
get gregMonths() {
|
|
996
|
+
return this.monthLang === 'ar' ? gregMonthNamesAr : gregMonthNamesEn;
|
|
997
|
+
}
|
|
998
|
+
get weekDays() {
|
|
999
|
+
return this.monthLang === 'ar' ? weekDaysAr : weekDaysEn;
|
|
1000
|
+
}
|
|
1001
|
+
get currentMonthName() {
|
|
1002
|
+
return this.mode() === 'islamic'
|
|
1003
|
+
? this.hijriMonths[this.selectedMonth - 1]
|
|
1004
|
+
: this.gregMonths[this.selectedMonth - 1];
|
|
1005
|
+
}
|
|
1006
|
+
constructor() {
|
|
1007
|
+
const today = new Date();
|
|
1008
|
+
const h = hijriFromGregorian(today);
|
|
1009
|
+
this.hy.set(h.hy);
|
|
1010
|
+
this.hm.set(h.hm);
|
|
1011
|
+
this.gy.set(today.getFullYear());
|
|
1012
|
+
this.gm.set(today.getMonth() + 1);
|
|
1013
|
+
// initialize selectors with start mode
|
|
1014
|
+
this.mode.set(this.startMode);
|
|
1015
|
+
if (this.mode() === 'islamic') {
|
|
1016
|
+
this.selectedYear = this.hy();
|
|
1017
|
+
this.selectedMonth = this.hm();
|
|
1018
|
+
}
|
|
1019
|
+
else {
|
|
1020
|
+
this.selectedYear = this.gy();
|
|
1021
|
+
this.selectedMonth = this.gm();
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
ngOnInit() {
|
|
1025
|
+
this.buildYearRange();
|
|
1026
|
+
}
|
|
1027
|
+
ngOnChanges(changes) {
|
|
1028
|
+
// If value changed from outside, update both calendars
|
|
1029
|
+
if (changes['value'] && this.value instanceof Date) {
|
|
1030
|
+
const g = stripTime(this.value);
|
|
1031
|
+
const h = hijriFromGregorian(g);
|
|
1032
|
+
this.gy.set(g.getFullYear());
|
|
1033
|
+
this.gm.set(g.getMonth() + 1);
|
|
1034
|
+
this.hy.set(h.hy);
|
|
1035
|
+
this.hm.set(h.hm);
|
|
1036
|
+
// Update dropdowns based on current mode
|
|
1037
|
+
if (this.mode() === 'islamic') {
|
|
1038
|
+
this.selectedYear = this.hy();
|
|
1039
|
+
this.selectedMonth = this.hm();
|
|
1040
|
+
}
|
|
1041
|
+
else {
|
|
1042
|
+
this.selectedYear = this.gy();
|
|
1043
|
+
this.selectedMonth = this.gm();
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
// Rebuild year range if min/max changed
|
|
1047
|
+
if (changes['minDate'] || changes['maxDate'] || changes['value']) {
|
|
1048
|
+
this.buildYearRange();
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
// Build years based on min/max expressed in Hijri for Hijri mode, Gregorian for Gregorian mode
|
|
1052
|
+
buildYearRange() {
|
|
1053
|
+
if (this.mode() === 'islamic') {
|
|
1054
|
+
const minH = this.minDate ? hijriFromGregorian(stripTime(this.minDate)) : { hy: 1356, hm: 1, hd: 1 };
|
|
1055
|
+
const maxH = this.maxDate ? hijriFromGregorian(stripTime(this.maxDate)) : { hy: 1499, hm: 12, hd: 30 };
|
|
1056
|
+
this.yearRange = [];
|
|
1057
|
+
for (let y = minH.hy; y <= maxH.hy; y++)
|
|
1058
|
+
this.yearRange.push(y);
|
|
1059
|
+
}
|
|
1060
|
+
else {
|
|
1061
|
+
const minG = this.minDate ? stripTime(this.minDate) : new Date(1937, 0, 1);
|
|
1062
|
+
const maxG = this.maxDate ? stripTime(this.maxDate) : new Date(2076, 11, 31);
|
|
1063
|
+
this.yearRange = [];
|
|
1064
|
+
for (let y = minG.getFullYear(); y <= maxG.getFullYear(); y++)
|
|
1065
|
+
this.yearRange.push(y);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
// 🔁 Mode switch (SYNCED view — keep same instant)
|
|
1069
|
+
switchMode(value) {
|
|
1070
|
+
const pivot = this.value ?? this.firstOfMonth(); // use selected date or current view start
|
|
1071
|
+
if (value === 'gregory') {
|
|
1072
|
+
// To Gregorian mode
|
|
1073
|
+
const g = stripTime(pivot);
|
|
1074
|
+
this.gy.set(g.getFullYear());
|
|
1075
|
+
this.gm.set(g.getMonth() + 1);
|
|
1076
|
+
this.mode.set('gregory');
|
|
1077
|
+
this.selectedYear = this.gy();
|
|
1078
|
+
this.selectedMonth = this.gm();
|
|
1079
|
+
}
|
|
1080
|
+
else {
|
|
1081
|
+
// To Hijri mode
|
|
1082
|
+
const h = hijriFromGregorian(stripTime(pivot));
|
|
1083
|
+
this.hy.set(h.hy);
|
|
1084
|
+
this.hm.set(h.hm);
|
|
1085
|
+
this.mode.set('islamic');
|
|
1086
|
+
this.selectedYear = this.hy();
|
|
1087
|
+
this.selectedMonth = this.hm();
|
|
1088
|
+
}
|
|
1089
|
+
this.buildYearRange();
|
|
1090
|
+
}
|
|
1091
|
+
// called when dropdowns change
|
|
1092
|
+
onMonthYearChange() {
|
|
1093
|
+
if (this.mode() === 'islamic') {
|
|
1094
|
+
this.hy.set(this.selectedYear);
|
|
1095
|
+
this.hm.set(this.selectedMonth);
|
|
1096
|
+
}
|
|
1097
|
+
else {
|
|
1098
|
+
this.gy.set(this.selectedYear);
|
|
1099
|
+
this.gm.set(this.selectedMonth);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
// Start of visible month (Gregorian date)
|
|
1103
|
+
firstOfMonth() {
|
|
1104
|
+
if (this.mode() === 'islamic') {
|
|
1105
|
+
return findGregorianForHijri({ hy: this.hy(), hm: this.hm(), hd: 1 });
|
|
1106
|
+
}
|
|
1107
|
+
return new Date(this.gy(), this.gm() - 1, 1);
|
|
1108
|
+
}
|
|
1109
|
+
// Calendar cells for current view
|
|
1110
|
+
cells() {
|
|
1111
|
+
const firstG = this.firstOfMonth();
|
|
1112
|
+
const firstH = hijriFromGregorian(firstG);
|
|
1113
|
+
// grid start (Sunday-first; adjust if you want Saturday-first)
|
|
1114
|
+
const start = new Date(firstG);
|
|
1115
|
+
start.setDate(firstG.getDate() - firstG.getDay());
|
|
1116
|
+
const result = [];
|
|
1117
|
+
for (let i = 0; i < 42; i++) {
|
|
1118
|
+
const g = new Date(start);
|
|
1119
|
+
g.setDate(start.getDate() + i);
|
|
1120
|
+
const h = hijriFromGregorian(g);
|
|
1121
|
+
let disabled = false;
|
|
1122
|
+
if (this.minDate && g < stripTime(this.minDate))
|
|
1123
|
+
disabled = true;
|
|
1124
|
+
if (this.maxDate && g > stripTime(this.maxDate))
|
|
1125
|
+
disabled = true;
|
|
1126
|
+
const inCurrentMonth = this.mode() === 'islamic'
|
|
1127
|
+
? (h.hy === firstH.hy && h.hm === firstH.hm)
|
|
1128
|
+
: (g.getFullYear() === firstG.getFullYear() && g.getMonth() === firstG.getMonth());
|
|
1129
|
+
result.push({ g, h, inCurrentMonth, disabled });
|
|
1130
|
+
}
|
|
1131
|
+
return result;
|
|
1132
|
+
}
|
|
1133
|
+
prevMonth() {
|
|
1134
|
+
if (!this.canPrev())
|
|
1135
|
+
return;
|
|
1136
|
+
if (this.mode() === 'islamic') {
|
|
1137
|
+
let m = this.hm(), y = this.hy();
|
|
1138
|
+
if (--m < 1) {
|
|
1139
|
+
m = 12;
|
|
1140
|
+
y--;
|
|
1141
|
+
}
|
|
1142
|
+
this.hm.set(m);
|
|
1143
|
+
this.hy.set(y);
|
|
1144
|
+
this.selectedYear = y;
|
|
1145
|
+
this.selectedMonth = m;
|
|
1146
|
+
}
|
|
1147
|
+
else {
|
|
1148
|
+
let m = this.gm(), y = this.gy();
|
|
1149
|
+
if (--m < 1) {
|
|
1150
|
+
m = 12;
|
|
1151
|
+
y--;
|
|
1152
|
+
}
|
|
1153
|
+
this.gm.set(m);
|
|
1154
|
+
this.gy.set(y);
|
|
1155
|
+
this.selectedYear = y;
|
|
1156
|
+
this.selectedMonth = m;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
nextMonth() {
|
|
1160
|
+
if (!this.canNext())
|
|
1161
|
+
return;
|
|
1162
|
+
if (this.mode() === 'islamic') {
|
|
1163
|
+
let m = this.hm(), y = this.hy();
|
|
1164
|
+
if (++m > 12) {
|
|
1165
|
+
m = 1;
|
|
1166
|
+
y++;
|
|
1167
|
+
}
|
|
1168
|
+
this.hm.set(m);
|
|
1169
|
+
this.hy.set(y);
|
|
1170
|
+
this.selectedYear = y;
|
|
1171
|
+
this.selectedMonth = m;
|
|
1172
|
+
}
|
|
1173
|
+
else {
|
|
1174
|
+
let m = this.gm(), y = this.gy();
|
|
1175
|
+
if (++m > 12) {
|
|
1176
|
+
m = 1;
|
|
1177
|
+
y++;
|
|
1178
|
+
}
|
|
1179
|
+
this.gm.set(m);
|
|
1180
|
+
this.gy.set(y);
|
|
1181
|
+
this.selectedYear = y;
|
|
1182
|
+
this.selectedMonth = m;
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
canPrev() {
|
|
1186
|
+
if (!this.minDate)
|
|
1187
|
+
return true;
|
|
1188
|
+
const minG = stripTime(this.minDate);
|
|
1189
|
+
const viewFirst = this.firstOfMonth();
|
|
1190
|
+
// Can go prev if the first day of previous month is >= minG
|
|
1191
|
+
const prev = new Date(viewFirst.getFullYear(), viewFirst.getMonth() - 1, 1);
|
|
1192
|
+
return prev >= new Date(minG.getFullYear(), minG.getMonth(), 1);
|
|
1193
|
+
}
|
|
1194
|
+
canNext() {
|
|
1195
|
+
if (!this.maxDate)
|
|
1196
|
+
return true;
|
|
1197
|
+
const maxG = stripTime(this.maxDate);
|
|
1198
|
+
const viewFirst = this.firstOfMonth();
|
|
1199
|
+
const next = new Date(viewFirst.getFullYear(), viewFirst.getMonth() + 1, 1);
|
|
1200
|
+
// move to the first of the month after max
|
|
1201
|
+
const maxMonthAfter = new Date(maxG.getFullYear(), maxG.getMonth() + 1, 1);
|
|
1202
|
+
return next < maxMonthAfter;
|
|
1203
|
+
}
|
|
1204
|
+
isToday(g) {
|
|
1205
|
+
const t = new Date();
|
|
1206
|
+
return g.getFullYear() === t.getFullYear()
|
|
1207
|
+
&& g.getMonth() === t.getMonth()
|
|
1208
|
+
&& g.getDate() === t.getDate();
|
|
1209
|
+
}
|
|
1210
|
+
isSelected(g) {
|
|
1211
|
+
return !!this.value &&
|
|
1212
|
+
g.getFullYear() === this.value.getFullYear() &&
|
|
1213
|
+
g.getMonth() === this.value.getMonth() &&
|
|
1214
|
+
g.getDate() === this.value.getDate();
|
|
1215
|
+
}
|
|
1216
|
+
select(c) {
|
|
1217
|
+
if (c.disabled)
|
|
1218
|
+
return;
|
|
1219
|
+
this.value = stripTime(c.g);
|
|
1220
|
+
this.valueChange.emit(this.value);
|
|
1221
|
+
// keep both views in sync after selection
|
|
1222
|
+
const h = hijriFromGregorian(this.value);
|
|
1223
|
+
this.hy.set(h.hy);
|
|
1224
|
+
this.hm.set(h.hm);
|
|
1225
|
+
this.gy.set(this.value.getFullYear());
|
|
1226
|
+
this.gm.set(this.value.getMonth() + 1);
|
|
1227
|
+
if (this.mode() === 'islamic') {
|
|
1228
|
+
this.selectedYear = this.hy();
|
|
1229
|
+
this.selectedMonth = this.hm();
|
|
1230
|
+
}
|
|
1231
|
+
else {
|
|
1232
|
+
this.selectedYear = this.gy();
|
|
1233
|
+
this.selectedMonth = this.gm();
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: FlexDualCalendarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1237
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: FlexDualCalendarComponent, isStandalone: true, selector: "flex-dual-calendar", inputs: { value: "value", minDate: "minDate", maxDate: "maxDate", monthLang: "monthLang", startMode: "startMode" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"card border-0\">\r\n <div class=\"card-header p-1\">\r\n <div class=\"row g-1\">\r\n <div class=\"col-1\">\r\n <button class=\"btn btn-sm w-100 btn-info\" (click)=\"prevMonth()\" [disabled]=\"!canPrev()\">«</button>\r\n </div>\r\n <div class=\"col-6\">\r\n <select class=\"form-select form-select-sm\" [(ngModel)]=\"selectedMonth\" (change)=\"onMonthYearChange()\">\r\n @if (mode() === 'islamic') {\r\n @for (m of hijriMonths; track $index) {\r\n <option [value]=\"$index + 1\">{{ hijriMonths[$index] }} ({{ hijriMonths[$index] }})</option>\r\n }\r\n } @else {\r\n @for (m of gregMonths; track $index) {\r\n <option [value]=\"$index + 1\">{{ gregMonths[$index] }}</option>\r\n }\r\n }\r\n </select>\r\n </div>\r\n <div class=\"col-4\">\r\n <select class=\"form-select form-select-sm\" [(ngModel)]=\"selectedYear\" (change)=\"onMonthYearChange()\">\r\n @for (y of yearRange; track y) {\r\n <option [value]=\"y\">{{ y }}</option>\r\n }\r\n </select>\r\n </div>\r\n <div class=\"col-1 \">\r\n <button class=\"btn btn-sm w-100 btn-info\" (click)=\"nextMonth()\" [disabled]=\"!canNext()\">»</button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"card-body p-0\">\r\n <!-- Weekday labels -->\r\n <div class=\"grid\">\r\n @for (w of weekDays; track $index) {\r\n <div class=\"col fw-bold border pb-1\">{{ w }}</div>\r\n }\r\n </div>\r\n\r\n <!-- Calendar cells -->\r\n <div class=\"grid\">\r\n @for (c of cells(); track $index) {\r\n <div class=\"cell\">\r\n <button class=\"btn w-100\" [class.muted]=\"!c.inCurrentMonth\" [class.today]=\"isToday(c.g)\"\r\n [class.selected]=\"isSelected(c.g)\" [disabled]=\"c.disabled\" (click)=\"select(c)\">\r\n <!-- show Hijri day in Hijri mode, otherwise Gregorian -->\r\n @if(mode() === 'islamic'){\r\n {{ c.h.hd }}\r\n }\r\n @else{\r\n {{ c.g.getDate() }}\r\n }\r\n </button>\r\n </div>\r\n }\r\n\r\n </div>\r\n </div>\r\n <div class=\"card-footer px-0 py-1\">\r\n <div class=\"d-flex justify-content-center\">\r\n <div class=\"btn-group\" role=\"group\" aria-label=\"Basic example\">\r\n <button class=\"btn btn-sm btn-outline-secondary\" type=\"button\" (click)=\"switchMode('gregory')\">\r\n Gregorian\r\n </button>\r\n <button class=\"btn btn-sm btn-outline-secondary\" type=\"button\" (click)=\"switchMode('islamic')\">\r\n \u0647\u062C\u0631\u064A\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n</div>", styles: [".grid{display:grid;grid-template-columns:repeat(7,1fr)}.cell{border:1px solid #ddd}.cell.muted{opacity:.5}.cell.today{outline:2px solid #0d6efd}.cell.selected{background-color:#0d6efd;color:#fff}.cell:disabled{background-color:#f8f9fa;color:#aaa;cursor:not-allowed}.col{text-align:center}.btn{font-size:90%;--bs-btn-padding-x: .3rem;--bs-btn-padding-y: .25rem}.btn:hover{background-color:azure}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
|
|
1238
|
+
}
|
|
1239
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: FlexDualCalendarComponent, decorators: [{
|
|
1240
|
+
type: Component,
|
|
1241
|
+
args: [{ selector: 'flex-dual-calendar', standalone: true, imports: [CommonModule, FormsModule], template: "<div class=\"card border-0\">\r\n <div class=\"card-header p-1\">\r\n <div class=\"row g-1\">\r\n <div class=\"col-1\">\r\n <button class=\"btn btn-sm w-100 btn-info\" (click)=\"prevMonth()\" [disabled]=\"!canPrev()\">«</button>\r\n </div>\r\n <div class=\"col-6\">\r\n <select class=\"form-select form-select-sm\" [(ngModel)]=\"selectedMonth\" (change)=\"onMonthYearChange()\">\r\n @if (mode() === 'islamic') {\r\n @for (m of hijriMonths; track $index) {\r\n <option [value]=\"$index + 1\">{{ hijriMonths[$index] }} ({{ hijriMonths[$index] }})</option>\r\n }\r\n } @else {\r\n @for (m of gregMonths; track $index) {\r\n <option [value]=\"$index + 1\">{{ gregMonths[$index] }}</option>\r\n }\r\n }\r\n </select>\r\n </div>\r\n <div class=\"col-4\">\r\n <select class=\"form-select form-select-sm\" [(ngModel)]=\"selectedYear\" (change)=\"onMonthYearChange()\">\r\n @for (y of yearRange; track y) {\r\n <option [value]=\"y\">{{ y }}</option>\r\n }\r\n </select>\r\n </div>\r\n <div class=\"col-1 \">\r\n <button class=\"btn btn-sm w-100 btn-info\" (click)=\"nextMonth()\" [disabled]=\"!canNext()\">»</button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"card-body p-0\">\r\n <!-- Weekday labels -->\r\n <div class=\"grid\">\r\n @for (w of weekDays; track $index) {\r\n <div class=\"col fw-bold border pb-1\">{{ w }}</div>\r\n }\r\n </div>\r\n\r\n <!-- Calendar cells -->\r\n <div class=\"grid\">\r\n @for (c of cells(); track $index) {\r\n <div class=\"cell\">\r\n <button class=\"btn w-100\" [class.muted]=\"!c.inCurrentMonth\" [class.today]=\"isToday(c.g)\"\r\n [class.selected]=\"isSelected(c.g)\" [disabled]=\"c.disabled\" (click)=\"select(c)\">\r\n <!-- show Hijri day in Hijri mode, otherwise Gregorian -->\r\n @if(mode() === 'islamic'){\r\n {{ c.h.hd }}\r\n }\r\n @else{\r\n {{ c.g.getDate() }}\r\n }\r\n </button>\r\n </div>\r\n }\r\n\r\n </div>\r\n </div>\r\n <div class=\"card-footer px-0 py-1\">\r\n <div class=\"d-flex justify-content-center\">\r\n <div class=\"btn-group\" role=\"group\" aria-label=\"Basic example\">\r\n <button class=\"btn btn-sm btn-outline-secondary\" type=\"button\" (click)=\"switchMode('gregory')\">\r\n Gregorian\r\n </button>\r\n <button class=\"btn btn-sm btn-outline-secondary\" type=\"button\" (click)=\"switchMode('islamic')\">\r\n \u0647\u062C\u0631\u064A\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n</div>", styles: [".grid{display:grid;grid-template-columns:repeat(7,1fr)}.cell{border:1px solid #ddd}.cell.muted{opacity:.5}.cell.today{outline:2px solid #0d6efd}.cell.selected{background-color:#0d6efd;color:#fff}.cell:disabled{background-color:#f8f9fa;color:#aaa;cursor:not-allowed}.col{text-align:center}.btn{font-size:90%;--bs-btn-padding-x: .3rem;--bs-btn-padding-y: .25rem}.btn:hover{background-color:azure}\n"] }]
|
|
1242
|
+
}], ctorParameters: () => [], propDecorators: { value: [{
|
|
1243
|
+
type: Input
|
|
1244
|
+
}], valueChange: [{
|
|
1245
|
+
type: Output
|
|
1246
|
+
}], minDate: [{
|
|
1247
|
+
type: Input
|
|
1248
|
+
}], maxDate: [{
|
|
1249
|
+
type: Input
|
|
1250
|
+
}], monthLang: [{
|
|
1251
|
+
type: Input
|
|
1252
|
+
}], startMode: [{
|
|
1253
|
+
type: Input
|
|
1254
|
+
}] } });
|
|
1255
|
+
|
|
1256
|
+
class FlexDualDatepickerComponent {
|
|
1257
|
+
dateService;
|
|
1258
|
+
value = model(...(ngDevMode ? [undefined, { debugName: "value" }] : []));
|
|
1259
|
+
enabled = input(true, ...(ngDevMode ? [{ debugName: "enabled" }] : []));
|
|
1260
|
+
cssClass = input(...(ngDevMode ? [undefined, { debugName: "cssClass" }] : []));
|
|
1261
|
+
format = input("DD/MM/YYYY", ...(ngDevMode ? [{ debugName: "format" }] : []));
|
|
1262
|
+
//selectedDate is needed to keep the current data for process
|
|
1263
|
+
selectedDate;
|
|
1264
|
+
showCalendar = false;
|
|
1265
|
+
calendar = "gregory";
|
|
1266
|
+
inputDate = "";
|
|
1267
|
+
isMobile = false;
|
|
1268
|
+
constructor(dateService) {
|
|
1269
|
+
this.dateService = dateService;
|
|
1270
|
+
effect(() => {
|
|
1271
|
+
const iso = this.value();
|
|
1272
|
+
if (iso) {
|
|
1273
|
+
const date = dayjs(iso);
|
|
1274
|
+
if (date.isValid()) {
|
|
1275
|
+
this.selectedDate = date.toDate();
|
|
1276
|
+
this.inputDate = this.formatDate(this.selectedDate);
|
|
1277
|
+
}
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
this.selectedDate = undefined;
|
|
1281
|
+
this.inputDate = "";
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
switchCalendar() {
|
|
1285
|
+
if (this.calendar == "gregory") {
|
|
1286
|
+
this.calendar = "islamic";
|
|
1287
|
+
}
|
|
1288
|
+
else {
|
|
1289
|
+
this.calendar = "gregory";
|
|
1290
|
+
}
|
|
1291
|
+
this.inputDate = this.formatDate(this.selectedDate);
|
|
1292
|
+
}
|
|
1293
|
+
toggleCalendar() {
|
|
1294
|
+
this.showCalendar = !this.showCalendar;
|
|
1295
|
+
}
|
|
1296
|
+
onDateSelectedFromCalender(e) {
|
|
1297
|
+
// console.log("onDateSelectedFromCalender: ", e);
|
|
1298
|
+
this.dateService.log(e);
|
|
1299
|
+
this.inputDate = this.formatDate(e);
|
|
1300
|
+
//var time = this.selectedDate ?? new Date();
|
|
1301
|
+
// e.setHours(time.getHours());
|
|
1302
|
+
// e.setMinutes(time.getMinutes());
|
|
1303
|
+
this.updateDate(e);
|
|
1304
|
+
this.showCalendar = false;
|
|
1305
|
+
// console.log(e);
|
|
1306
|
+
}
|
|
1307
|
+
/** Triggered on blur OR Enter key */
|
|
1308
|
+
onManualDateEntered() {
|
|
1309
|
+
if (!this.inputDate)
|
|
1310
|
+
return;
|
|
1311
|
+
const parsed = this.parseInputDate(this.inputDate);
|
|
1312
|
+
if (parsed) {
|
|
1313
|
+
this.updateDate(parsed);
|
|
1314
|
+
}
|
|
1315
|
+
else {
|
|
1316
|
+
console.warn("Invalid date entered:", this.inputDate);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
/** Reusable update method */
|
|
1320
|
+
updateDate(d) {
|
|
1321
|
+
this.selectedDate = d;
|
|
1322
|
+
this.inputDate = this.formatDate(d);
|
|
1323
|
+
this.value.set(d);
|
|
1324
|
+
//this.valueChange.emit(d);
|
|
1325
|
+
}
|
|
1326
|
+
formatDate(date) {
|
|
1327
|
+
return this.dateService.format(date, this.calendar, this.format());
|
|
1328
|
+
}
|
|
1329
|
+
// private formatDateForInput(date: Date): string {
|
|
1330
|
+
// const fmt = this.format()?.toUpperCase() ?? "DD/MM/YYYY";
|
|
1331
|
+
// //return dayjs(date).format(fmt);
|
|
1332
|
+
// return this.formatDate(date, fmt);
|
|
1333
|
+
// }
|
|
1334
|
+
parseInputDate(str) {
|
|
1335
|
+
const fmt = this.format()?.toUpperCase() ?? "DD/MM/YYYY";
|
|
1336
|
+
console.log(str, "-", fmt);
|
|
1337
|
+
const d = dayjs(str, fmt); // strict parse
|
|
1338
|
+
console.log(d.toDate());
|
|
1339
|
+
if (!d.isValid())
|
|
1340
|
+
return null;
|
|
1341
|
+
if (!this.selectedDate) {
|
|
1342
|
+
this.selectedDate = new Date();
|
|
1343
|
+
}
|
|
1344
|
+
// gd.set("hour", this.selectedDate.getHours());
|
|
1345
|
+
// gd.set("minute", this.selectedDate.getMinutes());
|
|
1346
|
+
// gd.set("second", this.selectedDate.getSeconds());
|
|
1347
|
+
return null;
|
|
1348
|
+
}
|
|
1349
|
+
inputDiv;
|
|
1350
|
+
modalDiv;
|
|
1351
|
+
popupDiv;
|
|
1352
|
+
onDocumentClick(event) {
|
|
1353
|
+
if (this.inputDiv?.nativeElement.contains(event.target)
|
|
1354
|
+
|| this.modalDiv?.nativeElement.contains(event.target)
|
|
1355
|
+
|| this.popupDiv?.nativeElement.contains(event.target)) {
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
this.showCalendar = false;
|
|
1359
|
+
}
|
|
1360
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: FlexDualDatepickerComponent, deps: [{ token: DateServiceService }], target: i0.ɵɵFactoryTarget.Component });
|
|
1361
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.1", type: FlexDualDatepickerComponent, isStandalone: true, selector: "flex-dual-datepicker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, enabled: { classPropertyName: "enabled", publicName: "enabled", isSignal: true, isRequired: false, transformFunction: null }, cssClass: { classPropertyName: "cssClass", publicName: "cssClass", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, viewQueries: [{ propertyName: "inputDiv", first: true, predicate: ["inputDiv"], descendants: true }, { propertyName: "modalDiv", first: true, predicate: ["modalDiv"], descendants: true }, { propertyName: "popupDiv", first: true, predicate: ["popupDiv"], descendants: true }], ngImport: i0, template: "<div class=\"input-group flex-nowrap\" #inputDiv>\r\n <span class=\"input-group-text\" style=\"cursor: pointer;\" (click)=\"switchCalendar()\"> @if(calendar == 'gregory') {G}\r\n @else{H}</span>\r\n <input type=\"text\" class=\"form-control\" [(ngModel)]=\"inputDate\" (blur)=\"onManualDateEntered()\"\r\n [disabled]=\"!enabled()\" [placeholder]=\"format()\" (keyup.enter)=\"onManualDateEntered()\">\r\n\r\n\r\n <button class=\"btn btn-outline-secondary\" type=\"button\" (click)=\"toggleCalendar()\" [disabled]=\"!enabled()\">\r\n <i class=\"bi bi-calendar3\"></i>\r\n </button>\r\n</div>\r\n\r\n@if(showCalendar){\r\n@if(isMobile){\r\n<div class=\"modal modal-backdrop\" tabindex=\"-1\" role=\"dialog\" style=\"display: block;\" #modalDiv>\r\n <div class=\"modal-dialog modal-dialog-scrollable\" role=\"document\">\r\n <div class=\"modal-content\">\r\n <div class=\"modal-header\">\r\n <button type=\"button\" class=\"btn btn-danger close ms-auto btn-sm\" (click)=\"showCalendar = false\">\r\n <i class=\"bi bi-x\"></i>\r\n </button>\r\n </div>\r\n <div class=\"modal-body\">\r\n\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n}\r\n@else{\r\n<div class=\"dropdown-menu show\" #popupDiv>\r\n <flex-dual-calendar (valueChange)=\"onDateSelectedFromCalender($event)\"\r\n [(value)]=\"selectedDate\"></flex-dual-calendar>\r\n</div>\r\n}\r\n}", styles: [".dropdown-menu{--bs-dropdown-padding-y: 0}.card-body{padding:0!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: FlexDualCalendarComponent, selector: "flex-dual-calendar", inputs: ["value", "minDate", "maxDate", "monthLang", "startMode"], outputs: ["valueChange"] }] });
|
|
1362
|
+
}
|
|
1363
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: FlexDualDatepickerComponent, decorators: [{
|
|
1364
|
+
type: Component,
|
|
1365
|
+
args: [{ selector: 'flex-dual-datepicker', imports: [CommonModule, FormsModule, FlexDualCalendarComponent], template: "<div class=\"input-group flex-nowrap\" #inputDiv>\r\n <span class=\"input-group-text\" style=\"cursor: pointer;\" (click)=\"switchCalendar()\"> @if(calendar == 'gregory') {G}\r\n @else{H}</span>\r\n <input type=\"text\" class=\"form-control\" [(ngModel)]=\"inputDate\" (blur)=\"onManualDateEntered()\"\r\n [disabled]=\"!enabled()\" [placeholder]=\"format()\" (keyup.enter)=\"onManualDateEntered()\">\r\n\r\n\r\n <button class=\"btn btn-outline-secondary\" type=\"button\" (click)=\"toggleCalendar()\" [disabled]=\"!enabled()\">\r\n <i class=\"bi bi-calendar3\"></i>\r\n </button>\r\n</div>\r\n\r\n@if(showCalendar){\r\n@if(isMobile){\r\n<div class=\"modal modal-backdrop\" tabindex=\"-1\" role=\"dialog\" style=\"display: block;\" #modalDiv>\r\n <div class=\"modal-dialog modal-dialog-scrollable\" role=\"document\">\r\n <div class=\"modal-content\">\r\n <div class=\"modal-header\">\r\n <button type=\"button\" class=\"btn btn-danger close ms-auto btn-sm\" (click)=\"showCalendar = false\">\r\n <i class=\"bi bi-x\"></i>\r\n </button>\r\n </div>\r\n <div class=\"modal-body\">\r\n\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n}\r\n@else{\r\n<div class=\"dropdown-menu show\" #popupDiv>\r\n <flex-dual-calendar (valueChange)=\"onDateSelectedFromCalender($event)\"\r\n [(value)]=\"selectedDate\"></flex-dual-calendar>\r\n</div>\r\n}\r\n}", styles: [".dropdown-menu{--bs-dropdown-padding-y: 0}.card-body{padding:0!important}\n"] }]
|
|
1366
|
+
}], ctorParameters: () => [{ type: DateServiceService }], propDecorators: { inputDiv: [{
|
|
1367
|
+
type: ViewChild,
|
|
1368
|
+
args: ["inputDiv"]
|
|
1369
|
+
}], modalDiv: [{
|
|
1370
|
+
type: ViewChild,
|
|
1371
|
+
args: ["modalDiv"]
|
|
1372
|
+
}], popupDiv: [{
|
|
1373
|
+
type: ViewChild,
|
|
1374
|
+
args: ["popupDiv"]
|
|
1375
|
+
}], onDocumentClick: [{
|
|
1376
|
+
type: HostListener,
|
|
1377
|
+
args: ['document:click', ['$event']]
|
|
829
1378
|
}] } });
|
|
830
1379
|
|
|
831
1380
|
/*
|
|
@@ -836,5 +1385,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImpor
|
|
|
836
1385
|
* Generated bundle index. Do not edit.
|
|
837
1386
|
*/
|
|
838
1387
|
|
|
839
|
-
export { ComboDatePickerComponent, FlexColumnComponent, FlexColumnsComponent, FlexContainerComponent, FlexGridComponent, FlexPaginationComponent, FlexPanelComponent, HijriDatepickerComponent };
|
|
1388
|
+
export { ComboDatePickerComponent, FlexColumnComponent, FlexColumnsComponent, FlexContainerComponent, FlexDualDatepickerComponent, FlexGridComponent, FlexPaginationComponent, FlexPanelComponent, HijriDatepickerComponent };
|
|
840
1389
|
//# sourceMappingURL=dcsl-flex-ui.mjs.map
|