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

IgniteUI / igniteui-angular / 23848129113

01 Apr 2026 12:17PM UTC coverage: 90.163% (+0.7%) from 89.452%
23848129113

Pull #16784

github

web-flow
Merge b19ccc7d2 into f00a27f08
Pull Request #16784: fix(grid-pinning): remove hide call in scrollToRow - master

14823 of 17264 branches covered (85.86%)

Branch coverage included in aggregate %.

15 of 17 new or added lines in 2 files covered. (88.24%)

264 existing lines in 15 files now uncovered.

29886 of 32323 relevant lines covered (92.46%)

34071.84 hits per line

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

96.31
/projects/igniteui-angular/date-picker/src/date-picker/date-picker.component.ts
1
import {
2
    AfterViewChecked,
3
    AfterViewInit,
4
    AfterContentChecked,
5
    ChangeDetectorRef,
6
    Component,
7
    ContentChild,
8
    ElementRef,
9
    EventEmitter,
10
    HostBinding,
11
    HostListener,
12
    Injector,
13
    Input,
14
    OnDestroy,
15
    OnInit,
16
    Output,
17
    PipeTransform,
18
    Renderer2,
19
    ViewChild,
20
    ViewContainerRef,
21
    booleanAttribute,
22
    inject
23
} from '@angular/core';
24
import {
25
    AbstractControl,
26
    ControlValueAccessor,
27
    NgControl,
28
    NG_VALIDATORS,
29
    NG_VALUE_ACCESSOR,
30
    ValidationErrors,
31
    Validator
32
} from '@angular/forms';
33
import {
34
    IgxCalendarComponent, IgxCalendarHeaderTemplateDirective, IgxCalendarHeaderTitleTemplateDirective, IgxCalendarSubheaderTemplateDirective,
35
     IFormattingViews, IFormattingOptions
36
} from 'igniteui-angular/calendar';
37
import {
38
    IgxLabelDirective, IgxInputState, IgxInputGroupComponent, IgxPrefixDirective, IgxInputDirective, IgxSuffixDirective,
39
    IgxReadOnlyInputDirective
40
} from 'igniteui-angular/input-group';
41
import { fromEvent, Subscription, noop, MonoTypeOperatorFunction } from 'rxjs';
42
import { filter, takeUntil } from 'rxjs/operators';
43

44
import { IgxDateTimeEditorDirective, IgxTextSelectionDirective } from 'igniteui-angular/directives';
45
import {
46
    AbsoluteScrollStrategy,
47
    AutoPositionStrategy,
48
    IgxOverlayService,
49
    OverlayCancelableEventArgs,
50
    OverlayEventArgs,
51
    OverlaySettings,
52
    IgxPickerActionsDirective,
53
    DatePickerResourceStringsEN,
54
    IDatePickerResourceStrings,
55
    DateRangeDescriptor,
56
    DateRangeType,
57
    IBaseCancelableBrowserEventArgs,
58
    isDate,
59
    PlatformUtil,
60
    getCurrentResourceStrings,
61
    PickerCalendarOrientation,
62
    DateTimeUtil,
63
    DatePartDeltas,
64
    DatePart,
65
    isDateInRanges,
66
    IgxOverlayOutletDirective,
67
    I18N_FORMATTER
68
} from 'igniteui-angular/core';
69
import { IDatePickerValidationFailedEventArgs } from './date-picker.common';
70
import { IgxIconComponent } from 'igniteui-angular/icon';
71
import { fadeIn, fadeOut } from 'igniteui-angular/animations';
72
import { PickerBaseDirective } from './picker-base.directive';
73
import { IgxCalendarContainerComponent } from './calendar-container/calendar-container.component';
74

75
let NEXT_ID = 0;
3✔
76

77
/**
78
 * Date Picker displays a popup calendar that lets users select a single date.
79
 *
80
 * @igxModule IgxDatePickerModule
81
 * @igxTheme igx-calendar-theme, igx-icon-theme
82
 * @igxGroup Scheduling
83
 * @igxKeywords datepicker, calendar, schedule, date
84
 * @example
85
 * ```html
86
 * <igx-date-picker [(ngModel)]="selectedDate"></igx-date-picker>
87
 * ```
88
 */
