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

IgniteUI / igniteui-angular / 13331632524

14 Feb 2025 02:51PM CUT coverage: 22.015% (-69.6%) from 91.622%
13331632524

Pull #15372

github

web-flow
Merge d52d57714 into bcb78ae0a
Pull Request #15372: chore(*): test ci passing

1990 of 15592 branches covered (12.76%)

431 of 964 new or added lines in 18 files covered. (44.71%)

19956 existing lines in 307 files now uncovered.

6452 of 29307 relevant lines covered (22.02%)

249.17 hits per line

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

0.8
/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 { NgIf } from '@angular/common';
29
import {
30
    AbstractControl,
31
    ControlValueAccessor,
32
    NgControl,
33
    NG_VALIDATORS,
34
    NG_VALUE_ACCESSOR,
35
    ValidationErrors,
36
    Validator
37
} from '@angular/forms';
38
import {
39
    IgxCalendarComponent, IgxCalendarHeaderTemplateDirective, IgxCalendarHeaderTitleTemplateDirective, IgxCalendarSubheaderTemplateDirective,
40
     IFormattingViews, IFormattingOptions
41
} from '../calendar/public_api';
42
import { isDateInRanges } from '../calendar/common/helpers';
43
import {
44
    IgxLabelDirective, IGX_INPUT_GROUP_TYPE, IgxInputGroupType, IgxInputState, IgxInputGroupComponent, IgxPrefixDirective, IgxInputDirective, IgxSuffixDirective
45
} from '../input-group/public_api';
46
import { fromEvent, Subscription, noop, MonoTypeOperatorFunction } from 'rxjs';
47
import { filter, takeUntil } from 'rxjs/operators';
48

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

73
let NEXT_ID = 0;
2✔
74

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

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

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

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

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

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

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

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

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

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

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

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

252
    //#region calendar members
253

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

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

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

301

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

313
    //#endregion
314

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

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

349
    public get minValue(): Date | string {
UNCOV
350
        return this._minValue;
×
351
    }
352

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

365
    public get maxValue(): Date | string {
UNCOV
366
        return this._maxValue;
×
367
    }
368

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

376
    /** @hidden @internal */
377
    @Input({ transform: booleanAttribute })
UNCOV
378
    public readOnly = false;
×
379

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

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

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

409
    /** @hidden @internal */
410
    @ContentChild(IgxLabelDirective)
411
    public label: IgxLabelDirective;
412

413
    @ContentChild(IgxCalendarHeaderTitleTemplateDirective)
414
    private headerTitleTemplate: IgxCalendarHeaderTitleTemplateDirective;
415

416
    @ContentChild(IgxCalendarHeaderTemplateDirective)
417
    private headerTemplate: IgxCalendarHeaderTemplateDirective;
418

419
    @ViewChild(IgxDateTimeEditorDirective, { static: true })
420
    private dateTimeEditor: IgxDateTimeEditorDirective;
421

422
    @ViewChild(IgxInputGroupComponent, { read: ViewContainerRef })
423
    private viewContainerRef: ViewContainerRef;
424

425
    @ViewChild(IgxLabelDirective)
426
    private labelDirective: IgxLabelDirective;
427

428
    @ViewChild(IgxInputDirective)
429
    private inputDirective: IgxInputDirective;
430

431
    @ContentChild(IgxCalendarSubheaderTemplateDirective)
432
    private subheaderTemplate: IgxCalendarSubheaderTemplateDirective;
433

434
    @ContentChild(IgxPickerActionsDirective)
435
    private pickerActions: IgxPickerActionsDirective;
436

437
    private get dialogOverlaySettings(): OverlaySettings {
UNCOV
438
        return Object.assign({}, this._dialogOverlaySettings, this.overlaySettings);
×
439
    }
440

441
    private get dropDownOverlaySettings(): OverlaySettings {
UNCOV
442
        return Object.assign({}, this._dropDownOverlaySettings, this.overlaySettings);
×
443
    }
444

445
    private get inputGroupElement(): HTMLElement {
UNCOV
446
        return this.inputGroup?.element.nativeElement;
×
447
    }
448

449
    private get dateValue(): Date {
UNCOV
450
        return this._dateValue;
×
451
    }
452

453
    private get pickerFormatViews(): IFormattingViews {
UNCOV
454
        return Object.assign({}, this._defFormatViews, this.formatViews);
×
455
    }
456

457
    private get pickerCalendarFormat(): IFormattingOptions {
UNCOV
458
        return Object.assign({}, this._calendarFormat, this.calendarFormat);
×
459
    }
460

461
    /** @hidden @internal */
UNCOV
462
    public displayValue: PipeTransform = { transform: (date: Date) => this.formatter(date) };
×
463

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

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

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

UNCOV
532
        return false;
×
533
    }
534

535
    /** @hidden @internal */
536
    public get pickerResourceStrings(): IDatePickerResourceStrings {
UNCOV
537
        return Object.assign({}, this._resourceStrings, this.resourceStrings);
×
538
    }
539

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

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

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

UNCOV
580
        const overlaySettings = Object.assign({}, this.isDropdown
×
581
            ? this.dropDownOverlaySettings
582
            : this.dialogOverlaySettings
583
            , settings);
584

UNCOV
585
        if (this.isDropdown && this.inputGroupElement) {
×
UNCOV
586
            overlaySettings.target = this.inputGroupElement;
×
587
        }
UNCOV
588
        if (this.outlet) {
×
UNCOV
589
            overlaySettings.outlet = this.outlet;
×
590
        }
UNCOV
591
        this._overlayId = this._overlayService
×
592
            .attach(IgxCalendarContainerComponent, this.viewContainerRef, overlaySettings);
UNCOV
593
        this._overlayService.show(this._overlayId);
×
594
    }
595

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

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

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

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

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

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

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

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

718
    /** @hidden @internal */
719
    public registerOnChange(fn: any) {
UNCOV
720
        this._onChangeCallback = fn;
×
721
    }
722

723
    /** @hidden @internal */
724
    public registerOnTouched(fn: any) {
UNCOV
725
        this._onTouchedCallback = fn;
×
726
    }
727

728
    /** @hidden @internal */
729
    public setDisabledState?(isDisabled: boolean): void {
UNCOV
730
        this.disabled = isDisabled;
×
731
    }
732
    //#endregion
733

734
    //#region Validator
735
    /** @hidden @internal */
736
    public registerOnValidatorChange(fn: any) {
UNCOV
737
        this._onValidatorChange = fn;
×
738
    }
739

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

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

UNCOV
757
        return Object.keys(errors).length > 0 ? errors : null;
×
758
    }
759
    //#endregion
760

761
    /** @hidden @internal */
762
    public ngOnInit(): void {
UNCOV
763
        this._ngControl = this._injector.get<NgControl>(NgControl, null);
×
764

UNCOV
765
        this.locale = this.locale || this._localeId;
×
766
    }
767

768
    /** @hidden @internal */
769
    public override ngAfterViewInit() {
UNCOV
770
        super.ngAfterViewInit();
×
UNCOV
771
        this.subscribeToClick();
×
UNCOV
772
        this.subscribeToOverlayEvents();
×
UNCOV
773
        this.subscribeToDateEditorEvents();
×
774

UNCOV
775
        this.subToIconsClicked(this.clearComponents, () => this.clear());
×
UNCOV
776
        this.clearComponents.changes.pipe(takeUntil(this._destroy$))
×
UNCOV
777
            .subscribe(() => this.subToIconsClicked(this.clearComponents, () => this.clear()));
×
778

UNCOV
779
        this._dropDownOverlaySettings.excludeFromOutsideClick = [this.inputGroup.element.nativeElement];
×
780

UNCOV
781
        fromEvent(this.inputDirective.nativeElement, 'blur')
×
782
            .pipe(takeUntil(this._destroy$))
783
            .subscribe(() => {
UNCOV
784
                if (this.collapsed) {
×
UNCOV
785
                    this._onTouchedCallback();
×
UNCOV
786
                    this.updateValidity();
×
787
                }
788
            });
789

UNCOV
790
        if (this._ngControl) {
×
UNCOV
791
            this._statusChanges$ =
×
792
                this._ngControl.statusChanges.subscribe(this.onStatusChanged.bind(this));
UNCOV
793
            if (this._ngControl.control.validator) {
×
UNCOV
794
                this.inputGroup.isRequired = this.required;
×
UNCOV
795
                this.cdr.detectChanges();
×
796
            }
797
        }
798
    }
799

800
    /** @hidden @internal */
801
    public ngAfterViewChecked() {
UNCOV
802
        if (this.labelDirective) {
×
UNCOV
803
            this._renderer.setAttribute(this.inputDirective.nativeElement, 'aria-labelledby', this.labelDirective.id);
×
804
        }
805
    }
806

807
    /** @hidden @internal */
808
    public override ngOnDestroy(): void {
UNCOV
809
        super.ngOnDestroy();
×
UNCOV
810
        if (this._statusChanges$) {
×
UNCOV
811
            this._statusChanges$.unsubscribe();
×
812
        }
UNCOV
813
        if (this._overlayId) {
×
UNCOV
814
            this._overlayService.detach(this._overlayId);
×
UNCOV
815
            delete this._overlayId;
×
816
        }
817
    }
818

819
    /** @hidden @internal */
820
    public getEditElement(): HTMLInputElement {
UNCOV
821
        return this.inputDirective.nativeElement;
×
822
    }
823

824
    private subscribeToClick() {
UNCOV
825
        fromEvent(this.getEditElement(), 'click')
×
826
            .pipe(takeUntil(this._destroy$))
827
            .subscribe(() => {
UNCOV
828
                if (!this.isDropdown) {
×
UNCOV
829
                    this.toggle();
×
830
                }
831
            });
832
    }
833

834
    private setDateValue(value: Date | string) {
UNCOV
835
        if (isDate(value) && isNaN(value.getTime())) {
×
UNCOV
836
            this._dateValue = value;
×
UNCOV
837
            return;
×
838
        }
UNCOV
839
        this._dateValue = DateTimeUtil.isValidDate(value) ? value : DateTimeUtil.parseIsoDate(value);
×
840
    }
841

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

855
    private get isTouchedOrDirty(): boolean {
UNCOV
856
        return (this._ngControl.control.touched || this._ngControl.control.dirty);
×
857
    }
858

859
    private get hasValidators(): boolean {
UNCOV
860
        return (!!this._ngControl.control.validator || !!this._ngControl.control.asyncValidator);
×
861
    }
862

UNCOV
863
    private onStatusChanged = () => {
×
UNCOV
864
        this.disabled = this._ngControl.disabled;
×
UNCOV
865
        this.updateValidity();
×
UNCOV
866
        this.inputGroup.isRequired = this.required;
×
867
    };
868

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

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

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

UNCOV
906
            this._initializeCalendarContainer(e.componentRef.instance);
×
UNCOV
907
            this._calendarContainer = e.componentRef.location.nativeElement;
×
UNCOV
908
            this._collapsed = false;
×
909
        });
910

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

UNCOV
914
            this._calendar.wrapper?.nativeElement?.focus();
×
915

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

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

UNCOV
941
        this._overlayService.closed.pipe(...this._overlaySubFilter).subscribe(() => {
×
UNCOV
942
            this.closed.emit({ owner: this });
×
UNCOV
943
            this._overlayService.detach(this._overlayId);
×
UNCOV
944
            this._collapsed = true;
×
UNCOV
945
            this._overlayId = null;
×
UNCOV
946
            this._calendar = null;
×
UNCOV
947
            this._calendarContainer = undefined;
×
948
        });
949
    }
950

951
    private getMinMaxDates() {
UNCOV
952
        const minValue = DateTimeUtil.isValidDate(this.minValue) ? this.minValue : DateTimeUtil.parseIsoDate(this.minValue);
×
UNCOV
953
        const maxValue = DateTimeUtil.isValidDate(this.maxValue) ? this.maxValue : DateTimeUtil.parseIsoDate(this.maxValue);
×
UNCOV
954
        return { minValue, maxValue };
×
955
    }
956

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

968

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

UNCOV
987
        if (DateTimeUtil.isValidDate(this.dateValue)) {
×
988
            // calendar will throw if the picker's value is InvalidDate #9208
UNCOV
989
            this._calendar.value = this.dateValue;
×
990
        }
UNCOV
991
        this.setCalendarViewDate();
×
992

UNCOV
993
        componentInstance.mode = this.mode;
×
994
        // componentInstance.headerOrientation = this.headerOrientation;
UNCOV
995
        componentInstance.closeButtonLabel = this.cancelButtonLabel;
×
UNCOV
996
        componentInstance.todayButtonLabel = this.todayButtonLabel;
×
UNCOV
997
        componentInstance.pickerActions = this.pickerActions;
×
998

UNCOV
999
        componentInstance.calendarClose.pipe(takeUntil(this._destroy$)).subscribe(() => this.close());
×
UNCOV
1000
        componentInstance.todaySelection.pipe(takeUntil(this._destroy$)).subscribe(() => this.selectToday());
×
1001
    }
1002

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

© 2025 Coveralls, Inc