• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IgniteUI / igniteui-angular / 13331632524

14 Feb 2025 02:51PM UTC coverage: 22.015% (-69.6%) from 91.622%
13331632524

Pull #15372

github

web-flow
Merge d52d57714 into bcb78ae0a
Pull Request #15372: chore(*): test ci passing

1990 of 15592 branches covered (12.76%)

431 of 964 new or added lines in 18 files covered. (44.71%)

19956 existing lines in 307 files now uncovered.

6452 of 29307 relevant lines covered (22.02%)

249.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.38
/projects/igniteui-angular/src/lib/calendar/calendar-base.ts
1
import { Input, Output, EventEmitter, Directive, Inject, LOCALE_ID, HostListener, booleanAttribute, ViewChildren, QueryList, ElementRef, ChangeDetectorRef } from '@angular/core';
2
import { WEEKDAYS, IFormattingOptions, IFormattingViews, IViewDateChangeEventArgs, ScrollDirection, IgxCalendarView, CalendarSelection } from './calendar';
3
import { ControlValueAccessor } from '@angular/forms';
4
import { DateRangeDescriptor } from '../core/dates';
5
import { noop, Subject } from 'rxjs';
6
import { isDate, isEqual, PlatformUtil } from '../core/utils';
7
import { CalendarResourceStringsEN, ICalendarResourceStrings } from '../core/i18n/calendar-resources';
8
import { DateTimeUtil } from '../date-common/util/date-time.util';
9
import { getLocaleFirstDayOfWeek } from "@angular/common";
10
import { getCurrentResourceStrings } from '../core/i18n/resources';
11
import { KeyboardNavigationService } from './calendar.services';
12
import { getYearRange, isDateInRanges } from './common/helpers';
13
import { CalendarDay } from './common/model';
14

