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

IgniteUI / igniteui-angular / 17295360428

28 Aug 2025 12:09PM UTC coverage: 91.63% (+0.05%) from 91.577%
17295360428

push

github

web-flow
Clear icon enhancements for IgxDateRangePicker (#16115)

13673 of 16003 branches covered (85.44%)

23 of 24 new or added lines in 2 files covered. (95.83%)

68 existing lines in 7 files now uncovered.

27543 of 30059 relevant lines covered (91.63%)

34383.6 hits per line

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

95.1
/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.ts
1
import {
2
    AfterViewInit, booleanAttribute, ChangeDetectorRef, Component, ContentChild, ContentChildren, ElementRef,
3
    EventEmitter, HostBinding, HostListener, Inject, Injector, Input, LOCALE_ID,
4
    OnChanges, OnDestroy, OnInit, Optional, Output, QueryList,
5
    SimpleChanges, TemplateRef, ViewChild, ViewContainerRef
6
} from '@angular/core';
7
import { NgTemplateOutlet, getLocaleFirstDayOfWeek } from '@angular/common';
8
import {
9
    AbstractControl, ControlValueAccessor, NgControl,
10
    NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator
11
} from '@angular/forms';
12

13
import { fromEvent, merge, MonoTypeOperatorFunction, noop, Subscription } from 'rxjs';
14
import { filter, takeUntil } from 'rxjs/operators';
15

16
import { CalendarSelection, IgxCalendarComponent, IgxCalendarHeaderTemplateDirective, IgxCalendarHeaderTitleTemplateDirective, IgxCalendarSubheaderTemplateDirective } from '../calendar/public_api';
17
import { DateRangeDescriptor, DateRangeType } from '../core/dates';
18
import { DateRangePickerResourceStringsEN, IDateRangePickerResourceStrings } from '../core/i18n/date-range-picker-resources';
19
import { clamp, IBaseCancelableBrowserEventArgs, isDate, parseDate, PlatformUtil } from '../core/utils';
20
import { IgxCalendarContainerComponent } from '../date-common/calendar-container/calendar-container.component';
21
import { PickerBaseDirective } from '../date-common/picker-base.directive';
22
import { IgxPickerActionsDirective } from '../date-common/picker-icons.common';
23
import { DateTimeUtil } from '../date-common/util/date-time.util';
24
import { IgxOverlayOutletDirective } from '../directives/toggle/toggle.directive';
25
import {
26
    IgxInputDirective, IgxInputGroupComponent, IgxInputGroupType, IgxInputState,
27
    IgxLabelDirective, IGX_INPUT_GROUP_TYPE, IgxSuffixDirective
28
} from '../input-group/public_api';
29
import {
30
    AutoPositionStrategy, IgxOverlayService, OverlayCancelableEventArgs, OverlayEventArgs,
31
    OverlaySettings, PositionSettings
32
} from '../services/public_api';
33
import { DateRange, IgxDateRangeEndComponent, IgxDateRangeInputsBaseComponent, IgxDateRangeSeparatorDirective, IgxDateRangeStartComponent, DateRangePickerFormatPipe, CustomDateRange } from './date-range-picker-inputs.common';
34
import { IgxPrefixDirective } from '../directives/prefix/prefix.directive';
35
import { IgxIconComponent } from '../icon/icon.component';
36
import { getCurrentResourceStrings } from '../core/i18n/resources';
37
import { fadeIn, fadeOut } from 'igniteui-angular/animations';
38
import { PickerCalendarOrientation } from '../date-common/types';
39
import { calendarRange, isDateInRanges } from '../calendar/common/helpers';
40

41
const SingleInputDatesConcatenationString = ' - ';
3✔
42

43
/**
44
 * Provides the ability to select a range of dates from a calendar UI or editable inputs.
45
 *
46
 * @igxModule IgxDateRangeModule
47
 *
48
 * @igxTheme igx-input-group-theme, igx-calendar-theme, igx-date-range-picker-theme
49
 *
50
 * @igxKeywords date, range, date range, date picker
51
 *
52
 * @igxGroup scheduling
53
 *
54
 * @remarks
55
 * It displays the range selection in a single or two input fields.
56
 * The default template displays a single *readonly* input field
57
 * while projecting `igx-date-range-start` and `igx-date-range-end`
58
 * displays two *editable* input fields.
59
 *
60
 * @example
61
 * ```html
62
 * <igx-date-range-picker mode="dropdown"></igx-date-range-picker>
63
 * ```
64
 */
65
@Component({
66
    selector: 'igx-date-range-picker',
67
    templateUrl: './date-range-picker.component.html',
68
    providers: [
69
        { provide: NG_VALUE_ACCESSOR, useExisting: IgxDateRangePickerComponent, multi: true },
70
        { provide: NG_VALIDATORS, useExisting: IgxDateRangePickerComponent, multi: true }
71
    ],
72
    imports: [
73
        NgTemplateOutlet,
74
        IgxIconComponent,
75
        IgxInputGroupComponent,
76
        IgxInputDirective,
77
        IgxPrefixDirective,
78
        IgxSuffixDirective,
79
        DateRangePickerFormatPipe
80
    ]
81
})
82
export class IgxDateRangePickerComponent extends PickerBaseDirective
3✔
83
    implements OnChanges, OnInit, AfterViewInit, OnDestroy, ControlValueAccessor, Validator {
84

85
    /**
86
     * The number of displayed month views.
87
     *
88
     * @remarks
89
     * Default is `2`.
90
     *
91
     * @example
92
     * ```html
93
     * <igx-date-range-picker [displayMonthsCount]="3"></igx-date-range-picker>
94
     * ```
95
     */
96
    @Input()
97
    public get displayMonthsCount(): number {
98
        return this._displayMonthsCount;
2✔
99
    }
100

101
    public set displayMonthsCount(value: number) {
102
        this._displayMonthsCount = clamp(value, 1, 2);
2✔
103
    }
104

105
    /**
106
     * Gets/Sets the orientation of the multiple months displayed in the picker's calendar's days view.
107
     *
108
     * @example
109
     * <igx-date-range-picker orientation="vertical"></igx-date-range-picker>
110
     */
111
    @Input()
112
    public orientation: PickerCalendarOrientation = PickerCalendarOrientation.Horizontal;
133✔
113

114
    /**
115
     * Gets/Sets whether dates that are not part of the current month will be displayed.
116
     *
117
     * @remarks
118
     * Default value is `false`.
119
     *
120
     * @example
121
     * ```html
122
     * <igx-date-range-picker [hideOutsideDays]="true"></igx-date-range-picker>
123
     * ```
124
     */
125
    @Input({ transform: booleanAttribute })
126
    public hideOutsideDays: boolean;
127

128
    /**
129
     * A custom formatter function, applied on the selected or passed in date.
130
     *
131
     * @example
132
     * ```typescript
133
     * private dayFormatter = new Intl.DateTimeFormat("en", { weekday: "long" });
134
     * private monthFormatter = new Intl.DateTimeFormat("en", { month: "long" });
135
     *
136
     * public formatter(date: Date): string {
137
     *  return `${this.dayFormatter.format(date)} - ${this.monthFormatter.format(date)} - ${date.getFullYear()}`;
138
     * }
139
     * ```
140
     * ```html
141
     * <igx-date-range-picker [formatter]="formatter"></igx-date-range-picker>
142
     * ```
143
     */
144
    @Input()
145
    public formatter: (val: DateRange) => string;
146

147
    /**
148
     * Overrides the default text of the calendar dialog **Done** button.
149
     *
150
     * @remarks
151
     * Defaults to the value from resource strings, `"Done"` for the built-in EN.
152
     * The button will only show up in `dialog` mode.
153
     *
154
     * @example
155
     * ```html
156
     * <igx-date-range-picker doneButtonText="完了"></igx-date-range-picker>
157
     * ```
158
     */
159
    @Input()
160
    public set doneButtonText(value: string) {
161
        this._doneButtonText = value;
1✔
162
    }
163

164
    public get doneButtonText(): string {
165
        if (this._doneButtonText === null) {
16✔
166
            return this.resourceStrings.igx_date_range_picker_done_button;
15✔
167
        }
168
        return this._doneButtonText;
1✔
169
    }
170
    /**
171
     * Overrides the default text of the calendar dialog **Cancel** button.
172
     *
173
     * @remarks
174
     * Defaults to the value from resource strings, `"Cancel"` for the built-in EN.
175
     * The button will only show up in `dialog` mode.
176
     *
177
     * @example
178
     * ```html
179
     * <igx-date-range-picker cancelButtonText="取消"></igx-date-range-picker>
180
     * ```
181
     */
182
    @Input()
183
    public set cancelButtonText(value: string) {
184
        this._cancelButtonText = value;
1✔
185
    }
186

187
    public get cancelButtonText(): string {
188
        if (this._cancelButtonText === null) {
16✔
189
            return this.resourceStrings.igx_date_range_picker_cancel_button;
15✔
190
        }
191
        return this._cancelButtonText;
1✔
192
    }
193
    /**
194
     * Custom overlay settings that should be used to display the calendar.
195
     *
196
     * @example
197
     * ```html
198
     * <igx-date-range-picker [overlaySettings]="customOverlaySettings"></igx-date-range-picker>
199
     * ```
200
     */
201
    @Input()
202
    public override overlaySettings: OverlaySettings;
203

204
    /**
205
     * The format used when editable inputs are not focused.
206
     *
207
     * @remarks
208
     * Uses Angular's DatePipe.
209
     *
210
     * @example
211
     * ```html
212
     * <igx-date-range-picker displayFormat="EE/M/yy"></igx-date-range-picker>
213
     * ```
214
     *
215
     */
216
    @Input()
217
    public override displayFormat: string;
218

219
    /**
220
     * The expected user input format and placeholder.
221
     *
222
     * @example
223
     * ```html
224
     * <igx-date-range-picker inputFormat="dd/MM/yy"></igx-date-range-picker>
225
     * ```
226
     */
227
    @Input()
228
    public override inputFormat: string;
229

230
    /**
231
     * The minimum value in a valid range.
232
     *
233
     * @example
234
     * <igx-date-range-picker [minValue]="minDate"></igx-date-range-picker>
235
     */
236
    @Input()
237
    public set minValue(value: Date | string) {
238
        this._minValue = value;
44✔
239
        this.onValidatorChange();
44✔
240
    }
241

242
    public get minValue(): Date | string {
243
        return this._minValue;
268✔
244
    }
245

246
    /**
247
     * The maximum value in a valid range.
248
     *
249
     * @example
250
     * <igx-date-range-picker [maxValue]="maxDate"></igx-date-range-picker>
251
     */
252
    @Input()
253
    public set maxValue(value: Date | string) {
254
        this._maxValue = value;
44✔
255
        this.onValidatorChange();
44✔
256
    }
257

258
    public get maxValue(): Date | string {
259
        return this._maxValue;
268✔
260
    }
261

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

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

297
    /**
298
     * An accessor that sets the resource strings.
299
     * By default it uses EN resources.
300
     */
301
    @Input()
302
    public set resourceStrings(value: IDateRangePickerResourceStrings) {
303
        this._resourceStrings = Object.assign({}, this._resourceStrings, value);
1✔
304
    }
305

306
    /**
307
     * An accessor that returns the resource strings.
308
     */
309
    public get resourceStrings(): IDateRangePickerResourceStrings {
310
        return this._resourceStrings;
178✔
311
    }
312

313
    /**
314
     * Sets the `placeholder` for single-input `IgxDateRangePickerComponent`.
315
     *
316
     *   @example
317
     * ```html
318
     * <igx-date-range-picker [placeholder]="'Choose your dates'"></igx-date-range-picker>
319
     * ```
320
     */
321
    @Input()
322
    public override placeholder = '';
133✔
323

324
    /**
325
     * Gets/Sets the container used for the popup element.
326
     *
327
     * @remarks
328
     *  `outlet` is an instance of `IgxOverlayOutletDirective` or an `ElementRef`.
329
     * @example
330
     * ```html
331
     * <div igxOverlayOutlet #outlet="overlay-outlet"></div>
332
     * //..
333
     * <igx-date-range-picker [outlet]="outlet"></igx-date-range-picker>
334
     * //..
335
     * ```
336
     */
337
    @Input()
338
    public override outlet: IgxOverlayOutletDirective | ElementRef<any>;
339

340
    /**
341
     * Show/hide week numbers
342
     *
343
     * @remarks
344
     * Default is `false`.
345
     *
346
     * @example
347
     * ```html
348
     * <igx-date-range-picker [showWeekNumbers]="true"></igx-date-range-picker>
349
     * ``
350
     */
351
    @Input({ transform: booleanAttribute })
352
    public showWeekNumbers = false;
133✔
353

354
    /**
355
     * Emitted when the picker's value changes. Used for two-way binding.
356
     *
357
     * @example
358
     * ```html
359
     * <igx-date-range-picker [(value)]="date"></igx-date-range-picker>
360
     * ```
361
     */
362

363
     /**
364
      * Whether to render built-in predefined ranges.
365
      *
366
      * @example
367
      * ```html
368
      * <igx-date-range-picker [(usePredefinedRanges)]="true"></igx-date-range-picker>
369
      * ``
370
      *  */
371
    @Input() public usePredefinedRanges = false;
133✔
372

373
    /**
374
     *  Custom ranges rendered as chips.
375
     *
376
     * @example
377
     * ```html
378
     * <igx-date-range-picker [(usePredefinedRanges)]="true"></igx-date-range-picker>
379
     * ``
380
    */
381
    @Input() public customRanges: CustomDateRange[] = [];
133✔
382

383
    @Output()
384
    public valueChange = new EventEmitter<DateRange>();
133✔
385

386
    /** @hidden @internal */
387
    @HostBinding('class.igx-date-range-picker')
388
    public cssClass = 'igx-date-range-picker';
133✔
389

390
    @ViewChild(IgxInputGroupComponent, { read: ViewContainerRef })
391
    private viewContainerRef: ViewContainerRef;
392

393
    /** @hidden @internal */
394
    @ViewChild(IgxInputDirective)
395
    public inputDirective: IgxInputDirective;
396

397
    /** @hidden @internal */
398
    @ContentChildren(IgxDateRangeInputsBaseComponent)
399
    public projectedInputs: QueryList<IgxDateRangeInputsBaseComponent>;
400

401
    @ContentChild(IgxLabelDirective)
402
    public label: IgxLabelDirective;
403

404
    @ContentChild(IgxPickerActionsDirective)
405
    public pickerActions: IgxPickerActionsDirective;
406

407
    /** @hidden @internal */
408
    @ContentChild(IgxDateRangeSeparatorDirective, { read: TemplateRef })
409
    public dateSeparatorTemplate: TemplateRef<any>;
410

411

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

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

418
    @ContentChild(IgxCalendarSubheaderTemplateDirective)
419
    private subheaderTemplate: IgxCalendarSubheaderTemplateDirective;
420

421
    /** @hidden @internal */
422
    public get dateSeparator(): string {
423
        if (this._dateSeparator === null) {
87✔
424
            return this.resourceStrings.igx_date_range_picker_date_separator;
87✔
425
        }
UNCOV
426
        return this._dateSeparator;
×
427
    }
428

429
    /** @hidden @internal */
430
    public get appliedFormat(): string {
431
        return DateTimeUtil.getLocaleDateFormat(this.locale, this.displayFormat)
1,104✔
432
            || DateTimeUtil.DEFAULT_INPUT_FORMAT;
433
    }
434

435
    /**
436
     * Gets/Sets the date which is shown in the calendar picker and is highlighted.
437
     * By default it is the current date, or the value of the picker, if set.
438
    */
439
    @Input()
440
    public get activeDate(): Date {
441
        const today = new Date(new Date().setHours(0, 0, 0, 0));
411✔
442
        const dateValue = DateTimeUtil.isValidDate(this._firstDefinedInRange) ? new Date(this._firstDefinedInRange.setHours(0, 0, 0, 0)) : null;
411✔
443
        return this._activeDate ?? dateValue ?? this._calendar?.activeDate ?? today;
411✔
444
    }
445

446
    public set activeDate(value: Date) {
447
        this._activeDate = value;
1✔
448
    }
449

450
    /**
451
     * @example
452
     * ```html
453
     * <igx-date-range-picker locale="jp"></igx-date-range-picker>
454
     * ```
455
     */
456
    /**
457
     * Gets the `locale` of the date-range-picker.
458
     * If not set, defaults to application's locale.
459
     */
460
    @Input()
461
    public override get locale(): string {
462
        return this._locale;
2,164✔
463
    }
464

465
    /**
466
     * Sets the `locale` of the date-picker.
467
     * Expects a valid BCP 47 language tag.
468
     */
469
    public override set locale(value: string) {
470
        this._locale = value;
403✔
471
        // if value is invalid, set it back to _localeId
472
        try {
403✔
473
            getLocaleFirstDayOfWeek(this._locale);
403✔
474
        } catch (e) {
475
            this._locale = this._localeId;
1✔
476
        }
477
        if (this.hasProjectedInputs) {
403✔
478
            this.updateInputLocale();
3✔
479
            this.updateDisplayFormat();
3✔
480
        }
481
    }
482

483
    /** @hidden @internal */
484
    public get singleInputFormat(): string {
485
        if (this.placeholder !== '') {
512✔
486
            return this.placeholder;
2✔
487
        }
488

489
        const format = this.appliedFormat;
510✔
490
        return `${format}${SingleInputDatesConcatenationString}${format}`;
510✔
491
    }
492

493
    /**
494
     * Gets calendar state.
495
     *
496
     * ```typescript
497
     * let state = this.dateRange.collapsed;
498
     * ```
499
     */
500
    public override get collapsed(): boolean {
501
        return this._collapsed;
802✔
502
    }
503

504
    /**
505
     * The currently selected value / range from the calendar
506
     *
507
     * @remarks
508
     * The current value is of type `DateRange`
509
     *
510
     * @example
511
     * ```typescript
512
     * const newValue: DateRange = { start: new Date("2/2/2012"), end: new Date("3/3/2013")};
513
     * this.dateRangePicker.value = newValue;
514
     * ```
515
     */
516
    public get value(): DateRange | null {
517
        return this._value;
3,379✔
518
    }
519

520
    @Input()
521
    public set value(value: DateRange | null) {
522
        this.updateValue(value);
127✔
523
        this.onChangeCallback(value);
127✔
524
        this.valueChange.emit(value);
127✔
525
    }
526

527
    /** @hidden @internal */
528
    public get hasProjectedInputs(): boolean {
529
        return this.projectedInputs?.length > 0;
3,415✔
530
    }
531

532
    /** @hidden @internal */
533
    public get separatorClass(): string {
534
        return 'igx-date-range-picker__label';
528✔
535
    }
536

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

541
    private get required(): boolean {
542
        if (this._ngControl && this._ngControl.control && this._ngControl.control.validator) {
257✔
543
            const error = this._ngControl.control.validator({} as AbstractControl);
181✔
544
            return (error && error.required) ? true : false;
181!
545
        }
546

547
        return false;
76✔
548
    }
549

550
    private get calendar(): IgxCalendarComponent {
551
        return this._calendar;
837✔
552
    }
553

554
    private get dropdownOverlaySettings(): OverlaySettings {
555
        return Object.assign({}, this._dropDownOverlaySettings, this.overlaySettings);
45✔
556
    }
557

558
    private get dialogOverlaySettings(): OverlaySettings {
559
        return Object.assign({}, this._dialogOverlaySettings, this.overlaySettings);
16✔
560
    }
561

562
    private get _firstDefinedInRange(): Date | null {
563
        if (!this.value) {
593✔
564
            return null;
229✔
565
        }
566
        const range = this.toRangeOfDates(this.value);
364✔
567
        return range?.start ?? range?.end ?? null;
364!
568
    }
569

570
    private _resourceStrings = getCurrentResourceStrings(DateRangePickerResourceStringsEN);
133✔
571
    private _doneButtonText = null;
133✔
572
    private _cancelButtonText = null;
133✔
573
    private _dateSeparator = null;
133✔
574
    private _value: DateRange | null;
575
    private _originalValue: DateRange | null;
576
    private _overlayId: string;
577
    private _ngControl: NgControl;
578
    private _statusChanges$: Subscription;
579
    private _calendar: IgxCalendarComponent;
580
    private _calendarContainer?: HTMLElement;
581
    private _positionSettings: PositionSettings;
582
    private _focusedInput: IgxDateRangeInputsBaseComponent;
583
    private _displayMonthsCount = 2;
133✔
584
    private _specialDates: DateRangeDescriptor[] = null;
133✔
585
    private _disabledDates: DateRangeDescriptor[] = null;
133✔
586
    private _activeDate: Date | null = null;
133✔
587
    private _overlaySubFilter:
133✔
588
        [MonoTypeOperatorFunction<OverlayEventArgs>, MonoTypeOperatorFunction<OverlayEventArgs | OverlayCancelableEventArgs>] = [
589
            filter(x => x.id === this._overlayId),
177✔
590
            takeUntil(merge(this._destroy$, this.closed))
591
        ];
592
    private _dialogOverlaySettings: OverlaySettings = {
133✔
593
        closeOnOutsideClick: true,
594
        modal: true,
595
        closeOnEscape: true
596
    };
597
    private _dropDownOverlaySettings: OverlaySettings = {
133✔
598
        closeOnOutsideClick: true,
599
        modal: false,
600
        closeOnEscape: true
601
    };
602
    private onChangeCallback: (dateRange: DateRange) => void = noop;
133✔
603
    private onTouchCallback: () => void = noop;
133✔
604
    private onValidatorChange: () => void = noop;
133✔
605

606
    constructor(element: ElementRef,
607
        @Inject(LOCALE_ID) _localeId: string,
608
        protected platform: PlatformUtil,
133✔
609
        private _injector: Injector,
133✔
610
        private _cdr: ChangeDetectorRef,
133✔
611
        @Inject(IgxOverlayService) private _overlayService: IgxOverlayService,
133✔
612
        @Optional() @Inject(IGX_INPUT_GROUP_TYPE) _inputGroupType?: IgxInputGroupType) {
613
        super(element, _localeId, _inputGroupType);
133✔
614
        this.locale = this.locale || this._localeId;
133!
615
    }
616

617
    /** @hidden @internal */
618
    @HostListener('keydown', ['$event'])
619
    /** @hidden @internal */
620
    public onKeyDown(event: KeyboardEvent): void {
621
        switch (event.key) {
5!
622
            case this.platform.KEYMAP.ARROW_UP:
UNCOV
623
                if (event.altKey) {
×
UNCOV
624
                    this.close();
×
625
                }
UNCOV
626
                break;
×
627
            case this.platform.KEYMAP.ARROW_DOWN:
628
                if (event.altKey) {
5✔
629
                    this.open();
5✔
630
                }
631
                break;
5✔
632
        }
633
    }
634

635
    /**
636
     * Opens the date range picker's dropdown or dialog.
637
     *
638
     * @example
639
     * ```html
640
     * <igx-date-range-picker #dateRange></igx-date-range-picker>
641
     *
642
     * <button type="button" igxButton (click)="dateRange.open()">Open Dialog</button
643
     * ```
644
     */
645
    public open(overlaySettings?: OverlaySettings): void {
646
        if (!this.collapsed || this.disabled) {
66✔
647
            return;
5✔
648
        }
649

650
        this._originalValue = this._value
61✔
651
            ? { start: new Date(this._value.start), end: new Date(this._value.end) }
652
            : null;
653

654
        const settings = Object.assign({}, this.isDropdown
61✔
655
            ? this.dropdownOverlaySettings
656
            : this.dialogOverlaySettings
657
            , overlaySettings);
658

659
        this._overlayId = this._overlayService
61✔
660
            .attach(IgxCalendarContainerComponent, this.viewContainerRef, settings);
661
        this.subscribeToOverlayEvents();
61✔
662
        this._overlayService.show(this._overlayId);
61✔
663
    }
664

665
    /**
666
     * Closes the date range picker's dropdown or dialog.
667
     *
668
     * @example
669
     * ```html
670
     * <igx-date-range-picker #dateRange></igx-date-range-picker>
671
     *
672
     * <button type="button" igxButton (click)="dateRange.close()">Close Dialog</button>
673
     * ```
674
     */
675
    public close(): void {
676
        if (!this.collapsed) {
63✔
677
            this._overlayService.hide(this._overlayId);
46✔
678
        }
679
    }
680

681
    /**
682
     * Toggles the date range picker's dropdown or dialog
683
     *
684
     * @example
685
     * ```html
686
     * <igx-date-range-picker #dateRange></igx-date-range-picker>
687
     *
688
     * <button type="button" igxButton (click)="dateRange.toggle()">Toggle Dialog</button>
689
     * ```
690
     */
691
    public toggle(overlaySettings?: OverlaySettings): void {
692
        if (!this.collapsed) {
13✔
693
            this.close();
2✔
694
        } else {
695
            this.open(overlaySettings);
11✔
696
        }
697
    }
698

699
    /**
700
     * Selects a range of dates. If no `endDate` is passed, range is 1 day (only `startDate`)
701
     *
702
     * @example
703
     * ```typescript
704
     * public selectFiveDayRange() {
705
     *  const today = new Date();
706
     *  const inFiveDays = new Date(new Date().setDate(today.getDate() + 5));
707
     *  this.dateRange.select(today, inFiveDays);
708
     * }
709
     * ```
710
     */
711
    public select(startDate: Date, endDate?: Date): void {
712
        endDate = endDate ?? startDate;
25✔
713
        const dateRange = [startDate, endDate];
25✔
714
        this.handleSelection(dateRange);
25✔
715
    }
716

717
    /**
718
     * Clears the input field(s) and the picker's value.
719
     *
720
     * @example
721
     * ```typescript
722
     * this.dateRangePicker.clear();
723
     * ```
724
     */
725
    public clear(): void {
726
        if (this.disabled) {
4!
NEW
727
            return;
×
728
        }
729

730
        this.value = null;
4✔
731
        this._calendar?.deselectDate();
4✔
732
        if (this.hasProjectedInputs) {
4✔
733
            this.projectedInputs.forEach((i) => {
2✔
734
                i.inputDirective.clear();
4✔
735
            });
736
        } else {
737
            this.inputDirective.clear();
2✔
738
        }
739
    }
740

741
    /** @hidden @internal */
742
    public writeValue(value: DateRange): void {
743
        this.updateValue(value);
102✔
744
    }
745

746
    /** @hidden @internal */
747
    public registerOnChange(fn: any): void {
748
        this.onChangeCallback = fn;
61✔
749
    }
750

751
    /** @hidden @internal */
752
    public registerOnTouched(fn: any): void {
753
        this.onTouchCallback = fn;
59✔
754
    }
755

756
    /** @hidden @internal */
757
    public validate(control: AbstractControl): ValidationErrors | null {
758
        const value: DateRange = control.value;
355✔
759
        const errors = {};
355✔
760
        if (value) {
355✔
761
            if (this.hasProjectedInputs) {
84✔
762
                const startInput = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
79✔
763
                const endInput = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
158✔
764
                if (!startInput.dateTimeEditor.value) {
79!
UNCOV
765
                    Object.assign(errors, { startValue: true });
×
766
                }
767
                if (!endInput.dateTimeEditor.value) {
79✔
768
                    Object.assign(errors, { endValue: true });
1✔
769
                }
770
            }
771

772
            if (this._isValueInDisabledRange(value)) {
84✔
773
                Object.assign(errors, { dateIsDisabled: true });
1✔
774
            }
775

776
            const { minValue, maxValue } = this._getMinMaxDates();
84✔
777
            const start = parseDate(value.start);
84✔
778
            const end = parseDate(value.end);
84✔
779
            if ((minValue && start && DateTimeUtil.lessThanMinValue(start, minValue, false))
84✔
780
                || (minValue && end && DateTimeUtil.lessThanMinValue(end, minValue, false))) {
781
                Object.assign(errors, { minValue: true });
2✔
782
            }
783
            if ((maxValue && start && DateTimeUtil.greaterThanMaxValue(start, maxValue, false))
84✔
784
                || (maxValue && end && DateTimeUtil.greaterThanMaxValue(end, maxValue, false))) {
785
                Object.assign(errors, { maxValue: true });
1✔
786
            }
787
        }
788

789
        return Object.keys(errors).length > 0 ? errors : null;
355✔
790
    }
791

792
    /** @hidden @internal */
793
    public registerOnValidatorChange?(fn: any): void {
794
        this.onValidatorChange = fn;
60✔
795
    }
796

797
    /** @hidden @internal */
798
    public setDisabledState?(isDisabled: boolean): void {
799
        this.disabled = isDisabled;
60✔
800
    }
801

802
    /** @hidden */
803
    public ngOnInit(): void {
804
        this._ngControl = this._injector.get<NgControl>(NgControl, null);
130✔
805

806
        this.locale = this.locale || this._localeId;
130!
807
    }
808

809
    /** @hidden */
810
    public override ngAfterViewInit(): void {
811
        super.ngAfterViewInit();
126✔
812
        this.subscribeToDateEditorEvents();
126✔
813
        this.subscribeToClick();
126✔
814
        this.configPositionStrategy();
126✔
815
        this.configOverlaySettings();
126✔
816
        this.cacheFocusedInput();
126✔
817
        this.attachOnTouched();
126✔
818

819
        this.setRequiredToInputs();
126✔
820

821
        if (this._ngControl) {
126✔
822
            this._statusChanges$ = this._ngControl.statusChanges.subscribe(this.onStatusChanged.bind(this));
52✔
823
        }
824

825
        // delay invocations until the current change detection cycle has completed
826
        Promise.resolve().then(() => {
126✔
827
            this.updateDisabledState();
126✔
828
            this.initialSetValue();
126✔
829
            this.updateInputs();
126✔
830
            // B.P. 07 July 2021 - IgxDateRangePicker not showing initial disabled state with ChangeDetectionStrategy.OnPush #9776
831
            /**
832
             * if disabled is placed on the range picker element and there are projected inputs
833
             * run change detection since igxInput will initially set the projected inputs' disabled to false
834
             */
835
            if (this.hasProjectedInputs && this.disabled) {
126✔
836
                this._cdr.markForCheck();
3✔
837
            }
838
        });
839
        this.updateDisplayFormat();
126✔
840
        this.updateInputFormat();
126✔
841
    }
842

843
    /** @hidden @internal */
844
    public ngOnChanges(changes: SimpleChanges): void {
845
        if (changes['displayFormat'] && this.hasProjectedInputs) {
118✔
846
            this.updateDisplayFormat();
11✔
847
        }
848
        if (changes['inputFormat'] && this.hasProjectedInputs) {
118✔
849
            this.updateInputFormat();
4✔
850
        }
851
        if (changes['disabled']) {
118✔
852
            this.updateDisabledState();
93✔
853
        }
854
    }
855

856
    /** @hidden @internal */
857
    public override ngOnDestroy(): void {
858
        super.ngOnDestroy();
90✔
859
        if (this._statusChanges$) {
90✔
860
            this._statusChanges$.unsubscribe();
49✔
861
        }
862
        if (this._overlayId) {
90✔
863
            this._overlayService.detach(this._overlayId);
18✔
864
        }
865
    }
866

867
    /** @hidden @internal */
868
    public getEditElement(): HTMLInputElement | undefined {
869
        return this.inputDirective?.nativeElement;
76✔
870
    }
871

872
    protected onStatusChanged = () => {
133✔
873
        if (this.inputGroup) {
130✔
874
            this.setValidityState(this.inputDirective, this.inputGroup.isFocused);
7✔
875
        } else if (this.hasProjectedInputs) {
123✔
876
            this.projectedInputs
123✔
877
                .forEach((i) => {
878
                    this.setValidityState(i.inputDirective, i.isFocused);
246✔
879
                });
880
        }
881
        this.setRequiredToInputs();
130✔
882
    };
883

884
    private setValidityState(inputDirective: IgxInputDirective, isFocused: boolean) {
885
        if (this._ngControl && !this._ngControl.disabled && this.isTouchedOrDirty) {
253✔
886
            if (this.hasValidators && isFocused) {
233✔
887
                inputDirective.valid = this._ngControl.valid ? IgxInputState.VALID : IgxInputState.INVALID;
22✔
888
            } else {
889
                inputDirective.valid = this._ngControl.valid ? IgxInputState.INITIAL : IgxInputState.INVALID;
211✔
890
            }
891
        } else {
892
            inputDirective.valid = IgxInputState.INITIAL;
20✔
893
        }
894
    }
895

896
    private get isTouchedOrDirty(): boolean {
897
        return (this._ngControl.control.touched || this._ngControl.control.dirty);
243✔
898
    }
899

900
    private get hasValidators(): boolean {
901
        return (!!this._ngControl.control.validator || !!this._ngControl.control.asyncValidator);
233✔
902
    }
903

904
    private handleSelection(selectionData: Date[]): void {
905
        let newValue = this.extractRange(selectionData);
45✔
906
        if (!newValue.start && !newValue.end) {
45!
UNCOV
907
            newValue = null;
×
908
        }
909
        this.value = newValue;
45✔
910
        if (this.isDropdown && selectionData?.length > 1) {
45✔
911
            this.close();
32✔
912
        }
913
        this._setCalendarActiveDate();
45✔
914
    }
915

916
    private handleClosing(e: IBaseCancelableBrowserEventArgs): void {
917
        const args = { owner: this, cancel: e?.cancel, event: e?.event };
50✔
918
        this.closing.emit(args);
50✔
919
        e.cancel = args.cancel;
50✔
920
        if (args.cancel) {
50✔
921
            return;
3✔
922
        }
923

924
        if (this.isDropdown && e?.event && !this.isFocused) {
47!
925
            // outside click
UNCOV
926
            this.updateValidityOnBlur();
×
927
        } else {
928
            this.onTouchCallback();
47✔
929
            // input click
930
            if (this.hasProjectedInputs && this._focusedInput) {
47✔
931
                this._focusedInput.setFocus();
7✔
932
            }
933
            if (this.inputDirective) {
47✔
934
                this.inputDirective.focus();
18✔
935
            }
936
        }
937
    }
938

939
    private subscribeToOverlayEvents() {
940
        this._overlayService.opening.pipe(...this._overlaySubFilter).subscribe((e) => {
61✔
941
            const overlayEvent = e as OverlayCancelableEventArgs;
61✔
942
            const args = { owner: this, cancel: overlayEvent?.cancel, event: e.event };
61✔
943
            this.opening.emit(args);
61✔
944
            if (args.cancel) {
61!
UNCOV
945
                this._overlayService.detach(this._overlayId);
×
UNCOV
946
                overlayEvent.cancel = true;
×
UNCOV
947
                return;
×
948
            }
949

950
            this._initializeCalendarContainer(e.componentRef.instance);
61✔
951
            this._calendarContainer = e.componentRef.location.nativeElement;
61✔
952
            this._collapsed = false;
61✔
953
            this.updateCalendar();
61✔
954
        });
955

956
        this._overlayService.opened.pipe(...this._overlaySubFilter).subscribe(() => {
61✔
957
            this.calendar.wrapper.nativeElement.focus();
47✔
958
            this.opened.emit({ owner: this });
47✔
959
        });
960

961
        this._overlayService.closing.pipe(...this._overlaySubFilter).subscribe((e: OverlayCancelableEventArgs) => {
61✔
962
            const isEscape = e.event && (e.event as KeyboardEvent).key === this.platform.KEYMAP.ESCAPE;
50✔
963
            if (this.isProjectedInputTarget(e.event) && !isEscape) {
50✔
964
                e.cancel = true;
1✔
965
            }
966
            this.handleClosing(e as OverlayCancelableEventArgs);
50✔
967
        });
968

969
        this._overlayService.closed.pipe(...this._overlaySubFilter).subscribe(() => {
61✔
970
            this._overlayService.detach(this._overlayId);
19✔
971
            this._collapsed = true;
19✔
972
            this._overlayId = null;
19✔
973
            this._calendar = null;
19✔
974
            this._calendarContainer = undefined;
19✔
975
            this.closed.emit({ owner: this });
19✔
976
        });
977
    }
978

979
    private isProjectedInputTarget(event: Event): boolean {
980
        if (!this.hasProjectedInputs || !event) {
50✔
981
            return false;
47✔
982
        }
983
        const path = event.composed ? event.composedPath() : [event.target];
3!
984
        return this.projectedInputs.some(i =>
3✔
985
            path.includes(i.dateTimeEditor.nativeElement)
5✔
986
        );
987
    }
988

989
    private updateValue(value: DateRange) {
990
        this._value = value ? value : null;
229✔
991
        this.updateInputs();
229✔
992
        this.updateCalendar();
229✔
993
    }
994

995
    private updateValidityOnBlur() {
996
        this._focusedInput = null;
2✔
997
        this.onTouchCallback();
2✔
998
        if (this._ngControl) {
2✔
999
            if (this.hasProjectedInputs) {
1✔
1000
                this.projectedInputs.forEach(i => {
1✔
1001
                    if (!this._ngControl.valid) {
2!
1002
                        i.updateInputValidity(IgxInputState.INVALID);
2✔
1003
                    } else {
UNCOV
1004
                        i.updateInputValidity(IgxInputState.INITIAL);
×
1005
                    }
1006
                });
1007
            }
1008

1009
            if (this.inputDirective) {
1!
1010
                if (!this._ngControl.valid) {
×
UNCOV
1011
                    this.inputDirective.valid = IgxInputState.INVALID;
×
1012
                } else {
UNCOV
1013
                    this.inputDirective.valid = IgxInputState.INITIAL;
×
1014
                }
1015
            }
1016
        }
1017
    }
1018

1019
    private updateDisabledState() {
1020
        if (this.hasProjectedInputs) {
219✔
1021
            const start = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
57✔
1022
            const end = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
114✔
1023
            start.inputDirective.disabled = this.disabled;
57✔
1024
            end.inputDirective.disabled = this.disabled;
57✔
1025
            return;
57✔
1026
        }
1027
    }
1028

1029
    private setRequiredToInputs(): void {
1030
        // workaround for igxInput setting required
1031
        Promise.resolve().then(() => {
256✔
1032
            const isRequired = this.required;
256✔
1033
            if (this.inputGroup && this.inputGroup.isRequired !== isRequired) {
256✔
1034
                this.inputGroup.isRequired = isRequired;
4✔
1035
            } else if (this.hasProjectedInputs && this._ngControl) {
252✔
1036
                this.projectedInputs.forEach(i => i.isRequired = isRequired);
344✔
1037
            }
1038
        });
1039
    }
1040

1041
    private parseMinValue(value: string | Date): Date | null {
1042
        let minValue: Date = parseDate(value);
266✔
1043
        if (!minValue && this.hasProjectedInputs) {
266✔
1044
            const start = this.projectedInputs.filter(i => i instanceof IgxDateRangeStartComponent)[0];
346✔
1045
            if (start) {
173✔
1046
                minValue = parseDate(start.dateTimeEditor.minValue);
173✔
1047
            }
1048
        }
1049

1050
        return minValue;
266✔
1051
    }
1052

1053
    private parseMaxValue(value: string | Date): Date | null {
1054
        let maxValue: Date = parseDate(value);
266✔
1055
        if (!maxValue && this.projectedInputs) {
266✔
1056
            const end = this.projectedInputs.filter(i => i instanceof IgxDateRangeEndComponent)[0];
346✔
1057
            if (end) {
256✔
1058
                maxValue = parseDate(end.dateTimeEditor.maxValue);
173✔
1059
            }
1060
        }
1061

1062
        return maxValue;
266✔
1063
    }
1064

1065
    private updateCalendar(): void {
1066
        if (!this.calendar) {
293✔
1067
            return;
172✔
1068
        }
1069
        this._setDisabledDates();
121✔
1070

1071
        const range: Date[] = [];
121✔
1072
        if (this.value) {
121✔
1073
            const _value = this.toRangeOfDates(this.value);
59✔
1074
            if (_value.start && _value.end) {
59✔
1075
                if (DateTimeUtil.greaterThanMaxValue(_value.start, _value.end)) {
57✔
1076
                    this.swapEditorDates();
2✔
1077
                }
1078
            }
1079
            if (_value.start) {
59✔
1080
                range.push(_value.start);
58✔
1081
            }
1082
            if (_value.end) {
59✔
1083
                range.push(_value.end);
58✔
1084
            }
1085
        }
1086

1087
        if (range.length > 0) {
121✔
1088
            this.calendar.selectDate(range);
59✔
1089
        } else if (range.length === 0 && this.calendar.monthViews) {
62✔
1090
            this.calendar.deselectDate();
6✔
1091
        }
1092
        this._setCalendarActiveDate();
121✔
1093
        this._cdr.detectChanges();
121✔
1094
    }
1095

1096
    private swapEditorDates(): void {
1097
        if (this.hasProjectedInputs) {
2✔
1098
            const start = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
2✔
1099
            const end = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
4✔
1100
            [start.dateTimeEditor.value, end.dateTimeEditor.value] = [end.dateTimeEditor.value, start.dateTimeEditor.value];
2✔
1101
            [this.value.start, this.value.end] = [this.value.end, this.value.start];
2✔
1102
        }
1103
    }
1104

1105
    private extractRange(selection: Date[]): DateRange {
1106
        return {
45✔
1107
            start: selection[0] || null,
45!
1108
            end: selection.length > 0 ? selection[selection.length - 1] : null
45!
1109
        };
1110
    }
1111

1112
    private toRangeOfDates(range: DateRange): { start: Date; end: Date } {
1113
        let start;
1114
        let end;
1115
        if (!isDate(range.start)) {
507✔
1116
            start = DateTimeUtil.parseIsoDate(range.start);
9✔
1117
        }
1118
        if (!isDate(range.end)) {
507✔
1119
            end = DateTimeUtil.parseIsoDate(range.end);
12✔
1120
        }
1121

1122
        if (start || end) {
507!
UNCOV
1123
            return { start, end };
×
1124
        }
1125

1126
        return { start: range.start as Date, end: range.end as Date };
507✔
1127
    }
1128

1129
    private subscribeToClick() {
1130
        const inputs = this.hasProjectedInputs
126✔
1131
            ? this.projectedInputs.map(i => i.inputDirective.nativeElement)
104✔
1132
            : [this.getEditElement()];
1133
        inputs.forEach(input => {
126✔
1134
            fromEvent(input, 'click')
178✔
1135
                .pipe(takeUntil(this._destroy$))
1136
                .subscribe(() => {
1137
                    if (!this.isDropdown) {
4✔
1138
                        this.toggle();
2✔
1139
                    }
1140
                });
1141
        });
1142
    }
1143

1144
    private subscribeToDateEditorEvents(): void {
1145
        if (this.hasProjectedInputs) {
126✔
1146
            const start = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
52✔
1147
            const end = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
104✔
1148
            if (start && end) {
52✔
1149
                start.dateTimeEditor.valueChange
52✔
1150
                    .pipe(takeUntil(this._destroy$))
1151
                    .subscribe(value => {
1152
                        if (this.value) {
21✔
1153
                            this.value = { start: value, end: this.value.end };
20✔
1154
                        } else {
1155
                            this.value = { start: value, end: null };
1✔
1156
                        }
1157
                        if (this.calendar) {
21✔
1158
                            this._setCalendarActiveDate(parseDate(value));
20✔
1159
                            this._cdr.detectChanges();
20✔
1160
                        }
1161
                    });
1162
                end.dateTimeEditor.valueChange
52✔
1163
                    .pipe(takeUntil(this._destroy$))
1164
                    .subscribe(value => {
1165
                        if (this.value) {
1!
1166
                            this.value = { start: this.value.start, end: value as Date };
1✔
1167
                        } else {
UNCOV
1168
                            this.value = { start: null, end: value as Date };
×
1169
                        }
1170
                        if (this.calendar) {
1!
UNCOV
1171
                            this._setCalendarActiveDate(parseDate(value));
×
UNCOV
1172
                            this._cdr.detectChanges();
×
1173
                        }
1174
                    });
1175
            }
1176
        }
1177
    }
1178

1179
    private attachOnTouched(): void {
1180
        if (this.hasProjectedInputs) {
126✔
1181
            this.projectedInputs.forEach(i => {
52✔
1182
                fromEvent(i.dateTimeEditor.nativeElement, 'blur')
104✔
1183
                    .pipe(takeUntil(this._destroy$))
1184
                    .subscribe(() => {
1185
                        if (this.collapsed) {
11✔
1186
                            this.updateValidityOnBlur();
1✔
1187
                        }
1188
                    });
1189
            });
1190
        } else {
1191
            fromEvent(this.inputDirective.nativeElement, 'blur')
74✔
1192
                .pipe(takeUntil(this._destroy$))
1193
                .subscribe(() => {
1194
                    if (this.collapsed) {
8!
UNCOV
1195
                        this.updateValidityOnBlur();
×
1196
                    }
1197
                });
1198
        }
1199
    }
1200

1201
    private cacheFocusedInput(): void {
1202
        if (this.hasProjectedInputs) {
126✔
1203
            this.projectedInputs.forEach(i => {
52✔
1204
                fromEvent(i.dateTimeEditor.nativeElement, 'focus')
104✔
1205
                    .pipe(takeUntil(this._destroy$))
1206
                    .subscribe(() => this._focusedInput = i);
16✔
1207
            });
1208
        }
1209
    }
1210

1211
    private configPositionStrategy(): void {
1212
        this._positionSettings = {
126✔
1213
            openAnimation: fadeIn,
1214
            closeAnimation: fadeOut
1215
        };
1216
        this._dropDownOverlaySettings.positionStrategy = new AutoPositionStrategy(this._positionSettings);
126✔
1217
        this._dropDownOverlaySettings.target = this.element.nativeElement;
126✔
1218
    }
1219

1220
    private configOverlaySettings(): void {
1221
        if (this.overlaySettings !== null) {
126✔
1222
            this._dropDownOverlaySettings = Object.assign({}, this._dropDownOverlaySettings, this.overlaySettings);
126✔
1223
            this._dialogOverlaySettings = Object.assign({}, this._dialogOverlaySettings, this.overlaySettings);
126✔
1224
        }
1225
    }
1226

1227
    private initialSetValue() {
1228
        // if there is no value and no ngControl on the picker but we have inputs we may have value set through
1229
        // their ngModels - we should generate our initial control value
1230
        if ((!this.value || (!this.value.start && !this.value.end)) && this.hasProjectedInputs && !this._ngControl) {
126✔
1231
            const start = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent);
3✔
1232
            const end = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent);
6✔
1233
            this._value = {
3✔
1234
                start: start.dateTimeEditor.value as Date,
1235
                end: end.dateTimeEditor.value as Date
1236
            };
1237
        }
1238
    }
