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

IgniteUI / igniteui-angular / 13331632524

14 Feb 2025 02:51PM CUT 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.77
/projects/igniteui-angular/src/lib/calendar/calendar.component.ts
1
import {
2
        Component,
3
        ContentChild,
4
        forwardRef,
5
        HostBinding,
6
        Input,
7
        ViewChild,
8
        ElementRef,
9
        AfterViewInit,
10
        ViewChildren,
11
        QueryList,
12
        OnDestroy,
13
        booleanAttribute,
14
    HostListener,
15
} from '@angular/core';
16
import { NgIf, NgTemplateOutlet, NgFor, DatePipe } from '@angular/common';
17
import { NG_VALUE_ACCESSOR } from '@angular/forms';
18

19
import {
20
        IgxCalendarHeaderTemplateDirective,
21
    IgxCalendarHeaderTitleTemplateDirective,
22
        IgxCalendarSubheaderTemplateDirective,
23
    IgxCalendarScrollPageDirective,
24
} from './calendar.directives';
25
import { IgxCalendarView, ScrollDirection } from './calendar';
26
import { IgxMonthsViewComponent } from './months-view/months-view.component';
27
import { IgxYearsViewComponent } from './years-view/years-view.component';
28
import { IgxDaysViewComponent } from './days-view/days-view.component';
29
import { interval } from 'rxjs';
30
import { takeUntil, debounce, skipLast, switchMap } from 'rxjs/operators';
31
import { IgxMonthViewSlotsCalendar, IgxGetViewDateCalendar } from './months-view.pipe';
32
import { IgxIconComponent } from '../icon/icon.component';
33
import { areSameMonth, formatToParts, getClosestActiveDate, isDateInRanges } from './common/helpers';
34
import { CalendarDay } from './common/model';
35
import { IgxCalendarBaseDirective } from './calendar-base';
36
import { KeyboardNavigationService } from './calendar.services';
37

38
let NEXT_ID = 0;
2✔
39

40
/**
41
 * Calendar provides a way to display date information.
42
 *
43
 * @igxModule IgxCalendarModule
44
 *
45
 * @igxTheme igx-calendar-theme, igx-icon-theme
46
 *
47
 * @igxKeywords calendar, datepicker, schedule, date
48
 *
49
 * @igxGroup Scheduling
50
 *
51
 * @remarks
52
 * The Ignite UI Calendar provides an easy way to display a calendar and allow users to select dates using single, multiple
53
 * or range selection.
54
 *
55
 * @example:
56
 * ```html
57
 * <igx-calendar selection="range"></igx-calendar>
58
 * ```
59
 */