89
@Component({
90
    providers: [
91
        { provide: NG_VALUE_ACCESSOR, useExisting: IgxDatePickerComponent, multi: true },
92
        { provide: NG_VALIDATORS, useExisting: IgxDatePickerComponent, multi: true }
93
    ],
94
    selector: 'igx-date-picker',
95
    templateUrl: 'date-picker.component.html',
96
    styles: [':host { display: block; }'],
97
    imports: [
98
        IgxInputGroupComponent,
99
        IgxPrefixDirective,
100
        IgxIconComponent,
101
        IgxInputDirective,
102
        IgxReadOnlyInputDirective,
103
        IgxDateTimeEditorDirective,
104
        IgxTextSelectionDirective,
105
        IgxSuffixDirective
106
    ]
107
})
108
export class IgxDatePickerComponent extends PickerBaseDirective implements ControlValueAccessor, Validator,
3✔
109
    OnInit, AfterViewInit, OnDestroy, AfterViewChecked, AfterContentChecked {
110
    private _overlayService = inject<IgxOverlayService>(IgxOverlayService);
126✔
111
    private _injector = inject(Injector);
126✔
112
    private _renderer = inject(Renderer2);
126✔
113
    private platform = inject(PlatformUtil);
126✔
114
    private cdr = inject(ChangeDetectorRef);
126✔
115
    private _i18nFormatter = inject(I18N_FORMATTER);
126✔
116

117

118
    /**
119
     * Gets/Sets whether the inactive dates will be hidden.
120
     *
121
     * @remarks
122
     * Applies to dates that are out of the current month.
123
     * Default value is `false`.
124
     * @example
125
     * ```html
126
     * <igx-date-picker [hideOutsideDays]="true"></igx-date-picker>
127
     * ```
128
     * @example
129
     * ```typescript
130
     * let hideOutsideDays = this.datePicker.hideOutsideDays;
131
     * ```
132
     */
133
    @Input({ transform: booleanAttribute })
134
    public hideOutsideDays: boolean;
135

136
    /**
137
     * Gets/Sets the number of month views displayed.
138
     *
139
     * @remarks
140
     * Default value is `1`.
141
     *
142
     * @example
143
     * ```html
144
     * <igx-date-picker [displayMonthsCount]="2"></igx-date-picker>
145
     * ```
146
     * @example
147
     * ```typescript
148
     * let monthViewsDisplayed = this.datePicker.displayMonthsCount;
149
     * ```
150
     */
151
    @Input()
152
    public displayMonthsCount = 1;
126✔
153

154
    /**
155
    * Gets/Sets the orientation of the multiple months displayed in the picker's calendar's days view.
156
    *
157
    * @example
158
    * <igx-date-picker orientation="vertical"></igx-date-picker>
159
    */
160
    @Input()
161
    public orientation: PickerCalendarOrientation = PickerCalendarOrientation.Horizontal;
126✔
162

163
    /**
164
     * Show/hide week numbers
165
     *
166
     * @example
167
     * ```html
168
     * <igx-date-picker [showWeekNumbers]="true"></igx-date-picker>
169
     * ``
170
     */
171
    @Input({ transform: booleanAttribute })
172
    public showWeekNumbers: boolean;
173

174

175
    /**
176
     * Gets/Sets the date which is shown in the calendar picker and is highlighted.
177
     * By default it is the current date, or the value of the picker, if set.
178
     */
179
    @Input()
180
    public get activeDate(): Date {
181
        const today = new Date(new Date().setHours(0, 0, 0, 0));
210✔
182
        const dateValue = DateTimeUtil.isValidDate(this._dateValue) ? new Date(this._dateValue.setHours(0, 0, 0, 0)) : null;
210✔
183
        return this._activeDate ?? dateValue ?? this._calendar?.activeDate ?? today;
210✔
184
    }
185

186
    public set activeDate(value: Date) {
187
        this._activeDate = value;
1✔
188
    }
189

190
    /**
191
     * Gets/Sets a custom formatter function on the selected or passed date.
192
     *
193
     * @example
194
     * ```html
195
     * <igx-date-picker [value]="date" [formatter]="formatter"></igx-date-picker>
196
     * ```
197
     */
198
    @Input()
199
    public formatter: (val: Date) => string;
200

201
    /**
202
     * Gets/Sets the today button's label.
203
     *
204
     *  @example
205
     * ```html
206
     * <igx-date-picker todayButtonLabel="Today"></igx-date-picker>
207
     * ```
208
     */
209
    @Input()
210
    public todayButtonLabel: string;
211

212
    /**
213
     * Gets/Sets the cancel button's label.
214
     *
215
     * @example
216
     * ```html
217
     * <igx-date-picker cancelButtonLabel="Cancel"></igx-date-picker>
218
     * ```
219
     */
220
    @Input()
221
    public cancelButtonLabel: string;
222

223
    /**
224
     * Specify if the currently spun date segment should loop over.
225
     *
226
     *  @example
227
     * ```html
228
     * <igx-date-picker [spinLoop]="false"></igx-date-picker>
229
     * ```
230
     */
231
    @Input({ transform: booleanAttribute })
232
    public spinLoop = true;
126✔
233

234
    /**
235
     * Delta values used to increment or decrement each editor date part on spin actions.
236
     * All values default to `1`.
237
     *
238
     * @example
239
     * ```html
240
     * <igx-date-picker [spinDelta]="{ date: 5, month: 2 }"></igx-date-picker>
241
     * ```
242
     */
243
    @Input()
244
    public spinDelta: Pick<DatePartDeltas, 'date' | 'month' | 'year'>;
245

246
    /**
247
     * Gets/Sets the container used for the popup element.
248
     * @remarks
249
     *  `outlet` is an instance of `IgxOverlayOutletDirective` or an `ElementRef`.
250
     * @example
251
     * ```html
252
     * <div igxOverlayOutlet #outlet="overlay-outlet"></div>
253
     * //..
254
     * <igx-date-picker [outlet]="outlet"></igx-date-picker>
255
     * //..
256
     * ```
257
     *
258
     * @deprecated in version 21.2.0. Overlays now use the HTML Popover API and no longer move to the document
259
     * body by default, so using outlet is also no longer needed - just define the overlay in the intended
260
     * DOM tree position instead.
261
     */
262
    @Input()
263
    public override outlet: IgxOverlayOutletDirective | ElementRef;
264

265
    /**
266
     * Gets/Sets the value of `id` attribute.
267
     *
268
     * @remarks If not provided it will be automatically generated.
269
     * @example
270
     * ```html
271
     * <igx-date-picker [id]="'igx-date-picker-3'" cancelButtonLabel="cancel" todayButtonLabel="today"></igx-date-picker>
272
     * ```
273
     */
274
    @Input()
275
    @HostBinding('attr.id')
276
    public id = `igx-date-picker-${NEXT_ID++}`;
126✔
277

278
    //#region calendar members
279

280
    /**
281
     * Gets/Sets the format views of the `IgxDatePickerComponent`.
282
     *
283
     * @example
284
     * ```typescript
285
     * let formatViews = this.datePicker.formatViews;
286
     *  this.datePicker.formatViews = {day:false, month: false, year:false};
287
     * ```
288
     */
289
    @Input()
290
    public formatViews: IFormattingViews;
291

292
    /**
293
     * Gets/Sets the disabled dates descriptors.
294
     *
295
     * @example
296
     * ```typescript
297
     * let disabledDates = this.datepicker.disabledDates;
298
     * this.datePicker.disabledDates = [ {type: DateRangeType.Weekends}, ...];
299
     * ```
300
     */
301
    @Input()
302
    public get disabledDates(): DateRangeDescriptor[] {
303
        return this._disabledDates;
125✔
304
    }
305
    public set disabledDates(value: DateRangeDescriptor[]) {
306
        this._disabledDates = value;
3✔
307
        this._onValidatorChange();
3✔
308
    }
309

310
    /**
311
     * Gets/Sets the special dates descriptors.
312
     *
313
     * @example
314
     * ```typescript
315
     * let specialDates = this.datepicker.specialDates;
316
     * this.datePicker.specialDates = [ {type: DateRangeType.Weekends}, ... ];
317
     * ```
318
     */
319
    @Input()
320
    public get specialDates(): DateRangeDescriptor[] {
321
        return this._specialDates;
55✔
322
    }
323
    public set specialDates(value: DateRangeDescriptor[]) {
324
        this._specialDates = value;
1✔
325
    }
326

327

328
    /**
329
     * Gets/Sets the format options of the `IgxDatePickerComponent`.
330
     *
331
     * @example
332
     * ```typescript
333
     * this.datePicker.calendarFormat = {day: "numeric",  month: "long", weekday: "long", year: "numeric"};
334
     * ```
335
     */
336
    @Input()
337
    public calendarFormat: IFormattingOptions;
338

339
    //#endregion
340

341
    /**
342
     * Gets/Sets the selected date.
343
     *
344
     *  @example
345
     * ```html
346
     * <igx-date-picker [value]="date"></igx-date-picker>
347
     * ```
348
     */
349
    @Input()
350
    public get value(): Date | string {
351
        return this._value;
836✔
352
    }
353
    public set value(date: Date | string) {
354
        this._value = date;
146✔
355
        this.setDateValue(date);
146✔
356
        if (this.dateTimeEditor.value !== date) {
146✔
357
            this.dateTimeEditor.value = this._dateValue;
109✔
358
        }
359
        this.valueChange.emit(this.dateValue);
146✔
360
        this._onChangeCallback(this.dateValue);
146✔
361
    }
362

363
    /**
364
     * The minimum value the picker will accept.
365
     *
366
     * @example
367
     * <igx-date-picker [minValue]="minDate"></igx-date-picker>
368
     */
369
    @Input()
370
    public set minValue(value: Date | string) {
371
        this._minValue = value;
30✔
372
        this._onValidatorChange();
30✔
373
    }
374

375
    public get minValue(): Date | string {
376
        return this._minValue;
886✔
377
    }
378

379
    /**
380
     * The maximum value the picker will accept.
381
     *
382
     * @example
383
     * <igx-date-picker [maxValue]="maxDate"></igx-date-picker>
384
     */
385
    @Input()
386
    public set maxValue(value: Date | string) {
387
        this._maxValue = value;
33✔
388
        this._onValidatorChange();
33✔
389
    }
390

391
    public get maxValue(): Date | string {
392
        return this._maxValue;
886✔
393
    }
394

395
    /**
396
     * Gets/Sets the resource strings for the picker's default toggle icon.
397
     * By default it uses EN resources.
398
     */
399
    @Input()
400
    public resourceStrings: IDatePickerResourceStrings;
401

402
    /** @hidden @internal */
403
    @Input({ transform: booleanAttribute })
404
    public readOnly = false;
126✔
405

406
    /**
407
     * Emitted when the picker's value changes.
408
     *
409
     * @remarks
410
     * Used for `two-way` bindings.
411
     *
412
     * @example
413
     * ```html
414
     * <igx-date-picker [(value)]="date"></igx-date-picker>
415
     * ```
416
     */
417
    @Output()
418
    public valueChange = new EventEmitter<Date>();
126✔
419

420
    /**
421
     * Emitted when the user types/spins invalid date in the date-picker editor.
422
     *
423
     *  @example
424
     * ```html
425
     * <igx-date-picker (validationFailed)="onValidationFailed($event)"></igx-date-picker>
426
     * ```
427
     */
428
    @Output()
429
    public validationFailed = new EventEmitter<IDatePickerValidationFailedEventArgs>();
126✔
430

431
    /** @hidden @internal */
432
    @ContentChild(IgxLabelDirective)
433
    public label: IgxLabelDirective;
434

435
    @ContentChild(IgxCalendarHeaderTitleTemplateDirective)
436
    private headerTitleTemplate: IgxCalendarHeaderTitleTemplateDirective;
437

438
    @ContentChild(IgxCalendarHeaderTemplateDirective)
439
    private headerTemplate: IgxCalendarHeaderTemplateDirective;
440

441
    @ViewChild(IgxDateTimeEditorDirective, { static: true })
442
    private dateTimeEditor: IgxDateTimeEditorDirective;
443

444
    @ViewChild(IgxInputGroupComponent, { read: ViewContainerRef })
445
    private viewContainerRef: ViewContainerRef;
446

447
    @ViewChild(IgxLabelDirective)
448
    private labelDirective: IgxLabelDirective;
449

450
    @ViewChild(IgxInputDirective)
451
    private inputDirective: IgxInputDirective;
452

453
    @ContentChild(IgxCalendarSubheaderTemplateDirective)
454
    private subheaderTemplate: IgxCalendarSubheaderTemplateDirective;
455

456
    @ContentChild(IgxPickerActionsDirective)
457
    private pickerActions: IgxPickerActionsDirective;
458

459
    private get dialogOverlaySettings(): OverlaySettings {
460
        return Object.assign({}, this._dialogOverlaySettings, this.overlaySettings);
6✔
461
    }
462

463
    private get dropDownOverlaySettings(): OverlaySettings {
464
        return Object.assign({}, this._dropDownOverlaySettings, this.overlaySettings);
47✔
465
    }
466

467
    private get inputGroupElement(): HTMLElement {
468
        return this.inputGroup?.element.nativeElement.querySelector('.igx-input-group__bundle');
220✔
469
    }
470

471
    private get dateValue(): Date {
472
        return this._dateValue;
384✔
473
    }
474

475
    private get pickerFormatViews(): IFormattingViews {
476
        return Object.assign({}, this._defFormatViews, this.formatViews);
58✔
477
    }
478

479
    private get pickerCalendarFormat(): IFormattingOptions {
480
        return Object.assign({}, this._calendarFormat, this.calendarFormat);
55✔
481
    }
482

483
    /** @hidden @internal */
484
    public displayValue: PipeTransform = { transform: (date: Date) => this.formatter(date) };
126✔
485

486
    private _resourceStrings = getCurrentResourceStrings(DatePickerResourceStringsEN);
126✔
487
    private _dateValue: Date;
488
    private _overlayId: string;
489
    private _value: Date | string;
490
    private _ngControl: NgControl = null;
126✔
491
    private _statusChanges$: Subscription;
492
    private _calendar: IgxCalendarComponent;
493
    private _calendarContainer?: HTMLElement;
494
    private _specialDates: DateRangeDescriptor[] = null;
126✔
495
    private _disabledDates: DateRangeDescriptor[] = null;
126✔
496
    private _activeDate: Date = null;
126✔
497
    private _overlaySubFilter:
126✔
498
        [MonoTypeOperatorFunction<OverlayEventArgs>,
499
            MonoTypeOperatorFunction<OverlayEventArgs | OverlayCancelableEventArgs>] = [
500
            filter(x => x.id === this._overlayId),
350✔
501
            takeUntil(this._destroy$)
502
        ];
503
    private _dropDownOverlaySettings: OverlaySettings = {
126✔
504
        target: this.inputGroupElement,
505
        closeOnOutsideClick: true,
506
        modal: false,
507
        closeOnEscape: true,
508
        scrollStrategy: new AbsoluteScrollStrategy(),
509
        positionStrategy: new AutoPositionStrategy({
510
            openAnimation: fadeIn,
511
            closeAnimation: fadeOut
512
        })
513
    };
514
    private _dialogOverlaySettings: OverlaySettings = {
126✔
515
        closeOnOutsideClick: true,
516
        modal: true,
517
        closeOnEscape: true
518
    };
519
    private _calendarFormat: IFormattingOptions = {
126✔
520
        day: 'numeric',
521
        month: 'short',
522
        weekday: 'short',
523
        year: 'numeric'
524
    };
525
    private _defFormatViews: IFormattingViews = {
126✔
526
        day: false,
527
        month: true,
528
        year: false
529
    };
530
    private _onChangeCallback: (_: Date) => void = noop;
126✔
531
    private _onTouchedCallback: () => void = noop;
126✔
532
    private _onValidatorChange: () => void = noop;
126✔
533

534
    constructor() {
535
        super();
126✔
536
        this.initLocale();
126✔
537
    }
538

539
    /** @hidden @internal */
540
    public get required(): boolean {
541
        if (this._ngControl && this._ngControl.control && this._ngControl.control.validator) {
141✔
542
            // Run the validation with empty object to check if required is enabled.
543
            const error = this._ngControl.control.validator({} as AbstractControl);
135✔
544
            return error && error.required;
135✔
545
        }
546

547
        return false;
6✔
548
    }
549

550
    /** @hidden @internal */
551
    public get pickerResourceStrings(): IDatePickerResourceStrings {
552
        return Object.assign({}, this._resourceStrings, this.resourceStrings);
408✔
553
    }
554

555
    protected override get toggleContainer(): HTMLElement | undefined {
556
        return this._calendarContainer;
1✔
557
    }
558

559
    /** @hidden @internal */
560
    @HostListener('keydown', ['$event'])
561
    public onKeyDown(event: KeyboardEvent) {
562
        switch (event.key) {
3!
563
            case this.platform.KEYMAP.ARROW_UP:
564
                if (event.altKey) {
×
UNCOV
565
                    this.close();
×
566
                }
UNCOV
567
                break;
×
568
            case this.platform.KEYMAP.ARROW_DOWN:
569
                if (event.altKey) {
1✔
570
                    this.open();
1✔
571
                }
572
                break;
1✔
573
            case this.platform.KEYMAP.SPACE:
574
                event.preventDefault();
1✔
575
                this.open();
1✔
576
                break;
1✔
577
        }
578
    }
579

580
    /**
581
     * Opens the picker's dropdown or dialog.
582
     *
583
     * @example
584
     * ```html
585
     * <igx-date-picker #picker></igx-date-picker>
586
     *
587
     * <button type="button" igxButton (click)="picker.open()">Open Dialog</button>
588
     * ```
589
     */
590
    public open(settings?: OverlaySettings): void {
591
        if (!this.collapsed || this.disabled || this.readOnly) {
57✔
592
            return;
4✔
593
        }
594

595
        const overlaySettings = Object.assign({}, this.isDropdown
53✔
596
            ? this.dropDownOverlaySettings
597
            : this.dialogOverlaySettings
598
            , settings);
599

600
        if (this.isDropdown && this.inputGroupElement) {
53✔
601
            overlaySettings.target = this.inputGroupElement;
47✔
602
        }
603
        if (this.outlet) {
53✔
604
            overlaySettings.outlet = this.outlet;
16✔
605
        }
606
        this._overlayId = this._overlayService
53✔
607
            .attach(IgxCalendarContainerComponent, this.viewContainerRef, overlaySettings);
608
        this._overlayService.show(this._overlayId);
53✔
609
    }
610

611
    /**
612
     * Toggles the picker's dropdown or dialog
613
     *
614
     * @example
615
     * ```html
616
     * <igx-date-picker #picker></igx-date-picker>
617
     *
618
     * <button type="button" igxButton (click)="picker.toggle()">Toggle Dialog</button>
619
     * ```
620
     */
621
    public toggle(settings?: OverlaySettings): void {
622
        if (this.collapsed) {
9✔
623
            this.open(settings);
8✔
624
        } else {
625
            this.close();
1✔
626
        }
627
    }
628

629
    /**
630
     * Closes the picker's dropdown or dialog.
631
     *
632
     * @example
633
     * ```html
634
     * <igx-date-picker #picker></igx-date-picker>
635
     *
636
     * <button type="button" igxButton (click)="picker.close()">Close Dialog</button>
637
     * ```
638
     */
639
    public close(): void {
640
        if (!this.collapsed) {
24✔
641
            this._overlayService.hide(this._overlayId);
23✔
642
        }
643
    }
644

645
    /**
646
     * Selects a date.
647
     *
648
     * @remarks Updates the value in the input field.
649
     *
650
     * @example
651
     * ```typescript
652
     * this.datePicker.select(date);
653
     * ```
654
     * @param date passed date that has to be set to the calendar.
655
     */
656
    public select(value: Date): void {
657
        this.value = value;
3✔
658
    }
659

660
    /**
661
     * Selects today's date and closes the picker.
662
     *
663
     * @example
664
     * ```html
665
     * <igx-date-picker #picker></igx-date-picker>
666
     *
667
     * <button type="button" igxButton (click)="picker.selectToday()">Select Today</button>
668
     * ```
669
     * */
670
    public selectToday(): void {
671
        const today = new Date();
1✔
672
        today.setHours(0);
1✔
673
        today.setMinutes(0);
1✔
674
        today.setSeconds(0);
1✔
675
        today.setMilliseconds(0);
1✔
676
        this.select(today);
1✔
677
        this.close();
1✔
678
    }
679

680
    /**
681
     * Clears the input field and the picker's value.
682
     *
683
     * @example
684
     * ```typescript
685
     * this.datePicker.clear();
686
     * ```
687
     */
688
    public clear(): void {
689
        if (!this.disabled || !this.readOnly) {
7!
690
            this._calendar?.deselectDate();
7✔
691
            this.dateTimeEditor.clear();
7✔
692
        }
693
    }
694

695
    /**
696
     * Increment a specified `DatePart`.
697
     *
698
     * @param datePart The optional DatePart to increment. Defaults to Date.
699
     * @param delta The optional delta to increment by. Overrides `spinDelta`.
700
     * @example
701
     * ```typescript
702
     * this.datePicker.increment(DatePart.Date);
703
     * ```
704
     */
705
    public increment(datePart?: DatePart, delta?: number): void {
706
        this.dateTimeEditor.increment(datePart, delta);
4✔
707
    }
708

709
    /**
710
     * Decrement a specified `DatePart`
711
     *
712
     * @param datePart The optional DatePart to decrement. Defaults to Date.
713
     * @param delta The optional delta to decrement by. Overrides `spinDelta`.
714
     * @example
715
     * ```typescript
716
     * this.datePicker.decrement(DatePart.Date);
717
     * ```
718
     */
719
    public decrement(datePart?: DatePart, delta?: number): void {
720
        this.dateTimeEditor.decrement(datePart, delta);
4✔
721
    }
722

723
    //#region Control Value Accessor
724
    /** @hidden @internal */
725
    public writeValue(value: Date | string) {
726
        this._value = value;
110✔
727
        this.setDateValue(value);
110✔
728
        if (this.dateTimeEditor.value !== value) {
110✔
729
            this.dateTimeEditor.value = this._dateValue;
54✔
730
        }
731
    }
732

733
    /** @hidden @internal */
734
    public registerOnChange(fn: any) {
735
        this._onChangeCallback = fn;
91✔
736
    }
737

738
    /** @hidden @internal */
739
    public registerOnTouched(fn: any) {
740
        this._onTouchedCallback = fn;
91✔
741
    }
742

743
    /** @hidden @internal */
744
    public setDisabledState?(isDisabled: boolean): void {
745
        this.disabled = isDisabled;
66✔
746
    }
747
    //#endregion
748

749
    //#region Validator
750
    /** @hidden @internal */
751
    public registerOnValidatorChange(fn: any) {
752
        this._onValidatorChange = fn;
91✔
753
    }
754

755
    /** @hidden @internal */
756
    public validate(control: AbstractControl): ValidationErrors | null {
757
        if (!control.value) {
262✔
758
            return null;
195✔
759
        }
760
        // InvalidDate handling
761
        if (isDate(control.value) && !DateTimeUtil.isValidDate(control.value)) {
67!
UNCOV
762
            return { value: true };
×
763
        }
764

765
        const errors = {};
67✔
766
        const value = DateTimeUtil.isValidDate(control.value) ? control.value : DateTimeUtil.parseIsoDate(control.value);
67!
767
        if (value && this.disabledDates && isDateInRanges(value, this.disabledDates)) {
67!
UNCOV
768
            Object.assign(errors, { dateIsDisabled: true });
×
769
        }
770
        Object.assign(errors, DateTimeUtil.validateMinMax(value, this.minValue, this.maxValue, false));
67✔
771

772
        return Object.keys(errors).length > 0 ? errors : null;
67!
773
    }
774
    //#endregion
775

776
    /** @hidden @internal */
777
    public ngOnInit(): void {
778
        this._ngControl = this._injector.get<NgControl>(NgControl, null);
111✔
779
    }
780

781
    /** @hidden @internal */
782
    public override ngAfterViewInit() {
783
        super.ngAfterViewInit();
121✔
784
        this.subscribeToClick();
121✔
785
        this.subscribeToOverlayEvents();
121✔
786
        this.subscribeToDateEditorEvents();
121✔
787

788
        this._dropDownOverlaySettings.excludeFromOutsideClick = [this.inputGroup.element.nativeElement];
121✔
789

790
        fromEvent(this.inputDirective.nativeElement, 'blur')
121✔
791
            .pipe(takeUntil(this._destroy$))
792
            .subscribe(() => {
793
                if (this.collapsed) {
44✔
794
                    this._onTouchedCallback();
18✔
795
                    this.updateValidity();
18✔
796
                }
797
            });
798

799
        if (this._ngControl) {
121✔
800
            this._statusChanges$ =
64✔
801
                this._ngControl.statusChanges.subscribe(this.onStatusChanged.bind(this));
802
            if (this._ngControl.control.validator) {
64✔
803
                this.inputGroup.isRequired = this.required;
63✔
804
                this.cdr.detectChanges();
63✔
805
            }
806
        }
807
    }
808

809
    /** @hidden @internal */
810
    public ngAfterViewChecked() {
811
        if (this.labelDirective) {
371✔
812
            this._renderer.setAttribute(this.inputDirective.nativeElement, 'aria-labelledby', this.labelDirective.id);
1✔
813
        }
814
    }
815

816
    /** @hidden @internal */
817
    public override ngOnDestroy(): void {
818
        super.ngOnDestroy();
128✔
819
        if (this._statusChanges$) {
128✔
820
            this._statusChanges$.unsubscribe();
66✔
821
        }
822
        if (this._overlayId) {
128✔
823
            this._overlayService.detach(this._overlayId);
6✔
824
            delete this._overlayId;
6✔
825
        }
826
    }
827

828
    /** @hidden @internal */
829
    public getEditElement(): HTMLInputElement {
830
        return this.inputDirective.nativeElement;
220✔
831
    }
832

833
    private subscribeToClick() {
834
        fromEvent(this.getEditElement(), 'click')
121✔
835
            .pipe(takeUntil(this._destroy$))
836
            .subscribe(() => {
837
                if (!this.isDropdown) {
15✔
838
                    this.toggle();
1✔
839
                }
840
            });
841
    }
842

843
    private setDateValue(value: Date | string) {
844
        if (isDate(value) && isNaN(value.getTime())) {
256✔
845
            this._dateValue = value;
1✔
846
            return;
1✔
847
        }
848
        this._dateValue = DateTimeUtil.isValidDate(value) ? value : DateTimeUtil.parseIsoDate(value);
255✔
849
        if (this._calendar) {
255✔
850
            this._calendar.selectDate(this._dateValue);
37✔
851
            this._calendar.activeDate = this.activeDate;
37✔
852
            this._calendar.viewDate = this.activeDate;
37✔
853
            this.cdr.detectChanges();
37✔
854
        }
855
    }
856

857
    private updateValidity() {
858
        // B.P. 18 May 2021: IgxDatePicker does not reset its state upon resetForm #9526
859
        if (this._ngControl && !this.disabled && this.isTouchedOrDirty) {
96✔
860
            if (this.hasValidators && this.inputGroup.isFocused) {
46✔
861
                this.inputDirective.valid = this._ngControl.valid ? IgxInputState.VALID : IgxInputState.INVALID;
18✔
862
            } else {
863
                this.inputDirective.valid = this._ngControl.valid ? IgxInputState.INITIAL : IgxInputState.INVALID;
28✔
864
            }
865
        } else {
866
            this.inputDirective.valid = IgxInputState.INITIAL;
50✔
867
        }
868
    }
869

870
    private get isTouchedOrDirty(): boolean {
871
        return (this._ngControl.control.touched || this._ngControl.control.dirty);
81✔
872
    }
873

874
    private get hasValidators(): boolean {
875
        return (!!this._ngControl.control.validator || !!this._ngControl.control.asyncValidator);
46✔
876
    }
877

878
    private onStatusChanged = () => {
126✔
879
        this.disabled = this._ngControl.disabled;
77✔
880
        this.updateValidity();
77✔
881
        this.inputGroup.isRequired = this.required;
77✔
882
    };
883

884
    private handleSelection(date: Date): void {
885
        if (this.dateValue && DateTimeUtil.isValidDate(this.dateValue)) {
13✔
886
            date.setHours(this.dateValue.getHours());
1✔
887
            date.setMinutes(this.dateValue.getMinutes());
1✔
888
            date.setSeconds(this.dateValue.getSeconds());
1✔
889
            date.setMilliseconds(this.dateValue.getMilliseconds());
1✔
890
        }
891
        this.value = date;
13✔
892
        if (this._calendar) {
13✔
893
            this._calendar.activeDate = this.activeDate;
13✔
894
            this._calendar.viewDate = this.activeDate;
13✔
895
        }
896
        this.close();
13✔
897
    }
898

899
    private subscribeToDateEditorEvents(): void {
900
        this.dateTimeEditor.valueChange.pipe(
121✔
901
            takeUntil(this._destroy$)).subscribe(val => {
902
                this.value = val;
32✔
903
            });
904
        this.dateTimeEditor.validationFailed.pipe(
121✔
905
            takeUntil(this._destroy$)).subscribe((event) => {
906
                this.validationFailed.emit({
1✔
907
                    owner: this,
908
                    prevValue: event.oldValue,
909
                    currentValue: this.value
910
                });
911
            });
912
    }
913

914
    private subscribeToOverlayEvents() {
915
        this._overlayService.opening.pipe(...this._overlaySubFilter).subscribe((e: OverlayCancelableEventArgs) => {
121✔
916
            const args: IBaseCancelableBrowserEventArgs = { owner: this, event: e.event, cancel: e.cancel };
53✔
917
            this.opening.emit(args);
53✔
918
            e.cancel = args.cancel;
53✔
919
            if (args.cancel) {
53✔
920
                this._overlayService.detach(this._overlayId);
1✔
921
                return;
1✔
922
            }
923

924
            this._initializeCalendarContainer(e.componentRef.instance);
52✔
925
            this._calendarContainer = e.componentRef.location.nativeElement;
52✔
926
            this._collapsed = false;
52✔
927
        });
928

929
        this._overlayService.opened.pipe(...this._overlaySubFilter).subscribe(() => {
121✔
930
            this.opened.emit({ owner: this });
45✔
931

932
            this._calendar.wrapper?.nativeElement?.focus();
45✔
933
        });
934

935
        this._overlayService.closing.pipe(...this._overlaySubFilter).subscribe((e: OverlayCancelableEventArgs) => {
121✔
936
            const args: IBaseCancelableBrowserEventArgs = { owner: this, event: e.event, cancel: e.cancel };
25✔
937
            this.closing.emit(args);
25✔
938
            e.cancel = args.cancel;
25✔
939
            if (args.cancel) {
25✔
940
                return;
1✔
941
            }
942
            // do not focus the input if clicking outside in dropdown mode
943
            const outsideEvent = args.event && (args.event as KeyboardEvent).key !== this.platform.KEYMAP.ESCAPE;
24✔
944
            if (this.getEditElement() && !(outsideEvent && this.isDropdown)) {
24✔
945
                this.inputDirective.focus();
23✔
946
            } else {
947
                this._onTouchedCallback();
1✔
948
                this.updateValidity();
1✔
949
            }
950
        });
951

952
        this._overlayService.closed.pipe(...this._overlaySubFilter).subscribe(() => {
121✔
953
            this.closed.emit({ owner: this });
25✔
954
            this._overlayService.detach(this._overlayId);
25✔
955
            this._collapsed = true;
25✔
956
            this._overlayId = null;
25✔
957
            this._calendar = null;
25✔
958
            this._calendarContainer = undefined;
25✔
959
        });
960
    }
961

962
    private getMinMaxDates() {
963
        const minValue = DateTimeUtil.isValidDate(this.minValue) ? this.minValue : DateTimeUtil.parseIsoDate(this.minValue);
52✔
964
        const maxValue = DateTimeUtil.isValidDate(this.maxValue) ? this.maxValue : DateTimeUtil.parseIsoDate(this.maxValue);
52✔
965
        return { minValue, maxValue };
52✔
966
    }
967

968
    private setDisabledDates(): void {
969
        this._calendar.disabledDates = this.disabledDates ? [...this.disabledDates] : [];
52✔
970
        const { minValue, maxValue } = this.getMinMaxDates();
52✔
971
        if (minValue) {
52✔
972
            this._calendar.disabledDates.push({ type: DateRangeType.Before, dateRange: [minValue] });
4✔
973
        }
974
        if (maxValue) {
52✔
975
            this._calendar.disabledDates.push({ type: DateRangeType.After, dateRange: [maxValue] });
5✔
976
        }
977
    }
978

979
    private _initializeCalendarContainer(componentInstance: IgxCalendarContainerComponent) {
980
        this._calendar = componentInstance.calendar;
52✔
981
        this._calendar.hasHeader = !this.isDropdown && !this.hideHeader;
52✔
982
        this._calendar.formatOptions = this.pickerCalendarFormat;
52✔
983
        this._calendar.formatViews = this.pickerFormatViews;
52✔
984
        this._calendar.locale = this.locale;
52✔
985
        this._calendar.weekStart = this.weekStart;
52✔
986
        this._calendar.specialDates = this.specialDates;
52✔
987
        this._calendar.headerTitleTemplate = this.headerTitleTemplate;
52✔
988
        this._calendar.headerTemplate = this.headerTemplate;
52✔
989
        this._calendar.subheaderTemplate = this.subheaderTemplate;
52✔
990
        this._calendar.headerOrientation = this.headerOrientation;
52✔
991
        this._calendar.hideOutsideDays = this.hideOutsideDays;
52✔
992
        this._calendar.monthsViewNumber = this.displayMonthsCount;
52✔
993
        this._calendar.showWeekNumbers = this.showWeekNumbers;
52✔
994
        this._calendar.orientation = this.orientation;
52✔
995
        this._calendar.selected.pipe(takeUntil(this._destroy$)).subscribe((ev: Date) => this.handleSelection(ev));
52✔
996
        this.setDisabledDates();
52✔
997

998
        if (DateTimeUtil.isValidDate(this.dateValue)) {
52✔
999
            // calendar will throw if the picker's value is InvalidDate #9208
1000
            this._calendar.value = this.dateValue;
21✔
1001
        }
1002
        this._calendar.activeDate = this.activeDate;
52✔
1003
        this._calendar.viewDate = this.activeDate;
52✔
1004

1005
        componentInstance.mode = this.mode;
52✔
1006
        componentInstance.closeButtonLabel = this.cancelButtonLabel;
52✔
1007
        componentInstance.todayButtonLabel = this.todayButtonLabel;
52✔
1008
        componentInstance.pickerActions = this.pickerActions;
52✔
1009

1010
        componentInstance.calendarClose.pipe(takeUntil(this._destroy$)).subscribe(() => this.close());
52✔
1011
        componentInstance.todaySelection.pipe(takeUntil(this._destroy$)).subscribe(() => this.selectToday());
52✔
1012
    }
1013

1014
    protected override updateResources(): void {
1015
        this._resourceStrings = getCurrentResourceStrings(DatePickerResourceStringsEN, false, this._locale);
300✔
1016
    }
1017
}
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

© 2026 Coveralls, Inc