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

IgniteUI / igniteui-angular / 11833985697

14 Nov 2024 09:05AM CUT coverage: 91.603% (+0.03%) from 91.577%
11833985697

Pull #15060

github

web-flow
Merge 74eb1a28b into ce5e8e71f
Pull Request #15060: fix(grid): fix column resize line positioning and column resizing for scaled grid - master

12961 of 15187 branches covered (85.34%)

10 of 10 new or added lines in 2 files covered. (100.0%)

7 existing lines in 2 files now uncovered.

26280 of 28689 relevant lines covered (91.6%)

33913.29 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 { 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
    standalone: true,
96
    imports: [
97
        NgIf,
98
        IgxInputGroupComponent,
99
        IgxPrefixDirective,
100
        IgxIconComponent,
101
        IgxInputDirective,
102
        IgxDateTimeEditorDirective,
103
        IgxTextSelectionDirective,
104
        IgxSuffixDirective
105
    ]
106
})
107
export class IgxDatePickerComponent extends PickerBaseDirective implements ControlValueAccessor, Validator,
2✔
108
    OnInit, AfterViewInit, OnDestroy, AfterViewChecked, AfterContentChecked {
109

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

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

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

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

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

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

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

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

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

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

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

253
    //#region calendar members
254

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

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

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

302

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

314
    //#endregion
315

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

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

350
    public get minValue(): Date | string {
351
        return this._minValue;
760✔
352
    }
353

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

366
    public get maxValue(): Date | string {
367
        return this._maxValue;
760✔
368
    }
369

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

377
    /** @hidden @internal */
378
    @Input({ transform: booleanAttribute })
379
    public readOnly = false;
105✔
380

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

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

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

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

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

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

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

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

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

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

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

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

438
    private get dialogOverlaySettings(): OverlaySettings {
439
        return Object.assign({}, this._dialogOverlaySettings, this.overlaySettings);
5✔
440
    }
441

442
    private get dropDownOverlaySettings(): OverlaySettings {
443
        return Object.assign({}, this._dropDownOverlaySettings, this.overlaySettings);
37✔
444
    }
445

446
    private get inputGroupElement(): HTMLElement {
447
        return this.inputGroup?.element.nativeElement;
179✔
448
    }
449

450
    private get dateValue(): Date {
451
        return this._dateValue;
386✔
452
    }
453

454
    private get pickerFormatViews(): IFormattingViews {
455
        return Object.assign({}, this._defFormatViews, this.formatViews);
47✔
456
    }
457

458
    private get pickerCalendarFormat(): IFormattingOptions {
459
        return Object.assign({}, this._calendarFormat, this.calendarFormat);
44✔
460
    }
461

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

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

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

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

533
        return false;
6✔
534
    }
535

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

541
    protected override get toggleContainer(): HTMLElement | undefined {
542
        return this._calendarContainer;
3✔
543
    }
544

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

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

581
        const overlaySettings = Object.assign({}, this.isDropdown
42✔
582
            ? this.dropDownOverlaySettings
583
            : this.dialogOverlaySettings
584
            , settings);
585

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

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

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

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

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

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

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

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

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

719
    /** @hidden @internal */
720
    public registerOnChange(fn: any) {
721
        this._onChangeCallback = fn;
67✔
722
    }
723

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

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

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

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

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

758
        return Object.keys(errors).length > 0 ? errors : null;
55!
759
    }
760
    //#endregion
761

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

766
        this.locale = this.locale || this._localeId;
90!
767
    }
768

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

776
        this.subToIconsClicked(this.clearComponents, () => this.clear());
100✔
777
        this.clearComponents.changes.pipe(takeUntil(this._destroy$))
100✔
778
            .subscribe(() => this.subToIconsClicked(this.clearComponents, () => this.clear()));
4✔
779

780
        this._dropDownOverlaySettings.excludeFromOutsideClick = [this.inputGroup.element.nativeElement];
100✔
781

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

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

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

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

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

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

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

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

856
    private get isTouchedOrDirty(): boolean {
857
        return (this._ngControl.control.touched || this._ngControl.control.dirty);
44✔
858
    }
859

860
    private get hasValidators(): boolean {
861
        return (!!this._ngControl.control.validator || !!this._ngControl.control.asyncValidator);
32✔
862
    }
863

864
    private onStatusChanged = () => {
105✔
865
        this.disabled = this._ngControl.disabled;
40✔
866
        this.updateValidity();
40✔
867
        this.inputGroup.isRequired = this.required;
40✔
868
    };
869

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

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

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

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

912
        this._overlayService.opened.pipe(...this._overlaySubFilter).subscribe(() => {
100✔
913
            this.opened.emit({ owner: this });
34✔
914

915
            this._calendar.wrapper?.nativeElement?.focus();
34✔
916

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

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

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

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

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

969

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

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

994
        componentInstance.mode = this.mode;
41✔
995
        // componentInstance.headerOrientation = this.headerOrientation;
996
        componentInstance.closeButtonLabel = this.cancelButtonLabel;
41✔
997
        componentInstance.todayButtonLabel = this.todayButtonLabel;
41✔
998
        componentInstance.pickerActions = this.pickerActions;
41✔
999

1000
        componentInstance.calendarClose.pipe(takeUntil(this._destroy$)).subscribe(() => this.close());
41✔
1001
        componentInstance.todaySelection.pipe(takeUntil(this._destroy$)).subscribe(() => this.selectToday());
41✔
1002
    }
1003

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