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

IgniteUI / igniteui-angular / 13561607909

27 Feb 2025 08:03AM UTC coverage: 91.644% (+0.003%) from 91.641%
13561607909

push

github

web-flow
fix(grid): Update grid cell active state selector specificity (#15402)

13328 of 15596 branches covered (85.46%)

26882 of 29333 relevant lines covered (91.64%)

33708.8 hits per line

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

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

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

72
let NEXT_ID = 0;
2✔
73

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

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

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

143
    /**
144
     * Show/hide week numbers
145
     *
146
     * @example
147
     * ```html
148
     * <igx-date-picker [showWeekNumbers]="true"></igx-date-picker>
149
     * ``
150
     */
151
    @Input({ transform: booleanAttribute })
152
    public showWeekNumbers: boolean;
153

154
    /**
155
     * Gets/Sets a custom formatter function on the selected or passed date.
156
     *
157
     * @example
158
     * ```html
159
     * <igx-date-picker [value]="date" [formatter]="formatter"></igx-date-picker>
160
     * ```
161
     */
162
    @Input()
163
    public formatter: (val: Date) => string;
164

165
    /**
166
     * Gets/Sets the orientation of the `IgxDatePickerComponent` header.
167
     *
168
     *  @example
169
     * ```html
170
     * <igx-date-picker headerOrientation="vertical"></igx-date-picker>
171
     * ```
172
     */
173
    @Input()
174
    public headerOrientation: PickerHeaderOrientation = PickerHeaderOrientation.Horizontal;
115✔
175

176
    /**
177
     * Gets/Sets the today button's label.
178
     *
179
     *  @example
180
     * ```html
181
     * <igx-date-picker todayButtonLabel="Today"></igx-date-picker>
182
     * ```
183
     */
184
    @Input()
185
    public todayButtonLabel: string;
186

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

198
    /**
199
     * Specify if the currently spun date segment should loop over.
200
     *
201
     *  @example
202
     * ```html
203
     * <igx-date-picker [spinLoop]="false"></igx-date-picker>
204
     * ```
205
     */
206
    @Input({ transform: booleanAttribute })
207
    public spinLoop = true;
115✔
208

209
    /**
210
     * Delta values used to increment or decrement each editor date part on spin actions.
211
     * All values default to `1`.
212
     *
213
     * @example
214
     * ```html
215
     * <igx-date-picker [spinDelta]="{ date: 5, month: 2 }"></igx-date-picker>
216
     * ```
217
     */
218
    @Input()
219
    public spinDelta: Pick<DatePartDeltas, 'date' | 'month' | 'year'>;
220

221
    /**
222
     * Gets/Sets the container used for the popup element.
223
     *
224
     * @remarks
225
     *  `outlet` is an instance of `IgxOverlayOutletDirective` or an `ElementRef`.
226
     * @example
227
     * ```html
228
     * <div igxOverlayOutlet #outlet="overlay-outlet"></div>
229
     * //..
230
     * <igx-date-picker [outlet]="outlet"></igx-date-picker>
231
     * //..
232
     * ```
233
     */
234
    @Input()
235
    public override outlet: IgxOverlayOutletDirective | ElementRef;
236

237
    /**
238
     * Gets/Sets the value of `id` attribute.
239
     *
240
     * @remarks If not provided it will be automatically generated.
241
     * @example
242
     * ```html
243
     * <igx-date-picker [id]="'igx-date-picker-3'" cancelButtonLabel="cancel" todayButtonLabel="today"></igx-date-picker>
244
     * ```
245
     */
246
    @Input()
247
    @HostBinding('attr.id')
248
    public id = `igx-date-picker-${NEXT_ID++}`;
115✔
249

250
    //#region calendar members
251

252
    /**
253
     * Gets/Sets the format views of the `IgxDatePickerComponent`.
254
     *
255
     * @example
256
     * ```typescript
257
     * let formatViews = this.datePicker.formatViews;
258
     *  this.datePicker.formatViews = {day:false, month: false, year:false};
259
     * ```
260
     */
261
    @Input()
262
    public formatViews: IFormattingViews;
263

264
    /**
265
     * Gets/Sets the disabled dates descriptors.
266
     *
267
     * @example
268
     * ```typescript
269
     * let disabledDates = this.datepicker.disabledDates;
270
     * this.datePicker.disabledDates = [ {type: DateRangeType.Weekends}, ...];
271
     * ```
272
     */
273
    @Input()
274
    public get disabledDates(): DateRangeDescriptor[] {
275
        return this._disabledDates;
102✔
276
    }
277
    public set disabledDates(value: DateRangeDescriptor[]) {
278
        this._disabledDates = value;
3✔
279
        this._onValidatorChange();
3✔
280
    }
281

282
    /**
283
     * Gets/Sets the special dates descriptors.
284
     *
285
     * @example
286
     * ```typescript
287
     * let specialDates = this.datepicker.specialDates;
288
     * this.datePicker.specialDates = [ {type: DateRangeType.Weekends}, ... ];
289
     * ```
290
     */
291
    @Input()
292
    public get specialDates(): DateRangeDescriptor[] {
293
        return this._specialDates;
44✔
294
    }
295
    public set specialDates(value: DateRangeDescriptor[]) {
296
        this._specialDates = value;
1✔
297
    }
298

299

300
    /**
301
     * Gets/Sets the format options of the `IgxDatePickerComponent`.
302
     *
303
     * @example
304
     * ```typescript
305
     * this.datePicker.calendarFormat = {day: "numeric",  month: "long", weekday: "long", year: "numeric"};
306
     * ```
307
     */
308
    @Input()
309
    public calendarFormat: IFormattingOptions;
310

311
    //#endregion
312

313
    /**
314
     * Gets/Sets the selected date.
315
     *
316
     *  @example
317
     * ```html
318
     * <igx-date-picker [value]="date"></igx-date-picker>
319
     * ```
320
     */
321
    @Input()
322
    public get value(): Date | string {
323
        return this._value;
652✔
324
    }
325
    public set value(date: Date | string) {
326
        this._value = date;
160✔
327
        this.setDateValue(date);
160✔
328
        if (this.dateTimeEditor.value !== date) {
160✔
329
            this.dateTimeEditor.value = this._dateValue;
143✔
330
        }
331
        this.valueChange.emit(this.dateValue);
160✔
332
        this._onChangeCallback(this.dateValue);
160✔
333
    }
334

335
    /**
336
     * The minimum value the picker will accept.
337
     *
338
     * @example
339
     * <igx-date-picker [minValue]="minDate"></igx-date-picker>
340
     */
341
    @Input()
342
    public set minValue(value: Date | string) {
343
        this._minValue = value;
24✔
344
        this._onValidatorChange();
24✔
345
    }
346

347
    public get minValue(): Date | string {
348
        return this._minValue;
813✔
349
    }
350

351
    /**
352
     * The maximum value the picker will accept.
353
     *
354
     * @example
355
     * <igx-date-picker [maxValue]="maxDate"></igx-date-picker>
356
     */
357
    @Input()
358
    public set maxValue(value: Date | string) {
359
        this._maxValue = value;
26✔
360
        this._onValidatorChange();
26✔
361
    }
362

363
    public get maxValue(): Date | string {
364
        return this._maxValue;
813✔
365
    }
366

367
    /**
368
     * Gets/Sets the resource strings for the picker's default toggle icon.
369
     * By default it uses EN resources.
370
     */
371
    @Input()
372
    public resourceStrings: IDatePickerResourceStrings;
373

374
    /** @hidden @internal */
375
    @Input({ transform: booleanAttribute })
376
    public readOnly = false;
115✔
377

378
    /**
379
     * Emitted when the picker's value changes.
380
     *
381
     * @remarks
382
     * Used for `two-way` bindings.
383
     *
384
     * @example
385
     * ```html
386
     * <igx-date-picker [(value)]="date"></igx-date-picker>
387
     * ```
388
     */
389
    @Output()
390
    public valueChange = new EventEmitter<Date>();
115✔
391

392
    /**
393
     * Emitted when the user types/spins invalid date in the date-picker editor.
394
     *
395
     *  @example
396
     * ```html
397
     * <igx-date-picker (validationFailed)="onValidationFailed($event)"></igx-date-picker>
398
     * ```
399
     */
400
    @Output()
401
    public validationFailed = new EventEmitter<IDatePickerValidationFailedEventArgs>();
115✔
402

403
    /** @hidden @internal */
404
    @ContentChildren(IgxPickerClearComponent)
405
    public clearComponents: QueryList<IgxPickerClearComponent>;
406

407
    /** @hidden @internal */
408
    @ContentChild(IgxLabelDirective)
409
    public label: IgxLabelDirective;
410

411
    @ContentChild(IgxCalendarHeaderTitleTemplateDirective)
412
    private headerTitleTemplate: IgxCalendarHeaderTitleTemplateDirective;
413

414
    @ContentChild(IgxCalendarHeaderTemplateDirective)
415
    private headerTemplate: IgxCalendarHeaderTemplateDirective;
416

417
    @ViewChild(IgxDateTimeEditorDirective, { static: true })
418
    private dateTimeEditor: IgxDateTimeEditorDirective;
419

420
    @ViewChild(IgxInputGroupComponent, { read: ViewContainerRef })
421
    private viewContainerRef: ViewContainerRef;
422

423
    @ViewChild(IgxLabelDirective)
424
    private labelDirective: IgxLabelDirective;
425

426
    @ViewChild(IgxInputDirective)
427
    private inputDirective: IgxInputDirective;
428

429
    @ContentChild(IgxCalendarSubheaderTemplateDirective)
430
    private subheaderTemplate: IgxCalendarSubheaderTemplateDirective;
431

432
    @ContentChild(IgxPickerActionsDirective)
433
    private pickerActions: IgxPickerActionsDirective;
434

435
    private get dialogOverlaySettings(): OverlaySettings {
436
        return Object.assign({}, this._dialogOverlaySettings, this.overlaySettings);
5✔
437
    }
438

439
    private get dropDownOverlaySettings(): OverlaySettings {
440
        return Object.assign({}, this._dropDownOverlaySettings, this.overlaySettings);
37✔
441
    }
442

443
    private get inputGroupElement(): HTMLElement {
444
        return this.inputGroup?.element.nativeElement;
189✔
445
    }
446

447
    private get dateValue(): Date {
448
        return this._dateValue;
444✔
449
    }
450

451
    private get pickerFormatViews(): IFormattingViews {
452
        return Object.assign({}, this._defFormatViews, this.formatViews);
47✔
453
    }
454

455
    private get pickerCalendarFormat(): IFormattingOptions {
456
        return Object.assign({}, this._calendarFormat, this.calendarFormat);
44✔
457
    }
458

459
    /** @hidden @internal */
460
    public displayValue: PipeTransform = { transform: (date: Date) => this.formatter(date) };
115✔
461

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

510
    constructor(element: ElementRef<HTMLElement>,
511
        @Inject(LOCALE_ID) _localeId: string,
512
        @Inject(IgxOverlayService) private _overlayService: IgxOverlayService,
115✔
513
        private _injector: Injector,
115✔
514
        private _renderer: Renderer2,
115✔
515
        private platform: PlatformUtil,
115✔
516
        private cdr: ChangeDetectorRef,
115✔
517
        @Optional() @Inject(IGX_INPUT_GROUP_TYPE) _inputGroupType?: IgxInputGroupType) {
518
        super(element, _localeId, _inputGroupType);
115✔
519
        this.locale = this.locale || this._localeId;
115!
520
    }
521

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

530
        return false;
6✔
531
    }