15
/** @hidden @internal */
16
@Directive({
17
    selector: '[igxCalendarBase]',
18
    standalone: true,
19
    providers: [KeyboardNavigationService]
20
})
21
export class IgxCalendarBaseDirective implements ControlValueAccessor {
2✔
22
    /**
23
     * Holds month view index we are operating on.
24
     */
UNCOV
25
    protected activeViewIdx = 0;
×
26

27
    /**
28
     * @hidden
29
     */
UNCOV
30
    private _activeView: IgxCalendarView = IgxCalendarView.Month;
×
31

32
    /**
33
     * @hidden
34
     */
UNCOV
35
    private activeViewSubject = new Subject<IgxCalendarView>();
×
36

37
    /**
38
     * @hidden
39
     */
UNCOV
40
    protected activeView$ = this.activeViewSubject.asObservable();
×
41

42
    /**
43
     * Sets/gets whether the outside dates (dates that are out of the current month) will be hidden.
44
     * Default value is `false`.
45
     * ```html
46
     * <igx-calendar [hideOutsideDays]="true"></igx-calendar>
47
     * ```
48
     * ```typescript
49
     * let hideOutsideDays = this.calendar.hideOutsideDays;
50
     * ```
51
     */
52

53
    @Input({ transform: booleanAttribute })
UNCOV
54
    public hideOutsideDays = false;
×
55

56
    /**
57
     * Emits an event when a date is selected.
58
     * Provides reference the `selectedDates` property.
59
     */
60
    @Output()
UNCOV
61
    public selected = new EventEmitter<Date | Date[]>();
×
62

63
    /**
64
     * Emits an event when the month in view is changed.
65
     * ```html
66
     * <igx-calendar (viewDateChanged)="viewDateChanged($event)"></igx-calendar>
67
     * ```
68
     * ```typescript
69
     * public viewDateChanged(event: IViewDateChangeEventArgs) {
70
     *  let viewDate = event.currentValue;
71
     * }
72
     * ```
73
     */
74
    @Output()
UNCOV
75
    public viewDateChanged = new EventEmitter<IViewDateChangeEventArgs>();
×
76

77
    /**
78
     * Emits an event when the active view is changed.
79
     * ```html
80
     * <igx-calendar (activeViewChanged)="activeViewChanged($event)"></igx-calendar>
81
     * ```
82
     * ```typescript
83
     * public activeViewChanged(event: CalendarView) {
84
     *  let activeView = event;
85
     * }
86
     * ```
87
     */
88
    @Output()
UNCOV
89
    public activeViewChanged = new EventEmitter<IgxCalendarView>();
×
90

91
    /**
92
     * @hidden
93
     */
UNCOV
94
    public rangeStarted = false;
×
95

96
    /**
97
     * @hidden
98
     */
UNCOV
99
    public pageScrollDirection = ScrollDirection.NONE;
×
100

101
    /**
102
     * @hidden
103
     */
UNCOV
104
    public scrollPage$ = new Subject<void>();
×
105

106
    /**
107
     * @hidden
108
     */
UNCOV
109
    public stopPageScroll$ = new Subject<boolean>();
×
110

111
    /**
112
     * @hidden
113
     */
UNCOV
114
    public startPageScroll$ = new Subject<void>();
×
115

116
    /**
117
     * @hidden
118
     */
119
    public selectedDates: Date[];
120

121
    /**
122
     * @hidden
123
     */
UNCOV
124
    public shiftKey = false;
×
125

126
    /**
127
    * @hidden
128
    */
129
    public lastSelectedDate: Date;
130

131
    /**
132
     * @hidden
133
     */
134
    protected formatterWeekday: Intl.DateTimeFormat;
135

136
    /**
137
     * @hidden
138
     */
139
    protected formatterDay: Intl.DateTimeFormat;
140

141
    /**
142
     * @hidden
143
     */
144
    protected formatterMonth: Intl.DateTimeFormat;
145

146
    /**
147
     * @hidden
148
     */
149
    protected formatterYear: Intl.DateTimeFormat;
150

151
    /**
152
     * @hidden
153
     */
154
    protected formatterMonthday: Intl.DateTimeFormat;
155

156
    /**
157
     * @hidden
158
     */
159
    protected formatterRangeday: Intl.DateTimeFormat;
160

161
    /**
162
     * @hidden
163
     */
UNCOV
164
    protected _onTouchedCallback: () => void = noop;
×
165
    /**
166
     * @hidden
167
     */
UNCOV
168
    protected _onChangeCallback: (_: Date | Date[]) => void = noop;
×
169

170
    /**
171
      * @hidden
172
      */
173
    protected _deselectDate: boolean;
174

175
    /**
176
     * @hidden
177
     */
178
    private initialSelection: Date | Date[];
179

180
    /**
181
     * @hidden
182
     */
183
    private _locale: string;
184

185
    /**
186
     * @hidden
187
     */
188
    private _weekStart: WEEKDAYS | number;
189

190
    /**
191
     * @hidden
192
     */
193
    private _viewDate: Date;
194

195
    /**
196
     * @hidden
197
     */
198
    private _startDate: Date;
199

200
    /**
201
     * @hidden
202
     */
203
    private _endDate: Date;
204

205
    /**
206
     * @hidden
207
     */
UNCOV
208
    private _disabledDates: DateRangeDescriptor[] = [];
×
209

210
    /**
211
     * @hidden
212
     */
UNCOV
213
    private _specialDates: DateRangeDescriptor[] = [];
×
214

215
    /**
216
     * @hidden
217
     */
UNCOV
218
    private _selection: CalendarSelection | string = CalendarSelection.SINGLE;
×
219

220
    /** @hidden @internal */
UNCOV
221
    private _resourceStrings = getCurrentResourceStrings(CalendarResourceStringsEN);
×
222

223
    /**
224
     * @hidden
225
     */
UNCOV
226
    private _formatOptions: IFormattingOptions = {
×
227
        day: 'numeric',
228
        month: 'long',
229
        weekday: 'narrow',
230
        year: 'numeric'
231
    };
232

233
    /**
234
     * @hidden
235
     */
UNCOV
236
    private _formatViews: IFormattingViews = {
×
237
        day: false,
238
        month: true,
239
        year: false
240
    };
241

242
    /**
243
     * An accessor that sets the resource strings.
244
     * By default it uses EN resources.
245
     */
246
    @Input()
247
    public set resourceStrings(value: ICalendarResourceStrings) {
248
        this._resourceStrings = Object.assign({}, this._resourceStrings, value);
×
249
    }
250

251
    /**
252
     * An accessor that returns the resource strings.
253
     */
254
    public get resourceStrings(): ICalendarResourceStrings {
UNCOV
255
        return this._resourceStrings;
×
256
    }
257

258
    /**
259
     * Gets the start day of the week.
260
     * Can return a numeric or an enum representation of the week day.
261
     * If not set, defaults to the first day of the week for the application locale.
262
     */
263
    @Input()
264
    public get weekStart(): WEEKDAYS | number {
UNCOV
265
        return this._weekStart;
×
266
    }
267

268
    /**
269
     * Sets the start day of the week.
270
     * Can be assigned to a numeric value or to `WEEKDAYS` enum value.
271
     */
272
    public set weekStart(value: WEEKDAYS | number) {
UNCOV
273
        this._weekStart = value;
×
274
    }
275

276
    /**
277
     * Gets the `locale` of the calendar.
278
     * If not set, defaults to application's locale.
279
     */
280
    @Input()
281
    public get locale(): string {
UNCOV
282
        return this._locale;
×
283
    }
284

285
    /**
286
     * Sets the `locale` of the calendar.
287
     * Expects a valid BCP 47 language tag.
288
     */
289
    public set locale(value: string) {
UNCOV
290
        this._locale = value;
×
291

292
        // if value is not a valid BCP 47 tag, set it back to _localeId
UNCOV
293
        try {
×
UNCOV
294
            getLocaleFirstDayOfWeek(this._locale);
×
295
        } catch (e) {
UNCOV
296
            this._locale = this._localeId;
×
297
        }
298

299
        // changing locale runtime needs to update the `weekStart` too, if `weekStart` is not explicitly set
UNCOV
300
        if (!this.weekStart) {
×
UNCOV
301
            this.weekStart = getLocaleFirstDayOfWeek(this._locale);
×
302
        }
303

UNCOV
304
        this.initFormatters();
×
305
    }
306

307
    /**
308
     * Gets the date format options of the views.
309
     */
310
    @Input()
311
    public get formatOptions(): IFormattingOptions {
UNCOV
312
        return this._formatOptions;
×
313
    }
314

315
    /**
316
     * Sets the date format options of the views.
317
     * Default is { day: 'numeric', month: 'short', weekday: 'short', year: 'numeric' }
318
     */
319
    public set formatOptions(formatOptions: IFormattingOptions) {
UNCOV
320
        this._formatOptions = {...this._formatOptions, ...formatOptions};
×
UNCOV
321
        this.initFormatters();
×
322
    }
323

324
    /**
325
     * Gets whether the `day`, `month` and `year` should be rendered
326
     * according to the locale and formatOptions, if any.
327
     */
328
    @Input()
329
    public get formatViews(): IFormattingViews {
UNCOV
330
        return this._formatViews;
×
331
    }
332

333
    /**
334
     * Sets whether the `day`, `month` and `year` should be rendered
335
     * according to the locale and formatOptions, if any.
336
     */
337
    public set formatViews(formatViews: IFormattingViews) {
UNCOV
338
        this._formatViews = Object.assign(this._formatViews, formatViews);
×
339
    }
340

341
    /**
342
     * Gets the current active view.
343
     * ```typescript
344
     * this.activeView = calendar.activeView;
345
     * ```
346
     */
347
    @Input()
348
    public get activeView(): IgxCalendarView {
UNCOV
349
        return this._activeView;
×
350
    }
351

352
    /**
353
     * Sets the current active view.
354
     * ```html
355
     * <igx-calendar [activeView]="year" #calendar></igx-calendar>
356
     * ```
357
     * ```typescript
358
     * calendar.activeView = IgxCalendarView.YEAR;
359
     * ```
360
     */
361
    public set activeView(val: IgxCalendarView) {
UNCOV
362
        this._activeView = val;
×
UNCOV
363
        this.activeViewSubject.next(val);
×
364
    }
365

366
    /**
367
     * @hidden
368
     */
369
    public get isDefaultView(): boolean {
UNCOV
370
        return this._activeView === IgxCalendarView.Month;
×
371
    }
372

373
    /**
374
     * @hidden
375
     */
376
    public get isDecadeView(): boolean {
UNCOV
377
        return this._activeView === IgxCalendarView.Decade;
×
378
    }
379

380
    /**
381
     * @hidden
382
     */
383
    public activeViewDecade(activeViewIdx = 0): void {
×
UNCOV
384
        this.activeView = IgxCalendarView.Decade;
×
UNCOV
385
        this.activeViewIdx = activeViewIdx;
×
386
    }
387

388
    /**
389
     * @hidden
390
     */
391
    public activeViewDecadeKB(event: KeyboardEvent, activeViewIdx = 0) {
×
UNCOV
392
        event.stopPropagation();
×
393

UNCOV
394
        if (this.platform.isActivationKey(event)) {
×
UNCOV
395
            event.preventDefault();
×
UNCOV
396
            this.activeViewDecade(activeViewIdx);
×
397
        }
398
    }
399

400
    /**
401
     * @hidden
402
     */
403
    @ViewChildren('yearsBtn')
404
    public yearsBtns: QueryList<ElementRef>;
405

406
    /**
407
     * @hidden @internal
408
     */
409
    public previousViewDate: Date;
410

411
    /**
412
     * @hidden
413
     */
414
    public changeYear(date: Date) {
415
        this.previousViewDate = this.viewDate;
×
416
        this.viewDate = CalendarDay.from(date).add('month', -this.activeViewIdx).native;
×
417
        this.activeView = IgxCalendarView.Month;
×
418
    }
419

420
    /**
421
     * Returns the locale representation of the year in the year view if enabled,
422
     * otherwise returns the default `Date.getFullYear()` value.
423
     *
424
     * @hidden
425
     */
426
    public formattedYear(value: Date | Date[]): string {
UNCOV
427
                if (Array.isArray(value)) {
×
428
                        return;
×
429
                }
430

UNCOV
431
        if (this.formatViews.year) {
×
UNCOV
432
            return this.formatterYear.format(value);
×
433
        }
434

UNCOV
435
            return `${value.getFullYear()}`;
×
436
    }
437

438
        public formattedYears(value: Date) {
439
                const dates = value as unknown as Date[];
×
440
                return dates.map(date => this.formattedYear(date)).join(' - ');
×
441
        }
442

443
    protected prevNavLabel(detail?: string): string {
UNCOV
444
        switch (this.activeView) {
×
445
            case 'month':
UNCOV
446
                return `${this.resourceStrings.igx_calendar_previous_month}, ${detail}`
×
447
            case 'year':
UNCOV
448
                return this.resourceStrings.igx_calendar_previous_year.replace('{0}', '15');
×
449
            case 'decade':
UNCOV
450
                return this.resourceStrings.igx_calendar_previous_years.replace('{0}', '15');
×
451
        }
452
    }
453

454
    protected nextNavLabel(detail?: string): string {
UNCOV
455
        switch (this.activeView) {
×
456
            case 'month':
UNCOV
457
                return `${this.resourceStrings.igx_calendar_next_month}, ${detail}`
×
458
            case 'year':
UNCOV
459
                return this.resourceStrings.igx_calendar_next_year.replace('{0}', '15');
×
460
            case 'decade':
UNCOV
461
                return this.resourceStrings.igx_calendar_next_years.replace('{0}', '15');
×
462
        }
463
    }
464

465
        protected getDecadeRange(): { start: string; end: string } {
UNCOV
466
        const range = getYearRange(this.viewDate, 15);
×
UNCOV
467
        const start = CalendarDay.from(this.viewDate).set({ date: 1, year: range.start });
×
UNCOV
468
        const end = CalendarDay.from(this.viewDate).set({ date: 1, year: range.end });
×
469

UNCOV
470
                return {
×
471
                        start: this.formatterYear.format(start.native),
472
                        end: this.formatterYear.format(end.native)
473
                }
474
        }
475
    /**
476
     *
477
     * Gets the selection type.
478
     * Default value is `"single"`.
479
     * Changing the type of selection resets the currently
480
     * selected values if any.
481
     */
482
    @Input()
483
    public get selection(): string {
UNCOV
484
        return this._selection;
×
485
    }
486

487
    /**
488
     * Sets the selection.
489
     */
490
    public set selection(value: string) {
UNCOV
491
        switch (value) {
×
492
            case CalendarSelection.SINGLE:
UNCOV
493
                this.selectedDates = null;
×
UNCOV
494
                break;
×
495
            case CalendarSelection.MULTI:
496
            case CalendarSelection.RANGE:
UNCOV
497
                this.selectedDates = [];
×
UNCOV
498
                break;
×
499
            default:
UNCOV
500
                throw new Error('Invalid selection value');
×
501
        }
UNCOV
502
        this._onChangeCallback(this.selectedDates);
×
UNCOV
503
        this.rangeStarted = false;
×
UNCOV
504
        this._selection = value;
×
505
    }
506

507
    /**
508
     * Gets the date that is presented. By default it is the current date.
509
     */
510
    @Input()
511
    public get viewDate(): Date {
UNCOV
512
        return this._viewDate;
×
513
    }
514

515
    /**
516
     * Sets the date that will be presented in the default view when the component renders.
517
     */
518
    public set viewDate(value: Date | string) {
UNCOV
519
        if (Array.isArray(value)) {
×
520
            return;
×
521
        }
522

UNCOV
523
        if (typeof value === 'string') {
×
UNCOV
524
            value = DateTimeUtil.parseIsoDate(value);
×
525
        }
526

UNCOV
527
        const validDate = this.validateDate(value);
×
528

UNCOV
529
        if (this._viewDate) {
×
UNCOV
530
            this.initialSelection = validDate;
×
531
        }
532

UNCOV
533
        const date = this.getDateOnly(validDate).setDate(1);
×
UNCOV
534
        this._viewDate = new Date(date);
×
535
    }
536

537
    /**
538
     * Gets the disabled dates descriptors.
539
     */
540
    @Input()
541
    public get disabledDates(): DateRangeDescriptor[] {
UNCOV
542
        return this._disabledDates;
×
543
    }
544

545
    /**
546
     * Sets the disabled dates' descriptors.
547
     * ```typescript
548
     * @ViewChild("MyCalendar")
549
     * public calendar: IgxCalendarComponent;
550
     * ngOnInit(){
551
     *    this.calendar.disabledDates = [
552
     *     {type: DateRangeType.Between, dateRange: [new Date("2020-1-1"), new Date("2020-1-15")]},
553
     *     {type: DateRangeType.Weekends}];
554
     * }
555
     * ```
556
     */
557
    public set disabledDates(value: DateRangeDescriptor[]) {
UNCOV
558
        this._disabledDates = value;
×
559
    }
560

561
    /**
562
     * Checks whether a date is disabled.
563
     *
564
     * @hidden
565
     */
566
    public isDateDisabled(date: Date | string) {
UNCOV
567
        if (!this.disabledDates) {
×
568
            return false;
×
569
        }
570

UNCOV
571
        if (typeof date === 'string') {
×
572
            date = DateTimeUtil.parseIsoDate(date);
×
573
        }
574

UNCOV
575
        return isDateInRanges(date, this.disabledDates);
×
576
    }
577

578
    /**
579
     * Gets the special dates descriptors.
580
     */
581
    @Input()
582
    public get specialDates(): DateRangeDescriptor[] {
UNCOV
583
        return this._specialDates;
×
584
    }
585

586
    /**
587
     * Sets the special dates' descriptors.
588
     * ```typescript
589
     * @ViewChild("MyCalendar")
590
     * public calendar: IgxCalendarComponent;
591
     * ngOnInit(){
592
     *    this.calendar.specialDates = [
593
     *     {type: DateRangeType.Between, dateRange: [new Date("2020-1-1"), new Date("2020-1-15")]},
594
     *     {type: DateRangeType.Weekends}];
595
     * }
596
     * ```
597
     */
598
    public set specialDates(value: DateRangeDescriptor[]) {
UNCOV
599
        this._specialDates = value;
×
600
    }
601

602
    /**
603
     * Gets the selected date(s).
604
     *
605
     * When selection is set to `single`, it returns
606
     * a single `Date` object.
607
     * Otherwise it is an array of `Date` objects.
608
     */
609
    @Input()
610
    public get value(): Date | Date[] {
UNCOV
611
        if (this.selection === CalendarSelection.SINGLE) {
×
UNCOV
612
            return this.selectedDates?.at(0);
×
613
        }
614

UNCOV
615
        return this.selectedDates;
×
616
    }
617

618
    /**
619
     * Sets the selected date(s).
620
     *
621
     * When selection is set to `single`, it accepts
622
     * a single `Date` object.
623
     * Otherwise it is an array of `Date` objects.
624
     */
625
    public set value(value: Date | Date[] | string) {
626
        // Validate the date if it is of type string and it is IsoDate
UNCOV
627
        if (typeof value === 'string') {
×
UNCOV
628
            value = DateTimeUtil.parseIsoDate(value);
×
629
        }
630

631
        // Check if value is set initially by the user,
632
        // if it's not set the initial selection to the current date
UNCOV
633
        if (!value || (Array.isArray(value) && value.length === 0)) {
×
UNCOV
634
            this.initialSelection = new Date();
×
UNCOV
635
            return;
×
636
        }
637

638
        // Value is provided, but there's no initial selection, set the initial selection to the passed value
UNCOV
639
        if (!this.initialSelection) {
×
UNCOV
640
            this.viewDate = Array.isArray(value) ? new Date(Math.min(...value as unknown as number[])) : value;
×
641
        }
642

643
        // we then call selectDate with either a single date or an array of dates
644
        // we also set the initial selection to the provided value
UNCOV
645
        this.selectDate(value);
×
UNCOV
646
        this.initialSelection = value;
×
647
    }
648

649
    /**
650
     * @hidden
651
     */
652
    constructor(
UNCOV
653
        protected platform: PlatformUtil,
×
654
        @Inject(LOCALE_ID)
UNCOV
655
        protected _localeId: string,
×
UNCOV
656
        protected keyboardNavigation?: KeyboardNavigationService,
×
UNCOV
657
        protected cdr?: ChangeDetectorRef,
×
658
    ) {
UNCOV
659
        this.locale = _localeId;
×
UNCOV
660
        this.viewDate = this.viewDate ? this.viewDate : new Date();
×
UNCOV
661
        this.initFormatters();
×
662
    }
663

664
    /**
665
     * Multi/Range selection with shift key
666
     *
667
     * @hidden
668
     * @internal
669
     */
670
    @HostListener('pointerdown', ['$event'])
671
    public onPointerdown(event: MouseEvent) {
UNCOV
672
        this.shiftKey = event.button === 0 && event.shiftKey;
×
673
    }
674

675
    /**
676
     * @hidden
677
     */
678
    public registerOnChange(fn: (v: Date | Date[]) => void) {
UNCOV
679
        this._onChangeCallback = fn;
×
680
    }
681

682
    /**
683
     * @hidden
684
     */
685
    public registerOnTouched(fn: () => void) {
UNCOV
686
        this._onTouchedCallback = fn;
×
687
    }
688

689
    /**
690
     * @hidden
691
     */
692
    public writeValue(value: Date | Date[]) {
UNCOV
693
        this.value = value;
×
694
    }
695

696
    /**
697
     * Selects date(s) (based on the selection type).
698
     */
699
    public selectDate(value: Date | Date[] | string) {
UNCOV
700
        if (typeof value === 'string') {
×
UNCOV
701
            value = DateTimeUtil.parseIsoDate(value);
×
702
        }
703

UNCOV
704
        if (value === null || value === undefined || (Array.isArray(value) && value.length === 0)) {
×
705
            return;
×
706
        }
707

UNCOV
708
        switch (this.selection) {
×
709
            case CalendarSelection.SINGLE:
UNCOV
710
                if (isDate(value) && !this.isDateDisabled(value as Date)) {
×
UNCOV
711
                    this.selectSingle(value as Date);
×
712
                }
UNCOV
713
                break;
×
714
            case CalendarSelection.MULTI:
UNCOV
715
                this.selectMultiple(value);
×
UNCOV
716
                break;
×
717
            case CalendarSelection.RANGE:
UNCOV
718
                this.selectRange(value, true);
×
UNCOV
719
                break;
×
720
        }
721
    }
722

723
    /**
724
     * Deselects date(s) (based on the selection type).
725
     */
726
    public deselectDate(value?: Date | Date[] | string) {
UNCOV
727
        if (!this.selectedDates || this.selectedDates.length === 0) {
×
728
            return;
×
729
        }
730

UNCOV
731
        if (typeof value === 'string') {
×
UNCOV
732
            value = DateTimeUtil.parseIsoDate(value);
×
733
        }
734

UNCOV
735
        if (value === null || value === undefined) {
×
UNCOV
736
            this.selectedDates = this.selection === CalendarSelection.SINGLE ? null : [];
×
UNCOV
737
            this.rangeStarted = false;
×
UNCOV
738
            this._onChangeCallback(this.selectedDates);
×
UNCOV
739
            return;
×
740
        }
741

UNCOV
742
        switch (this.selection) {
×
743
            case CalendarSelection.SINGLE:
UNCOV
744
                this.deselectSingle(value as Date);
×
UNCOV
745
                break;
×
746
            case CalendarSelection.MULTI:
UNCOV
747
                this.deselectMultiple(value as Date[]);
×
UNCOV
748
                break;
×
749
            case CalendarSelection.RANGE:
UNCOV
750
                this.deselectRange(value as Date[]);
×
UNCOV
751
                break;
×
752
        }
753
    }
754

755
    /**
756
     * Performs a single selection.
757
     *
758
     * @hidden
759
     */
760
    private selectSingle(value: Date) {
UNCOV
761
        if (!isEqual(this.selectedDates?.at(0), value)) {
×
UNCOV
762
            this.selectedDates = [this.getDateOnly(value)];
×
UNCOV
763
            this._onChangeCallback(this.selectedDates.at(0));
×
764
        }
765
    }
766

767
    /**
768
     * Performs a single deselection.
769
     *
770
     * @hidden
771
     */
772
    private deselectSingle(value: Date) {
UNCOV
773
        if (this.selectedDates !== null &&
×
774
            this.getDateOnlyInMs(value as Date) === this.getDateOnlyInMs(this.selectedDates.at(0))) {
UNCOV
775
            this.selectedDates = null;
×
UNCOV
776
            this._onChangeCallback(this.selectedDates);
×
777
        }
778
    }
779

780
    /**
781
     * Performs a multiple selection
782
     *
783
     * @hidden
784
     */
785
    private selectMultiple(value: Date | Date[]) {
UNCOV
786
        if (Array.isArray(value)) {
×
UNCOV
787
            const newDates = value.map(v => this.getDateOnly(v).getTime());
×
UNCOV
788
            const selDates = this.selectedDates.map(v => this.getDateOnly(v).getTime());
×
789

UNCOV
790
            if (JSON.stringify(newDates) === JSON.stringify(selDates)) {
×
UNCOV
791
                return;
×
792
            }
793

UNCOV
794
            if (selDates.length === 0 || selDates.length > newDates.length) {
×
795
                // deselect the dates that are part of currently selectedDates and not part of updated new values
UNCOV
796
                this.selectedDates = newDates.map(v => new Date(v));
×
797
            } else {
UNCOV
798
                this.selectedDates = Array.from(new Set([...newDates, ...selDates])).map(v => new Date(v));
×
799
            }
800
        } else {
UNCOV
801
            let newSelection = [];
×
802

UNCOV
803
            if (this.shiftKey && this.lastSelectedDate) {
×
804

UNCOV
805
                [this._startDate, this._endDate] = this.lastSelectedDate.getTime() < value.getTime()
×
806
                    ? [this.lastSelectedDate, value]
807
                    : [value, this.lastSelectedDate];
808

UNCOV
809
                const unselectedDates = [this._startDate, ...this.generateDateRange(this._startDate, this._endDate)]
×
UNCOV
810
                    .filter(date => !this.isDateDisabled(date)
×
UNCOV
811
                        && this.selectedDates.every((d: Date) => d.getTime() !== date.getTime())
×
812
                    );
813

814
                // select all dates from last selected to shift clicked date
UNCOV
815
                if (this.selectedDates.some((date: Date) => date.getTime() === this.lastSelectedDate.getTime())
×
816
                    && unselectedDates.length) {
817

UNCOV
818
                    newSelection = unselectedDates;
×
819
                } else {
820
                    // delesect all dates from last clicked to shift clicked date (excluding)
UNCOV
821
                    this.selectedDates = this.selectedDates.filter((date: Date) =>
×
UNCOV
822
                        date.getTime() < this._startDate.getTime() || date.getTime() > this._endDate.getTime()
×
823
                    );
824

UNCOV
825
                    this.selectedDates.push(value);
×
UNCOV
826
                    this._deselectDate = true;
×
827
                }
828

UNCOV
829
                this._startDate = this._endDate = undefined;
×
830

UNCOV
831
            } else if (this.selectedDates.every((date: Date) => date.getTime() !== value.getTime())) {
×
UNCOV
832
                newSelection.push(value);
×
833

834
            } else {
UNCOV
835
                this.selectedDates = this.selectedDates.filter(
×
UNCOV
836
                    (date: Date) => date.getTime() !== value.getTime()
×
837
                );
838

UNCOV
839
                this._deselectDate = true;
×
840
            }
841

UNCOV
842
            if (newSelection.length > 0) {
×
UNCOV
843
                this.selectedDates = this.selectedDates.concat(newSelection);
×
UNCOV
844
                this._deselectDate = false;
×
845
            }
846

UNCOV
847
            this.lastSelectedDate = value;
×
848
        }
849

UNCOV
850
        this.selectedDates = this.selectedDates.filter(d => !this.isDateDisabled(d));
×
UNCOV
851
        this.selectedDates.sort((a: Date, b: Date) => a.valueOf() - b.valueOf());
×
UNCOV
852
        this._onChangeCallback(this.selectedDates);
×
853
    }
854

855
    /**
856
     * Performs a multiple deselection.
857
     *
858
     * @hidden
859
     */
860
    private deselectMultiple(value: Date[]) {
UNCOV
861
        value = value.filter(v => v !== null);
×
UNCOV
862
        const selectedDatesCount = this.selectedDates.length;
×
UNCOV
863
        const datesInMsToDeselect: Set<number> = new Set<number>(
×
UNCOV
864
            value.map(v => this.getDateOnlyInMs(v)));
×
865

UNCOV
866
        for (let i = this.selectedDates.length - 1; i >= 0; i--) {
×
UNCOV
867
            if (datesInMsToDeselect.has(this.getDateOnlyInMs(this.selectedDates[i]))) {
×
UNCOV
868
                this.selectedDates.splice(i, 1);
×
869
            }
870
        }
871

UNCOV
872
        if (this.selectedDates.length !== selectedDatesCount) {
×
UNCOV
873
            this._onChangeCallback(this.selectedDates);
×
874
        }
875
    }
876

877
    /**
878
     * @hidden
879
     */
880
    private selectRange(value: Date | Date[], excludeDisabledDates = false) {
×
UNCOV
881
        if (Array.isArray(value)) {
×
UNCOV
882
            value.sort((a: Date, b: Date) => a.valueOf() - b.valueOf());
×
UNCOV
883
            this._startDate = this.getDateOnly(value[0]);
×
UNCOV
884
            this._endDate = this.getDateOnly(value[value.length - 1]);
×
885
        } else {
886

UNCOV
887
            if (this.shiftKey && this.lastSelectedDate) {
×
888

UNCOV
889
                if (this.lastSelectedDate.getTime() === value.getTime()) {
×
890
                    this.selectedDates = this.selectedDates.length === 1 ? [] : [value];
×
891
                    this.rangeStarted = !!this.selectedDates.length;
×
892
                    this._onChangeCallback(this.selectedDates);
×
893
                    return;
×
894
                }
895

896
                // shortens the range when selecting a date inside of it
UNCOV
897
                if (this.selectedDates.some((date: Date) => date.getTime() === value.getTime())) {
×
898

UNCOV
899
                    this.lastSelectedDate.getTime() < value.getTime()
×
900
                        ? this._startDate = value
901
                        : this._endDate = value;
902

903
                } else {
904
                    // extends the range when selecting a date outside of it
905
                    // allows selection from last deselected to current selected date
UNCOV
906
                    if (this.lastSelectedDate.getTime() < value.getTime()) {
×
UNCOV
907
                        this._startDate = this._startDate ?? this.lastSelectedDate;
×
UNCOV
908
                        this._endDate = value;
×
909
                    } else {
UNCOV
910
                        this._startDate = value;
×
UNCOV
911
                        this._endDate = this._endDate ?? this.lastSelectedDate;
×
912
                    }
913
                }
914

UNCOV
915
                this.rangeStarted = false;
×
916

UNCOV
917
            } else if (!this.rangeStarted) {
×
UNCOV
918
                this.rangeStarted = true;
×
UNCOV
919
                this.selectedDates = [value];
×
UNCOV
920
                this._startDate = this._endDate = undefined;
×
921
            } else {
UNCOV
922
                this.rangeStarted = false;
×
923

UNCOV
924
                if (this.selectedDates?.at(0)?.getTime() === value.getTime()) {
×
UNCOV
925
                    this.selectedDates = [];
×
UNCOV
926
                    this._onChangeCallback(this.selectedDates);
×
UNCOV
927
                    return;
×
928
                }
929

UNCOV
930
                [this._startDate, this._endDate] = this.lastSelectedDate.getTime() < value.getTime()
×
931
                    ? [this.lastSelectedDate, value]
932
                    : [value, this.lastSelectedDate];
933
            }
934

UNCOV
935
            this.lastSelectedDate = value;
×
936
        }
937

UNCOV
938
        if (this._startDate && this._endDate) {
×
UNCOV
939
            this.selectedDates = [this._startDate, ...this.generateDateRange(this._startDate, this._endDate)];
×
940
        }
941

UNCOV
942
        if (excludeDisabledDates) {
×
UNCOV
943
            this.selectedDates = this.selectedDates.filter(d => !this.isDateDisabled(d));
×
944
        }
945

UNCOV
946
        this._onChangeCallback(this.selectedDates);
×
947
    }
948

949
    /**
950
     * Performs a range deselection.
951
     *
952
     * @hidden
953
     */
954
    private deselectRange(value: Date[]) {
UNCOV
955
        value = value.filter(v => v !== null);
×
956

UNCOV
957
        if (value.length < 1) {
×
958
            return;
×
959
        }
960

UNCOV
961
        value.sort((a: Date, b: Date) => a.valueOf() - b.valueOf());
×
962

UNCOV
963
        const valueStart = this.getDateOnlyInMs(value[0]);
×
UNCOV
964
        const valueEnd = this.getDateOnlyInMs(value[value.length - 1]);
×
965

UNCOV
966
        this.selectedDates.sort((a: Date, b: Date) => a.valueOf() - b.valueOf());
×
967

UNCOV
968
        const selectedDatesStart = this.getDateOnlyInMs(this.selectedDates[0]);
×
UNCOV
969
        const selectedDatesEnd = this.getDateOnlyInMs(this.selectedDates[this.selectedDates.length - 1]);
×
970

UNCOV
971
        if (!(valueEnd < selectedDatesStart) && !(valueStart > selectedDatesEnd)) {
×
UNCOV
972
            this.selectedDates = [];
×
UNCOV
973
            this.rangeStarted = false;
×
UNCOV
974
            this._onChangeCallback(this.selectedDates);
×
975
        }
976
    }
977

978
    /**
979
     * @hidden
980
     */
981
    protected initFormatters() {
UNCOV
982
        this.formatterDay = new Intl.DateTimeFormat(this._locale, { day: this._formatOptions.day });
×
UNCOV
983
        this.formatterWeekday = new Intl.DateTimeFormat(this._locale, { weekday: this._formatOptions.weekday });
×
UNCOV
984
        this.formatterMonth = new Intl.DateTimeFormat(this._locale, { month: this._formatOptions.month });
×
UNCOV
985
        this.formatterYear = new Intl.DateTimeFormat(this._locale, { year: this._formatOptions.year });
×
UNCOV
986
        this.formatterMonthday = new Intl.DateTimeFormat(this._locale, { month: this._formatOptions.month, day: this._formatOptions.day });
×
UNCOV
987
                this.formatterRangeday = new Intl.DateTimeFormat(this._locale, { day: this._formatOptions.day, month: 'short' });
×
988
    }
989

990
    /**
991
     * @hidden
992
     */
993
    protected getDateOnly(date: Date) {
UNCOV
994
        const validDate = this.validateDate(date);
×
UNCOV
995
        return new Date(validDate.getFullYear(), validDate.getMonth(), validDate.getDate());
×
996
    }
997

998
    /**
999
     * @hidden
1000
     */
1001
    private getDateOnlyInMs(date: Date) {
UNCOV
1002
        return this.getDateOnly(date).getTime();
×
1003
    }
1004

1005
    /**
1006
     * @hidden
1007
     */
1008
    private generateDateRange(start: Date, end: Date): Date[] {
UNCOV
1009
        const result = [];
×
UNCOV
1010
        start = this.getDateOnly(start);
×
UNCOV
1011
        end = this.getDateOnly(end);
×
1012

UNCOV
1013
        while (start.getTime() < end.getTime()) {
×
UNCOV
1014
            start = CalendarDay.from(start).add('day', 1).native;
×
UNCOV
1015
            result.push(start);
×
1016
        }
1017

UNCOV
1018
        return result;
×
1019
    }
1020

1021
    private validateDate(value: Date) {
UNCOV
1022
        return DateTimeUtil.isValidDate(value) ? value : new Date();
×
1023
    }
1024
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc