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

IgniteUI / igniteui-angular / 30820539303

03 Aug 2026 01:59PM UTC coverage: 90.157% (-0.03%) from 90.19%
30820539303

Pull #15125

github

web-flow
Merge cf62d17ef into 75c7853d3
Pull Request #15125: refactor(*): bundle styles with components

14962 of 17433 branches covered (85.83%)

Branch coverage included in aggregate %.

30113 of 32563 relevant lines covered (92.48%)

37531.2 hits per line

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

95.48
/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
    EventEmitter,
9
    HostBinding,
10
    HostListener,
11
    Injector,
12
    Input,
13
    OnDestroy,
14
    OnInit,
15
    Output,
16
    PipeTransform,
17
    Renderer2,
18
    ViewChild,
19
    ViewContainerRef,
20
    booleanAttribute,
21
    inject,
22
    ChangeDetectionStrategy,
23
    ViewEncapsulation,
24
} from '@angular/core';
25
import {
26
    AbstractControl,
27
    ControlValueAccessor,
28
    NgControl,
29
    NG_VALIDATORS,
30
    NG_VALUE_ACCESSOR,
31
    ValidationErrors,
32
    Validator
33
} from '@angular/forms';
34
import {
35
    IgxCalendarComponent, IgxCalendarHeaderTemplateDirective, IgxCalendarHeaderTitleTemplateDirective, IgxCalendarSubheaderTemplateDirective,
36
     IFormattingViews, IFormattingOptions
37
} from 'igniteui-angular/calendar';
38
import {
39
    IgxLabelDirective, IgxInputState, IgxInputGroupComponent, IgxPrefixDirective, IgxInputDirective, IgxSuffixDirective,
40
    IgxReadOnlyInputDirective
41
} from 'igniteui-angular/input-group';
42
import { fromEvent, Subscription, noop, MonoTypeOperatorFunction } from 'rxjs';
43
import { filter, takeUntil } from 'rxjs/operators';
44