1239

1240
    private updateInputs(): void {
1241
        const start = this.projectedInputs?.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
355✔
1242
        const end = this.projectedInputs?.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
362✔
1243
        if (start && end) {
355✔
1244
            const _value = this.value ? this.toRangeOfDates(this.value) : null;
181✔
1245
            start.updateInputValue(_value?.start || null);
181✔
1246
            end.updateInputValue(_value?.end || null);
181✔
1247
        }
1248
    }
1249

1250
    private updateDisplayFormat(): void {
1251
        this.projectedInputs.forEach(i => {
140✔
1252
            const input = i as IgxDateRangeInputsBaseComponent;
132✔
1253
            input.dateTimeEditor.displayFormat = this.displayFormat;
132✔
1254
        });
1255
    }
1256

1257
    private updateInputFormat(): void {
1258
        this.projectedInputs.forEach(i => {
130✔
1259
            const input = i as IgxDateRangeInputsBaseComponent;
112✔
1260
            if (input.dateTimeEditor.inputFormat !== this.inputFormat) {
112✔
1261
                input.dateTimeEditor.inputFormat = this.inputFormat;
112✔
1262
            }
1263
        });
1264
    }
1265

1266
    private updateInputLocale(): void {
1267
        this.projectedInputs.forEach(i => {
3✔
1268
            const input = i as IgxDateRangeInputsBaseComponent;
6✔
1269
            input.dateTimeEditor.locale = this.locale;
6✔
1270
        });
1271
    }
1272

1273
    private _initializeCalendarContainer(componentInstance: IgxCalendarContainerComponent) {
1274
        this._calendar = componentInstance.calendar;
61✔
1275
        this._calendar.hasHeader = !this.isDropdown && !this.hideHeader;
61✔
1276
        this._calendar.locale = this.locale;
61✔
1277
        this._calendar.selection = CalendarSelection.RANGE;
61✔
1278
        this._calendar.weekStart = this.weekStart;
61✔
1279
        this._calendar.hideOutsideDays = this.hideOutsideDays;
61✔
1280
        this._calendar.monthsViewNumber = this._displayMonthsCount;
61✔
1281
        this._calendar.showWeekNumbers = this.showWeekNumbers;
61✔
1282
        this._calendar.headerTitleTemplate = this.headerTitleTemplate;
61✔
1283
        this._calendar.headerTemplate = this.headerTemplate;
61✔
1284
        this._calendar.subheaderTemplate = this.subheaderTemplate;
61✔
1285
        this._calendar.headerOrientation = this.headerOrientation;
61✔
1286
        this._calendar.orientation = this.orientation;
61✔
1287
        this._calendar.specialDates = this.specialDates;
61✔
1288
        this._calendar.selected.pipe(takeUntil(this._destroy$)).subscribe((ev: Date[]) => this.handleSelection(ev));
61✔
1289

1290
        this._setDisabledDates();
61✔
1291
        this._setCalendarActiveDate();
61✔
1292

1293
        componentInstance.mode = this.mode;
61✔
1294
        componentInstance.closeButtonLabel = !this.isDropdown ? this.doneButtonText : null;
61✔
1295
        componentInstance.cancelButtonLabel = !this.isDropdown ? this.cancelButtonText : null;
61✔
1296
        componentInstance.pickerActions = this.pickerActions;
61✔
1297
        componentInstance.usePredefinedRanges = this.usePredefinedRanges;
61✔
1298
        componentInstance.customRanges = this.customRanges;
61✔
1299
        componentInstance.resourceStrings = this.resourceStrings;
61✔
1300
        componentInstance.calendarClose.pipe(takeUntil(this._destroy$)).subscribe(() => this.close());
61✔
1301
        componentInstance.calendarCancel.pipe(takeUntil(this._destroy$)).subscribe(() => {
61✔
1302
            this._value = this._originalValue;
2✔
1303
            this.close()
2✔
1304
        });
1305
        componentInstance.rangeSelected
61✔
1306
        .pipe(takeUntil(this._destroy$))
1307
        .subscribe((r: DateRange) => {
1308
            if (r?.start && r?.end) {
6✔
1309
            this.select(new Date(r.start), new Date(r.end));
6✔
1310
            }
1311

1312
            if (this.isDropdown) {
6✔
1313
            this.close();
6✔
1314
            }
1315
        });
1316
    }
1317

1318
    private _setDisabledDates(): void {
1319
        if (!this.calendar) {
182!
UNCOV
1320
            return;
×
1321
        }
1322
        this.calendar.disabledDates = this.disabledDates ? [...this.disabledDates] : [];
182✔
1323
        const { minValue, maxValue } = this._getMinMaxDates();
182✔
1324
        if (minValue) {
182✔
1325
            this.calendar.disabledDates.push({ type: DateRangeType.Before, dateRange: [minValue] });
2✔
1326
        }
1327
        if (maxValue) {
182✔
1328
            this.calendar.disabledDates.push({ type: DateRangeType.After, dateRange: [maxValue] });
2✔
1329
        }
1330
    }
1331

1332
    private _getMinMaxDates() {
1333
        const minValue = this.parseMinValue(this.minValue);
266✔
1334
        const maxValue = this.parseMaxValue(this.maxValue);
266✔
1335
        return { minValue, maxValue };
266✔
1336
    }
1337

1338
    private _isValueInDisabledRange(value: DateRange) {
1339
        if (value && value.start && value.end && this.disabledDates) {
84✔
1340
            const isOutsideDisabledRange = Array.from(
2✔
1341
                calendarRange({
1342
                    start: parseDate(this.value.start),
1343
                    end: parseDate(this.value.end),
1344
                    inclusive: true
1345
                })).every((date) => !isDateInRanges(date, this.disabledDates));
5✔
1346
            return !isOutsideDisabledRange;
2✔
1347
        }
1348
        return false;
82✔
1349
    }
1350

1351
    private _setCalendarActiveDate(value = null): void {
227✔
1352
        if (this._calendar) {
247✔
1353
            this._calendar.activeDate = value ?? this.activeDate;
223✔
1354
            this._calendar.viewDate = value ?? this.activeDate;
223✔
1355
        }
1356
    }
1357
}
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