hijri-date-time-picker 1.0.2 → 1.0.31

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.
@@ -0,0 +1,795 @@
1
+ import {
2
+ Component,
3
+ Input,
4
+ Output,
5
+ EventEmitter,
6
+ OnInit,
7
+ OnChanges,
8
+ SimpleChanges,
9
+ } from "@angular/core";
10
+ import { CommonModule } from "@angular/common";
11
+ import HijriDate, { toHijri } from "hijri-date/lib/safe";
12
+ import {
13
+ DateMode,
14
+ Direction,
15
+ Locale,
16
+ SelectedDate,
17
+ DatePickerStyles,
18
+ DatePickerLabels,
19
+ DatePickerDisplay,
20
+ TimeFormat,
21
+ GREGORIAN_MONTHS_EN,
22
+ GREGORIAN_MONTHS_AR,
23
+ HIJRI_MONTHS_EN,
24
+ HIJRI_MONTHS_AR,
25
+ WEEKDAY_NAMES_EN,
26
+ WEEKDAY_NAMES_AR,
27
+ } from "./hijri-date-picker.types";
28
+
29
+ @Component({
30
+ selector: "hijri-date-picker",
31
+ standalone: true,
32
+ imports: [CommonModule],
33
+ templateUrl: "./hijri-date-picker.component.html",
34
+ styleUrls: ["./hijri-date-picker.component.css"],
35
+ })
36
+ export class HijriDatePickerComponent implements OnInit, OnChanges {
37
+ // Mode & Configuration
38
+ @Input() canChangeMode: boolean = true;
39
+ @Input() mode: DateMode = "greg";
40
+ @Input() dir: Direction = "ltr";
41
+ @Input() locale: Locale = "en";
42
+
43
+ // Validation
44
+ @Input() futureValidation: boolean = true;
45
+ @Input() futureYearsLimit: number = 10;
46
+ @Input() isRequired: boolean = false;
47
+ @Input() minDate?: Date; // Minimum selectable date
48
+ @Input() maxDate?: Date; // Maximum selectable date
49
+
50
+ // Selection
51
+ @Input() multiple: boolean = false;
52
+ @Input() showConfirmButton: boolean = false;
53
+ @Input() initialSelectedDate?: Date; // For single selection mode - bind initial date
54
+ @Input() initialSelectedDates?: Date[]; // For multiple selection mode - bind initial dates
55
+
56
+ // Labels
57
+ @Input() submitTextButton: string = "Submit";
58
+ @Input() todaysDateText: string = "Today";
59
+ @Input() ummAlQuraDateText: string = "Umm Al-Qura Calendar";
60
+ @Input() yearSelectLabel: string = "Year";
61
+ @Input() monthSelectLabel: string = "Month";
62
+
63
+ // Display Options
64
+ @Input() todaysDateSection: boolean = true;
65
+ @Input() markToday: boolean = true;
66
+ @Input() disableYearPicker: boolean = false;
67
+ @Input() disableMonthPicker: boolean = false;
68
+ @Input() disableDayPicker: boolean = false;
69
+
70
+ // Styling
71
+ @Input() styles: DatePickerStyles = {};
72
+
73
+ // Time Configuration
74
+ @Input() enableTime: boolean = false;
75
+ @Input() timeFormat: TimeFormat = "24";
76
+ @Input() minuteStep: number = 1;
77
+ @Input() enableSeconds: boolean = false;
78
+ @Input() defaultTime?: { hours: number; minutes: number; seconds?: number };
79
+
80
+ // Outputs
81
+ @Output() dateSelected = new EventEmitter<SelectedDate | SelectedDate[]>();
82
+ @Output() modeChanged = new EventEmitter<DateMode>();
83
+
84
+ // Internal State
85
+ currentYear: number = new Date().getFullYear();
86
+ currentMonth: number = new Date().getMonth();
87
+ currentHijriYear: number = 0;
88
+ currentHijriMonth: number = 0;
89
+
90
+ selectedDates: SelectedDate[] = [];
91
+ calendarDays: any[] = [];
92
+ today: Date = new Date();
93
+
94
+ years: number[] = [];
95
+ months: string[] = [];
96
+ weekdays: string[] = [];
97
+
98
+ // Time state
99
+ selectedTime: { hours: number; minutes: number; seconds: number } = (() => {
100
+ const now = new Date();
101
+ return {
102
+ hours: now.getHours(),
103
+ minutes: now.getMinutes(),
104
+ seconds: now.getSeconds(),
105
+ };
106
+ })();
107
+ isPM: boolean = new Date().getHours() >= 12;
108
+
109
+ ngOnInit(): void {
110
+ this.initializeCalendar();
111
+ this.initializeSelectedDates();
112
+ this.initializeTime();
113
+ this.updateLocaleData();
114
+ this.generateYears();
115
+ this.generateCalendar();
116
+ this.submitTextButton = this.locale === "ar" ? "تأكيد" : "Submit";
117
+ this.todaysDateText = this.locale === "ar" ? "اليوم" : "Today";
118
+ this.ummAlQuraDateText =
119
+ this.locale === "ar" ? "تقويم أم القرى" : "Umm Al-Qura Calendar";
120
+ this.yearSelectLabel = this.locale === "ar" ? "السنة" : "Year";
121
+ this.monthSelectLabel = this.locale === "ar" ? "الشهر" : "Month";
122
+ }
123
+
124
+ ngOnChanges(changes: SimpleChanges): void {
125
+ if (changes["mode"] || changes["locale"]) {
126
+ this.updateLocaleData();
127
+ this.generateCalendar();
128
+ }
129
+
130
+ // Handle changes to initial selected dates
131
+ if (changes["initialSelectedDate"] || changes["initialSelectedDates"]) {
132
+ this.initializeSelectedDates();
133
+ this.generateCalendar();
134
+ }
135
+
136
+ // Handle changes to minDate or maxDate
137
+ if (changes["minDate"] || changes["maxDate"]) {
138
+ this.generateYears();
139
+ this.generateCalendar();
140
+ }
141
+ }
142
+
143
+ private initializeCalendar(): void {
144
+ // If initialSelectedDate is provided, use it to set the calendar month
145
+ let targetDate: Date;
146
+
147
+ if (!this.multiple && this.initialSelectedDate) {
148
+ targetDate = this.initialSelectedDate;
149
+ } else if (
150
+ this.multiple &&
151
+ this.initialSelectedDates &&
152
+ this.initialSelectedDates.length > 0
153
+ ) {
154
+ targetDate = this.initialSelectedDates[0];
155
+ } else {
156
+ targetDate = new Date();
157
+ }
158
+
159
+ // Set Gregorian calendar to the target date's month
160
+ this.currentYear = targetDate.getFullYear();
161
+ this.currentMonth = targetDate.getMonth();
162
+
163
+ // Set Hijri calendar to the target date's month
164
+ const hijriDate = toHijri(targetDate);
165
+ this.currentHijriYear = hijriDate.getFullYear();
166
+ this.currentHijriMonth = hijriDate.getMonth();
167
+ }
168
+
169
+ private initializeSelectedDates(): void {
170
+ // Handle single selection mode
171
+ if (!this.multiple && this.initialSelectedDate) {
172
+ this.selectedDates = [this.createSelectedDate(this.initialSelectedDate)];
173
+ }
174
+
175
+ // Handle multiple selection mode
176
+ if (
177
+ this.multiple &&
178
+ this.initialSelectedDates &&
179
+ this.initialSelectedDates.length > 0
180
+ ) {
181
+ this.selectedDates = this.initialSelectedDates.map((date) =>
182
+ this.createSelectedDate(date)
183
+ );
184
+ }
185
+ }
186
+
187
+ private initializeTime(): void {
188
+ if (!this.enableTime) {
189
+ return;
190
+ }
191
+
192
+ // Set default time if provided
193
+ if (this.defaultTime) {
194
+ this.selectedTime = {
195
+ hours: this.defaultTime.hours,
196
+ minutes: this.defaultTime.minutes,
197
+ seconds: this.defaultTime.seconds || 0,
198
+ };
199
+
200
+ // Set AM/PM for 12-hour format
201
+ if (this.timeFormat === "12") {
202
+ this.isPM = this.selectedTime.hours >= 12;
203
+ }
204
+ } else {
205
+ // Default to current time
206
+ const now = new Date();
207
+ this.selectedTime = {
208
+ hours: now.getHours(),
209
+ minutes: now.getMinutes(),
210
+ seconds: now.getSeconds(),
211
+ };
212
+ this.isPM = this.selectedTime.hours >= 12;
213
+ }
214
+ }
215
+
216
+ private updateLocaleData(): void {
217
+ // Update weekday names
218
+ this.weekdays = this.locale === "ar" ? WEEKDAY_NAMES_AR : WEEKDAY_NAMES_EN;
219
+
220
+ // Update month names based on mode and locale
221
+ if (this.mode === "hijri") {
222
+ this.months = this.locale === "ar" ? HIJRI_MONTHS_AR : HIJRI_MONTHS_EN;
223
+ } else {
224
+ this.months =
225
+ this.locale === "ar" ? GREGORIAN_MONTHS_AR : GREGORIAN_MONTHS_EN;
226
+ }
227
+ }
228
+
229
+ private generateYears(): void {
230
+ const currentYear =
231
+ this.mode === "hijri" ? this.currentHijriYear : this.currentYear;
232
+ let startYear = currentYear - 100;
233
+ let endYear = currentYear + this.futureYearsLimit;
234
+
235
+ // Adjust year range based on minDate and maxDate
236
+ if (this.mode === "greg") {
237
+ if (this.minDate) {
238
+ startYear = Math.max(startYear, this.minDate.getFullYear());
239
+ }
240
+ if (this.maxDate) {
241
+ endYear = Math.min(endYear, this.maxDate.getFullYear());
242
+ }
243
+ } else if (this.mode === "hijri") {
244
+ if (this.minDate) {
245
+ const minHijri = toHijri(this.minDate);
246
+ startYear = Math.max(startYear, minHijri.getFullYear());
247
+ }
248
+ if (this.maxDate) {
249
+ const maxHijri = toHijri(this.maxDate);
250
+ endYear = Math.min(endYear, maxHijri.getFullYear());
251
+ }
252
+ }
253
+
254
+ this.years = [];
255
+ for (let year = startYear; year <= endYear; year++) {
256
+ this.years.push(year);
257
+ }
258
+ }
259
+
260
+ private generateCalendar(): void {
261
+ this.calendarDays = [];
262
+
263
+ if (this.mode === "hijri") {
264
+ this.generateHijriCalendar();
265
+ } else {
266
+ this.generateGregorianCalendar();
267
+ }
268
+ }
269
+
270
+ private generateGregorianCalendar(): void {
271
+ const firstDay = new Date(this.currentYear, this.currentMonth, 1);
272
+ const lastDay = new Date(this.currentYear, this.currentMonth + 1, 0);
273
+ const startingDayOfWeek = firstDay.getDay();
274
+ const daysInMonth = lastDay.getDate();
275
+
276
+ // Add empty cells for days before the first day of the month
277
+ for (let i = 0; i < startingDayOfWeek; i++) {
278
+ this.calendarDays.push({ day: null, disabled: true });
279
+ }
280
+
281
+ // Add days of the month
282
+ for (let day = 1; day <= daysInMonth; day++) {
283
+ const date = new Date(this.currentYear, this.currentMonth, day);
284
+ const isToday = this.isSameDay(date, this.today);
285
+ const isDisabled = this.isDateDisabled(date);
286
+ const isSelected = this.isDateSelected(date);
287
+
288
+ this.calendarDays.push({
289
+ day,
290
+ date,
291
+ isToday,
292
+ isDisabled,
293
+ isSelected,
294
+ disabled: false,
295
+ });
296
+ }
297
+ }
298
+
299
+ private generateHijriCalendar(): void {
300
+ const hijriDate = new HijriDate(
301
+ this.currentHijriYear,
302
+ this.currentHijriMonth,
303
+ 1
304
+ );
305
+ const gregorianDate = hijriDate.toGregorian();
306
+ const startingDayOfWeek = gregorianDate.getDay();
307
+
308
+ // Get days in Hijri month (typically 29 or 30)
309
+ const daysInMonth = this.getDaysInHijriMonth(
310
+ this.currentHijriYear,
311
+ this.currentHijriMonth
312
+ );
313
+
314
+ // Add empty cells for days before the first day of the month
315
+ for (let i = 0; i < startingDayOfWeek; i++) {
316
+ this.calendarDays.push({ day: null, disabled: true });
317
+ }
318
+
319
+ // Add days of the month
320
+ for (let day = 1; day <= daysInMonth; day++) {
321
+ const hijriDay = new HijriDate(
322
+ this.currentHijriYear,
323
+ this.currentHijriMonth,
324
+ day
325
+ );
326
+ const gregorianDay = hijriDay.toGregorian();
327
+ const isToday = this.isSameDay(gregorianDay, this.today);
328
+ const isDisabled = this.isDateDisabled(gregorianDay);
329
+ const isSelected = this.isDateSelected(gregorianDay);
330
+
331
+ this.calendarDays.push({
332
+ day,
333
+ date: gregorianDay,
334
+ hijriDate: hijriDay,
335
+ isToday,
336
+ isDisabled,
337
+ isSelected,
338
+ disabled: false,
339
+ });
340
+ }
341
+ }
342
+
343
+ private getDaysInHijriMonth(year: number, month: number): number {
344
+ // Try to create the 30th day; if it fails, the month has 29 days
345
+ try {
346
+ new HijriDate(year, month, 30);
347
+ return 30;
348
+ } catch {
349
+ return 29;
350
+ }
351
+ }
352
+
353
+ private isSameDay(date1: Date, date2: Date): boolean {
354
+ return (
355
+ date1.getFullYear() === date2.getFullYear() &&
356
+ date1.getMonth() === date2.getMonth() &&
357
+ date1.getDate() === date2.getDate()
358
+ );
359
+ }
360
+
361
+ private isDateDisabled(date: Date): boolean {
362
+ // Check minDate constraint
363
+ if (this.minDate) {
364
+ const minDateOnly = new Date(
365
+ this.minDate.getFullYear(),
366
+ this.minDate.getMonth(),
367
+ this.minDate.getDate()
368
+ );
369
+ const dateOnly = new Date(
370
+ date.getFullYear(),
371
+ date.getMonth(),
372
+ date.getDate()
373
+ );
374
+ if (dateOnly < minDateOnly) {
375
+ return true;
376
+ }
377
+ }
378
+
379
+ // Check maxDate constraint
380
+ if (this.maxDate) {
381
+ const maxDateOnly = new Date(
382
+ this.maxDate.getFullYear(),
383
+ this.maxDate.getMonth(),
384
+ this.maxDate.getDate()
385
+ );
386
+ const dateOnly = new Date(
387
+ date.getFullYear(),
388
+ date.getMonth(),
389
+ date.getDate()
390
+ );
391
+ if (dateOnly > maxDateOnly) {
392
+ return true;
393
+ }
394
+ }
395
+
396
+ // Check futureValidation constraint
397
+ if (this.futureValidation) {
398
+ const maxDate = new Date();
399
+ maxDate.setFullYear(maxDate.getFullYear() + this.futureYearsLimit);
400
+ return date > maxDate;
401
+ }
402
+
403
+ return false;
404
+ }
405
+
406
+ private isDateSelected(date: Date): boolean {
407
+ return this.selectedDates.some((selected) =>
408
+ this.isSameDay(selected.gregorian, date)
409
+ );
410
+ }
411
+
412
+ onDayClick(dayInfo: any): void {
413
+ if (dayInfo.disabled || dayInfo.isDisabled) {
414
+ return;
415
+ }
416
+
417
+ const selectedDate = this.createSelectedDate(dayInfo.date);
418
+
419
+ if (this.multiple) {
420
+ const index = this.selectedDates.findIndex((d) =>
421
+ this.isSameDay(d.gregorian, dayInfo.date)
422
+ );
423
+
424
+ if (index > -1) {
425
+ // Remove the clicked date if already selected
426
+ this.selectedDates.splice(index, 1);
427
+ } else {
428
+ // If we already have 2 or more dates, clear and start new range
429
+ if (this.selectedDates.length >= 2) {
430
+ this.selectedDates = [selectedDate];
431
+ } else {
432
+ this.selectedDates.push(selectedDate);
433
+
434
+ // Auto-fill range if exactly 2 dates are selected
435
+ if (this.selectedDates.length === 2) {
436
+ this.fillDateRange();
437
+ }
438
+ }
439
+ }
440
+
441
+ if (!this.showConfirmButton) {
442
+ this.dateSelected.emit([...this.selectedDates]);
443
+ }
444
+ } else {
445
+ this.selectedDates = [selectedDate];
446
+
447
+ if (!this.showConfirmButton) {
448
+ this.dateSelected.emit(selectedDate);
449
+ }
450
+ }
451
+
452
+ this.generateCalendar();
453
+ }
454
+
455
+ private fillDateRange(): void {
456
+ if (this.selectedDates.length !== 2) return;
457
+
458
+ // Sort the two dates
459
+ const dates = [...this.selectedDates].sort(
460
+ (a, b) => a.gregorian.getTime() - b.gregorian.getTime()
461
+ );
462
+
463
+ const startDate = dates[0].gregorian;
464
+ const endDate = dates[1].gregorian;
465
+
466
+ // Clear current selection
467
+ this.selectedDates = [];
468
+
469
+ // Fill all dates between start and end (inclusive)
470
+ const currentDate = new Date(startDate);
471
+ while (currentDate <= endDate) {
472
+ const dateToAdd = new Date(currentDate);
473
+ this.selectedDates.push(this.createSelectedDate(dateToAdd));
474
+ currentDate.setDate(currentDate.getDate() + 1);
475
+ }
476
+ }
477
+
478
+ private createSelectedDate(date: Date): SelectedDate {
479
+ // Create a new Date object to avoid mutating the original
480
+ const dateObj = new Date(
481
+ date.getFullYear(),
482
+ date.getMonth(),
483
+ date.getDate()
484
+ );
485
+ const hijriDate = toHijri(dateObj);
486
+
487
+ // If time is enabled, set the time on the date object
488
+ if (this.enableTime) {
489
+ let hours24 = this.selectedTime.hours;
490
+ if (this.timeFormat === "12") {
491
+ if (this.isPM && this.selectedTime.hours !== 12) {
492
+ hours24 = this.selectedTime.hours + 12;
493
+ } else if (!this.isPM && this.selectedTime.hours === 12) {
494
+ hours24 = 0;
495
+ }
496
+ }
497
+ dateObj.setHours(
498
+ hours24,
499
+ this.selectedTime.minutes,
500
+ this.selectedTime.seconds
501
+ );
502
+ }
503
+
504
+ return {
505
+ gregorian: dateObj,
506
+ hijri: {
507
+ year: hijriDate.getFullYear(),
508
+ month: hijriDate.getMonth(),
509
+ day: hijriDate.getDate(),
510
+ },
511
+ time: this.enableTime
512
+ ? {
513
+ hours: dateObj.getHours(),
514
+ minutes: dateObj.getMinutes(),
515
+ seconds: dateObj.getSeconds(),
516
+ }
517
+ : undefined,
518
+ formatted: {
519
+ gregorian: this.formatGregorianDate(dateObj),
520
+ hijri: this.formatHijriDate(hijriDate),
521
+ time: this.enableTime
522
+ ? this.formatTime(
523
+ dateObj.getHours(),
524
+ dateObj.getMinutes(),
525
+ dateObj.getSeconds()
526
+ )
527
+ : undefined,
528
+ },
529
+ };
530
+ }
531
+
532
+ private formatGregorianDate(date: Date): string {
533
+ const day = date.getDate();
534
+ const month =
535
+ this.locale === "ar"
536
+ ? GREGORIAN_MONTHS_AR[date.getMonth()]
537
+ : GREGORIAN_MONTHS_EN[date.getMonth()];
538
+ const year = date.getFullYear();
539
+
540
+ return this.locale === "ar"
541
+ ? `${day} ${month} ${year}`
542
+ : `${month} ${day}, ${year}`;
543
+ }
544
+
545
+ private formatHijriDate(hijriDate: HijriDate): string {
546
+ const day = hijriDate.getDate();
547
+ const month =
548
+ this.locale === "ar"
549
+ ? HIJRI_MONTHS_AR[hijriDate.getMonth()]
550
+ : HIJRI_MONTHS_EN[hijriDate.getMonth()];
551
+ const year = hijriDate.getFullYear();
552
+
553
+ return this.locale === "ar"
554
+ ? `${day} ${month} ${year} هـ`
555
+ : `${day} ${month} ${year} AH`;
556
+ }
557
+
558
+ onYearChange(event: Event): void {
559
+ const year = parseInt((event.target as HTMLSelectElement).value);
560
+
561
+ if (this.mode === "hijri") {
562
+ this.currentHijriYear = year;
563
+ } else {
564
+ this.currentYear = year;
565
+ }
566
+
567
+ this.generateCalendar();
568
+ }
569
+
570
+ onMonthChange(event: Event): void {
571
+ const month = parseInt((event.target as HTMLSelectElement).value);
572
+
573
+ if (this.mode === "hijri") {
574
+ this.currentHijriMonth = month;
575
+ } else {
576
+ this.currentMonth = month;
577
+ }
578
+
579
+ this.generateCalendar();
580
+ }
581
+
582
+ previousMonth(): void {
583
+ if (this.mode === "hijri") {
584
+ if (this.currentHijriMonth === 0) {
585
+ this.currentHijriMonth = 11;
586
+ this.currentHijriYear--;
587
+ } else {
588
+ this.currentHijriMonth--;
589
+ }
590
+ } else {
591
+ if (this.currentMonth === 0) {
592
+ this.currentMonth = 11;
593
+ this.currentYear--;
594
+ } else {
595
+ this.currentMonth--;
596
+ }
597
+ }
598
+
599
+ this.generateCalendar();
600
+ }
601
+
602
+ nextMonth(): void {
603
+ if (this.mode === "hijri") {
604
+ if (this.currentHijriMonth === 11) {
605
+ this.currentHijriMonth = 0;
606
+ this.currentHijriYear++;
607
+ } else {
608
+ this.currentHijriMonth++;
609
+ }
610
+ } else {
611
+ if (this.currentMonth === 11) {
612
+ this.currentMonth = 0;
613
+ this.currentYear++;
614
+ } else {
615
+ this.currentMonth++;
616
+ }
617
+ }
618
+
619
+ this.generateCalendar();
620
+ }
621
+
622
+ toggleMode(): void {
623
+ if (!this.canChangeMode) {
624
+ return;
625
+ }
626
+
627
+ this.mode = this.mode === "greg" ? "hijri" : "greg";
628
+ this.updateLocaleData();
629
+ this.generateYears();
630
+ this.generateCalendar();
631
+ this.modeChanged.emit(this.mode);
632
+ }
633
+
634
+ selectToday(): void {
635
+ const today = new Date();
636
+ this.onDayClick({ date: today, disabled: false, isDisabled: false });
637
+ }
638
+
639
+ onSubmit(): void {
640
+ if (this.isRequired && this.selectedDates.length === 0) {
641
+ return;
642
+ }
643
+
644
+ if (this.multiple) {
645
+ this.dateSelected.emit([...this.selectedDates]);
646
+ } else if (this.selectedDates.length > 0) {
647
+ this.dateSelected.emit(this.selectedDates[0]);
648
+ }
649
+ }
650
+
651
+ // Time handling methods
652
+ incrementTime(type: "hours" | "minutes" | "seconds"): void {
653
+ if (type === "hours") {
654
+ const max = this.timeFormat === "12" ? 12 : 23;
655
+ const min = this.timeFormat === "12" ? 1 : 0;
656
+ this.selectedTime.hours =
657
+ this.selectedTime.hours >= max ? min : this.selectedTime.hours + 1;
658
+ } else if (type === "minutes") {
659
+ this.selectedTime.minutes =
660
+ this.selectedTime.minutes + this.minuteStep > 59
661
+ ? 0
662
+ : this.selectedTime.minutes + this.minuteStep;
663
+ } else if (type === "seconds") {
664
+ this.selectedTime.seconds =
665
+ this.selectedTime.seconds + 1 > 59 ? 0 : this.selectedTime.seconds + 1;
666
+ }
667
+ this.updateSelectedDateTime();
668
+ }
669
+
670
+ decrementTime(type: "hours" | "minutes" | "seconds"): void {
671
+ if (type === "hours") {
672
+ const min = this.timeFormat === "12" ? 1 : 0;
673
+ const max = this.timeFormat === "12" ? 12 : 23;
674
+ this.selectedTime.hours =
675
+ this.selectedTime.hours - 1 < min ? max : this.selectedTime.hours - 1;
676
+ } else if (type === "minutes") {
677
+ this.selectedTime.minutes =
678
+ this.selectedTime.minutes - this.minuteStep < 0
679
+ ? 59
680
+ : this.selectedTime.minutes - this.minuteStep;
681
+ } else if (type === "seconds") {
682
+ this.selectedTime.seconds =
683
+ this.selectedTime.seconds - 1 < 0 ? 59 : this.selectedTime.seconds - 1;
684
+ }
685
+ this.updateSelectedDateTime();
686
+ }
687
+
688
+ onTimeChange(type: "hours" | "minutes" | "seconds", event: any): void {
689
+ const value = parseInt(event.target.value) || 0;
690
+
691
+ if (type === "hours") {
692
+ const min = this.timeFormat === "12" ? 1 : 0;
693
+ const max = this.timeFormat === "12" ? 12 : 23;
694
+ this.selectedTime.hours = Math.max(min, Math.min(max, value));
695
+ } else if (type === "minutes") {
696
+ this.selectedTime.minutes = Math.max(0, Math.min(59, value));
697
+ } else if (type === "seconds") {
698
+ this.selectedTime.seconds = Math.max(0, Math.min(59, value));
699
+ }
700
+
701
+ this.updateSelectedDateTime();
702
+ }
703
+
704
+ setAMPM(pm: boolean): void {
705
+ this.isPM = pm;
706
+ this.updateSelectedDateTime();
707
+ }
708
+
709
+ private formatTime(hours: number, minutes: number, seconds: number): string {
710
+ if (this.timeFormat === "12") {
711
+ const displayHours = hours % 12 || 12;
712
+ const ampm = hours >= 12 ? "PM" : "AM";
713
+ const minutesStr = minutes.toString().padStart(2, "0");
714
+
715
+ if (this.enableSeconds) {
716
+ const secondsStr = seconds.toString().padStart(2, "0");
717
+ return `${displayHours}:${minutesStr}:${secondsStr} ${ampm}`;
718
+ }
719
+ return `${displayHours}:${minutesStr} ${ampm}`;
720
+ } else {
721
+ const hoursStr = hours.toString().padStart(2, "0");
722
+ const minutesStr = minutes.toString().padStart(2, "0");
723
+
724
+ if (this.enableSeconds) {
725
+ const secondsStr = seconds.toString().padStart(2, "0");
726
+ return `${hoursStr}:${minutesStr}:${secondsStr}`;
727
+ }
728
+ return `${hoursStr}:${minutesStr}`;
729
+ }
730
+ }
731
+
732
+ private updateSelectedDateTime(): void {
733
+ if (!this.enableTime || this.selectedDates.length === 0) {
734
+ return;
735
+ }
736
+
737
+ // Convert 12-hour to 24-hour if needed
738
+ let hours24 = this.selectedTime.hours;
739
+ if (this.timeFormat === "12") {
740
+ if (this.isPM && this.selectedTime.hours !== 12) {
741
+ hours24 = this.selectedTime.hours + 12;
742
+ } else if (!this.isPM && this.selectedTime.hours === 12) {
743
+ hours24 = 0;
744
+ }
745
+ }
746
+
747
+ // Update all selected dates with new time
748
+ this.selectedDates = this.selectedDates.map((selectedDate) => {
749
+ const newDate = new Date(selectedDate.gregorian);
750
+ newDate.setHours(
751
+ hours24,
752
+ this.selectedTime.minutes,
753
+ this.selectedTime.seconds
754
+ );
755
+
756
+ return {
757
+ ...selectedDate,
758
+ gregorian: newDate,
759
+ time: {
760
+ hours: hours24,
761
+ minutes: this.selectedTime.minutes,
762
+ seconds: this.selectedTime.seconds,
763
+ },
764
+ formatted: {
765
+ ...selectedDate.formatted,
766
+ time: this.formatTime(
767
+ hours24,
768
+ this.selectedTime.minutes,
769
+ this.selectedTime.seconds
770
+ ),
771
+ },
772
+ };
773
+ });
774
+ }
775
+
776
+ getCustomStyles(): any {
777
+ return {
778
+ "--primary-color": this.styles.primaryColor || "#4f46e5",
779
+ "--secondary-color": this.styles.secondaryColor || "#818cf8",
780
+ "--background-color": this.styles.backgroundColor || "#ffffff",
781
+ "--text-color": this.styles.textColor || "#1f2937",
782
+ "--selected-date-color": this.styles.selectedDateColor || "#ffffff",
783
+ "--selected-date-background":
784
+ this.styles.selectedDateBackground || "#4f46e5",
785
+ "--today-color": this.styles.todayColor || "#10b981",
786
+ "--disabled-color": this.styles.disabledColor || "#d1d5db",
787
+ "--border-color": this.styles.borderColor || "#e5e7eb",
788
+ "--hover-color": this.styles.hoverColor || "#f3f4f6",
789
+ "--font-family":
790
+ this.styles.fontFamily || "system-ui, -apple-system, sans-serif",
791
+ "--font-size": this.styles.fontSize || "14px",
792
+ "--border-radius": this.styles.borderRadius || "8px",
793
+ };
794
+ }
795
+ }