532

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

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

542
    /** @hidden @internal */
543
    @HostListener('keydown', ['$event'])
544
    public onKeyDown(event: KeyboardEvent) {
545
        switch (event.key) {
2!
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) {
45✔
575
            return;
3✔
576
        }
577

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

583
        if (this.isDropdown && this.inputGroupElement) {
42✔
584
            overlaySettings.target = this.inputGroupElement;
37✔
585
        }
586
        if (this.outlet) {
42✔
587
            overlaySettings.outlet = this.outlet;
14✔
588
        }
589
        this._overlayId = this._overlayService
42✔
590
            .attach(IgxCalendarContainerComponent, this.viewContainerRef, overlaySettings);
591
        this._overlayService.show(this._overlayId);
42✔
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) {
8✔
606
            this.open(settings);
7✔
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) {
22✔
624
            this._overlayService.hide(this._overlayId);
21✔
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) {
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;
56✔
710
        this.setDateValue(value);
56✔
711
        if (this.dateTimeEditor.value !== value) {
56✔
712
            this.dateTimeEditor.value = this._dateValue;
24✔
713
        }
714
    }
715

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

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

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

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

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

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

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

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

763
        this.locale = this.locale || this._localeId;
100!
764
    }
765

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

773
        this.subToIconsClicked(this.clearComponents, () => this.clear());
110✔
774
        this.clearComponents.changes.pipe(takeUntil(this._destroy$))
110✔
775
            .subscribe(() => this.subToIconsClicked(this.clearComponents, () => this.clear()));
4✔
776

777
        this._dropDownOverlaySettings.excludeFromOutsideClick = [this.inputGroup.element.nativeElement];
110✔
778

779
        fromEvent(this.inputDirective.nativeElement, 'blur')
110✔
780
            .pipe(takeUntil(this._destroy$))
781
            .subscribe(() => {
782
                if (this.collapsed) {
37✔
783
                    this._onTouchedCallback();
18✔
784
                    this.updateValidity();
18✔
785
                }
786
            });
787

788
        if (this._ngControl) {
110✔
789
            this._statusChanges$ =
40✔
790
                this._ngControl.statusChanges.subscribe(this.onStatusChanged.bind(this));
791
            if (this._ngControl.control.validator) {
40✔
792
                this.inputGroup.isRequired = this.required;
39✔
793
                this.cdr.detectChanges();
39✔
794
            }
795
        }
796
    }
797

798
    /** @hidden @internal */
799
    public ngAfterViewChecked() {
800
        if (this.labelDirective) {
348✔
801
            this._renderer.setAttribute(this.inputDirective.nativeElement, 'aria-labelledby', this.labelDirective.id);
1✔
802
        }
803
    }
804

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

817
    /** @hidden @internal */
818
    public getEditElement(): HTMLInputElement {
819
        return this.inputDirective.nativeElement;
206✔
820
    }
821

822
    private subscribeToClick() {
823
        fromEvent(this.getEditElement(), 'click')
110✔
824
            .pipe(takeUntil(this._destroy$))
825
            .subscribe(() => {
826
                if (!this.isDropdown) {
13✔
827
                    this.toggle();
1✔
828
                }
829
            });
830
    }
831

832
    private setDateValue(value: Date | string) {
833
        if (isDate(value) && isNaN(value.getTime())) {
216✔
834
            this._dateValue = value;
1✔
835
            return;
1✔
836
        }
837
        this._dateValue = DateTimeUtil.isValidDate(value) ? value : DateTimeUtil.parseIsoDate(value);
215✔
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) {
59✔
843
            if (this.hasValidators && this.inputGroup.isFocused) {
32✔
844
                this.inputDirective.valid = this._ngControl.valid ? IgxInputState.VALID : IgxInputState.INVALID;
11✔
845
            } else {
846
                this.inputDirective.valid = this._ngControl.valid ? IgxInputState.INITIAL : IgxInputState.INVALID;
21✔
847
            }
848
        } else {
849
            this.inputDirective.valid = IgxInputState.INITIAL;
27✔
850
        }
851
    }
852

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

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

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

867
    private handleSelection(date: Date): void {
868
        if (this.dateValue && DateTimeUtil.isValidDate(this.dateValue)) {
12✔
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;
12✔
875
        this._calendar.viewDate = date;
12✔
876
        this.close();
12✔
877
    }
878

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

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

904
            this._initializeCalendarContainer(e.componentRef.instance);
41✔
905
            this._calendarContainer = e.componentRef.location.nativeElement;
41✔
906
            this._collapsed = false;
41✔
907
        });
908

909
        this._overlayService.opened.pipe(...this._overlaySubFilter).subscribe(() => {
110✔
910
            this.opened.emit({ owner: this });
34✔
911

912
            this._calendar.wrapper?.nativeElement?.focus();
34✔
913

914
            if (this._targetViewDate) {
34✔
915
                this._targetViewDate.setHours(0, 0, 0, 0);
34✔
916
                // INFO: We need to set the active date to the target view date so there's something to
917
                // navigate when the calendar is opened.
918
                this._calendar.activeDate = this._targetViewDate;
34✔
919
            }
920
        });
921

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

939
        this._overlayService.closed.pipe(...this._overlaySubFilter).subscribe(() => {
110✔
940
            this.closed.emit({ owner: this });
23✔
941
            this._overlayService.detach(this._overlayId);
23✔
942
            this._collapsed = true;
23✔
943
            this._overlayId = null;
23✔
944
            this._calendar = null;
23✔
945
            this._calendarContainer = undefined;
23✔
946
        });
947
    }
948

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

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

966

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

985
        if (DateTimeUtil.isValidDate(this.dateValue)) {
41✔
986
            // calendar will throw if the picker's value is InvalidDate #9208
987
            this._calendar.value = this.dateValue;
12✔
988
        }
989
        this.setCalendarViewDate();
41✔
990

991
        componentInstance.mode = this.mode;
41✔
992
        // componentInstance.headerOrientation = this.headerOrientation;
993
        componentInstance.closeButtonLabel = this.cancelButtonLabel;
41✔
994
        componentInstance.todayButtonLabel = this.todayButtonLabel;
41✔
995
        componentInstance.pickerActions = this.pickerActions;
41✔
996

997
        componentInstance.calendarClose.pipe(takeUntil(this._destroy$)).subscribe(() => this.close());
41✔
998
        componentInstance.todaySelection.pipe(takeUntil(this._destroy$)).subscribe(() => this.selectToday());
41✔
999
    }
1000

1001
    private setCalendarViewDate() {
1002
        const { minValue, maxValue } = this.getMinMaxDates();
41✔
1003
        const dateValue = DateTimeUtil.isValidDate(this.dateValue) ? this.dateValue : new Date();
41✔
1004
        if (minValue && DateTimeUtil.lessThanMinValue(dateValue, minValue)) {
41!
1005
            this._calendar.viewDate = this._targetViewDate = minValue;
×
1006
            return;
×
1007
        }
1008
        if (maxValue && DateTimeUtil.greaterThanMaxValue(dateValue, maxValue)) {
41✔
1009
            this._calendar.viewDate = this._targetViewDate = maxValue;
4✔
1010
            return;
4✔
1011
        }
1012
        this._calendar.viewDate = this._targetViewDate = dateValue;
37✔
1013
    }
1014
}
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