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

IgniteUI / igniteui-angular / 17295360428

28 Aug 2025 12:09PM UTC coverage: 91.63% (+0.05%) from 91.577%
17295360428

push

github

web-flow
Clear icon enhancements for IgxDateRangePicker (#16115)

13673 of 16003 branches covered (85.44%)

23 of 24 new or added lines in 2 files covered. (95.83%)

68 existing lines in 7 files now uncovered.

27543 of 30059 relevant lines covered (91.63%)

34383.6 hits per line

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

97.99
/projects/igniteui-angular/src/lib/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
    Inject,
13
    Injector,
14
    Input,
15
    LOCALE_ID,
16
    OnDestroy,
17
    OnInit,
18
    Optional,
19
    Output,
20
    PipeTransform,
21
    Renderer2,
22
    ViewChild,
23
    ViewContainerRef,
24
    booleanAttribute
25
} from '@angular/core';
26
import {
27
    AbstractControl,
28
    ControlValueAccessor,
29
    NgControl,
30
    NG_VALIDATORS,
31
    NG_VALUE_ACCESSOR,
32
    ValidationErrors,
33
    Validator
34
} from '@angular/forms';
35
import {
36
    IgxCalendarComponent, IgxCalendarHeaderTemplateDirective, IgxCalendarHeaderTitleTemplateDirective, IgxCalendarSubheaderTemplateDirective,
37
     IFormattingViews, IFormattingOptions
38
} from '../calendar/public_api';
39
import { isDateInRanges } from '../calendar/common/helpers';
40
import {
41
    IgxLabelDirective, IGX_INPUT_GROUP_TYPE, IgxInputGroupType, IgxInputState, IgxInputGroupComponent, IgxPrefixDirective, IgxInputDirective, IgxSuffixDirective
42
} from '../input-group/public_api';
43
import { fromEvent, Subscription, noop, MonoTypeOperatorFunction } from 'rxjs';
44
import { filter, takeUntil } from 'rxjs/operators';
45

46
import { DateRangeDescriptor, DateRangeType } from '../core/dates/dateRange';
47
import { DatePickerResourceStringsEN, IDatePickerResourceStrings } from '../core/i18n/date-picker-resources';
48
import { IBaseCancelableBrowserEventArgs, isDate, PlatformUtil } from '../core/utils';
49
import { IgxCalendarContainerComponent } from '../date-common/calendar-container/calendar-container.component';
50
import { PickerBaseDirective } from '../date-common/picker-base.directive';
51
import { IgxPickerActionsDirective } from '../date-common/public_api';
52
import { DateTimeUtil } from '../date-common/util/date-time.util';
53
import { DatePart, DatePartDeltas, IgxDateTimeEditorDirective } from '../directives/date-time-editor/public_api';
54
import { IgxOverlayOutletDirective } from '../directives/toggle/toggle.directive';
55
import {
56
    AbsoluteScrollStrategy,
57
    AutoPositionStrategy,
58
    IgxOverlayService,
59
    OverlayCancelableEventArgs,
60
    OverlayEventArgs,
61
    OverlaySettings
62
} from '../services/public_api';
63
import { IDatePickerValidationFailedEventArgs } from './date-picker.common';
64
import { IgxIconComponent } from '../icon/icon.component';
65
import { IgxTextSelectionDirective } from '../directives/text-selection/text-selection.directive';
66
import { getCurrentResourceStrings } from '../core/i18n/resources';
67
import { fadeIn, fadeOut } from 'igniteui-angular/animations';
68
import { PickerCalendarOrientation } from '../date-common/types';
69

70
let NEXT_ID = 0;
3✔
71

72
/**
73
 * Date Picker displays a popup calendar that lets users select a single date.
74
 *
75
 * @igxModule IgxDatePickerModule
76
 * @igxTheme igx-calendar-theme, igx-icon-theme
77
 * @igxGroup Scheduling
78
 * @igxKeywords datepicker, calendar, schedule, date
79
 * @example
80
 * ```html
81
 * <igx-date-picker [(ngModel)]="selectedDate"></igx-date-picker>
82
 * ```
83
 */
84
@Component({
85
    providers: [
86
        { provide: NG_VALUE_ACCESSOR, useExisting: IgxDatePickerComponent, multi: true },
87
        { provide: NG_VALIDATORS, useExisting: IgxDatePickerComponent, multi: true }
88
    ],
89
    selector: 'igx-date-picker',
90
    templateUrl: 'date-picker.component.html',
91
    styles: [':host { display: block; }'],
92
    imports: [
93
        IgxInputGroupComponent,
94
        IgxPrefixDirective,
95
        IgxIconComponent,
96
        IgxInputDirective,
97
        IgxDateTimeEditorDirective,
98
        IgxTextSelectionDirective,
99
        IgxSuffixDirective
100
    ]
101
})
102
export class IgxDatePickerComponent extends PickerBaseDirective implements ControlValueAccessor, Validator,
3✔
103
    OnInit, AfterViewInit, OnDestroy, AfterViewChecked, AfterContentChecked {
104

105
    /**
106
     * Gets/Sets whether the inactive dates will be hidden.
107
     *
108
     * @remarks
109
     * Applies to dates that are out of the current month.
110
     * Default value is `false`.
111
     * @example
112
     * ```html
113
     * <igx-date-picker [hideOutsideDays]="true"></igx-date-picker>
114
     * ```
115
     * @example
116
     * ```typescript
117
     * let hideOutsideDays = this.datePicker.hideOutsideDays;
118
     * ```
119
     */
120
    @Input({ transform: booleanAttribute })
121
    public hideOutsideDays: boolean;
122

123
    /**
124
     * Gets/Sets the number of month views displayed.
125
     *
126
     * @remarks
127
     * Default value is `1`.
128
     *
129
     * @example
130
     * ```html
131
     * <igx-date-picker [displayMonthsCount]="2"></igx-date-picker>
132
     * ```
133
     * @example
134
     * ```typescript
135
     * let monthViewsDisplayed = this.datePicker.displayMonthsCount;
136
     * ```
137
     */
138
    @Input()
139
    public displayMonthsCount = 1;
123✔
140

141
    /**
142
    * Gets/Sets the orientation of the multiple months displayed in the picker's calendar's days view.
143
    *
144
    * @example
145
    * <igx-date-picker orientation="vertical"></igx-date-picker>
146
    */
147
    @Input()
148
    public orientation: PickerCalendarOrientation = PickerCalendarOrientation.Horizontal;
123✔
149

150
    /**
151
     * Show/hide week numbers
152
     *
153
     * @example
154
     * ```html
155
     * <igx-date-picker [showWeekNumbers]="true"></igx-date-picker>
156
     * ``
157
     */
158
    @Input({ transform: booleanAttribute })
159
    public showWeekNumbers: boolean;
160

161

162
    /**
163
     * Gets/Sets the date which is shown in the calendar picker and is highlighted.
164
     * By default it is the current date, or the value of the picker, if set.
165
     */
166
    @Input()
167
    public get activeDate(): Date {
168
        const today = new Date(new Date().setHours(0, 0, 0, 0));
202✔
169
        const dateValue = DateTimeUtil.isValidDate(this._dateValue) ? new Date(this._dateValue.setHours(0, 0, 0, 0)) : null;
202✔
170
        return this._activeDate ?? dateValue ?? this._calendar?.activeDate ?? today;
202✔
171
    }
172

173
    public set activeDate(value: Date) {
174
        this._activeDate = value;
1✔
175
    }
176

177
    /**
178
     * Gets/Sets a custom formatter function on the selected or passed date.
179
     *
180
     * @example
181
     * ```html
182
     * <igx-date-picker [value]="date" [formatter]="formatter"></igx-date-picker>
183
     * ```
184
     */
185
    @Input()
186
    public formatter: (val: Date) => string;
187

188
    /**
189
     * Gets/Sets the today button's label.
190
     *
191
     *  @example
192
     * ```html
193
     * <igx-date-picker todayButtonLabel="Today"></igx-date-picker>
194
     * ```
195
     */
196
    @Input()
197
    public todayButtonLabel: string;
198

199
    /**
200
     * Gets/Sets the cancel button's label.
201
     *
202
     * @example
203
     * ```html
204
     * <igx-date-picker cancelButtonLabel="Cancel"></igx-date-picker>
205
     * ```
206
     */
207
    @Input()
208
    public cancelButtonLabel: string;
209

210
    /**
211
     * Specify if the currently spun date segment should loop over.
212
     *
213
     *  @example
214
     * ```html
215
     * <igx-date-picker [spinLoop]="false"></igx-date-picker>
216
     * ```
217
     */
218
    @Input({ transform: booleanAttribute })
219
    public spinLoop = true;
123✔
220

221
    /**
222
     * Delta values used to increment or decrement each editor date part on spin actions.
223
     * All values default to `1`.
224
     *
225
     * @example
226
     * ```html
227
     * <igx-date-picker [spinDelta]="{ date: 5, month: 2 }"></igx-date-picker>
228
     * ```
229
     */
230
    @Input()
231
    public spinDelta: Pick<DatePartDeltas, 'date' | 'month' | 'year'>;
232

233
    /**
234
     * Gets/Sets the container used for the popup element.
235
     *
236
     * @remarks
237
     *  `outlet` is an instance of `IgxOverlayOutletDirective` or an `ElementRef`.
238
     * @example
239
     * ```html
240
     * <div igxOverlayOutlet #outlet="overlay-outlet"></div>
241
     * //..
242
     * <igx-date-picker [outlet]="outlet"></igx-date-picker>
243
     * //..
244
     * ```
245
     */
246
    @Input()
247
    public override outlet: IgxOverlayOutletDirective | ElementRef;
248

249
    /**
250
     * Gets/Sets the value of `id` attribute.
251
     *
252
     * @remarks If not provided it will be automatically generated.
253
     * @example
254
     * ```html
255
     * <igx-date-picker [id]="'igx-date-picker-3'" cancelButtonLabel="cancel" todayButtonLabel="today"></igx-date-picker>
256
     * ```
257
     */
258
    @Input()
259
    @HostBinding('attr.id')
260
    public id = `igx-date-picker-${NEXT_ID++}`;
123✔
261

262
    //#region calendar members
263

264
    /**
265
     * Gets/Sets the format views of the `IgxDatePickerComponent`.
266
     *
267
     * @example
268
     * ```typescript
269
     * let formatViews = this.datePicker.formatViews;
270
     *  this.datePicker.formatViews = {day:false, month: false, year:false};
271
     * ```
272
     */
273
    @Input()
274
    public formatViews: IFormattingViews;
275

276
    /**
277
     * Gets/Sets the disabled dates descriptors.
278
     *
279
     * @example
280
     * ```typescript
281
     * let disabledDates = this.datepicker.disabledDates;
282
     * this.datePicker.disabledDates = [ {type: DateRangeType.Weekends}, ...];
283
     * ```
284
     */
285
    @Input()
286
    public get disabledDates(): DateRangeDescriptor[] {
287
        return this._disabledDates;
111✔
288
    }
289
    public set disabledDates(value: DateRangeDescriptor[]) {
290
        this._disabledDates = value;
3✔
291
        this._onValidatorChange();
3✔
292
    }
293

294
    /**
295
     * Gets/Sets the special dates descriptors.
296
     *
297
     * @example
298
     * ```typescript
299
     * let specialDates = this.datepicker.specialDates;
300
     * this.datePicker.specialDates = [ {type: DateRangeType.Weekends}, ... ];
301
     * ```
302
     */
303
    @Input()
304
    public get specialDates(): DateRangeDescriptor[] {
305
        return this._specialDates;
53✔
306
    }
307
    public set specialDates(value: DateRangeDescriptor[]) {
308
        this._specialDates = value;
1✔
309
    }
310

311

312
    /**
313
     * Gets/Sets the format options of the `IgxDatePickerComponent`.
314
     *
315
     * @example
316
     * ```typescript
317
     * this.datePicker.calendarFormat = {day: "numeric",  month: "long", weekday: "long", year: "numeric"};
318
     * ```
319
     */
320
    @Input()
321
    public calendarFormat: IFormattingOptions;
322

323
    //#endregion
324

325
    /**
326
     * Gets/Sets the selected date.
327
     *
328
     *  @example
329
     * ```html
330
     * <igx-date-picker [value]="date"></igx-date-picker>
331
     * ```
332
     */
333
    @Input()
334
    public get value(): Date | string {
335
        return this._value;
817✔
336
    }
337
    public set value(date: Date | string) {
338
        this._value = date;
192✔
339
        this.setDateValue(date);
192✔
340
        if (this.dateTimeEditor.value !== date) {
192✔
341
            this.dateTimeEditor.value = this._dateValue;
155✔
342
        }
343
        this.valueChange.emit(this.dateValue);
192✔
344
        this._onChangeCallback(this.dateValue);
192✔
345
    }
346

347
    /**
348
     * The minimum value the picker will accept.
349
     *
350
     * @example
351
     * <igx-date-picker [minValue]="minDate"></igx-date-picker>
352
     */
353
    @Input()
354
    public set minValue(value: Date | string) {
355
        this._minValue = value;
30✔
356
        this._onValidatorChange();
30✔
357
    }
358

359
    public get minValue(): Date | string {
360
        return this._minValue;
840✔
361
    }
362

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

375
    public get maxValue(): Date | string {
376
        return this._maxValue;
840✔
377
    }
378

379
    /**
380
     * Gets/Sets the resource strings for the picker's default toggle icon.
381
     * By default it uses EN resources.
382
     */
383
    @Input()
384
    public resourceStrings: IDatePickerResourceStrings;
385

386
    /** @hidden @internal */
387
    @Input({ transform: booleanAttribute })
388
    public readOnly = false;
123✔
389

390
    /**
391
     * Emitted when the picker's value changes.
392
     *
393
     * @remarks
394
     * Used for `two-way` bindings.
395
     *
396
     * @example
397
     * ```html
398
     * <igx-date-picker [(value)]="date"></igx-date-picker>
399
     * ```
400
     */
401
    @Output()
402
    public valueChange = new EventEmitter<Date>();
123✔
403

404
    /**
405
     * Emitted when the user types/spins invalid date in the date-picker editor.
406
     *
407
     *  @example
408
     * ```html
409
     * <igx-date-picker (validationFailed)="onValidationFailed($event)"></igx-date-picker>
410
     * ```
411
     */
412
    @Output()
413
    public validationFailed = new EventEmitter<IDatePickerValidationFailedEventArgs>();
123✔
414

415
    /** @hidden @internal */
416
    @ContentChild(IgxLabelDirective)
417
    public label: IgxLabelDirective;
418

419
    @ContentChild(IgxCalendarHeaderTitleTemplateDirective)
420
    private headerTitleTemplate: IgxCalendarHeaderTitleTemplateDirective;
421

422
    @ContentChild(IgxCalendarHeaderTemplateDirective)
423
    private headerTemplate: IgxCalendarHeaderTemplateDirective;
424

425
    @ViewChild(IgxDateTimeEditorDirective, { static: true })
426
    private dateTimeEditor: IgxDateTimeEditorDirective;
427

428
    @ViewChild(IgxInputGroupComponent, { read: ViewContainerRef })
429
    private viewContainerRef: ViewContainerRef;
430

431
    @ViewChild(IgxLabelDirective)
432
    private labelDirective: IgxLabelDirective;
433

434
    @ViewChild(IgxInputDirective)
435
    private inputDirective: IgxInputDirective;
436

437
    @ContentChild(IgxCalendarSubheaderTemplateDirective)
438
    private subheaderTemplate: IgxCalendarSubheaderTemplateDirective;
439

440
    @ContentChild(IgxPickerActionsDirective)
441
    private pickerActions: IgxPickerActionsDirective;
442

443
    private get dialogOverlaySettings(): OverlaySettings {
444
        return Object.assign({}, this._dialogOverlaySettings, this.overlaySettings);
6✔
445
    }
446

447
    private get dropDownOverlaySettings(): OverlaySettings {
448
        return Object.assign({}, this._dropDownOverlaySettings, this.overlaySettings);
45✔
449
    }
450

451
    private get inputGroupElement(): HTMLElement {
452
        return this.inputGroup?.element.nativeElement;
213✔
453
    }
454

455
    private get dateValue(): Date {
456
        return this._dateValue;
472✔
457
    }
458

459
    private get pickerFormatViews(): IFormattingViews {
460
        return Object.assign({}, this._defFormatViews, this.formatViews);
56✔
461
    }
462

463
    private get pickerCalendarFormat(): IFormattingOptions {
464
        return Object.assign({}, this._calendarFormat, this.calendarFormat);
53✔
465
    }
466

467
    /** @hidden @internal */
468
    public displayValue: PipeTransform = { transform: (date: Date) => this.formatter(date) };
123✔
469

470
    private _resourceStrings = getCurrentResourceStrings(DatePickerResourceStringsEN);
123✔
471
    private _dateValue: Date;
472
    private _overlayId: string;
473
    private _value: Date | string;
474
    private _ngControl: NgControl = null;
123✔
475
    private _statusChanges$: Subscription;
476
    private _calendar: IgxCalendarComponent;
477
    private _calendarContainer?: HTMLElement;
478
    private _specialDates: DateRangeDescriptor[] = null;
123✔
479
    private _disabledDates: DateRangeDescriptor[] = null;
123✔
480
    private _activeDate: Date = null;
123✔
481
    private _overlaySubFilter:
123✔
482
        [MonoTypeOperatorFunction<OverlayEventArgs>,
483
            MonoTypeOperatorFunction<OverlayEventArgs | OverlayCancelableEventArgs>] = [
484
            filter(x => x.id === this._overlayId),
332✔
485
            takeUntil(this._destroy$)
486
        ];
487
    private _dropDownOverlaySettings: OverlaySettings = {
123✔
488
        target: this.inputGroupElement,
489
        closeOnOutsideClick: true,
490
        modal: false,
491
        closeOnEscape: true,
492
        scrollStrategy: new AbsoluteScrollStrategy(),
493
        positionStrategy: new AutoPositionStrategy({
494
            openAnimation: fadeIn,
495
            closeAnimation: fadeOut
496
        })
497
    };
498
    private _dialogOverlaySettings: OverlaySettings = {
123✔
499
        closeOnOutsideClick: true,
500
        modal: true,
501
        closeOnEscape: true
502
    };
503
    private _calendarFormat: IFormattingOptions = {
123✔
504
        day: 'numeric',
505
        month: 'short',
506
        weekday: 'short',
507
        year: 'numeric'
508
    };
509
    private _defFormatViews: IFormattingViews = {
123✔
510
        day: false,
511
        month: true,
512
        year: false
513
    };
514
    private _onChangeCallback: (_: Date) => void = noop;
123✔
515
    private _onTouchedCallback: () => void = noop;
123✔
516
    private _onValidatorChange: () => void = noop;
123✔
517

518
    constructor(element: ElementRef<HTMLElement>,
519
        @Inject(LOCALE_ID) _localeId: string,
520
        @Inject(IgxOverlayService) private _overlayService: IgxOverlayService,
123✔
521
        private _injector: Injector,
123✔
522
        private _renderer: Renderer2,
123✔
523
        private platform: PlatformUtil,
123✔
524
        private cdr: ChangeDetectorRef,
123✔
525
        @Optional() @Inject(IGX_INPUT_GROUP_TYPE) _inputGroupType?: IgxInputGroupType) {
526
        super(element, _localeId, _inputGroupType);
123✔
527
        this.locale = this.locale || this._localeId;
123!
528
    }
529

530
    /** @hidden @internal */
531
    public get required(): boolean {
532
        if (this._ngControl && this._ngControl.control && this._ngControl.control.validator) {
80✔
533
            // Run the validation with empty object to check if required is enabled.
534
            const error = this._ngControl.control.validator({} as AbstractControl);
74✔
535
            return error && error.required;
74✔
536
        }
537

538
        return false;
6✔
539
    }
540

541
    /** @hidden @internal */
542
    public get pickerResourceStrings(): IDatePickerResourceStrings {
543
        return Object.assign({}, this._resourceStrings, this.resourceStrings);
401✔
544
    }
545

546
    protected override get toggleContainer(): HTMLElement | undefined {
547
        return this._calendarContainer;
3✔
548
    }
549

550
    /** @hidden @internal */
551
    @HostListener('keydown', ['$event'])
552
    public onKeyDown(event: KeyboardEvent) {
553
        switch (event.key) {
2!
554
            case this.platform.KEYMAP.ARROW_UP:
UNCOV
555
                if (event.altKey) {
×
UNCOV
556
                    this.close();
×
557
                }
UNCOV
558
                break;
×
559
            case this.platform.KEYMAP.ARROW_DOWN:
560
                if (event.altKey) {
1✔
561
                    this.open();
1✔
562
                }
563
                break;
1✔
564
            case this.platform.KEYMAP.SPACE:
565
                event.preventDefault();
1✔
566
                this.open();
1✔
567
                break;
1✔
568
        }
569
    }
570

571
    /**
572
     * Opens the picker's dropdown or dialog.
573
     *
574
     * @example
575
     * ```html
576
     * <igx-date-picker #picker></igx-date-picker>
577
     *
578
     * <button type="button" igxButton (click)="picker.open()">Open Dialog</button>
579
     * ```
580
     */
581
    public open(settings?: OverlaySettings): void {
582
        if (!this.collapsed || this.disabled) {
54✔
583
            return;
3✔
584
        }
585

586
        const overlaySettings = Object.assign({}, this.isDropdown
51✔
587
            ? this.dropDownOverlaySettings
588
            : this.dialogOverlaySettings
589
            , settings);
590

591
        if (this.isDropdown && this.inputGroupElement) {
51✔
592
            overlaySettings.target = this.inputGroupElement;
45✔
593
        }
594
        if (this.outlet) {
51✔
595
            overlaySettings.outlet = this.outlet;
14✔
596
        }
597
        this._overlayId = this._overlayService
51✔
598
            .attach(IgxCalendarContainerComponent, this.viewContainerRef, overlaySettings);
599
        this._overlayService.show(this._overlayId);
51✔
600
    }
601

602
    /**
603
     * Toggles the picker's dropdown or dialog
604
     *
605
     * @example
606
     * ```html
607
     * <igx-date-picker #picker></igx-date-picker>
608
     *
609
     * <button type="button" igxButton (click)="picker.toggle()">Toggle Dialog</button>
610
     * ```
611
     */
612
    public toggle(settings?: OverlaySettings): void {
613
        if (this.collapsed) {
9✔
614
            this.open(settings);
8✔
615
        } else {
616
            this.close();
1✔
617
        }
618
    }
619

620
    /**
621
     * Closes the picker's dropdown or dialog.
622
     *
623
     * @example
624
     * ```html
625
     * <igx-date-picker #picker></igx-date-picker>
626
     *
627
     * <button type="button" igxButton (click)="picker.close()">Close Dialog</button>
628
     * ```
629
     */
630
    public close(): void {
631
        if (!this.collapsed) {
23✔
632
            this._overlayService.hide(this._overlayId);
22✔
633
        }
634
    }
635

636
    /**
637
     * Selects a date.
638
     *
639
     * @remarks Updates the value in the input field.
640
     *
641
     * @example
642
     * ```typescript
643
     * this.datePicker.select(date);
644
     * ```
645
     * @param date passed date that has to be set to the calendar.
646
     */
647
    public select(value: Date): void {
648
        this.value = value;
3✔
649
    }
650

651
    /**
652
     * Selects today's date and closes the picker.
653
     *
654
     * @example
655
     * ```html
656
     * <igx-date-picker #picker></igx-date-picker>
657
     *
658
     * <button type="button" igxButton (click)="picker.selectToday()">Select Today</button>
659
     * ```
660
     * */
661
    public selectToday(): void {
662
        const today = new Date();
1✔
663
        today.setHours(0);
1✔
664
        today.setMinutes(0);
1✔
665
        today.setSeconds(0);
1✔
666
        today.setMilliseconds(0);
1✔
667
        this.select(today);
1✔
668
        this.close();
1✔
669
    }
670

671
    /**
672
     * Clears the input field and the picker's value.
673
     *
674
     * @example
675
     * ```typescript
676
     * this.datePicker.clear();
677
     * ```
678
     */
679
    public clear(): void {
680
        if (!this.disabled) {
7✔
681
            this._calendar?.deselectDate();
7✔
682
            this.dateTimeEditor.clear();
7✔
683
        }
684
    }
685

686
    /**
687
     * Increment a specified `DatePart`.
688
     *
689
     * @param datePart The optional DatePart to increment. Defaults to Date.
690
     * @param delta The optional delta to increment by. Overrides `spinDelta`.
691
     * @example
692
     * ```typescript
693
     * this.datePicker.increment(DatePart.Date);
694
     * ```
695
     */
696
    public increment(datePart?: DatePart, delta?: number): void {
697
        this.dateTimeEditor.increment(datePart, delta);
4✔
698
    }
699

700
    /**
701
     * Decrement a specified `DatePart`
702
     *
703
     * @param datePart The optional DatePart to decrement. Defaults to Date.
704
     * @param delta The optional delta to decrement by. Overrides `spinDelta`.
705
     * @example
706
     * ```typescript
707
     * this.datePicker.decrement(DatePart.Date);
708
     * ```
709
     */
710
    public decrement(datePart?: DatePart, delta?: number): void {
711
        this.dateTimeEditor.decrement(datePart, delta);
4✔
712
    }
713

714
    //#region Control Value Accessor
715
    /** @hidden @internal */
716
    public writeValue(value: Date | string) {
717
        this._value = value;
56✔
718
        this.setDateValue(value);
56✔
719
        if (this.dateTimeEditor.value !== value) {
56✔
720
            this.dateTimeEditor.value = this._dateValue;
24✔
721
        }
722
    }
723

724
    /** @hidden @internal */
725
    public registerOnChange(fn: any) {
726
        this._onChangeCallback = fn;
67✔
727
    }
728

729
    /** @hidden @internal */
730
    public registerOnTouched(fn: any) {
731
        this._onTouchedCallback = fn;
67✔
732
    }
733

734
    /** @hidden @internal */
735
    public setDisabledState?(isDisabled: boolean): void {
736
        this.disabled = isDisabled;
40✔
737
    }
738
    //#endregion
739

740
    //#region Validator
741
    /** @hidden @internal */
742
    public registerOnValidatorChange(fn: any) {
743
        this._onValidatorChange = fn;
67✔
744
    }
745

746
    /** @hidden @internal */
747
    public validate(control: AbstractControl): ValidationErrors | null {
748
        if (!control.value) {
142✔
749
            return null;
87✔
750
        }
751
        // InvalidDate handling
752
        if (isDate(control.value) && !DateTimeUtil.isValidDate(control.value)) {
55!
UNCOV
753
            return { value: true };
×
754
        }
755

756
        const errors = {};
55✔
757
        const value = DateTimeUtil.isValidDate(control.value) ? control.value : DateTimeUtil.parseIsoDate(control.value);
55!
758
        if (value && this.disabledDates && isDateInRanges(value, this.disabledDates)) {
55!
UNCOV
759
            Object.assign(errors, { dateIsDisabled: true });
×
760
        }
761
        Object.assign(errors, DateTimeUtil.validateMinMax(value, this.minValue, this.maxValue, false));
55✔
762

763
        return Object.keys(errors).length > 0 ? errors : null;
55!
764
    }
765
    //#endregion
766

767
    /** @hidden @internal */
768
    public ngOnInit(): void {
769
        this._ngControl = this._injector.get<NgControl>(NgControl, null);
108✔
770

771
        this.locale = this.locale || this._localeId;
108!
772
    }
773

774
    /** @hidden @internal */
775
    public override ngAfterViewInit() {
776
        super.ngAfterViewInit();
118✔
777
        this.subscribeToClick();
118✔
778
        this.subscribeToOverlayEvents();
118✔
779
        this.subscribeToDateEditorEvents();
118✔
780

781
        this._dropDownOverlaySettings.excludeFromOutsideClick = [this.inputGroup.element.nativeElement];
118✔
782

783
        fromEvent(this.inputDirective.nativeElement, 'blur')
118✔
784
            .pipe(takeUntil(this._destroy$))
785
            .subscribe(() => {
786
                if (this.collapsed) {
43✔
787
                    this._onTouchedCallback();
18✔
788
                    this.updateValidity();
18✔
789
                }
790
            });
791

792
        if (this._ngControl) {
118✔
793
            this._statusChanges$ =
40✔
794
                this._ngControl.statusChanges.subscribe(this.onStatusChanged.bind(this));
795
            if (this._ngControl.control.validator) {
40✔
796
                this.inputGroup.isRequired = this.required;
39✔
797
                this.cdr.detectChanges();
39✔
798
            }
799
        }
800
    }
801

802
    /** @hidden @internal */
803
    public ngAfterViewChecked() {
804
        if (this.labelDirective) {
374✔
805
            this._renderer.setAttribute(this.inputDirective.nativeElement, 'aria-labelledby', this.labelDirective.id);
1✔
806
        }
807
    }
808

809
    /** @hidden @internal */
810
    public override ngOnDestroy(): void {
811
        super.ngOnDestroy();
108✔
812
        if (this._statusChanges$) {
108✔
813
            this._statusChanges$.unsubscribe();
40✔
814
        }
815
        if (this._overlayId) {
108✔
816
            this._overlayService.detach(this._overlayId);
5✔
817
            delete this._overlayId;
5✔
818
        }
819
    }
820

821
    /** @hidden @internal */
822
    public getEditElement(): HTMLInputElement {
823
        return this.inputDirective.nativeElement;
215✔
824
    }
825

826
    private subscribeToClick() {
827
        fromEvent(this.getEditElement(), 'click')
118✔
828
            .pipe(takeUntil(this._destroy$))
829
            .subscribe(() => {
830
                if (!this.isDropdown) {
13✔
831
                    this.toggle();
1✔
832
                }
833
            });
834
    }
835

836
    private setDateValue(value: Date | string) {
837
        if (isDate(value) && isNaN(value.getTime())) {
248✔
838
            this._dateValue = value;
1✔
839
            return;
1✔
840
        }
841
        this._dateValue = DateTimeUtil.isValidDate(value) ? value : DateTimeUtil.parseIsoDate(value);
247✔
842
        if (this._calendar) {
247✔
843
            this._calendar.selectDate(this._dateValue);
36✔
844
            this._calendar.activeDate = this.activeDate;
36✔
845
            this._calendar.viewDate = this.activeDate;
36✔
846
            this.cdr.detectChanges();
36✔
847
        }
848
    }
849

850
    private updateValidity() {
851
        // B.P. 18 May 2021: IgxDatePicker does not reset its state upon resetForm #9526
852
        if (this._ngControl && !this.disabled && this.isTouchedOrDirty) {
59✔
853
            if (this.hasValidators && this.inputGroup.isFocused) {
32✔
854
                this.inputDirective.valid = this._ngControl.valid ? IgxInputState.VALID : IgxInputState.INVALID;
11✔
855
            } else {
856
                this.inputDirective.valid = this._ngControl.valid ? IgxInputState.INITIAL : IgxInputState.INVALID;
21✔
857
            }
858
        } else {
859
            this.inputDirective.valid = IgxInputState.INITIAL;
27✔
860
        }
861
    }
862

863
    private get isTouchedOrDirty(): boolean {
864
        return (this._ngControl.control.touched || this._ngControl.control.dirty);
44✔
865
    }
866

867
    private get hasValidators(): boolean {
868
        return (!!this._ngControl.control.validator || !!this._ngControl.control.asyncValidator);
32✔
869
    }
870

871
    private onStatusChanged = () => {
123✔
872
        this.disabled = this._ngControl.disabled;
40✔
873
        this.updateValidity();
40✔
874
        this.inputGroup.isRequired = this.required;
40✔
875
    };
876

877
    private handleSelection(date: Date): void {
878
        if (this.dateValue && DateTimeUtil.isValidDate(this.dateValue)) {
12✔
879
            date.setHours(this.dateValue.getHours());
1✔
880
            date.setMinutes(this.dateValue.getMinutes());
1✔
881
            date.setSeconds(this.dateValue.getSeconds());
1✔
882
            date.setMilliseconds(this.dateValue.getMilliseconds());
1✔
883
        }
884
        this.value = date;
12✔
885
        if (this._calendar) {
12✔
886
            this._calendar.activeDate = this.activeDate;
12✔
887
            this._calendar.viewDate = this.activeDate;
12✔
888
        }
889
        this.close();
12✔
890
    }
891

892
    private subscribeToDateEditorEvents(): void {
893
        this.dateTimeEditor.valueChange.pipe(
118✔
894
            takeUntil(this._destroy$)).subscribe(val => {
895
                this.value = val;
32✔
896
            });
897
        this.dateTimeEditor.validationFailed.pipe(
118✔
898
            takeUntil(this._destroy$)).subscribe((event) => {
899
                this.validationFailed.emit({
1✔
900
                    owner: this,
901
                    prevValue: event.oldValue,
902
                    currentValue: this.value
903
                });
904
            });
905
    }
906

907
    private subscribeToOverlayEvents() {
908
        this._overlayService.opening.pipe(...this._overlaySubFilter).subscribe((e: OverlayCancelableEventArgs) => {
118✔
909
            const args: IBaseCancelableBrowserEventArgs = { owner: this, event: e.event, cancel: e.cancel };
51✔
910
            this.opening.emit(args);
51✔
911
            e.cancel = args.cancel;
51✔
912
            if (args.cancel) {
51✔
913
                this._overlayService.detach(this._overlayId);
1✔
914
                return;
1✔
915
            }
916

917
            this._initializeCalendarContainer(e.componentRef.instance);
50✔
918
            this._calendarContainer = e.componentRef.location.nativeElement;
50✔
919
            this._collapsed = false;
50✔
920
        });
921

922
        this._overlayService.opened.pipe(...this._overlaySubFilter).subscribe(() => {
118✔
923
            this.opened.emit({ owner: this });
43✔
924

925
            this._calendar.wrapper?.nativeElement?.focus();
43✔
926
        });
927

928
        this._overlayService.closing.pipe(...this._overlaySubFilter).subscribe((e: OverlayCancelableEventArgs) => {
118✔
929
            const args: IBaseCancelableBrowserEventArgs = { owner: this, event: e.event, cancel: e.cancel };
24✔
930
            this.closing.emit(args);
24✔
931
            e.cancel = args.cancel;
24✔
932
            if (args.cancel) {
24✔
933
                return;
1✔
934
            }
935
            // do not focus the input if clicking outside in dropdown mode
936
            const outsideEvent = args.event && (args.event as KeyboardEvent).key !== this.platform.KEYMAP.ESCAPE;
23✔
937
            if (this.getEditElement() && !(outsideEvent && this.isDropdown)) {
23✔
938
                this.inputDirective.focus();
22✔
939
            } else {
940
                this._onTouchedCallback();
1✔
941
                this.updateValidity();
1✔
942
            }
943
        });
944

945
        this._overlayService.closed.pipe(...this._overlaySubFilter).subscribe(() => {
118✔
946
            this.closed.emit({ owner: this });
24✔
947
            this._overlayService.detach(this._overlayId);
24✔
948
            this._collapsed = true;
24✔
949
            this._overlayId = null;
24✔
950
            this._calendar = null;
24✔
951
            this._calendarContainer = undefined;
24✔
952
        });
953
    }
954

955
    private getMinMaxDates() {
956
        const minValue = DateTimeUtil.isValidDate(this.minValue) ? this.minValue : DateTimeUtil.parseIsoDate(this.minValue);
50✔
957
        const maxValue = DateTimeUtil.isValidDate(this.maxValue) ? this.maxValue : DateTimeUtil.parseIsoDate(this.maxValue);
50✔
958
        return { minValue, maxValue };
50✔
959
    }
960

961
    private setDisabledDates(): void {
962
        this._calendar.disabledDates = this.disabledDates ? [...this.disabledDates] : [];
50✔
963
        const { minValue, maxValue } = this.getMinMaxDates();
50✔
964
        if (minValue) {
50✔
965
            this._calendar.disabledDates.push({ type: DateRangeType.Before, dateRange: [minValue] });
4✔
966
        }
967
        if (maxValue) {
50✔
968
            this._calendar.disabledDates.push({ type: DateRangeType.After, dateRange: [maxValue] });
5✔
969
        }
970
    }
971

972
    private _initializeCalendarContainer(componentInstance: IgxCalendarContainerComponent) {
973
        this._calendar = componentInstance.calendar;
50✔
974
        this._calendar.hasHeader = !this.isDropdown && !this.hideHeader;
50✔
975
        this._calendar.formatOptions = this.pickerCalendarFormat;
50✔
976
        this._calendar.formatViews = this.pickerFormatViews;
50✔
977
        this._calendar.locale = this.locale;
50✔
978
        this._calendar.weekStart = this.weekStart;
50✔
979
        this._calendar.specialDates = this.specialDates;
50✔
980
        this._calendar.headerTitleTemplate = this.headerTitleTemplate;
50✔
981
        this._calendar.headerTemplate = this.headerTemplate;
50✔
982
        this._calendar.subheaderTemplate = this.subheaderTemplate;
50✔
983
        this._calendar.headerOrientation = this.headerOrientation;
50✔
984
        this._calendar.hideOutsideDays = this.hideOutsideDays;
50✔
985
        this._calendar.monthsViewNumber = this.displayMonthsCount;
50✔
986
        this._calendar.showWeekNumbers = this.showWeekNumbers;
50✔
987
        this._calendar.orientation = this.orientation;
50✔
988
        this._calendar.selected.pipe(takeUntil(this._destroy$)).subscribe((ev: Date) => this.handleSelection(ev));
50✔
989
        this.setDisabledDates();
50✔
990

991
        if (DateTimeUtil.isValidDate(this.dateValue)) {
50✔
992
            // calendar will throw if the picker's value is InvalidDate #9208
993
            this._calendar.value = this.dateValue;
20✔
994
        }
995
        this._calendar.activeDate = this.activeDate;
50✔
996
        this._calendar.viewDate = this.activeDate;
50✔
997

998
        componentInstance.mode = this.mode;
50✔
999
        componentInstance.closeButtonLabel = this.cancelButtonLabel;
50✔
1000
        componentInstance.todayButtonLabel = this.todayButtonLabel;
50✔
1001
        componentInstance.pickerActions = this.pickerActions;
50✔
1002

1003
        componentInstance.calendarClose.pipe(takeUntil(this._destroy$)).subscribe(() => this.close());
50✔
1004
        componentInstance.todaySelection.pipe(takeUntil(this._destroy$)).subscribe(() => this.selectToday());
50✔
1005
    }
1006
}
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