45
import { IgxDateTimeEditorDirective, IgxTextSelectionDirective } from 'igniteui-angular/directives';
46
import {
47
    AbsoluteScrollStrategy,
48
    AutoPositionStrategy,
49
    IgxOverlayService,
50
    OverlayCancelableEventArgs,
51
    OverlayEventArgs,
52
    OverlaySettings,
53
    IgxPickerActionsDirective,
54
    DatePickerResourceStringsEN,
55
    IDatePickerResourceStrings,
56
    DateRangeDescriptor,
57
    DateRangeType,
58
    IBaseCancelableBrowserEventArgs,
59
    isDate,
60
    PlatformUtil,
61
    getCurrentResourceStrings,
62
    PickerCalendarOrientation,
63
    DateTimeUtil,
64
    DatePartDeltas,
65
    DatePart,
66
    isDateInRanges,
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
    styleUrls: ['date-picker.component.css'],
97
    host: { 'class': 'igx-date-picker' },
98
    encapsulation: ViewEncapsulation.None,
99
    changeDetection: ChangeDetectionStrategy.Eager,
100
    imports: [
101
        IgxInputGroupComponent,
102
        IgxPrefixDirective,
103
        IgxIconComponent,
104
        IgxInputDirective,
105
        IgxReadOnlyInputDirective,
106
        IgxDateTimeEditorDirective,
107
        IgxTextSelectionDirective,
108
        IgxSuffixDirective
109
    ]
110
})
111
export class IgxDatePickerComponent extends PickerBaseDirective implements ControlValueAccessor, Validator,
3✔
112
    OnInit, AfterViewInit, OnDestroy, AfterViewChecked, AfterContentChecked {
113
    private _overlayService = inject<IgxOverlayService>(IgxOverlayService);
128✔
114
    private _injector = inject(Injector);
128✔
115
    private _renderer = inject(Renderer2);
128✔
116
    private platform = inject(PlatformUtil);
128✔
117
    private cdr = inject(ChangeDetectorRef);
128✔
118
    private _i18nFormatter = inject(I18N_FORMATTER);
128✔
119

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

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

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

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

176

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

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

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

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

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

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

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

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

261
    //#region calendar members
262

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

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

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

310

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

322
    //#endregion
323

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

450
    private get inputGroupElement(): HTMLElement {
451
        return this.inputGroup?.element.nativeElement.querySelector('.igx-input-group__bundle');
224✔
452
    }
453

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

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

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

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

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

517
    constructor() {
518
        super();
128✔
519
        this.initLocale();
128✔
520
    }
521

522
    /** @hidden @internal */
523
    public get required(): boolean {
524
        if (this._ngControl && this._ngControl.control && this._ngControl.control.validator) {
144✔
525
            // Run the validation with empty object to check if required is enabled.
526
            const error = this._ngControl.control.validator({} as AbstractControl);
138✔
527
            return error && error.required;
138✔
528
        }
529

530
        return false;
6✔
531
    }
532

533
    /** @hidden @internal */
534
    public get pickerResourceStrings(): IDatePickerResourceStrings {
535
        return Object.assign({}, this._resourceStrings, this.resourceStrings);
426✔
536
    }
537

538
    protected override get toggleContainer(): HTMLElement | undefined {
539
        return this._calendarContainer;
×
540
    }
541

542
    /** @hidden @internal */
543
    @HostListener('keydown', ['$event'])
544
    public onKeyDown(event: KeyboardEvent) {
545
        switch (event.key) {
3!
546
            case this.platform.KEYMAP.ARROW_UP:
547
                if (event.altKey) {
×
548
                    this.close();
×
549
                }
550
                break;
×
551
            case this.platform.KEYMAP.ARROW_DOWN:
552
                if (event.altKey) {
1✔
553
                    this.open();
1✔
554
                }
555
                break;
1✔
556
            case this.platform.KEYMAP.SPACE:
557
                event.preventDefault();
1✔
558
                this.open();
1✔
559
                break;
1✔
560
        }
561
    }
562

563
    /**
564
     * Opens the picker's dropdown or dialog.
565
     *
566
     * @example
567
     * ```html
568
     * <igx-date-picker #picker></igx-date-picker>
569
     *
570
     * <button type="button" igxButton (click)="picker.open()">Open Dialog</button>
571
     * ```
572
     */
573
    public open(settings?: OverlaySettings): void {
574
        if (!this.collapsed || this.disabled || this.readOnly) {
68✔
575
            return;
14✔
576
        }
577

578
        const overlaySettings = Object.assign({}, this.isDropdown
54✔
579
            ? this.dropDownOverlaySettings
580
            : this.dialogOverlaySettings
581
            , settings);
582

583
        if (this.isDropdown && this.inputGroupElement) {
54✔
584
            overlaySettings.target = this.inputGroupElement;
48✔
585
        }
586
        if (this.outlet) {
54!
587
            overlaySettings.outlet = this.outlet;
×
588
        }
589
        this._overlayId = this._overlayService
54✔
590
            .attach(IgxCalendarContainerComponent, this.viewContainerRef, overlaySettings);
591
        this._overlayService.show(this._overlayId);
54✔
592
    }
593

594
    /**
595
     * Toggles the picker's dropdown or dialog
596
     *
597
     * @example
598
     * ```html
599
     * <igx-date-picker #picker></igx-date-picker>
600
     *
601
     * <button type="button" igxButton (click)="picker.toggle()">Toggle Dialog</button>
602
     * ```
603
     */
604
    public toggle(settings?: OverlaySettings): void {
605
        if (this.collapsed) {
9✔
606
            this.open(settings);
8✔
607
        } else {
608
            this.close();
1✔
609
        }
610
    }
611

612
    /**
613
     * Closes the picker's dropdown or dialog.
614
     *
615
     * @example
616
     * ```html
617
     * <igx-date-picker #picker></igx-date-picker>
618
     *
619
     * <button type="button" igxButton (click)="picker.close()">Close Dialog</button>
620
     * ```
621
     */
622
    public close(): void {
623
        if (!this.collapsed) {
25✔
624
            this._overlayService.hide(this._overlayId);
24✔
625
        }
626
    }
627

628
    /**
629
     * Selects a date.
630
     *
631
     * @remarks Updates the value in the input field.
632
     *
633
     * @example
634
     * ```typescript
635
     * this.datePicker.select(date);
636
     * ```
637
     * @param date passed date that has to be set to the calendar.
638
     */
639
    public select(value: Date): void {
640
        this.value = value;
3✔
641
    }
642

643
    /**
644
     * Selects today's date and closes the picker.
645
     *
646
     * @example
647
     * ```html
648
     * <igx-date-picker #picker></igx-date-picker>
649
     *
650
     * <button type="button" igxButton (click)="picker.selectToday()">Select Today</button>
651
     * ```
652
     * */
653
    public selectToday(): void {
654
        const today = new Date();
1✔
655
        today.setHours(0);
1✔
656
        today.setMinutes(0);
1✔
657
        today.setSeconds(0);
1✔
658
        today.setMilliseconds(0);
1✔
659
        this.select(today);
1✔
660
        this.close();
1✔
661
    }
662

663
    /**
664
     * Clears the input field and the picker's value.
665
     *
666
     * @example
667
     * ```typescript
668
     * this.datePicker.clear();
669
     * ```
670
     */
671
    public clear(): void {
672
        if (!this.disabled || !this.readOnly) {
7!
673
            this._calendar?.deselectDate();
7✔
674
            this.dateTimeEditor.clear();
7✔
675
        }
676
    }
677

678
    /**
679
     * Increment a specified `DatePart`.
680
     *
681
     * @param datePart The optional DatePart to increment. Defaults to Date.
682
     * @param delta The optional delta to increment by. Overrides `spinDelta`.
683
     * @example
684
     * ```typescript
685
     * this.datePicker.increment(DatePart.Date);
686
     * ```
687
     */
688
    public increment(datePart?: DatePart, delta?: number): void {
689
        this.dateTimeEditor.increment(datePart, delta);
4✔
690
    }
691

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

706
    //#region Control Value Accessor
707
    /** @hidden @internal */
708
    public writeValue(value: Date | string) {
709
        this._value = value;
114✔
710
        this.setDateValue(value);
114✔
711
        if (this.dateTimeEditor.value !== value) {
114✔
712
            this.dateTimeEditor.value = this._dateValue;
56✔
713
        }
714
    }
715

716
    /** @hidden @internal */
717
    public registerOnChange(fn: any) {
718
        this._onChangeCallback = fn;
93✔
719
    }
720

721
    /** @hidden @internal */
722
    public registerOnTouched(fn: any) {
723
        this._onTouchedCallback = fn;
93✔
724
    }
725

726
    /** @hidden @internal */
727
    public setDisabledState?(isDisabled: boolean): void {
728
        this.disabled = isDisabled;
68✔
729
    }
730
    //#endregion
731

732
    //#region Validator
733
    /** @hidden @internal */
734
    public registerOnValidatorChange(fn: any) {
735
        this._onValidatorChange = fn;
93✔
736
    }
737

738
    /** @hidden @internal */
739
    public validate(control: AbstractControl): ValidationErrors | null {
740
        if (!control.value) {
272✔
741
            return null;
200✔
742
        }
743
        // InvalidDate handling
744
        if (isDate(control.value) && !DateTimeUtil.isValidDate(control.value)) {
72!
745
            return { value: true };
×
746
        }
747

748
        const errors = {};
72✔
749
        const value = DateTimeUtil.isValidDate(control.value) ? control.value : DateTimeUtil.parseIsoDate(control.value);
72!
750
        if (value && this.disabledDates && isDateInRanges(value, this.disabledDates)) {
72!
751
            Object.assign(errors, { dateIsDisabled: true });
×
752
        }
753
        Object.assign(errors, DateTimeUtil.validateMinMax(value, this.minValue, this.maxValue, false));
72✔
754

755
        return Object.keys(errors).length > 0 ? errors : null;
72!
756
    }
757
    //#endregion
758

759
    /** @hidden @internal */
760
    public ngOnInit(): void {
761
        this._ngControl = this._injector.get<NgControl>(NgControl, null);
113✔
762
    }
763

764
    /** @hidden @internal */
765
    public override ngAfterViewInit() {
766
        super.ngAfterViewInit();
123✔
767
        this.subscribeToClick();
123✔
768
        this.subscribeToOverlayEvents();
123✔
769
        this.subscribeToDateEditorEvents();
123✔
770

771
        this._dropDownOverlaySettings.excludeFromOutsideClick = [this.inputGroup.element.nativeElement];
123✔
772

773
        fromEvent(this.inputDirective.nativeElement, 'blur')
123✔
774
            .pipe(takeUntil(this._destroy$))
775
            .subscribe(() => {
776
                if (this.collapsed) {
44✔
777
                    this._onTouchedCallback();
19✔
778
                    this.updateValidity();
19✔
779
                }
780
            });
781

782
        if (this._ngControl) {
123✔
783
            this._statusChanges$ =
66✔
784
                this._ngControl.statusChanges.subscribe(this.onStatusChanged.bind(this));
785
            if (this._ngControl.control.validator) {
66✔
786
                this.inputGroup.isRequired = this.required;
65✔
787
                this.cdr.detectChanges();
65✔
788
            }
789
        }
790
    }
791

792
    /** @hidden @internal */
793
    public ngAfterViewChecked() {
794
        if (this.labelDirective) {
395✔
795
            this._renderer.setAttribute(this.inputDirective.nativeElement, 'aria-labelledby', this.labelDirective.id);
1✔
796
        }
797
    }
798

799
    /** @hidden @internal */
800
    public override ngOnDestroy(): void {
801
        super.ngOnDestroy();
129✔
802
        if (this._statusChanges$) {
129✔
803
            this._statusChanges$.unsubscribe();
67✔
804
        }
805
        if (this._overlayId) {
129✔
806
            this._overlayService.detach(this._overlayId);
6✔
807
            delete this._overlayId;
6✔
808
        }
809
    }
810

811
    /** @hidden @internal */
812
    public getEditElement(): HTMLInputElement {
813
        return this.inputDirective.nativeElement;
221✔
814
    }
815

816
    private subscribeToClick() {
817
        fromEvent(this.getEditElement(), 'click')
123✔
818
            .pipe(takeUntil(this._destroy$))
819
            .subscribe(() => {
820
                if (!this.isDropdown) {
15✔
821
                    this.toggle();
1✔
822
                }
823
            });
824
    }
825

826
    private setDateValue(value: Date | string) {
827
        if (isDate(value) && isNaN(value.getTime())) {
261✔
828
            this._dateValue = value;
1✔
829
            return;
1✔
830
        }
831
        this._dateValue = DateTimeUtil.isValidDate(value) ? value : DateTimeUtil.parseIsoDate(value);
260✔
832
        if (this._calendar) {
260✔
833
            this._calendar.selectDate(this._dateValue);
38✔
834
            this._calendar.activeDate = this.activeDate;
38✔
835
            this._calendar.viewDate = this.activeDate;
38✔
836
            this.cdr.detectChanges();
38✔
837
        }
838
    }
839

840
    private updateValidity() {
841
        // B.P. 18 May 2021: IgxDatePicker does not reset its state upon resetForm #9526
842
        if (this._ngControl && !this.disabled && this.isTouchedOrDirty) {
98✔
843
            if (this.hasValidators && this.inputGroup.isFocused) {
47✔
844
                this.inputDirective.valid = this._ngControl.valid ? IgxInputState.VALID : IgxInputState.INVALID;
17✔
845
            } else {
846
                this.inputDirective.valid = this._ngControl.valid ? IgxInputState.INITIAL : IgxInputState.INVALID;
30✔
847
            }
848
        } else {
849
            this.inputDirective.valid = IgxInputState.INITIAL;
51✔
850
        }
851
    }
852

853
    private get isTouchedOrDirty(): boolean {
854
        return (this._ngControl.control.touched || this._ngControl.control.dirty);
83✔
855
    }
856

857
    private get hasValidators(): boolean {
858
        return (!!this._ngControl.control.validator || !!this._ngControl.control.asyncValidator);
47✔
859
    }
860

861
    private onStatusChanged = () => {
128✔
862
        this.disabled = this._ngControl.disabled;
78✔
863
        this.updateValidity();
78✔
864
        this.inputGroup.isRequired = this.required;
78✔
865
    };
866

867
    private handleSelection(date: Date): void {
868
        if (this.dateValue && DateTimeUtil.isValidDate(this.dateValue)) {
13✔
869
            date.setHours(this.dateValue.getHours());
1✔
870
            date.setMinutes(this.dateValue.getMinutes());
1✔
871
            date.setSeconds(this.dateValue.getSeconds());
1✔
872
            date.setMilliseconds(this.dateValue.getMilliseconds());
1✔
873
        }
874
        this.value = date;
13✔
875
        if (this._calendar) {
13✔
876
            this._calendar.activeDate = this.activeDate;
13✔
877
            this._calendar.viewDate = this.activeDate;
13✔
878
        }
879
        this.close();
13✔
880
    }
881

882
    private subscribeToDateEditorEvents(): void {
883
        this.dateTimeEditor.valueChange.pipe(
123✔
884
            takeUntil(this._destroy$)).subscribe(val => {
885
                this.value = val;
32✔
886
            });
887
        this.dateTimeEditor.validationFailed.pipe(
123✔
888
            takeUntil(this._destroy$)).subscribe((event) => {
889
                this.validationFailed.emit({
1✔
890
                    owner: this,
891
                    prevValue: event.oldValue,
892
                    currentValue: this.value
893
                });
894
            });
895
    }
896

897
    private subscribeToOverlayEvents() {
898
        this._overlayService.opening.pipe(...this._overlaySubFilter).subscribe((e: OverlayCancelableEventArgs) => {
123✔
899
            const args: IBaseCancelableBrowserEventArgs = { owner: this, event: e.event, cancel: e.cancel };
54✔
900
            this.opening.emit(args);
54✔
901
            e.cancel = args.cancel;
54✔
902
            if (args.cancel) {
54✔
903
                this._overlayService.detach(this._overlayId);
1✔
904
                return;
1✔
905
            }
906

907
            this._initializeCalendarContainer(e.componentRef.instance);
53✔
908
            this._calendarContainer = e.componentRef.location.nativeElement;
53✔
909
            this._collapsed = false;
53✔
910
            this.cdr.markForCheck();
53✔
911
        });
912

913
        this._overlayService.opened.pipe(...this._overlaySubFilter).subscribe(() => {
123✔
914
            this.opened.emit({ owner: this });
46✔
915

916
            this._calendar.wrapper?.nativeElement?.focus();
46✔
917
        });
918

919
        this._overlayService.closing.pipe(...this._overlaySubFilter).subscribe((e: OverlayCancelableEventArgs) => {
123✔
920
            const args: IBaseCancelableBrowserEventArgs = { owner: this, event: e.event, cancel: e.cancel };
25✔
921
            this.closing.emit(args);
25✔
922
            e.cancel = args.cancel;
25✔
923
            if (args.cancel) {
25✔
924
                return;
1✔
925
            }
926
            // do not focus the input if clicking outside in dropdown mode
927
            const outsideEvent = args.event && (args.event as KeyboardEvent).key !== this.platform.KEYMAP.ESCAPE;
24✔
928
            if (this.getEditElement() && !(outsideEvent && this.isDropdown)) {
24✔
929
                this.inputDirective.focus();
23✔
930
            } else {
931
                this._onTouchedCallback();
1✔
932
                this.updateValidity();
1✔
933
            }
934
        });
935

936
        this._overlayService.closed.pipe(...this._overlaySubFilter).subscribe(() => {
123✔
937
            this.closed.emit({ owner: this });
25✔
938
            this._overlayService.detach(this._overlayId);
25✔
939
            this._collapsed = true;
25✔
940
            this._overlayId = null;
25✔
941
            this._calendar = null;
25✔
942
            this._calendarContainer = undefined;
25✔
943
            this.cdr.markForCheck();
25✔
944
        });
945
    }
946

947
    private getMinMaxDates() {
948
        const minValue = DateTimeUtil.isValidDate(this.minValue) ? this.minValue : DateTimeUtil.parseIsoDate(this.minValue);
53✔
949
        const maxValue = DateTimeUtil.isValidDate(this.maxValue) ? this.maxValue : DateTimeUtil.parseIsoDate(this.maxValue);
53✔
950
        return { minValue, maxValue };
53✔
951
    }
952

953
    private setDisabledDates(): void {
954
        this._calendar.disabledDates = this.disabledDates ? [...this.disabledDates] : [];
53✔
955
        const { minValue, maxValue } = this.getMinMaxDates();
53✔
956
        if (minValue) {
53✔
957
            this._calendar.disabledDates.push({ type: DateRangeType.Before, dateRange: [minValue] });
4✔
958
        }
959
        if (maxValue) {
53✔
960
            this._calendar.disabledDates.push({ type: DateRangeType.After, dateRange: [maxValue] });
5✔
961
        }
962
    }
963

964
    private _initializeCalendarContainer(componentInstance: IgxCalendarContainerComponent) {
965
        this._calendar = componentInstance.calendar;
53✔
966
        this._calendar.hasHeader = !this.isDropdown && !this.hideHeader;
53✔
967
        this._calendar.formatOptions = this.pickerCalendarFormat;
53✔
968
        this._calendar.formatViews = this.pickerFormatViews;
53✔
969
        this._calendar.locale = this.locale;
53✔
970
        this._calendar.weekStart = this.weekStart;
53✔
971
        this._calendar.specialDates = this.specialDates;
53✔
972
        this._calendar.headerTitleTemplate = this.headerTitleTemplate;
53✔
973
        this._calendar.headerTemplate = this.headerTemplate;
53✔
974
        this._calendar.subheaderTemplate = this.subheaderTemplate;
53✔
975
        this._calendar.headerOrientation = this.headerOrientation;
53✔
976
        this._calendar.hideOutsideDays = this.hideOutsideDays;
53✔
977
        this._calendar.monthsViewNumber = this.displayMonthsCount;
53✔
978
        this._calendar.showWeekNumbers = this.showWeekNumbers;
53✔
979
        this._calendar.orientation = this.orientation;
53✔
980
        this._calendar.selected.pipe(takeUntil(this._destroy$)).subscribe((ev: Date) => this.handleSelection(ev));
53✔
981
        this.setDisabledDates();
53✔
982

983
        if (DateTimeUtil.isValidDate(this.dateValue)) {
53✔
984
            // calendar will throw if the picker's value is InvalidDate #9208
985
            this._calendar.value = this.dateValue;
22✔
986
        }
987
        this._calendar.activeDate = this.activeDate;
53✔
988
        this._calendar.viewDate = this.activeDate;
53✔
989

990
        componentInstance.mode = this.mode;
53✔
991
        componentInstance.closeButtonLabel = this.cancelButtonLabel;
53✔
992
        componentInstance.todayButtonLabel = this.todayButtonLabel;
53✔
993
        componentInstance.pickerActions = this.pickerActions;
53✔
994

995
        componentInstance.calendarClose.pipe(takeUntil(this._destroy$)).subscribe(() => this.close());
53✔
996
        componentInstance.todaySelection.pipe(takeUntil(this._destroy$)).subscribe(() => this.selectToday());
53✔
997
    }
998

999
    protected override updateResources(): void {
1000
        this._resourceStrings = getCurrentResourceStrings(DatePickerResourceStringsEN, false, this._locale);
450✔
1001
    }
1002
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc