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

IgniteUI / igniteui-angular / 28358286454

29 Jun 2026 08:17AM UTC coverage: 90.127% (-0.05%) from 90.174%
28358286454

Pull #17246

github

web-flow
Merge 21687d939 into 07bdcd752
Pull Request #17246: fix(pivot-grid): fix date format based on the localization

14921 of 17392 branches covered (85.79%)

Branch coverage included in aggregate %.

29 of 32 new or added lines in 4 files covered. (90.63%)

735 existing lines in 76 files now uncovered.

29992 of 32441 relevant lines covered (92.45%)

34632.46 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
} 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
    I18N_FORMATTER
67
} from 'igniteui-angular/core';
68
import { IDatePickerValidationFailedEventArgs } from './date-picker.common';
69
import { IgxIconComponent } from 'igniteui-angular/icon';
70
import { fadeIn, fadeOut } from 'igniteui-angular/animations';
71
import { PickerBaseDirective } from './picker-base.directive';
72
import { IgxCalendarContainerComponent } from './calendar-container/calendar-container.component';
73

74
let NEXT_ID = 0;
3✔
75

76
/**
77
 * Date Picker displays a popup calendar that lets users select a single date.
78
 *
79
 * @igxModule IgxDatePickerModule
80
 * @igxTheme igx-calendar-theme, igx-icon-theme
81
 * @igxGroup Scheduling
82
 * @igxKeywords datepicker, calendar, schedule, date
83
 * @example
84
 * ```html
85
 * <igx-date-picker [(ngModel)]="selectedDate"></igx-date-picker>
86
 * ```
87
 */
88
@Component({
89
    providers: [
90
        { provide: NG_VALUE_ACCESSOR, useExisting: IgxDatePickerComponent, multi: true },
91
        { provide: NG_VALIDATORS, useExisting: IgxDatePickerComponent, multi: true }
92
    ],
93
    selector: 'igx-date-picker',
94
    templateUrl: 'date-picker.component.html',
95
    styles: [':host { display: block; }'],
96
    changeDetection: ChangeDetectionStrategy.Eager,
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);
128✔
111
    private _injector = inject(Injector);
128✔
112
    private _renderer = inject(Renderer2);
128✔
113
    private platform = inject(PlatformUtil);
128✔
114
    private cdr = inject(ChangeDetectorRef);
128✔
115
    private _i18nFormatter = inject(I18N_FORMATTER);
128✔
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;
128✔
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;
128✔
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));
214✔
182
        const dateValue = DateTimeUtil.isValidDate(this._dateValue) ? new Date(this._dateValue.setHours(0, 0, 0, 0)) : null;
214✔
183
        return this._activeDate ?? dateValue ?? this._calendar?.activeDate ?? today;
214✔
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;
128✔
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 value of `id` attribute.
248
     *
249
     * @remarks If not provided it will be automatically generated.
250
     * @example
251
     * ```html
252
     * <igx-date-picker [id]="'igx-date-picker-3'" cancelButtonLabel="cancel" todayButtonLabel="today"></igx-date-picker>
253
     * ```
254
     */
255
    @Input()
256
    @HostBinding('attr.id')
257
    public id = `igx-date-picker-${NEXT_ID++}`;
128✔
258

259
    //#region calendar members
260

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

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

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

308

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

320
    //#endregion
321

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

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

356
    public get minValue(): Date | string {
357
        return this._minValue;
922✔
358
    }
359

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

372
    public get maxValue(): Date | string {
373
        return this._maxValue;
922✔
374
    }
375

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

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

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

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

412
    /** @hidden @internal */
413
    @ContentChild(IgxLabelDirective)
414
    public label: IgxLabelDirective;
415

416
    @ContentChild(IgxCalendarHeaderTitleTemplateDirective)
417
    private headerTitleTemplate: IgxCalendarHeaderTitleTemplateDirective;
418

419
    @ContentChild(IgxCalendarHeaderTemplateDirective)
420
    private headerTemplate: IgxCalendarHeaderTemplateDirective;
421

422
    @ViewChild(IgxDateTimeEditorDirective, { static: true })
423
    private dateTimeEditor: IgxDateTimeEditorDirective;
424

425
    @ViewChild(IgxInputGroupComponent, { read: ViewContainerRef })
426
    private viewContainerRef: ViewContainerRef;
427

428
    @ViewChild(IgxLabelDirective)
429
    private labelDirective: IgxLabelDirective;
430

431
    @ViewChild(IgxInputDirective)
432
    private inputDirective: IgxInputDirective;
433

434
    @ContentChild(IgxCalendarSubheaderTemplateDirective)
435
    private subheaderTemplate: IgxCalendarSubheaderTemplateDirective;
436

437
    @ContentChild(IgxPickerActionsDirective)
438
    private pickerActions: IgxPickerActionsDirective;
439

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

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

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

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

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

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

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

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

515
    constructor() {
516
        super();
128✔
517
        this.initLocale();
128✔
518
    }
519

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

528
        return false;
6✔
529
    }
530

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

769
        this._dropDownOverlaySettings.excludeFromOutsideClick = [this.inputGroup.element.nativeElement];
123✔
770

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

914
            this._calendar.wrapper?.nativeElement?.focus();
46✔
915
        });
916

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

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

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

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

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

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

988
        componentInstance.mode = this.mode;
53✔
989
        componentInstance.closeButtonLabel = this.cancelButtonLabel;
53✔
990
        componentInstance.todayButtonLabel = this.todayButtonLabel;
53✔
991
        componentInstance.pickerActions = this.pickerActions;
53✔
992

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

997
    protected override updateResources(): void {
998
        this._resourceStrings = getCurrentResourceStrings(DatePickerResourceStringsEN, false, this._locale);
309✔
999
    }
1000
}
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