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

IgniteUI / igniteui-angular / 23353730325

20 Mar 2026 05:03PM UTC coverage: 9.784% (-79.5%) from 89.264%
23353730325

Pull #17069

github

web-flow
Merge cfa7e86d1 into a4dc50177
Pull Request #17069: fix(IgxGrid): Do not apply width constraint to groups.

921 of 16963 branches covered (5.43%)

Branch coverage included in aggregate %.

1 of 3 new or added lines in 1 file covered. (33.33%)

25213 existing lines in 340 files now uncovered.

3842 of 31721 relevant lines covered (12.11%)

6.13 hits per line

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

0.29
/projects/igniteui-angular/date-picker/src/date-range-picker/date-range-picker.component.ts
1
import {
2
    AfterViewInit, booleanAttribute, ChangeDetectorRef, Component, ContentChild, ContentChildren, ElementRef,
3
    EventEmitter, HostBinding, HostListener, Injector, Input,
4
    OnChanges, OnDestroy, OnInit, Output, QueryList,
5
    SimpleChanges, TemplateRef, ViewChild, ViewContainerRef, inject
6
} from '@angular/core';
7
import { NgTemplateOutlet } 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 'igniteui-angular/calendar';
17
import {
18
    DateRangeDescriptor,
19
    DateRangeType,
20
    DateRangePickerResourceStringsEN,
21
    IDateRangePickerResourceStrings,
22
    clamp,
23
    IBaseCancelableBrowserEventArgs,
24
    isDate,
25
    parseDate,
26
    PlatformUtil,
27
    getCurrentResourceStrings,
28
    AutoPositionStrategy,
29
    IgxOverlayService,
30
    OverlayCancelableEventArgs,
31
    OverlayEventArgs,
32
    OverlaySettings,
33
    PositionSettings,
34
    calendarRange,
35
    CustomDateRange,
36
    DateRange,
37
    DateTimeUtil,
38
    IgxPickerActionsDirective,
39
    isDateInRanges,
40
    PickerCalendarOrientation,
41
    IgxOverlayOutletDirective
42
} from 'igniteui-angular/core';
43
import { IgxCalendarContainerComponent } from '../date-picker/calendar-container/calendar-container.component';
44
import { PickerBaseDirective } from '../date-picker/picker-base.directive';
45
import {
46
    IgxInputDirective,
47
    IgxInputGroupComponent,
48
    IgxInputState,
49
    IgxLabelDirective,
50
    IgxSuffixDirective,
51
    IgxPrefixDirective,
52
    IgxReadOnlyInputDirective,
53
    IgxHintDirective
54
} from 'igniteui-angular/input-group';
55
import {
56
    IgxDateRangeEndComponent,
57
    IgxDateRangeInputsBaseComponent,
58
    IgxDateRangeSeparatorDirective,
59
    IgxDateRangeStartComponent,
60
    DateRangePickerFormatPipe,
61
} from './date-range-picker-inputs.common';
62
import { IgxIconComponent } from 'igniteui-angular/icon';
63
import { fadeIn, fadeOut } from 'igniteui-angular/animations';
64
import { IResourceChangeEventArgs } from 'igniteui-i18n-core';
65

66
const SingleInputDatesConcatenationString = ' - ';
3✔
67

68
/**
69
 * Provides the ability to select a range of dates from a calendar UI or editable inputs.
70
 *
71
 * @igxModule IgxDateRangeModule
72
 *
73
 * @igxTheme igx-input-group-theme, igx-calendar-theme, igx-date-range-picker-theme
74
 *
75
 * @igxKeywords date, range, date range, date picker
76
 *
77
 * @igxGroup scheduling
78
 *
79
 * @remarks
80
 * It displays the range selection in a single or two input fields.
81
 * The default template displays a single *readonly* input field
82
 * while projecting `igx-date-range-start` and `igx-date-range-end`
83
 * displays two *editable* input fields.
84
 *
85
 * @example
86
 * ```html
87
 * <igx-date-range-picker mode="dropdown"></igx-date-range-picker>
88
 * ```
89
 */
90
@Component({
91
    selector: 'igx-date-range-picker',
92
    templateUrl: './date-range-picker.component.html',
93
    providers: [
94
        { provide: NG_VALUE_ACCESSOR, useExisting: IgxDateRangePickerComponent, multi: true },
95
        { provide: NG_VALIDATORS, useExisting: IgxDateRangePickerComponent, multi: true }
96
    ],
97
    imports: [
98
        NgTemplateOutlet,
99
        IgxIconComponent,
100
        IgxInputGroupComponent,
101
        IgxInputDirective,
102
        IgxPrefixDirective,
103
        IgxSuffixDirective,
104
        IgxReadOnlyInputDirective,
105
        DateRangePickerFormatPipe
106
    ]
107
})
108
export class IgxDateRangePickerComponent extends PickerBaseDirective
3✔
109
    implements OnChanges, OnInit, AfterViewInit, OnDestroy, ControlValueAccessor, Validator {
UNCOV
110
    protected platform = inject(PlatformUtil);
×
UNCOV
111
    private _injector = inject(Injector);
×
UNCOV
112
    private _cdr = inject(ChangeDetectorRef);
×
UNCOV
113
    private _overlayService = inject<IgxOverlayService>(IgxOverlayService);
×
114

115

116
    /**
117
     * The number of displayed month views.
118
     *
119
     * @remarks
120
     * Default is `2`.
121
     *
122
     * @example
123
     * ```html
124
     * <igx-date-range-picker [displayMonthsCount]="3"></igx-date-range-picker>
125
     * ```
126
     */
127
    @Input()
128
    public get displayMonthsCount(): number {
UNCOV
129
        return this._displayMonthsCount;
×
130
    }
131

132
    public set displayMonthsCount(value: number) {
UNCOV
133
        this._displayMonthsCount = clamp(value, 1, 2);
×
134
    }
135

136
    /**
137
     * Gets/Sets the orientation of the multiple months displayed in the picker's calendar's days view.
138
     *
139
     * @example
140
     * <igx-date-range-picker orientation="vertical"></igx-date-range-picker>
141
     */
142
    @Input()
UNCOV
143
    public orientation: PickerCalendarOrientation = PickerCalendarOrientation.Horizontal;
×
144

145
    /**
146
     * Gets/Sets whether dates that are not part of the current month will be displayed.
147
     *
148
     * @remarks
149
     * Default value is `false`.
150
     *
151
     * @example
152
     * ```html
153
     * <igx-date-range-picker [hideOutsideDays]="true"></igx-date-range-picker>
154
     * ```
155
     */
156
    @Input({ transform: booleanAttribute })
157
    public hideOutsideDays: boolean;
158

159
    /**
160
     * A custom formatter function, applied on the selected or passed in date.
161
     *
162
     * @example
163
     * ```typescript
164
     * private dayFormatter = new Intl.DateTimeFormat("en", { weekday: "long" });
165
     * private monthFormatter = new Intl.DateTimeFormat("en", { month: "long" });
166
     *
167
     * public formatter(date: Date): string {
168
     *  return `${this.dayFormatter.format(date)} - ${this.monthFormatter.format(date)} - ${date.getFullYear()}`;
169
     * }
170
     * ```
171
     * ```html
172
     * <igx-date-range-picker [formatter]="formatter"></igx-date-range-picker>
173
     * ```
174
     */
175
    @Input()
176
    public formatter: (val: DateRange) => string;
177

178
    /**
179
     * Overrides the default text of the calendar dialog **Done** button.
180
     *
181
     * @remarks
182
     * Defaults to the value from resource strings, `"Done"` for the built-in EN.
183
     * The button will only show up in `dialog` mode.
184
     *
185
     * @example
186
     * ```html
187
     * <igx-date-range-picker doneButtonText="完了"></igx-date-range-picker>
188
     * ```
189
     */
190
    @Input()
191
    public set doneButtonText(value: string) {
UNCOV
192
        this._doneButtonText = value;
×
193
    }
194

195
    public get doneButtonText(): string {
UNCOV
196
        if (this._doneButtonText === null) {
×
UNCOV
197
            return this.resourceStrings.igx_date_range_picker_done_button;
×
198
        }
UNCOV
199
        return this._doneButtonText;
×
200
    }
201
    /**
202
     * Overrides the default text of the calendar dialog **Cancel** button.
203
     *
204
     * @remarks
205
     * Defaults to the value from resource strings, `"Cancel"` for the built-in EN.
206
     * The button will only show up in `dialog` mode.
207
     *
208
     * @example
209
     * ```html
210
     * <igx-date-range-picker cancelButtonText="取消"></igx-date-range-picker>
211
     * ```
212
     */
213
    @Input()
214
    public set cancelButtonText(value: string) {
UNCOV
215
        this._cancelButtonText = value;
×
216
    }
217

218
    public get cancelButtonText(): string {
UNCOV
219
        if (this._cancelButtonText === null) {
×
UNCOV
220
            return this.resourceStrings.igx_date_range_picker_cancel_button;
×
221
        }
UNCOV
222
        return this._cancelButtonText;
×
223
    }
224
    /**
225
     * Custom overlay settings that should be used to display the calendar.
226
     *
227
     * @example
228
     * ```html
229
     * <igx-date-range-picker [overlaySettings]="customOverlaySettings"></igx-date-range-picker>
230
     * ```
231
     */
232
    @Input()
233
    public override overlaySettings: OverlaySettings;
234

235
    /**
236
     * The format used when editable inputs are not focused.
237
     *
238
     * @remarks
239
     * Uses Angular's DatePipe.
240
     *
241
     * @example
242
     * ```html
243
     * <igx-date-range-picker displayFormat="EE/M/yy"></igx-date-range-picker>
244
     * ```
245
     *
246
     */
247
    @Input()
248
    public override set displayFormat(value: string) {
UNCOV
249
        super.displayFormat = value;
×
250
    }
251

252
    public override get displayFormat(): string {
UNCOV
253
        return super.displayFormat;
×
254
    }
255

256
    /**
257
     * The expected user input format and placeholder.
258
     *
259
     * @example
260
     * ```html
261
     * <igx-date-range-picker inputFormat="dd/MM/yy"></igx-date-range-picker>
262
     * ```
263
     */
264
    @Input()
265
    public override set inputFormat(value: string) {
UNCOV
266
        super.inputFormat = value;
×
267
    };
268

269
    public override get inputFormat(): string {
270
        // We need to get default input format because igxDateRangePicker is not using igxDateTimeEditor, but a plain input ???
UNCOV
271
        return this._inputFormat ?? DateTimeUtil.getDefaultInputFormat(this.locale, this.i18nFormatter);
×
272
    }
273

274
    /**
275
     * The minimum value in a valid range.
276
     *
277
     * @example
278
     * <igx-date-range-picker [minValue]="minDate"></igx-date-range-picker>
279
     */
280
    @Input()
281
    public set minValue(value: Date | string) {
UNCOV
282
        this._minValue = value;
×
UNCOV
283
        this.onValidatorChange();
×
284
    }
285

286
    public get minValue(): Date | string {
UNCOV
287
        return this._minValue;
×
288
    }
289

290
    /**
291
     * The maximum value in a valid range.
292
     *
293
     * @example
294
     * <igx-date-range-picker [maxValue]="maxDate"></igx-date-range-picker>
295
     */
296
    @Input()
297
    public set maxValue(value: Date | string) {
UNCOV
298
        this._maxValue = value;
×
UNCOV
299
        this.onValidatorChange();
×
300
    }
301

302
    public get maxValue(): Date | string {
UNCOV
303
        return this._maxValue;
×
304
    }
305

306
    /**
307
     * Gets/Sets the disabled dates descriptors.
308
     *
309
     * @example
310
     * ```typescript
311
     * let disabledDates = this.dateRangePicker.disabledDates;
312
     * this.dateRangePicker.disabledDates = [ {type: DateRangeType.Weekends}, ...];
313
     * ```
314
     */
315
    @Input()
316
    public get disabledDates(): DateRangeDescriptor[] {
UNCOV
317
        return this._disabledDates;
×
318
    }
319
    public set disabledDates(value: DateRangeDescriptor[]) {
UNCOV
320
        this._disabledDates = value;
×
UNCOV
321
        this.onValidatorChange();
×
322
    }
323

324
    /**
325
     * Gets/Sets the special dates descriptors.
326
     *
327
     * @example
328
     * ```typescript
329
     * let specialDates = this.dateRangePicker.specialDates;
330
     * this.dateRangePicker.specialDates = [ {type: DateRangeType.Weekends}, ... ];
331
     * ```
332
     */
333
    @Input()
334
    public get specialDates(): DateRangeDescriptor[] {
UNCOV
335
        return this._specialDates;
×
336
    }
337
    public set specialDates(value: DateRangeDescriptor[]) {
UNCOV
338
        this._specialDates = value;
×
339
    }
340

341
    /**
342
     * An accessor that sets the resource strings.
343
     * By default it uses EN resources.
344
     */
345
    @Input()
346
    public set resourceStrings(value: IDateRangePickerResourceStrings) {
UNCOV
347
        this._resourceStrings = Object.assign({}, this._resourceStrings, value);
×
348
    }
349

350
    /**
351
     * An accessor that returns the resource strings.
352
     */
353
    public get resourceStrings(): IDateRangePickerResourceStrings {
UNCOV
354
        return this._resourceStrings || this._defaultResourceStrings;
×
355
    }
356

357
    /**
358
     * Sets the `placeholder` for single-input `IgxDateRangePickerComponent`.
359
     *
360
     *   @example
361
     * ```html
362
     * <igx-date-range-picker [placeholder]="'Choose your dates'"></igx-date-range-picker>
363
     * ```
364
     */
365
    @Input()
UNCOV
366
    public override placeholder = '';
×
367

368
    /**
369
     * Gets/Sets the container used for the popup element.
370
     *
371
     * @remarks
372
     *  `outlet` is an instance of `IgxOverlayOutletDirective` or an `ElementRef`.
373
     * @example
374
     * ```html
375
     * <div igxOverlayOutlet #outlet="overlay-outlet"></div>
376
     * //..
377
     * <igx-date-range-picker [outlet]="outlet"></igx-date-range-picker>
378
     * //..
379
     * ```
380
     */
381
    @Input()
382
    public override outlet: IgxOverlayOutletDirective | ElementRef<any>;
383

384
    /**
385
     * Show/hide week numbers
386
     *
387
     * @remarks
388
     * Default is `false`.
389
     *
390
     * @example
391
     * ```html
392
     * <igx-date-range-picker [showWeekNumbers]="true"></igx-date-range-picker>
393
     * ``
394
     */
395
    @Input({ transform: booleanAttribute })
UNCOV
396
    public showWeekNumbers = false;
×
397

398
    /** @hidden @internal */
399
    @Input({ transform: booleanAttribute })
UNCOV
400
    public readOnly = false;
×
401

402
    /**
403
     * Emitted when the picker's value changes. Used for two-way binding.
404
     *
405
     * @example
406
     * ```html
407
     * <igx-date-range-picker [(value)]="date"></igx-date-range-picker>
408
     * ```
409
     */
410

411
     /**
412
      * Whether to render built-in predefined ranges.
413
      *
414
      * @example
415
      * ```html
416
      * <igx-date-range-picker [(usePredefinedRanges)]="true"></igx-date-range-picker>
417
      * ``
418
      *  */
UNCOV
419
    @Input() public usePredefinedRanges = false;
×
420

421
    /**
422
     *  Custom ranges rendered as chips.
423
     *
424
     * @example
425
     * ```html
426
     * <igx-date-range-picker [(usePredefinedRanges)]="true"></igx-date-range-picker>
427
     * ``
428
    */
UNCOV
429
    @Input() public customRanges: CustomDateRange[] = [];
×
430

431
    @Output()
UNCOV
432
    public valueChange = new EventEmitter<DateRange>();
×
433

434
    /** @hidden @internal */
435
    @HostBinding('class.igx-date-range-picker')
UNCOV
436
    public cssClass = 'igx-date-range-picker';
×
437

438
    @ViewChild(IgxInputGroupComponent, { read: ViewContainerRef })
439
    private viewContainerRef: ViewContainerRef;
440

441
    /** @hidden @internal */
442
    @ViewChild(IgxInputDirective)
443
    public inputDirective: IgxInputDirective;
444

445
    /** @hidden @internal */
446
    @ContentChildren(IgxDateRangeInputsBaseComponent)
447
    public projectedInputs: QueryList<IgxDateRangeInputsBaseComponent>;
448

449
    @ContentChild(IgxLabelDirective)
450
    public label: IgxLabelDirective;
451

452
    @ContentChild(IgxHintDirective)
453
    public hint: IgxHintDirective;
454

455
    @ContentChild(IgxPickerActionsDirective)
456
    public pickerActions: IgxPickerActionsDirective;
457

458
    /** @hidden @internal */
459
    @ContentChild(IgxDateRangeSeparatorDirective, { read: TemplateRef })
460
    public dateSeparatorTemplate: TemplateRef<any>;
461

462

463
    @ContentChild(IgxCalendarHeaderTitleTemplateDirective)
464
    private headerTitleTemplate: IgxCalendarHeaderTitleTemplateDirective;
465

466
    @ContentChild(IgxCalendarHeaderTemplateDirective)
467
    private headerTemplate: IgxCalendarHeaderTemplateDirective;
468

469
    @ContentChild(IgxCalendarSubheaderTemplateDirective)
470
    private subheaderTemplate: IgxCalendarSubheaderTemplateDirective;
471

472
    /** @hidden @internal */
473
    public get dateSeparator(): string {
UNCOV
474
        if (this._dateSeparator === null) {
×
UNCOV
475
            return this.resourceStrings.igx_date_range_picker_date_separator;
×
476
        }
477
        return this._dateSeparator;
×
478
    }
479

480
    /** @hidden @internal */
481
    public get appliedFormat(): string {
482
        // Resolve display format since it can be custom specified one like short, long, shortDate, longDate and etc.
UNCOV
483
        const formatOptions = this.i18nFormatter.getFormatOptions(this.displayFormat);
×
UNCOV
484
        return formatOptions
×
485
            ? this.i18nFormatter.getLocaleDateTimeFormat(this.locale, false, formatOptions)
486
            : this.displayFormat ?? this.inputFormat;
×
487
    }
488

489
    /**
490
     * Gets/Sets the date which is shown in the calendar picker and is highlighted.
491
     * By default it is the current date, or the value of the picker, if set.
492
    */
493
    @Input()
494
    public get activeDate(): Date {
UNCOV
495
        const today = new Date(new Date().setHours(0, 0, 0, 0));
×
UNCOV
496
        const dateValue = DateTimeUtil.isValidDate(this._firstDefinedInRange) ? new Date(this._firstDefinedInRange.setHours(0, 0, 0, 0)) : null;
×
UNCOV
497
        return this._activeDate ?? dateValue ?? this._calendar?.activeDate ?? today;
×
498
    }
499

500
    public set activeDate(value: Date) {
UNCOV
501
        this._activeDate = value;
×
502
    }
503

504
    /**
505
     * @example
506
     * ```html
507
     * <igx-date-range-picker locale="jp"></igx-date-range-picker>
508
     * ```
509
     */
510
    /**
511
     * Gets the `locale` of the date-range-picker.
512
     * If not set, defaults to application's locale.
513
     */
514
    @Input()
515
    public override get locale(): string {
UNCOV
516
        return this._locale || this._defaultLocale;
×
517
    }
518

519
    /**
520
     * Sets the `locale` of the date-picker.
521
     * Expects a valid BCP 47 language tag.
522
     */
523
    public override set locale(value: string) {
UNCOV
524
        this._locale = this.i18nFormatter.verifyLocale(value);
×
UNCOV
525
        this.updateResources();
×
UNCOV
526
        if (this.hasProjectedInputs) {
×
UNCOV
527
            this.updateInputLocale();
×
UNCOV
528
            this.updateDisplayFormat();
×
529
        }
530
    }
531

532
    /** @hidden @internal */
533
    public get singleInputFormat(): string {
UNCOV
534
        if (this.placeholder !== '') {
×
UNCOV
535
            return this.placeholder;
×
536
        }
537

UNCOV
538
        const format = this.appliedFormat;
×
UNCOV
539
        return `${format}${SingleInputDatesConcatenationString}${format}`;
×
540
    }
541

542
    /**
543
     * Gets calendar state.
544
     *
545
     * ```typescript
546
     * let state = this.dateRange.collapsed;
547
     * ```
548
     */
549
    public override get collapsed(): boolean {
UNCOV
550
        return this._collapsed;
×
551
    }
552

553
    /**
554
     * The currently selected value / range from the calendar
555
     *
556
     * @remarks
557
     * The current value is of type `DateRange`
558
     *
559
     * @example
560
     * ```typescript
561
     * const newValue: DateRange = { start: new Date("2/2/2012"), end: new Date("3/3/2013")};
562
     * this.dateRangePicker.value = newValue;
563
     * ```
564
     */
565
    public get value(): DateRange | null {
UNCOV
566
        return this._value;
×
567
    }
568

569
    @Input()
570
    public set value(value: DateRange | null) {
UNCOV
571
        this.updateValue(value);
×
UNCOV
572
        this.onChangeCallback(value);
×
UNCOV
573
        this.valueChange.emit(value);
×
574
    }
575

576
    /** @hidden @internal */
577
    public get hasProjectedInputs(): boolean {
UNCOV
578
        return this.projectedInputs?.length > 0;
×
579
    }
580

581
    /** @hidden @internal */
582
    public get separatorClass(): string {
UNCOV
583
        const classes = ['igx-date-range-picker__label'];
×
UNCOV
584
        if (this.hint) classes.push('input-has-hint');
×
UNCOV
585
        return classes.join(' ');
×
586
    }
587

588
    protected override get toggleContainer(): HTMLElement | undefined {
UNCOV
589
        return this._calendarContainer;
×
590
    }
591

592
    private get required(): boolean {
UNCOV
593
        if (this._ngControl && this._ngControl.control && this._ngControl.control.validator) {
×
UNCOV
594
            const error = this._ngControl.control.validator({} as AbstractControl);
×
UNCOV
595
            return (error && error.required) ? true : false;
×
596
        }
597

UNCOV
598
        return false;
×
599
    }
600

601
    private get calendar(): IgxCalendarComponent {
UNCOV
602
        return this._calendar;
×
603
    }
604

605
    private get dropdownOverlaySettings(): OverlaySettings {
UNCOV
606
        return Object.assign({}, this._dropDownOverlaySettings, this.overlaySettings);
×
607
    }
608

609
    private get dialogOverlaySettings(): OverlaySettings {
UNCOV
610
        return Object.assign({}, this._dialogOverlaySettings, this.overlaySettings);
×
611
    }
612

613
    private get _firstDefinedInRange(): Date | null {
UNCOV
614
        if (!this.value) {
×
UNCOV
615
            return null;
×
616
        }
UNCOV
617
        const range = this.toRangeOfDates(this.value);
×
UNCOV
618
        return range?.start ?? range?.end ?? null;
×
619
    }
620

UNCOV
621
    private _resourceStrings: IDateRangePickerResourceStrings = null;
×
UNCOV
622
    private _defaultResourceStrings = getCurrentResourceStrings(DateRangePickerResourceStringsEN);
×
UNCOV
623
    private _doneButtonText = null;
×
UNCOV
624
    private _cancelButtonText = null;
×
UNCOV
625
    private _dateSeparator = null;
×
626
    private _value: DateRange | null;
627
    private _originalValue: DateRange | null;
628
    private _overlayId: string;
629
    private _ngControl: NgControl;
630
    private _statusChanges$: Subscription;
631
    private _calendar: IgxCalendarComponent;
632
    private _calendarContainer?: HTMLElement;
633
    private _positionSettings: PositionSettings;
634
    private _focusedInput: IgxDateRangeInputsBaseComponent;
UNCOV
635
    private _displayMonthsCount = 2;
×
UNCOV
636
    private _specialDates: DateRangeDescriptor[] = null;
×
UNCOV
637
    private _disabledDates: DateRangeDescriptor[] = null;
×
UNCOV
638
    private _activeDate: Date | null = null;
×
UNCOV
639
    private _overlaySubFilter:
×
640
        [MonoTypeOperatorFunction<OverlayEventArgs>, MonoTypeOperatorFunction<OverlayEventArgs | OverlayCancelableEventArgs>] = [
UNCOV
641
            filter(x => x.id === this._overlayId),
×
642
            takeUntil(merge(this._destroy$, this.closed))
643
        ];
UNCOV
644
    private _dialogOverlaySettings: OverlaySettings = {
×
645
        closeOnOutsideClick: true,
646
        modal: true,
647
        closeOnEscape: true
648
    };
UNCOV
649
    private _dropDownOverlaySettings: OverlaySettings = {
×
650
        closeOnOutsideClick: true,
651
        modal: false,
652
        closeOnEscape: true
653
    };
UNCOV
654
    private onChangeCallback: (dateRange: DateRange) => void = noop;
×
UNCOV
655
    private onTouchCallback: () => void = noop;
×
UNCOV
656
    private onValidatorChange: () => void = noop;
×
657

658
    constructor() {
UNCOV
659
        super();
×
UNCOV
660
        this.initLocale();
×
661
    }
662

663
    /** @hidden @internal */
664
    @HostListener('keydown', ['$event'])
665
    /** @hidden @internal */
666
    public onKeyDown(event: KeyboardEvent): void {
UNCOV
667
        switch (event.key) {
×
668
            case this.platform.KEYMAP.ARROW_UP:
669
                if (event.altKey) {
×
670
                    this.close();
×
671
                }
672
                break;
×
673
            case this.platform.KEYMAP.ARROW_DOWN:
UNCOV
674
                if (event.altKey) {
×
UNCOV
675
                    this.open();
×
676
                }
UNCOV
677
                break;
×
678
        }
679
    }
680

681
    /**
682
     * Opens 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.open()">Open Dialog</button
689
     * ```
690
     */
691
    public open(overlaySettings?: OverlaySettings): void {
UNCOV
692
        if (!this.collapsed || this.disabled || this.readOnly) {
×
UNCOV
693
            return;
×
694
        }
695

UNCOV
696
        this._originalValue = this._value
×
697
            ? { start: new Date(this._value.start), end: new Date(this._value.end) }
698
            : null;
699

UNCOV
700
        const settings = Object.assign({}, this.isDropdown
×
701
            ? this.dropdownOverlaySettings
702
            : this.dialogOverlaySettings
703
            , overlaySettings);
704

UNCOV
705
        this._overlayId = this._overlayService
×
706
            .attach(IgxCalendarContainerComponent, this.viewContainerRef, settings);
UNCOV
707
        this.subscribeToOverlayEvents();
×
UNCOV
708
        this._overlayService.show(this._overlayId);
×
709
    }
710

711
    /**
712
     * Closes the date range picker's dropdown or dialog.
713
     *
714
     * @example
715
     * ```html
716
     * <igx-date-range-picker #dateRange></igx-date-range-picker>
717
     *
718
     * <button type="button" igxButton (click)="dateRange.close()">Close Dialog</button>
719
     * ```
720
     */
721
    public close(): void {
UNCOV
722
        if (!this.collapsed) {
×
UNCOV
723
            this._overlayService.hide(this._overlayId);
×
724
        }
725
    }
726

727
    /**
728
     * Toggles the date range picker's dropdown or dialog
729
     *
730
     * @example
731
     * ```html
732
     * <igx-date-range-picker #dateRange></igx-date-range-picker>
733
     *
734
     * <button type="button" igxButton (click)="dateRange.toggle()">Toggle Dialog</button>
735
     * ```
736
     */
737
    public toggle(overlaySettings?: OverlaySettings): void {
UNCOV
738
        if (!this.collapsed) {
×
UNCOV
739
            this.close();
×
740
        } else {
UNCOV
741
            this.open(overlaySettings);
×
742
        }
743
    }
744

745
    /**
746
     * Selects a range of dates. If no `endDate` is passed, range is 1 day (only `startDate`)
747
     *
748
     * @example
749
     * ```typescript
750
     * public selectFiveDayRange() {
751
     *  const today = new Date();
752
     *  const inFiveDays = new Date(new Date().setDate(today.getDate() + 5));
753
     *  this.dateRange.select(today, inFiveDays);
754
     * }
755
     * ```
756
     */
757
    public select(startDate: Date, endDate?: Date): void {
UNCOV
758
        endDate = endDate ?? startDate;
×
UNCOV
759
        const dateRange = [startDate, endDate];
×
UNCOV
760
        this.handleSelection(dateRange);
×
761
    }
762

763
    /**
764
     * Clears the input field(s) and the picker's value.
765
     *
766
     * @example
767
     * ```typescript
768
     * this.dateRangePicker.clear();
769
     * ```
770
     */
771
    public clear(): void {
UNCOV
772
        if (this.disabled) {
×
773
            return;
×
774
        }
775

UNCOV
776
        this.value = null;
×
UNCOV
777
        this._calendar?.deselectDate();
×
UNCOV
778
        if (this.hasProjectedInputs) {
×
UNCOV
779
            this.projectedInputs.forEach((i) => {
×
UNCOV
780
                i.inputDirective.clear();
×
781
            });
782
        } else {
UNCOV
783
            this.inputDirective.clear();
×
784
        }
785
    }
786

787
    /** @hidden @internal */
788
    public writeValue(value: DateRange): void {
UNCOV
789
        this.updateValue(value);
×
790
    }
791

792
    /** @hidden @internal */
793
    public registerOnChange(fn: any): void {
UNCOV
794
        this.onChangeCallback = fn;
×
795
    }
796

797
    /** @hidden @internal */
798
    public registerOnTouched(fn: any): void {
UNCOV
799
        this.onTouchCallback = fn;
×
800
    }
801

802
    /** @hidden @internal */
803
    public validate(control: AbstractControl): ValidationErrors | null {
UNCOV
804
        const value: DateRange = control.value;
×
UNCOV
805
        const errors = {};
×
UNCOV
806
        if (value) {
×
UNCOV
807
            if (this.hasProjectedInputs) {
×
UNCOV
808
                const startInput = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
×
UNCOV
809
                const endInput = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
×
UNCOV
810
                if (!startInput.dateTimeEditor.value) {
×
811
                    Object.assign(errors, { startValue: true });
×
812
                }
UNCOV
813
                if (!endInput.dateTimeEditor.value) {
×
UNCOV
814
                    Object.assign(errors, { endValue: true });
×
815
                }
816
            }
817

UNCOV
818
            if (this._isValueInDisabledRange(value)) {
×
UNCOV
819
                Object.assign(errors, { dateIsDisabled: true });
×
820
            }
821

UNCOV
822
            const { minValue, maxValue } = this._getMinMaxDates();
×
UNCOV
823
            const start = parseDate(value.start);
×
UNCOV
824
            const end = parseDate(value.end);
×
UNCOV
825
            if ((minValue && start && DateTimeUtil.lessThanMinValue(start, minValue, false))
×
826
                || (minValue && end && DateTimeUtil.lessThanMinValue(end, minValue, false))) {
UNCOV
827
                Object.assign(errors, { minValue: true });
×
828
            }
UNCOV
829
            if ((maxValue && start && DateTimeUtil.greaterThanMaxValue(start, maxValue, false))
×
830
                || (maxValue && end && DateTimeUtil.greaterThanMaxValue(end, maxValue, false))) {
UNCOV
831
                Object.assign(errors, { maxValue: true });
×
832
            }
833
        }
834

UNCOV
835
        return Object.keys(errors).length > 0 ? errors : null;
×
836
    }
837

838
    /** @hidden @internal */
839
    public registerOnValidatorChange?(fn: any): void {
UNCOV
840
        this.onValidatorChange = fn;
×
841
    }
842

843
    /** @hidden @internal */
844
    public setDisabledState?(isDisabled: boolean): void {
UNCOV
845
        this.disabled = isDisabled;
×
846
    }
847

848
    /** @hidden */
849
    public ngOnInit(): void {
UNCOV
850
        this._ngControl = this._injector.get<NgControl>(NgControl, null);
×
851
    }
852

853
    /** @hidden */
854
    public override ngAfterViewInit(): void {
UNCOV
855
        super.ngAfterViewInit();
×
UNCOV
856
        this.subscribeToDateEditorEvents();
×
UNCOV
857
        this.subscribeToClick();
×
UNCOV
858
        this.configPositionStrategy();
×
UNCOV
859
        this.configOverlaySettings();
×
UNCOV
860
        this.cacheFocusedInput();
×
UNCOV
861
        this.attachOnTouched();
×
862

UNCOV
863
        this.setRequiredToInputs();
×
864

UNCOV
865
        if (this._ngControl) {
×
UNCOV
866
            this._statusChanges$ = this._ngControl.statusChanges.subscribe(this.onStatusChanged.bind(this));
×
867
        }
868

869
        // delay invocations until the current change detection cycle has completed
UNCOV
870
        Promise.resolve().then(() => {
×
UNCOV
871
            this.updateDisabledState();
×
UNCOV
872
            this.initialSetValue();
×
UNCOV
873
            this.updateInputs();
×
874
            // B.P. 07 July 2021 - IgxDateRangePicker not showing initial disabled state with ChangeDetectionStrategy.OnPush #9776
875
            /**
876
             * if disabled is placed on the range picker element and there are projected inputs
877
             * run change detection since igxInput will initially set the projected inputs' disabled to false
878
             */
UNCOV
879
            if (this.hasProjectedInputs && this.disabled) {
×
UNCOV
880
                this._cdr.markForCheck();
×
881
            }
882
        });
UNCOV
883
        this.updateDisplayFormat();
×
UNCOV
884
        this.updateInputFormat();
×
885
    }
886

887
    /** @hidden @internal */
888
    public ngOnChanges(changes: SimpleChanges): void {
UNCOV
889
        if (changes['displayFormat'] && this.hasProjectedInputs) {
×
UNCOV
890
            this.updateDisplayFormat();
×
891
        }
UNCOV
892
        if (changes['inputFormat'] && this.hasProjectedInputs) {
×
UNCOV
893
            this.updateInputFormat();
×
894
        }
UNCOV
895
        if (changes['disabled']) {
×
UNCOV
896
            this.updateDisabledState();
×
897
        }
898
    }
899

900
    /** @hidden @internal */
901
    public override ngOnDestroy(): void {
UNCOV
902
        super.ngOnDestroy();
×
UNCOV
903
        if (this._statusChanges$) {
×
UNCOV
904
            this._statusChanges$.unsubscribe();
×
905
        }
UNCOV
906
        if (this._overlayId) {
×
UNCOV
907
            this._overlayService.detach(this._overlayId);
×
908
        }
909
    }
910

911
    /** @hidden @internal */
912
    public getEditElement(): HTMLInputElement {
UNCOV
913
        return this.inputDirective!.nativeElement;
×
914
    }
915

UNCOV
916
    protected onStatusChanged = () => {
×
UNCOV
917
        if (this.inputGroup) {
×
UNCOV
918
            this.setValidityState(this.inputDirective, this.inputGroup.isFocused);
×
UNCOV
919
        } else if (this.hasProjectedInputs) {
×
UNCOV
920
            this.projectedInputs
×
921
                .forEach((i) => {
UNCOV
922
                    this.setValidityState(i.inputDirective, i.isFocused);
×
923
                });
924
        }
UNCOV
925
        this.setRequiredToInputs();
×
926
    };
927

928
    private setValidityState(inputDirective: IgxInputDirective, isFocused: boolean) {
UNCOV
929
        if (this._ngControl && !this._ngControl.disabled && this.isTouchedOrDirty) {
×
UNCOV
930
            if (this.hasValidators && isFocused) {
×
UNCOV
931
                inputDirective.valid = this._ngControl.valid ? IgxInputState.VALID : IgxInputState.INVALID;
×
932
            } else {
UNCOV
933
                inputDirective.valid = this._ngControl.valid ? IgxInputState.INITIAL : IgxInputState.INVALID;
×
934
            }
935
        } else {
UNCOV
936
            inputDirective.valid = IgxInputState.INITIAL;
×
937
        }
938
    }
939

940
    private get isTouchedOrDirty(): boolean {
UNCOV
941
        return (this._ngControl.control.touched || this._ngControl.control.dirty);
×
942
    }
943

944
    private get hasValidators(): boolean {
UNCOV
945
        return (!!this._ngControl.control.validator || !!this._ngControl.control.asyncValidator);
×
946
    }
947

948
    private handleSelection(selectionData: Date[]): void {
UNCOV
949
        let newValue = this.extractRange(selectionData);
×
UNCOV
950
        if (!newValue.start && !newValue.end) {
×
951
            newValue = null;
×
952
        }
UNCOV
953
        this.value = newValue;
×
UNCOV
954
        if (this.isDropdown && selectionData?.length > 1) {
×
UNCOV
955
            this.close();
×
956
        }
UNCOV
957
        this._setCalendarActiveDate();
×
958
    }
959

960
    private handleClosing(e: IBaseCancelableBrowserEventArgs): void {
UNCOV
961
        const args = { owner: this, cancel: e?.cancel, event: e?.event };
×
UNCOV
962
        this.closing.emit(args);
×
UNCOV
963
        e.cancel = args.cancel;
×
UNCOV
964
        if (args.cancel) {
×
UNCOV
965
            return;
×
966
        }
967

UNCOV
968
        if (this.isDropdown && e?.event && !this.isFocused) {
×
969
            // outside click
970
            this.updateValidityOnBlur();
×
971
        } else {
UNCOV
972
            this.onTouchCallback();
×
973
            // input click
UNCOV
974
            if (this.hasProjectedInputs && this._focusedInput) {
×
UNCOV
975
                this._focusedInput.setFocus();
×
976
            }
UNCOV
977
            if (this.inputDirective) {
×
UNCOV
978
                this.inputDirective.focus();
×
979
            }
980
        }
981
    }
982

983
    private subscribeToOverlayEvents() {
UNCOV
984
        this._overlayService.opening.pipe(...this._overlaySubFilter).subscribe((e) => {
×
UNCOV
985
            const overlayEvent = e as OverlayCancelableEventArgs;
×
UNCOV
986
            const args = { owner: this, cancel: overlayEvent?.cancel, event: e.event };
×
UNCOV
987
            this.opening.emit(args);
×
UNCOV
988
            if (args.cancel) {
×
989
                this._overlayService.detach(this._overlayId);
×
990
                overlayEvent.cancel = true;
×
991
                return;
×
992
            }
993

UNCOV
994
            this._initializeCalendarContainer(e.componentRef.instance);
×
UNCOV
995
            this._calendarContainer = e.componentRef.location.nativeElement;
×
UNCOV
996
            this._collapsed = false;
×
UNCOV
997
            this.updateCalendar();
×
998
        });
999

UNCOV
1000
        this._overlayService.opened.pipe(...this._overlaySubFilter).subscribe(() => {
×
UNCOV
1001
            this.calendar.wrapper.nativeElement.focus();
×
UNCOV
1002
            this.opened.emit({ owner: this });
×
1003
        });
1004

UNCOV
1005
        this._overlayService.closing.pipe(...this._overlaySubFilter).subscribe((e: OverlayCancelableEventArgs) => {
×
UNCOV
1006
            const isEscape = e.event && (e.event as KeyboardEvent).key === this.platform.KEYMAP.ESCAPE;
×
UNCOV
1007
            if (this.isProjectedInputTarget(e.event) && !isEscape) {
×
UNCOV
1008
                e.cancel = true;
×
1009
            }
UNCOV
1010
            this.handleClosing(e as OverlayCancelableEventArgs);
×
1011
        });
1012

UNCOV
1013
        this._overlayService.closed.pipe(...this._overlaySubFilter).subscribe(() => {
×
UNCOV
1014
            this._overlayService.detach(this._overlayId);
×
UNCOV
1015
            this._collapsed = true;
×
UNCOV
1016
            this._overlayId = null;
×
UNCOV
1017
            this._calendar = null;
×
UNCOV
1018
            this._calendarContainer = undefined;
×
UNCOV
1019
            this.closed.emit({ owner: this });
×
1020
        });
1021
    }
1022

1023
    private isProjectedInputTarget(event: Event): boolean {
UNCOV
1024
        if (!this.hasProjectedInputs || !event) {
×
UNCOV
1025
            return false;
×
1026
        }
UNCOV
1027
        const path = event.composed ? event.composedPath() : [event.target];
×
UNCOV
1028
        return this.projectedInputs.some(i =>
×
UNCOV
1029
            path.includes(i.dateTimeEditor.nativeElement)
×
1030
        );
1031
    }
1032

1033
    private updateValue(value: DateRange) {
UNCOV
1034
        this._value = value ? value : null;
×
UNCOV
1035
        this.updateInputs();
×
UNCOV
1036
        this.updateCalendar();
×
1037
    }
1038

1039
    private updateValidityOnBlur() {
UNCOV
1040
        this._focusedInput = null;
×
UNCOV
1041
        this.onTouchCallback();
×
UNCOV
1042
        if (this._ngControl) {
×
UNCOV
1043
            if (this.hasProjectedInputs) {
×
UNCOV
1044
                this.projectedInputs.forEach(i => {
×
UNCOV
1045
                    if (!this._ngControl.valid) {
×
UNCOV
1046
                        i.updateInputValidity(IgxInputState.INVALID);
×
1047
                    } else {
1048
                        i.updateInputValidity(IgxInputState.INITIAL);
×
1049
                    }
1050
                });
1051
            }
1052

UNCOV
1053
            if (this.inputDirective) {
×
1054
                if (!this._ngControl.valid) {
×
1055
                    this.inputDirective.valid = IgxInputState.INVALID;
×
1056
                } else {
1057
                    this.inputDirective.valid = IgxInputState.INITIAL;
×
1058
                }
1059
            }
1060
        }
1061
    }
1062

1063
    private updateDisabledState() {
UNCOV
1064
        if (this.hasProjectedInputs) {
×
UNCOV
1065
            const start = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
×
UNCOV
1066
            const end = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
×
UNCOV
1067
            start.inputDirective.disabled = this.disabled;
×
UNCOV
1068
            end.inputDirective.disabled = this.disabled;
×
UNCOV
1069
            return;
×
1070
        }
1071
    }
1072

1073
    private setRequiredToInputs(): void {
1074
        // workaround for igxInput setting required
UNCOV
1075
        Promise.resolve().then(() => {
×
UNCOV
1076
            const isRequired = this.required;
×
UNCOV
1077
            if (this.inputGroup && this.inputGroup.isRequired !== isRequired) {
×
UNCOV
1078
                this.inputGroup.isRequired = isRequired;
×
UNCOV
1079
            } else if (this.hasProjectedInputs && this._ngControl) {
×
UNCOV
1080
                this.projectedInputs.forEach(i => i.isRequired = isRequired);
×
1081
            }
1082
        });
1083
    }
1084

1085
    private parseMinValue(value: string | Date): Date | null {
UNCOV
1086
        let minValue: Date = parseDate(value);
×
UNCOV
1087
        if (!minValue && this.hasProjectedInputs) {
×
UNCOV
1088
            const start = this.projectedInputs.filter(i => i instanceof IgxDateRangeStartComponent)[0];
×
UNCOV
1089
            if (start) {
×
UNCOV
1090
                minValue = parseDate(start.dateTimeEditor.minValue);
×
1091
            }
1092
        }
1093

UNCOV
1094
        return minValue;
×
1095
    }
1096

1097
    private parseMaxValue(value: string | Date): Date | null {
UNCOV
1098
        let maxValue: Date = parseDate(value);
×
UNCOV
1099
        if (!maxValue && this.projectedInputs) {
×
UNCOV
1100
            const end = this.projectedInputs.filter(i => i instanceof IgxDateRangeEndComponent)[0];
×
UNCOV
1101
            if (end) {
×
UNCOV
1102
                maxValue = parseDate(end.dateTimeEditor.maxValue);
×
1103
            }
1104
        }
1105

UNCOV
1106
        return maxValue;
×
1107
    }
1108

1109
    private updateCalendar(): void {
UNCOV
1110
        if (!this.calendar) {
×
UNCOV
1111
            return;
×
1112
        }
UNCOV
1113
        this._setDisabledDates();
×
1114

UNCOV
1115
        const range: Date[] = [];
×
UNCOV
1116
        if (this.value) {
×
UNCOV
1117
            const _value = this.toRangeOfDates(this.value);
×
UNCOV
1118
            if (_value.start && _value.end) {
×
UNCOV
1119
                if (DateTimeUtil.greaterThanMaxValue(_value.start, _value.end)) {
×
UNCOV
1120
                    this.swapEditorDates();
×
1121
                }
1122
            }
UNCOV
1123
            if (_value.start) {
×
UNCOV
1124
                range.push(_value.start);
×
1125
            }
UNCOV
1126
            if (_value.end) {
×
UNCOV
1127
                range.push(_value.end);
×
1128
            }
1129
        }
1130

UNCOV
1131
        if (range.length > 0) {
×
UNCOV
1132
            this.calendar.selectDate(range);
×
UNCOV
1133
        } else if (range.length === 0 && this.calendar.monthViews) {
×
UNCOV
1134
            this.calendar.deselectDate();
×
1135
        }
UNCOV
1136
        this._setCalendarActiveDate();
×
UNCOV
1137
        this._cdr.detectChanges();
×
1138
    }
1139

1140
    private swapEditorDates(): void {
UNCOV
1141
        if (this.hasProjectedInputs) {
×
UNCOV
1142
            const start = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
×
UNCOV
1143
            const end = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
×
UNCOV
1144
            [start.dateTimeEditor.value, end.dateTimeEditor.value] = [end.dateTimeEditor.value, start.dateTimeEditor.value];
×
UNCOV
1145
            [this.value.start, this.value.end] = [this.value.end, this.value.start];
×
1146
        }
1147
    }
1148

1149
    private extractRange(selection: Date[]): DateRange {
UNCOV
1150
        return {
×
1151
            start: selection[0] || null,
×
1152
            end: selection.length > 0 ? selection[selection.length - 1] : null
×
1153
        };
1154
    }
1155

1156
    private toRangeOfDates(range: DateRange): { start: Date; end: Date } {
1157
        let start;
1158
        let end;
UNCOV
1159
        if (!isDate(range.start)) {
×
UNCOV
1160
            start = DateTimeUtil.parseIsoDate(range.start);
×
1161
        }
UNCOV
1162
        if (!isDate(range.end)) {
×
UNCOV
1163
            end = DateTimeUtil.parseIsoDate(range.end);
×
1164
        }
1165

UNCOV
1166
        if (start || end) {
×
1167
            return { start, end };
×
1168
        }
1169

UNCOV
1170
        return { start: range.start as Date, end: range.end as Date };
×
1171
    }
1172

1173
    private subscribeToClick() {
UNCOV
1174
        const inputs = this.hasProjectedInputs
×
UNCOV
1175
            ? this.projectedInputs.map(i => i.inputDirective.nativeElement)
×
1176
            : [this.getEditElement()];
UNCOV
1177
        inputs.forEach(input => {
×
UNCOV
1178
            fromEvent(input, 'click')
×
1179
                .pipe(takeUntil(this._destroy$))
1180
                .subscribe(() => {
UNCOV
1181
                    if (!this.isDropdown) {
×
UNCOV
1182
                        this.toggle();
×
1183
                    }
1184
                });
1185
        });
1186
    }
1187

1188
    private subscribeToDateEditorEvents(): void {
UNCOV
1189
        if (this.hasProjectedInputs) {
×
UNCOV
1190
            const start = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
×
UNCOV
1191
            const end = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
×
UNCOV
1192
            if (start && end) {
×
UNCOV
1193
                start.dateTimeEditor.valueChange
×
1194
                    .pipe(takeUntil(this._destroy$))
1195
                    .subscribe(value => {
UNCOV
1196
                        if (this.value) {
×
UNCOV
1197
                            this.value = { start: value, end: this.value.end };
×
1198
                        } else {
UNCOV
1199
                            this.value = { start: value, end: null };
×
1200
                        }
UNCOV
1201
                        if (this.calendar) {
×
UNCOV
1202
                            this._setCalendarActiveDate(parseDate(value));
×
UNCOV
1203
                            this._cdr.detectChanges();
×
1204
                        }
1205
                    });
UNCOV
1206
                end.dateTimeEditor.valueChange
×
1207
                    .pipe(takeUntil(this._destroy$))
1208
                    .subscribe(value => {
UNCOV
1209
                        if (this.value) {
×
UNCOV
1210
                            this.value = { start: this.value.start, end: value as Date };
×
1211
                        } else {
1212
                            this.value = { start: null, end: value as Date };
×
1213
                        }
UNCOV
1214
                        if (this.calendar) {
×
1215
                            this._setCalendarActiveDate(parseDate(value));
×
1216
                            this._cdr.detectChanges();
×
1217
                        }
1218
                    });
1219
            }
1220
        }
1221
    }
1222

1223
    private attachOnTouched(): void {
UNCOV
1224
        if (this.hasProjectedInputs) {
×
UNCOV
1225
            this.projectedInputs.forEach(i => {
×
UNCOV
1226
                fromEvent(i.dateTimeEditor.nativeElement, 'blur')
×
1227
                    .pipe(takeUntil(this._destroy$))
1228
                    .subscribe(() => {
UNCOV
1229
                        if (this.collapsed) {
×
UNCOV
1230
                            this.updateValidityOnBlur();
×
1231
                        }
1232
                    });
1233
            });
1234
        } else {
UNCOV
1235
            fromEvent(this.inputDirective.nativeElement, 'blur')
×
1236
                .pipe(takeUntil(this._destroy$))
1237
                .subscribe(() => {
UNCOV
1238
                    if (this.collapsed) {
×
1239
                        this.updateValidityOnBlur();
×
1240
                    }
1241
                });
1242
        }
1243
    }
1244

1245
    private cacheFocusedInput(): void {
UNCOV
1246
        if (this.hasProjectedInputs) {
×
UNCOV
1247
            this.projectedInputs.forEach(i => {
×
UNCOV
1248
                fromEvent(i.dateTimeEditor.nativeElement, 'focus')
×
1249
                    .pipe(takeUntil(this._destroy$))
UNCOV
1250
                    .subscribe(() => this._focusedInput = i);
×
1251
            });
1252
        }
1253
    }
1254

1255
    private configPositionStrategy(): void {
UNCOV
1256
        this._positionSettings = {
×
1257
            openAnimation: fadeIn,
1258
            closeAnimation: fadeOut,
1259
            offset: 1
1260
        };
UNCOV
1261
        this._dropDownOverlaySettings.positionStrategy = new AutoPositionStrategy(this._positionSettings);
×
1262

UNCOV
1263
        const bundle = this.hasProjectedInputs
×
1264
            ? this.projectedInputs.first?.nativeElement.querySelector('.igx-input-group__bundle')
1265
            : this.element.nativeElement.querySelector('.igx-input-group__bundle');
UNCOV
1266
        this._dropDownOverlaySettings.target = bundle || this.element.nativeElement;
×
1267
    }
1268

1269
    private configOverlaySettings(): void {
UNCOV
1270
        if (this.overlaySettings !== null) {
×
UNCOV
1271
            this._dropDownOverlaySettings = Object.assign({}, this._dropDownOverlaySettings, this.overlaySettings);
×
UNCOV
1272
            this._dialogOverlaySettings = Object.assign({}, this._dialogOverlaySettings, this.overlaySettings);
×
1273
        }
1274
    }
1275

1276
    private initialSetValue() {
1277
        // if there is no value and no ngControl on the picker but we have inputs we may have value set through
1278
        // their ngModels - we should generate our initial control value
UNCOV
1279
        if ((!this.value || (!this.value.start && !this.value.end)) && this.hasProjectedInputs && !this._ngControl) {
×
UNCOV
1280
            const start = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent);
×
UNCOV
1281
            const end = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent);
×
UNCOV
1282
            this._value = {
×
1283
                start: start.dateTimeEditor.value as Date,
1284
                end: end.dateTimeEditor.value as Date
1285
            };
1286
        }
1287
    }
1288

1289
    private updateInputs(): void {
UNCOV
1290
        const start = this.projectedInputs?.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
×
UNCOV
1291
        const end = this.projectedInputs?.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
×
UNCOV
1292
        if (start && end) {
×
UNCOV
1293
            const _value = this.value ? this.toRangeOfDates(this.value) : null;
×
UNCOV
1294
            start.updateInputValue(_value?.start || null);
×
UNCOV
1295
            end.updateInputValue(_value?.end || null);
×
1296
        }
1297
    }
1298

1299
    private updateDisplayFormat(): void {
UNCOV
1300
        this.projectedInputs.forEach(i => {
×
UNCOV
1301
            const input = i as IgxDateRangeInputsBaseComponent;
×
UNCOV
1302
            input.dateTimeEditor.displayFormat = this.displayFormat;
×
1303
        });
1304
    }
1305

1306
    private updateInputFormat(): void {
UNCOV
1307
        this.projectedInputs.forEach(i => {
×
UNCOV
1308
            const input = i as IgxDateRangeInputsBaseComponent;
×
UNCOV
1309
            if (input.dateTimeEditor.inputFormat !== this.inputFormat) {
×
UNCOV
1310
                input.dateTimeEditor.inputFormat = this.inputFormat;
×
1311
            }
1312
        });
1313
    }
1314

1315
    private updateInputLocale(): void {
UNCOV
1316
        this.projectedInputs.forEach(i => {
×
UNCOV
1317
            const input = i as IgxDateRangeInputsBaseComponent;
×
UNCOV
1318
            input.dateTimeEditor.locale = this.locale;
×
UNCOV
1319
            input.dateTimeEditor.updateMask();
×
1320
        });
1321
    }
1322

1323
    protected override onResourceChange(args: CustomEvent<IResourceChangeEventArgs>) {
UNCOV
1324
        super.onResourceChange(args);
×
UNCOV
1325
        if (args.detail.oldLocale !== args.detail.newLocale && this.hasProjectedInputs) {
×
1326
            this.updateInputLocale();
×
1327
            this.updateDisplayFormat();
×
1328
        }
1329
    }
1330

1331
    protected override updateResources(): void {
UNCOV
1332
        this._defaultResourceStrings = getCurrentResourceStrings(DateRangePickerResourceStringsEN, false, this._locale);
×
1333
    }
1334

1335
    private _initializeCalendarContainer(componentInstance: IgxCalendarContainerComponent) {
UNCOV
1336
        this._calendar = componentInstance.calendar;
×
UNCOV
1337
        this._calendar.hasHeader = !this.isDropdown && !this.hideHeader;
×
UNCOV
1338
        this._calendar.locale = this.locale;
×
UNCOV
1339
        this._calendar.selection = CalendarSelection.RANGE;
×
UNCOV
1340
        this._calendar.weekStart = this.weekStart;
×
UNCOV
1341
        this._calendar.hideOutsideDays = this.hideOutsideDays;
×
UNCOV
1342
        this._calendar.monthsViewNumber = this._displayMonthsCount;
×
UNCOV
1343
        this._calendar.showWeekNumbers = this.showWeekNumbers;
×
UNCOV
1344
        this._calendar.headerTitleTemplate = this.headerTitleTemplate;
×
UNCOV
1345
        this._calendar.headerTemplate = this.headerTemplate;
×
UNCOV
1346
        this._calendar.subheaderTemplate = this.subheaderTemplate;
×
UNCOV
1347
        this._calendar.headerOrientation = this.headerOrientation;
×
UNCOV
1348
        this._calendar.orientation = this.orientation;
×
UNCOV
1349
        this._calendar.specialDates = this.specialDates;
×
UNCOV
1350
        this._calendar.selected.pipe(takeUntil(this._destroy$)).subscribe((ev: Date[]) => this.handleSelection(ev));
×
1351

UNCOV
1352
        this._setDisabledDates();
×
UNCOV
1353
        this._setCalendarActiveDate();
×
1354

UNCOV
1355
        componentInstance.mode = this.mode;
×
UNCOV
1356
        componentInstance.closeButtonLabel = !this.isDropdown ? this.doneButtonText : null;
×
UNCOV
1357
        componentInstance.cancelButtonLabel = !this.isDropdown ? this.cancelButtonText : null;
×
UNCOV
1358
        componentInstance.pickerActions = this.pickerActions;
×
UNCOV
1359
        componentInstance.usePredefinedRanges = this.usePredefinedRanges;
×
UNCOV
1360
        componentInstance.customRanges = this.customRanges;
×
UNCOV
1361
        componentInstance.resourceStrings = this.resourceStrings;
×
UNCOV
1362
        componentInstance.calendarClose.pipe(takeUntil(this._destroy$)).subscribe(() => this.close());
×
UNCOV
1363
        componentInstance.calendarCancel.pipe(takeUntil(this._destroy$)).subscribe(() => {
×
UNCOV
1364
            this._value = this._originalValue;
×
UNCOV
1365
            this.close()
×
1366
        });
UNCOV
1367
        componentInstance.rangeSelected
×
1368
        .pipe(takeUntil(this._destroy$))
1369
        .subscribe((r: DateRange) => {
UNCOV
1370
            if (r?.start && r?.end) {
×
UNCOV
1371
            this.select(new Date(r.start), new Date(r.end));
×
1372
            }
1373

UNCOV
1374
            if (this.isDropdown) {
×
UNCOV
1375
            this.close();
×
1376
            }
1377
        });
1378
    }
1379

1380
    private _setDisabledDates(): void {
UNCOV
1381
        if (!this.calendar) {
×
1382
            return;
×
1383
        }
UNCOV
1384
        this.calendar.disabledDates = this.disabledDates ? [...this.disabledDates] : [];
×
UNCOV
1385
        const { minValue, maxValue } = this._getMinMaxDates();
×
UNCOV
1386
        if (minValue) {
×
UNCOV
1387
            this.calendar.disabledDates.push({ type: DateRangeType.Before, dateRange: [minValue] });
×
1388
        }
UNCOV
1389
        if (maxValue) {
×
UNCOV
1390
            this.calendar.disabledDates.push({ type: DateRangeType.After, dateRange: [maxValue] });
×
1391
        }
1392
    }
1393

1394
    private _getMinMaxDates() {
UNCOV
1395
        const minValue = this.parseMinValue(this.minValue);
×
UNCOV
1396
        const maxValue = this.parseMaxValue(this.maxValue);
×
UNCOV
1397
        return { minValue, maxValue };
×
1398
    }
1399

1400
    private _isValueInDisabledRange(value: DateRange) {
UNCOV
1401
        if (value && value.start && value.end && this.disabledDates) {
×
UNCOV
1402
            const isOutsideDisabledRange = Array.from(
×
1403
                calendarRange({
1404
                    start: parseDate(this.value.start),
1405
                    end: parseDate(this.value.end),
1406
                    inclusive: true
UNCOV
1407
                })).every((date) => !isDateInRanges(date, this.disabledDates));
×
UNCOV
1408
            return !isOutsideDisabledRange;
×
1409
        }
UNCOV
1410
        return false;
×
1411
    }
1412

1413
    private _setCalendarActiveDate(value = null): void {
×
UNCOV
1414
        if (this._calendar) {
×
UNCOV
1415
            this._calendar.activeDate = value ?? this.activeDate;
×
UNCOV
1416
            this._calendar.viewDate = value ?? this.activeDate;
×
1417
        }
1418
    }
1419
}
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