60
@Component({
61
    providers: [
62
        {
63
            multi: true,
64
            provide: NG_VALUE_ACCESSOR,
65
            useExisting: IgxCalendarComponent,
66
        },
67
        {
68
            multi: false,
69
            provide: KeyboardNavigationService,
70
        },
71
    ],
72
    selector: 'igx-calendar',
73
    templateUrl: 'calendar.component.html',
74
    imports: [NgIf, NgTemplateOutlet, IgxCalendarScrollPageDirective, IgxIconComponent, NgFor, IgxDaysViewComponent, IgxMonthsViewComponent, IgxYearsViewComponent, DatePipe, IgxMonthViewSlotsCalendar, IgxGetViewDateCalendar]
75
})
76
export class IgxCalendarComponent extends IgxCalendarBaseDirective implements AfterViewInit, OnDestroy {
2✔
77
    /**
78
     * @hidden
79
     * @internal
80
     */
81
    private _activeDescendant: number;
82

83
    /**
84
     * @hidden
85
     * @internal
86
     */
87
    @ViewChild("wrapper")
88
    public wrapper: ElementRef;
89

90
        /**
91
         * Sets/gets the `id` of the calendar.
92
         *
93
         * @remarks
94
         * If not set, the `id` will have value `"igx-calendar-0"`.
95
         *
96
         * @example
97
         * ```html
98
         * <igx-calendar id="my-first-calendar"></igx-calendar>
99
         * ```
100
         * @memberof IgxCalendarComponent
101
         */
102
        @HostBinding('attr.id')
103
        @Input()
UNCOV
104
        public id = `igx-calendar-${ NEXT_ID++ }`;
×
105

106
    /**
107
     * Sets/gets whether the calendar has header.
108
     * Default value is `true`.
109
     *
110
     * @example
111
     * ```html
112
     * <igx-calendar [hasHeader]="false"></igx-calendar>
113
     * ```
114
     */
115
    @Input({ transform: booleanAttribute })
UNCOV
116
    public hasHeader = true;
×
117

118
    /**
119
     * Sets/gets whether the calendar header will be in vertical position.
120
     * Default value is `false`.
121
     *
122
     * @example
123
     * ```html
124
     * <igx-calendar [vertical]="true"></igx-calendar>
125
     * ```
126
     */
127
    @Input({ transform: booleanAttribute })
UNCOV
128
    public vertical = false;
×
129

130
    @Input()
UNCOV
131
    public orientation: 'horizontal' | 'vertical' = 'horizontal';
×
132

133
    @Input()
UNCOV
134
    public headerOrientation: 'horizontal' | 'vertical' = 'horizontal';
×
135

136
        /**
137
         * Sets/gets the number of month views displayed.
138
         * Default value is `1`.
139
         *
140
         * @example
141
         * ```html
142
         * <igx-calendar [monthsViewNumber]="2"></igx-calendar>
143
         * ```
144
         */
145
        @Input()
146
        public get monthsViewNumber() {
UNCOV
147
                return this._monthsViewNumber;
×
148
        }
149

150
    public set monthsViewNumber(val: number) {
UNCOV
151
        if (val < 1) {
×
UNCOV
152
            return;
×
153
        }
154

UNCOV
155
                this._monthsViewNumber = val;
×
156
        }
157

158
    /**
159
     * Show/hide week numbers
160
     *
161
     * @example
162
     * ```html
163
     * <igx-calendar [showWeekNumbers]="true"></igx-calendar>
164
     * ``
165
     */
166
    @Input({ transform: booleanAttribute })
UNCOV
167
    public showWeekNumbers = false;
×
168

169
        /**
170
         * The default css class applied to the component.
171
         *
172
         * @hidden
173
         * @internal
174
         */
175
        @HostBinding('class.igx-calendar--vertical')
176
        public get styleVerticalClass(): boolean {
UNCOV
177
                return this.headerOrientation === 'vertical';
×
178
        }
179

180
        /**
181
         * The default css class applied to the component.
182
         *
183
         * @hidden
184
         * @internal
185
         */
186
        @HostBinding('class.igx-calendar')
UNCOV
187
        public styleClass = true;
×
188

189
        /**
190
         * Month button, that displays the months view.
191
         *
192
         * @hidden
193
         * @internal
194
         */
195
        @ViewChildren('monthsBtn')
196
        public monthsBtns: QueryList<ElementRef>;
197

198
        /**
199
         * ViewChild that represents the decade view.
200
         *
201
         * @hidden
202
         * @internal
203
         */
204
        @ViewChild('decade', { read: IgxYearsViewComponent })
205
        public dacadeView: IgxYearsViewComponent;
206

207
        /**
208
         * ViewChild that represents the months view.
209
         *
210
         * @hidden
211
         * @internal
212
         */
213
        @ViewChild('months', { read: IgxMonthsViewComponent })
214
        public monthsView: IgxMonthsViewComponent;
215

216
        /**
217
         * ViewChild that represents the days view.
218
         *
219
         * @hidden
220
         * @internal
221
         */
222
        @ViewChild('days', { read: IgxDaysViewComponent })
223
        public daysView: IgxDaysViewComponent;
224

225
        /**
226
         * ViewChildrenden representing all of the rendered days views.
227
         *
228
         * @hidden
229
         * @internal
230
         */
231
        @ViewChildren('days', { read: IgxDaysViewComponent })
232
        public monthViews: QueryList<IgxDaysViewComponent>;
233

234
        /**
235
         * Button for previous month.
236
         *
237
         * @hidden
238
         * @internal
239
         */
240
        @ViewChild('prevPageBtn')
241
        public prevPageBtn: ElementRef;
242

243
        /**
244
         * Button for next month.
245
         *
246
         * @hidden
247
         * @internal
248
         */
249
        @ViewChild('nextPageBtn')
250
        public nextPageBtn: ElementRef;
251

252
        /**
253
         * Denote if the year view is active.
254
         *
255
         * @hidden
256
         * @internal
257
         */
258
        public get isYearView(): boolean {
UNCOV
259
                return this.activeView === IgxCalendarView.Year;
×
260
        }
261

262
        /**
263
         * Gets the header template.
264
         *
265
         * @example
266
         * ```typescript
267
         * let headerTitleTemplate = this.calendar.headerTitleTeamplate;
268
         * ```
269
         * @memberof IgxCalendarComponent
270
         */
271
        public get headerTitleTemplate(): any {
UNCOV
272
                if (this.headerTitleTemplateDirective) {
×
UNCOV
273
                        return this.headerTitleTemplateDirective.template;
×
274
                }
UNCOV
275
                return null;
×
276
        }
277

278
        /**
279
         * Sets the header template.
280
         *
281
         * @example
282
         * ```html
283
         * <igx-calendar headerTitleTemplateDirective="igxCalendarHeaderTitle"></igx-calendar>
284
         * ```
285
         * @memberof IgxCalendarComponent
286
         */
287
        public set headerTitleTemplate(directive: any) {
UNCOV
288
                this.headerTitleTemplateDirective = directive;
×
289
        }
290

291
        /**
292
         * Gets the header template.
293
         *
294
         * @example
295
         * ```typescript
296
         * let headerTemplate =  this.calendar.headerTeamplate;
297
         * ```
298
         * @memberof IgxCalendarComponent
299
         */
300
        public get headerTemplate(): any {
UNCOV
301
                if (this.headerTemplateDirective) {
×
UNCOV
302
                        return this.headerTemplateDirective.template;
×
303
                }
UNCOV
304
                return null;
×
305
        }
306

307
        /**
308
         * Sets the header template.
309
         *
310
         * @example
311
         * ```html
312
         * <igx-calendar headerTemplateDirective="igxCalendarHeader"></igx-calendar>
313
         * ```
314
         * @memberof IgxCalendarComponent
315
         */
316
        public set headerTemplate(directive: any) {
UNCOV
317
                this.headerTemplateDirective = directive;
×
318
        }
319

320
        /**
321
         * Gets the subheader template.
322
         *
323
         * @example
324
         * ```typescript
325
         * let subheaderTemplate = this.calendar.subheaderTemplate;
326
         * ```
327
         */
328
        public get subheaderTemplate(): any {
UNCOV
329
                if (this.subheaderTemplateDirective) {
×
330
                        return this.subheaderTemplateDirective.template;
×
331
                }
UNCOV
332
                return null;
×
333
        }
334

335
        /**
336
         * Sets the subheader template.
337
         *
338
         * @example
339
         * ```html
340
         * <igx-calendar subheaderTemplate="igxCalendarSubheader"></igx-calendar>
341
         * ```
342
         * @memberof IgxCalendarComponent
343
         */
344
        public set subheaderTemplate(directive: any) {
UNCOV
345
                this.subheaderTemplateDirective = directive;
×
346
        }
347

348
        /**
349
         * Gets the context for the template marked with the `igxCalendarHeader` directive.
350
         *
351
         * @example
352
         * ```typescript
353
         * let headerContext =  this.calendar.headerContext;
354
         * ```
355
         */
356
        public get headerContext() {
UNCOV
357
                return this.generateContext(this.headerDate);
×
358
        }
359

360
        /**
361
         * Gets the context for the template marked with either `igxCalendarSubHeaderMonth`
362
         * or `igxCalendarSubHeaderYear` directive.
363
         *
364
         * @example
365
         * ```typescript
366
         * let context =  this.calendar.context;
367
         * ```
368
         */
369
        public get context() {
370
                const date: Date = this.viewDate;
×
371
                return this.generateContext(date);
×
372
        }
373

374
        /**
375
         * Date displayed in header
376
         *
377
         * @hidden
378
         * @internal
379
         */
380
        public get headerDate(): Date {
UNCOV
381
                return this.selectedDates?.at(0) ?? new Date();
×
382
        }
383

384
    /**
385
     * @hidden
386
     * @internal
387
     */
UNCOV
388
    @ContentChild(forwardRef(() => IgxCalendarHeaderTemplateDirective), { read: IgxCalendarHeaderTemplateDirective, static: true })
×
389
    private headerTemplateDirective: IgxCalendarHeaderTemplateDirective;
390

391
    /**
392
     * @hidden
393
     * @internal
394
     */
UNCOV
395
    @ContentChild(forwardRef(() => IgxCalendarHeaderTitleTemplateDirective), { read: IgxCalendarHeaderTitleTemplateDirective, static: true })
×
396
    private headerTitleTemplateDirective: IgxCalendarHeaderTitleTemplateDirective;
397

398
    /**
399
     * @hidden
400
     * @internal
401
     */
UNCOV
402
    @ContentChild(forwardRef(() => IgxCalendarSubheaderTemplateDirective), { read: IgxCalendarSubheaderTemplateDirective, static: true })
×
403
    private subheaderTemplateDirective: IgxCalendarSubheaderTemplateDirective;
404

405
        /**
406
         * @hidden
407
         * @internal
408
         */
UNCOV
409
        public activeDate = CalendarDay.today.native;
×
410

411
        /**
412
         * @hidden
413
         * @internal
414
         */
415
        protected previewRangeDate: Date;
416

417
        /**
418
         * Used to apply the active date when the calendar view is changed
419
         *
420
         * @hidden
421
         * @internal
422
         */
423
        public nextDate: Date;
424

425
        /**
426
         * Denote if the calendar view was changed with the keyboard
427
         *
428
         * @hidden
429
         * @internal
430
         */
UNCOV
431
        public isKeydownTrigger = false;
×
432

433
        /**
434
         * @hidden
435
         * @internal
436
         */
UNCOV
437
        private _monthsViewNumber = 1;
×
438

439
    @HostListener('mousedown', ['$event'])
440
    protected onMouseDown(event: MouseEvent) {
UNCOV
441
        event.stopPropagation();
×
UNCOV
442
        this.wrapper.nativeElement.focus();
×
443
    }
444

445
    private _showActiveDay: boolean;
446

447
        /**
448
         * @hidden
449
         * @internal
450
         */
451
    protected set showActiveDay(value: boolean) {
UNCOV
452
        this._showActiveDay = value;
×
UNCOV
453
        this.cdr.detectChanges();
×
454
    }
455

456
    protected get showActiveDay() {
UNCOV
457
        return this._showActiveDay;
×
458
    }
459

460
    protected get activeDescendant(): number {
UNCOV
461
        if (this.activeView === 'month') {
×
UNCOV
462
            return this.activeDate.getTime();
×
463
        }
464

UNCOV
465
        return this._activeDescendant ?? this.viewDate.getTime();
×
466
    }
467

468
    protected set activeDescendant(date: Date) {
UNCOV
469
        this._activeDescendant = date.getTime();
×
470
    }
471

472
        public ngAfterViewInit() {
UNCOV
473
        this.keyboardNavigation
×
474
            .attachKeyboardHandlers(this.wrapper, this)
475
            .set("ArrowUp", this.onArrowUp)
476
            .set("ArrowDown", this.onArrowDown)
477
            .set("ArrowLeft", this.onArrowLeft)
478
            .set("ArrowRight", this.onArrowRight)
479
            .set("Enter", this.onEnter)
480
            .set(" ", this.onEnter)
481
            .set("Home", this.onHome)
482
            .set("End", this.onEnd)
483
            .set("PageUp", this.handlePageUp)
484
            .set("PageDown", this.handlePageDown);
485

UNCOV
486
        this.wrapper.nativeElement.addEventListener('focus', (event: FocusEvent) => this.onWrapperFocus(event));
×
UNCOV
487
        this.wrapper.nativeElement.addEventListener('blur', (event: FocusEvent) => this.onWrapperBlur(event));
×
488

UNCOV
489
        this.startPageScroll$.pipe(
×
490
            takeUntil(this.stopPageScroll$),
UNCOV
491
            switchMap(() => this.scrollPage$.pipe(
×
492
                skipLast(1),
493
                debounce(() => interval(300)),
×
494
                takeUntil(this.stopPageScroll$)
495
            ))).subscribe(() => {
496
                switch (this.pageScrollDirection) {
×
497
                    case ScrollDirection.PREV:
498
                        this.previousPage();
×
499
                        break;
×
500
                    case ScrollDirection.NEXT:
501
                        this.nextPage();
×
502
                        break;
×
503
                    case ScrollDirection.NONE:
504
                    default:
505
                        break;
×
506
                }
507
            });
508

UNCOV
509
        this.activeView$.subscribe((view) => {
×
UNCOV
510
                        this.activeViewChanged.emit(view);
×
511

UNCOV
512
            this.viewDateChanged.emit({
×
513
                previousValue: this.previousViewDate,
514
                currentValue: this.viewDate
515
            });
516
        });
517
    }
518

519
    private onWrapperFocus(_event: FocusEvent) {
UNCOV
520
        this.showActiveDay = true;
×
UNCOV
521
        this.monthViews.forEach(view => view.changePreviewRange(this.activeDate));
×
522
    }
523

524
    private onWrapperBlur(_event: FocusEvent) {
UNCOV
525
        this.showActiveDay = false;
×
UNCOV
526
        this.monthViews.forEach(view => view.clearPreviewRange());
×
UNCOV
527
        this._onTouchedCallback();
×
528
    }
529

530
    private handleArrowKeydown(event: KeyboardEvent, delta: number) {
UNCOV
531
        event.preventDefault();
×
532

UNCOV
533
        const date = getClosestActiveDate(
×
534
            CalendarDay.from(this.activeDate),
535
            delta,
536
            this.disabledDates,
537
        );
538

UNCOV
539
        this.activeDate = date.native;
×
540

UNCOV
541
        const dates = this.viewDates;
×
UNCOV
542
        const isDateInView = dates.some(d => d.date.equalTo(this.activeDate));
×
UNCOV
543
        this.monthViews.forEach(view => view.clearPreviewRange());
×
544

UNCOV
545
        if (!isDateInView) {
×
UNCOV
546
            delta > 0 ? this.nextPage(true) : this.previousPage(true);
×
547
        }
548
    }
549

550
    private handlePageUpDown(event: KeyboardEvent, delta: number) {
UNCOV
551
        event.preventDefault();
×
552

UNCOV
553
        const dir = delta > 0 ? ScrollDirection.NEXT : ScrollDirection.PREV;
×
554

UNCOV
555
        if (this.activeView === IgxCalendarView.Month && event.shiftKey) {
×
UNCOV
556
            this.viewDate = CalendarDay.from(this.viewDate).add('year', delta).native;
×
UNCOV
557
            this.resetActiveDate(this.viewDate);
×
UNCOV
558
            this.cdr.detectChanges();
×
559
        } else {
UNCOV
560
            this.changePage(false, dir);
×
561
        }
562
    }
563

564
    private handlePageUp(event: KeyboardEvent) {
UNCOV
565
        this.handlePageUpDown(event, -1);
×
566
    }
567

568
    private handlePageDown(event: KeyboardEvent) {
UNCOV
569
        this.handlePageUpDown(event, 1);
×
570
    }
571

572
    private onArrowUp(event: KeyboardEvent) {
UNCOV
573
        if (this.activeView === IgxCalendarView.Month) {
×
UNCOV
574
            this.handleArrowKeydown(event, -7);
×
UNCOV
575
            this.cdr.detectChanges();
×
576
        }
577

UNCOV
578
        if (this.activeView === IgxCalendarView.Year) {
×
UNCOV
579
            this.monthsView.onKeydownArrowUp(event);
×
580
        }
581

UNCOV
582
        if (this.activeView === IgxCalendarView.Decade) {
×
UNCOV
583
            this.dacadeView.onKeydownArrowUp(event);
×
584
        }
585
    }
586

587
    private onArrowDown(event: KeyboardEvent) {
UNCOV
588
        if (this.activeView === IgxCalendarView.Month) {
×
UNCOV
589
            this.handleArrowKeydown(event, 7);
×
UNCOV
590
            this.cdr.detectChanges();
×
591
        }
592

UNCOV
593
        if (this.activeView === IgxCalendarView.Year) {
×
UNCOV
594
            this.monthsView.onKeydownArrowDown(event);
×
595
        }
596

UNCOV
597
        if (this.activeView === IgxCalendarView.Decade) {
×
UNCOV
598
            this.dacadeView.onKeydownArrowDown(event);
×
599
        }
600
    }
601

602
    private onArrowLeft(event: KeyboardEvent) {
UNCOV
603
        if (this.activeView === IgxCalendarView.Month) {
×
UNCOV
604
            this.handleArrowKeydown(event, -1);
×
UNCOV
605
            this.cdr.detectChanges();
×
606
        }
607

UNCOV
608
        if (this.activeView === IgxCalendarView.Year) {
×
UNCOV
609
            this.monthsView.onKeydownArrowLeft(event);
×
610
        }
611

UNCOV
612
        if (this.activeView === IgxCalendarView.Decade) {
×
UNCOV
613
            this.dacadeView.onKeydownArrowLeft(event);
×
614
        }
615
    }
616

617
    private onArrowRight(event: KeyboardEvent) {
UNCOV
618
        if (this.activeView === IgxCalendarView.Month) {
×
UNCOV
619
            this.handleArrowKeydown(event, 1);
×
UNCOV
620
            this.cdr.detectChanges();
×
621
        }
622

UNCOV
623
        if (this.activeView === IgxCalendarView.Year) {
×
UNCOV
624
            this.monthsView.onKeydownArrowRight(event);
×
625
        }
626

UNCOV
627
        if (this.activeView === IgxCalendarView.Decade) {
×
UNCOV
628
            this.dacadeView.onKeydownArrowRight(event);
×
629
        }
630
    }
631

632
    private onEnter(event: KeyboardEvent) {
UNCOV
633
        if (this.activeView === IgxCalendarView.Month) {
×
UNCOV
634
            this.handleDateSelection(this.activeDate);
×
UNCOV
635
            this.cdr.detectChanges();
×
636
        }
637

UNCOV
638
        if (this.activeView === IgxCalendarView.Year) {
×
UNCOV
639
            this.monthsView.onKeydownEnter(event);
×
640
        }
641

UNCOV
642
        if (this.activeView === IgxCalendarView.Decade) {
×
UNCOV
643
            this.dacadeView.onKeydownEnter(event);
×
644
        }
645

UNCOV
646
        this.monthViews.forEach(view => view.clearPreviewRange());
×
647
    }
648

649
    private onHome(event: KeyboardEvent) {
UNCOV
650
        if (this.activeView === IgxCalendarView.Month) {
×
UNCOV
651
            const dates = this.monthViews.toArray()
×
UNCOV
652
                .flatMap((view) => view.dates.toArray())
×
UNCOV
653
                .filter((d) => d.isCurrentMonth && d.isFocusable);
×
654

UNCOV
655
            this.activeDate = dates.at(0).date.native;
×
UNCOV
656
            this.cdr.detectChanges();
×
657
        }
658

UNCOV
659
        if (this.activeView === IgxCalendarView.Year) {
×
UNCOV
660
            this.monthsView.onKeydownHome(event);
×
661
        }
662

UNCOV
663
        if (this.activeView === IgxCalendarView.Decade) {
×
664
            this.dacadeView.onKeydownHome(event);
×
665
        }
666
    }
667

668
    private onEnd(event: KeyboardEvent) {
UNCOV
669
        if (this.activeView === IgxCalendarView.Month) {
×
UNCOV
670
            const dates = this.monthViews.toArray()
×
UNCOV
671
                .flatMap((view) => view.dates.toArray())
×
UNCOV
672
                .filter((d) => d.isCurrentMonth && d.isFocusable);
×
673

UNCOV
674
            this.activeDate = dates.at(-1).date.native;
×
UNCOV
675
            this.cdr.detectChanges();
×
676
        }
677

UNCOV
678
        if (this.activeView === IgxCalendarView.Year) {
×
UNCOV
679
            this.monthsView.onKeydownEnd(event);
×
680
        }
681

UNCOV
682
        if (this.activeView === IgxCalendarView.Decade) {
×
683
            this.dacadeView.onKeydownEnd(event);
×
684
        }
685
    }
686

687
        /**
688
         * Returns the locale representation of the month in the month view if enabled,
689
         * otherwise returns the default `Date.getMonth()` value.
690
         *
691
         * @hidden
692
         * @internal
693
         */
694
        public formattedMonth(value: Date): string {
UNCOV
695
                if (this.formatViews.month) {
×
UNCOV
696
                        return this.formatterMonth.format(value);
×
697
                }
698

UNCOV
699
                return `${ value.getMonth() }`;
×
700
        }
701

702
        /**
703
         * Change to previous page
704
         *
705
         * @hidden
706
         * @internal
707
         */
708
        public previousPage(isKeydownTrigger = false) {
×
UNCOV
709
                if (isKeydownTrigger && this.pageScrollDirection === ScrollDirection.NEXT) {
×
710
                        return;
×
711
                }
712

UNCOV
713
        this.changePage(isKeydownTrigger, ScrollDirection.PREV);
×
714
        }
715

716
        /**
717
         * Change to next page
718
         *
719
         * @hidden
720
         * @internal
721
         */
722
        public nextPage(isKeydownTrigger = false) {
×
UNCOV
723
                if (isKeydownTrigger && this.pageScrollDirection === ScrollDirection.PREV) {
×
724
                        return;
×
725
                }
726

UNCOV
727
        this.changePage(isKeydownTrigger, ScrollDirection.NEXT);
×
728
        }
729

730
        /**
731
         * Changes the current page
732
         *
733
         * @hidden
734
         * @internal
735
         */
736
    protected changePage(isKeydownTrigger = false, direction: ScrollDirection) {
×
UNCOV
737
                this.previousViewDate = this.viewDate;
×
UNCOV
738
                this.isKeydownTrigger = isKeydownTrigger;
×
739

UNCOV
740
        switch (this.activeView) {
×
741
            case "month":
UNCOV
742
                if (direction === ScrollDirection.PREV) {
×
UNCOV
743
                    this.viewDate = CalendarDay.from(this.viewDate).add('month', -1).native;
×
744
                }
745

UNCOV
746
                if (direction === ScrollDirection.NEXT) {
×
UNCOV
747
                    this.viewDate = CalendarDay.from(this.viewDate).add('month', 1).native;
×
748
                }
749

UNCOV
750
                this.viewDateChanged.emit({
×
751
                    previousValue: this.previousViewDate,
752
                    currentValue: this.viewDate
753
                });
754

UNCOV
755
                break;
×
756

757
            case "year":
758
                if (direction === ScrollDirection.PREV) {
×
759
                    this.viewDate = CalendarDay.from(this.viewDate).add('year', -1).native;
×
760
                }
761

762
                if (direction === ScrollDirection.NEXT) {
×
763
                    this.viewDate = CalendarDay.from(this.viewDate).add('year', 1).native;
×
764
                }
765

766
                break;
×
767

768
            case "decade":
769
                if (direction === ScrollDirection.PREV) {
×
770
                    this.viewDate = CalendarDay.from(this.viewDate).add('year', -15).native;
×
771
                }
772

773
                if (direction === ScrollDirection.NEXT) {
×
774
                    this.viewDate = CalendarDay.from(this.viewDate).add('year', 15).native;
×
775
                }
776

777
                break;
×
778
        }
779

780
        // XXX: Why only when it's not triggered by keyboard?
UNCOV
781
        if (!this.isKeydownTrigger) this.resetActiveDate(this.viewDate);
×
782
    }
783

784
        /**
785
         * Continious navigation through the previous pages
786
         *
787
         * @hidden
788
         * @internal
789
         */
UNCOV
790
        public startPrevPageScroll = (isKeydownTrigger = false) => {
×
UNCOV
791
                this.startPageScroll$.next();
×
UNCOV
792
                this.pageScrollDirection = ScrollDirection.PREV;
×
UNCOV
793
                this.previousPage(isKeydownTrigger);
×
794
        }
795

796
        /**
797
         * Continious navigation through the next pages
798
         *
799
         * @hidden
800
         * @internal
801
         */
UNCOV
802
        public startNextPageScroll = (isKeydownTrigger = false) => {
×
UNCOV
803
                this.startPageScroll$.next();
×
UNCOV
804
                this.pageScrollDirection = ScrollDirection.NEXT;
×
UNCOV
805
                this.nextPage(isKeydownTrigger);
×
806
        }
807

808
        /**
809
         * Stop continuous navigation
810
         *
811
         * @hidden
812
         * @internal
813
         */
UNCOV
814
        public stopPageScroll = (event: KeyboardEvent) => {
×
UNCOV
815
                event.stopPropagation();
×
816

UNCOV
817
                this.stopPageScroll$.next(true);
×
UNCOV
818
                this.stopPageScroll$.complete();
×
819

UNCOV
820
                if (this.platform.isActivationKey(event)) {
×
821
                        this.resetActiveDate(this.viewDate);
×
822
                }
823

UNCOV
824
                this.pageScrollDirection = ScrollDirection.NONE;
×
825
        }
826

827
        /**
828
         * @hidden
829
         * @internal
830
         */
831
        public onActiveViewDecade(event: MouseEvent, date: Date, activeViewIdx: number): void {
UNCOV
832
        event.preventDefault();
×
833

UNCOV
834
                super.activeViewDecade(activeViewIdx);
×
UNCOV
835
        this.viewDate = date;
×
836
        }
837

838
        /**
839
         * @hidden
840
         * @internal
841
         */
842
        public onActiveViewDecadeKB(date: Date, event: KeyboardEvent, activeViewIdx: number) {
UNCOV
843
                super.activeViewDecadeKB(event, activeViewIdx);
×
844

UNCOV
845
                if (this.platform.isActivationKey(event)) {
×
UNCOV
846
            this.viewDate = date;
×
UNCOV
847
            this.wrapper.nativeElement.focus();
×
848
                }
849
        }
850

851
        /**
852
         * @hidden
853
         * @internal
854
         */
855
    public onYearsViewClick(event: MouseEvent) {
856
        const path = event.composed ? event.composedPath() : [event.target];
×
857
        const years = this.dacadeView.viewItems.toArray();
×
858
        const validTarget = years.some(year => path.includes(year.nativeElement));
×
859

860
        if (validTarget) {
×
861
            this.activeView = IgxCalendarView.Year;
×
862
        }
863
    }
864

865
        /**
866
         * @hidden
867
         * @internal
868
         */
869
    public onYearsViewKeydown(event: KeyboardEvent) {
870
        if (this.platform.isActivationKey(event)) {
×
871
            this.activeView = IgxCalendarView.Year;
×
872
        }
873
    }
874

875
        /**
876
         * @hidden
877
         * @internal
878
         */
879
        protected getFormattedDate(): { weekday: string; monthday: string } {
UNCOV
880
                const date = this.headerDate;
×
UNCOV
881
        const monthFormatter = new Intl.DateTimeFormat(this.locale, { month: 'short', day: 'numeric' })
×
UNCOV
882
        const dayFormatter = new Intl.DateTimeFormat(this.locale, { weekday: 'short' })
×
883

UNCOV
884
                return {
×
885
                        monthday: monthFormatter.format(date),
886
                        weekday: dayFormatter.format(date),
887
                };
888
        }
889

890
        /**
891
         * @hidden
892
         * @internal
893
         */
894
        protected getFormattedRange(): { start: string; end: string } {
UNCOV
895
                const dates = this.selectedDates as Date[];
×
896

UNCOV
897
                return {
×
898
                        start: this.formatterRangeday.format(dates.at(0)),
899
                        end: this.formatterRangeday.format(dates.at(-1))
900
                };
901
        }
902

903
        /**
904
         * @hidden
905
         * @internal
906
         */
907
    protected get viewDates() {
UNCOV
908
        return this.monthViews.toArray()
×
UNCOV
909
            .flatMap(view => view.dates.toArray())
×
UNCOV
910
            .filter(d => d.isCurrentMonth);
×
911
    }
912

913
        /**
914
         * Handles invoked on date selection
915
         *
916
         * @hidden
917
         * @internal
918
         */
919
        protected handleDateSelection(date: Date) {
UNCOV
920
        const outOfRange = !this.viewDates.some(d => {
×
UNCOV
921
            return d.date.equalTo(date)
×
922
        });
923

UNCOV
924
        if (outOfRange) {
×
UNCOV
925
            this.viewDate = date;
×
926
        }
927

UNCOV
928
                this.selectDate(date);
×
929

930
        // keep views in sync
UNCOV
931
                this.monthViews.forEach((m) => {
×
UNCOV
932
                        m.shiftKey = this.shiftKey;
×
UNCOV
933
            m.selectedDates = this.selectedDates;
×
UNCOV
934
            m.cdr.markForCheck();
×
935
                });
936

UNCOV
937
        if (this.selection !== 'single') {
×
UNCOV
938
                    this.selected.emit(this.selectedDates);
×
939
        } else {
UNCOV
940
                    this.selected.emit(this.selectedDates.at(0));
×
941
        }
942
        }
943

944
        /**
945
         * @hidden
946
         * @intenal
947
         */
948
        public changeMonth(date: Date) {
UNCOV
949
                this.previousViewDate = this.viewDate;
×
UNCOV
950
        this.viewDate = CalendarDay.from(date).add('month', -this.activeViewIdx).native;
×
UNCOV
951
                this.activeView = IgxCalendarView.Month;
×
UNCOV
952
        this.resetActiveDate(date);
×
953
        }
954

955
        /**
956
         * @hidden
957
         * @intenal
958
         */
959
    public override changeYear(date: Date) {
UNCOV
960
        this.previousViewDate = this.viewDate;
×
UNCOV
961
        this.viewDate = CalendarDay.from(date).add('month', -this.activeViewIdx).native;
×
UNCOV
962
                this.activeView = IgxCalendarView.Year;
×
963
    }
964

965
        /**
966
         * @hidden
967
         * @intenal
968
         */
969
        public updateYear(date: Date) {
970
                this.previousViewDate = this.viewDate;
×
971
        this.viewDate = CalendarDay.from(date).add('year', -this.activeViewIdx).native;
×
972
        }
973

974
    public updateActiveDescendant(date: Date) {
UNCOV
975
        this.activeDescendant = date;
×
976
    }
977

978
        /**
979
         * @hidden
980
         * @internal
981
         */
982
        public onActiveViewYear(event: MouseEvent, date: Date, activeViewIdx: number): void {
UNCOV
983
        event.preventDefault();
×
984

UNCOV
985
                this.activeView = IgxCalendarView.Year;
×
UNCOV
986
                this.activeViewIdx = activeViewIdx;
×
UNCOV
987
        this.viewDate = date;
×
988
        }
989

990
        /**
991
         * @hidden
992
         * @internal
993
         */
994
        public onActiveViewYearKB(date: Date, event: KeyboardEvent, activeViewIdx: number): void {
UNCOV
995
        event.stopPropagation();
×
996

UNCOV
997
                if (this.platform.isActivationKey(event)) {
×
UNCOV
998
                    event.preventDefault();
×
UNCOV
999
            this.activeView = IgxCalendarView.Year;
×
UNCOV
1000
            this.activeViewIdx = activeViewIdx;
×
UNCOV
1001
            this.viewDate = date;
×
1002

UNCOV
1003
            this.wrapper.nativeElement.focus();
×
1004
                }
1005
        }
1006

1007
        /**
1008
         * Deselects date(s) (based on the selection type).
1009
         *
1010
         * @example
1011
         * ```typescript
1012
         *  this.calendar.deselectDate(new Date(`2018-06-12`));
1013
         * ````
1014
         */
1015
        public override deselectDate(value?: Date | Date[] | string) {
UNCOV
1016
                super.deselectDate(value);
×
1017

UNCOV
1018
                this.monthViews.forEach((m) => {
×
UNCOV
1019
                        m.selectedDates = this.selectedDates;
×
UNCOV
1020
                        m.rangeStarted = false;
×
UNCOV
1021
            m.cdr.markForCheck();
×
1022
                });
1023

UNCOV
1024
                this._onChangeCallback(this.selectedDates);
×
1025
        }
1026

1027

1028
        /**
1029
         * Getter for the context object inside the calendar templates.
1030
         *
1031
         * @hidden
1032
         * @internal
1033
         */
1034
        public getContext(i: number) {
UNCOV
1035
        const date = CalendarDay.from(this.viewDate).add('month', i).native;
×
UNCOV
1036
                return this.generateContext(date, i);
×
1037
        }
1038

1039
        /**
1040
         * @hidden
1041
         * @internal
1042
         */
1043
    // TODO: See if this can be incorporated in the DaysView directly
1044
        public resetActiveDate(date: Date) {
UNCOV
1045
        const target = CalendarDay.from(this.activeDate).set({
×
1046
            month: date.getMonth(),
1047
            year: date.getFullYear(),
1048
       });
1049
        const outOfRange =
UNCOV
1050
            !areSameMonth(date, target) ||
×
1051
            isDateInRanges(target, this.disabledDates);
1052

UNCOV
1053
        this.activeDate = outOfRange ? date : target.native;
×
1054
        }
1055

1056
        /**
1057
         * @hidden
1058
         * @internal
1059
         */
1060
        public ngOnDestroy(): void {
UNCOV
1061
        this.keyboardNavigation.detachKeyboardHandlers();
×
UNCOV
1062
        this.wrapper?.nativeElement.removeEventListener('focus', this.onWrapperFocus);
×
UNCOV
1063
        this.wrapper?.nativeElement.removeEventListener('blur', this.onWrapperBlur);
×
1064
        }
1065

1066
        /**
1067
         * @hidden
1068
         * @internal
1069
         */
1070
        public getPrevMonth(date: Date): Date {
UNCOV
1071
                return CalendarDay.from(date).add('month', -1).native;
×
1072
        }
1073

1074
        /**
1075
         * @hidden
1076
         * @internal
1077
         */
1078
        public getNextMonth(date: Date, viewIndex: number): Date {
UNCOV
1079
        return CalendarDay.from(date).add('month', viewIndex).native;
×
1080
        }
1081

1082
        /**
1083
         * Helper method building and returning the context object inside the calendar templates.
1084
         *
1085
         * @hidden
1086
         * @internal
1087
         */
1088
        private generateContext(value: Date | Date[], i?: number) {
UNCOV
1089
        const construct = (date: Date, index: number) => ({
×
1090
            index: index,
1091
            date,
1092
            ...formatToParts(date, this.locale, this.formatOptions, [
1093
                "era",
1094
                "year",
1095
                "month",
1096
                "day",
1097
                "weekday",
1098
            ]),
1099
        });
1100

UNCOV
1101
        const formatObject = Array.isArray(value)
×
1102
            ? value.map((date, index) => construct(date, index))
×
1103
            : construct(value, i);
1104

UNCOV
1105
        return { $implicit: formatObject };
×
1106
        }
1107
}
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