• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In
No new info detected.

IgniteUI / igniteui-angular / 23751240625

30 Mar 2026 02:53PM UTC coverage: 89.468% (+0.01%) from 89.456%
23751240625

push

github

web-flow
refactor(overlay): stop moving to body container & deprecate outlet (#16989)

Co-authored-by: Damyan Petev <damyanpetev@users.noreply.github.com>

14679 of 17249 branches covered (85.1%)

Branch coverage included in aggregate %.

61 of 65 new or added lines in 6 files covered. (93.85%)

5 existing lines in 3 files now uncovered.

29645 of 32293 relevant lines covered (91.8%)

33944.3 hits per line

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

93.22
/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 {
110
    protected platform = inject(PlatformUtil);
133✔
111
    private _injector = inject(Injector);
133✔
112
    private _cdr = inject(ChangeDetectorRef);
133✔
113
    private _overlayService = inject<IgxOverlayService>(IgxOverlayService);
133✔
114

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

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

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

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

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

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

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

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

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

251
    public override get displayFormat(): string {
252
        return super.displayFormat;
2,322✔
253
    }
254

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

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

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

285
    public get minValue(): Date | string {
286
        return this._minValue;
270✔
287
    }
288

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

301
    public get maxValue(): Date | string {
302
        return this._maxValue;
270✔
303
    }
304

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

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

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

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

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

367
    /**
368
     * Gets/Sets the container used for the popup element.
369
     *
370
     * @remarks
371
     *  `outlet` is an instance of `IgxOverlayOutletDirective` or an `ElementRef`.
372
     * @example
373
     * ```html
374
     * <div igxOverlayOutlet #outlet="overlay-outlet"></div>
375
     * //..
376
     * <igx-date-range-picker [outlet]="outlet"></igx-date-range-picker>
377
     * //..
378
     * ```
379
     *
380
     * @deprecated in version 21.2.0. Overlays now use the HTML Popover API and no longer move to the document
381
     * body by default, so using outlet is also no longer needed - just define the overlay in the intended
382
     * DOM tree position instead.
383
     */
384
    @Input()
385
    public override outlet: IgxOverlayOutletDirective | ElementRef<any>;
386

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

401
    /** @hidden @internal */
402
    @Input({ transform: booleanAttribute })
403
    public readOnly = false;
133✔
404

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

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

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

434
    @Output()
435
    public valueChange = new EventEmitter<DateRange>();
133✔
436

437
    /** @hidden @internal */
438
    @HostBinding('class.igx-date-range-picker')
439
    public cssClass = 'igx-date-range-picker';
133✔
440

441
    @ViewChild("container", { read: ViewContainerRef })
442
    private viewContainerRef: ViewContainerRef;
443

444
    /** @hidden @internal */
445
    @ViewChild(IgxInputDirective)
446
    public inputDirective: IgxInputDirective;
447

448
    /** @hidden @internal */
449
    @ContentChildren(IgxDateRangeInputsBaseComponent)
450
    public projectedInputs: QueryList<IgxDateRangeInputsBaseComponent>;
451

452
    @ContentChild(IgxLabelDirective)
453
    public label: IgxLabelDirective;
454

455
    @ContentChild(IgxHintDirective)
456
    public hint: IgxHintDirective;
457

458
    @ContentChild(IgxPickerActionsDirective)
459
    public pickerActions: IgxPickerActionsDirective;
460

461
    /** @hidden @internal */
462
    @ContentChild(IgxDateRangeSeparatorDirective, { read: TemplateRef })
463
    public dateSeparatorTemplate: TemplateRef<any>;
464

465

466
    @ContentChild(IgxCalendarHeaderTitleTemplateDirective)
467
    private headerTitleTemplate: IgxCalendarHeaderTitleTemplateDirective;
468

469
    @ContentChild(IgxCalendarHeaderTemplateDirective)
470
    private headerTemplate: IgxCalendarHeaderTemplateDirective;
471

472
    @ContentChild(IgxCalendarSubheaderTemplateDirective)
473
    private subheaderTemplate: IgxCalendarSubheaderTemplateDirective;
474

475
    /** @hidden @internal */
476
    public get dateSeparator(): string {
477
        if (this._dateSeparator === null) {
87✔
478
            return this.resourceStrings.igx_date_range_picker_date_separator;
87✔
479
        }
480
        return this._dateSeparator;
×
481
    }
482

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

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

503
    public set activeDate(value: Date) {
504
        this._activeDate = value;
1✔
505
    }
506

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

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

535
    /** @hidden @internal */
536
    public get singleInputFormat(): string {
537
        if (this.placeholder !== '') {
512✔
538
            return this.placeholder;
2✔
539
        }
540

541
        const format = this.appliedFormat;
510✔
542
        return `${format}${SingleInputDatesConcatenationString}${format}`;
510✔
543
    }
544

545
    /**
546
     * Gets calendar state.
547
     *
548
     * ```typescript
549
     * let state = this.dateRange.collapsed;
550
     * ```
551
     */
552
    public override get collapsed(): boolean {
553
        return this._collapsed;
796✔
554
    }
555

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

572
    @Input()
573
    public set value(value: DateRange | null) {
574
        this.updateValue(value);
128✔
575
        this.onChangeCallback(value);
128✔
576
        this.valueChange.emit(value);
128✔
577
    }
578

579
    /** @hidden @internal */
580
    public get hasProjectedInputs(): boolean {
581
        return this.projectedInputs?.length > 0;
3,170✔
582
    }
583

584
    /** @hidden @internal */
585
    public get separatorClass(): string {
586
        const classes = ['igx-date-range-picker__label'];
528✔
587
        if (this.hint) classes.push('input-has-hint');
528!
588
        return classes.join(' ');
528✔
589
    }
590

591
    protected override get toggleContainer(): HTMLElement | undefined {
UNCOV
592
        return this._calendarContainer;
×
593
    }
594

595
    private get required(): boolean {
596
        if (this._ngControl && this._ngControl.control && this._ngControl.control.validator) {
264✔
597
            const error = this._ngControl.control.validator({} as AbstractControl);
188✔
598
            return (error && error.required) ? true : false;
188!
599
        }
600

601
        return false;
76✔
602
    }
603

604
    private get calendar(): IgxCalendarComponent {
605
        return this._calendar;
842✔
606
    }
607

608
    private get dropdownOverlaySettings(): OverlaySettings {
609
        return Object.assign({}, this._dropDownOverlaySettings, this.overlaySettings);
45✔
610
    }
611

612
    private get dialogOverlaySettings(): OverlaySettings {
613
        return Object.assign({}, this._dialogOverlaySettings, this.overlaySettings);
16✔
614
    }
615

616
    private get _firstDefinedInRange(): Date | null {
617
        if (!this.value) {
597✔
618
            return null;
229✔
619
        }
620
        const range = this.toRangeOfDates(this.value);
368✔
621
        return range?.start ?? range?.end ?? null;
368!
622
    }
623

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

661
    constructor() {
662
        super();
133✔
663
        this.initLocale();
133✔
664
    }
665

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

684
    /**
685
     * Opens the date range picker's dropdown or dialog.
686
     *
687
     * @example
688
     * ```html
689
     * <igx-date-range-picker #dateRange></igx-date-range-picker>
690
     *
691
     * <button type="button" igxButton (click)="dateRange.open()">Open Dialog</button
692
     * ```
693
     */
694
    public open(overlaySettings?: OverlaySettings): void {
695
        if (!this.collapsed || this.disabled || this.readOnly) {
66✔
696
            return;
5✔
697
        }
698

699
        this._originalValue = this._value
61✔
700
            ? { start: new Date(this._value.start), end: new Date(this._value.end) }
701
            : null;
702

703
        const settings = Object.assign({}, this.isDropdown
61✔
704
            ? this.dropdownOverlaySettings
705
            : this.dialogOverlaySettings
706
            , overlaySettings);
707

708
        this._overlayId = this._overlayService
61✔
709
            .attach(IgxCalendarContainerComponent, this.viewContainerRef, settings);
710
        this.subscribeToOverlayEvents();
61✔
711
        this._overlayService.show(this._overlayId);
61✔
712
    }
713

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

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

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

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

779
        this.value = null;
4✔
780
        this._calendar?.deselectDate();
4✔
781
        if (this.hasProjectedInputs) {
4✔
782
            this.projectedInputs.forEach((i) => {
2✔
783
                i.inputDirective.clear();
4✔
784
            });
785
        } else {
786
            this.inputDirective.clear();
2✔
787
        }
788
    }
789

790
    /** @hidden @internal */
791
    public writeValue(value: DateRange): void {
792
        this.updateValue(value);
102✔
793
    }
794

795
    /** @hidden @internal */
796
    public registerOnChange(fn: any): void {
797
        this.onChangeCallback = fn;
61✔
798
    }
799

800
    /** @hidden @internal */
801
    public registerOnTouched(fn: any): void {
802
        this.onTouchCallback = fn;
59✔
803
    }
804

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

821
            if (this._isValueInDisabledRange(value)) {
85✔
822
                Object.assign(errors, { dateIsDisabled: true });
1✔
823
            }
824

825
            const { minValue, maxValue } = this._getMinMaxDates();
85✔
826
            const start = parseDate(value.start);
85✔
827
            const end = parseDate(value.end);
85✔
828
            if ((minValue && start && DateTimeUtil.lessThanMinValue(start, minValue, false))
85✔
829
                || (minValue && end && DateTimeUtil.lessThanMinValue(end, minValue, false))) {
830
                Object.assign(errors, { minValue: true });
2✔
831
            }
832
            if ((maxValue && start && DateTimeUtil.greaterThanMaxValue(start, maxValue, false))
85✔
833
                || (maxValue && end && DateTimeUtil.greaterThanMaxValue(end, maxValue, false))) {
834
                Object.assign(errors, { maxValue: true });
1✔
835
            }
836
        }
837

838
        return Object.keys(errors).length > 0 ? errors : null;
363✔
839
    }
840

841
    /** @hidden @internal */
842
    public registerOnValidatorChange?(fn: any): void {
843
        this.onValidatorChange = fn;
60✔
844
    }
845

846
    /** @hidden @internal */
847
    public setDisabledState?(isDisabled: boolean): void {
848
        this.disabled = isDisabled;
60✔
849
    }
850

851
    /** @hidden */
852
    public ngOnInit(): void {
853
        this._ngControl = this._injector.get<NgControl>(NgControl, null);
130✔
854
    }
855

856
    /** @hidden */
857
    public override ngAfterViewInit(): void {
858
        super.ngAfterViewInit();
126✔
859
        this.subscribeToDateEditorEvents();
126✔
860
        this.subscribeToClick();
126✔
861
        this.configPositionStrategy();
126✔
862
        this.configOverlaySettings();
126✔
863
        this.cacheFocusedInput();
126✔
864
        this.attachOnTouched();
126✔
865

866
        this.setRequiredToInputs();
126✔
867

868
        if (this._ngControl) {
126✔
869
            this._statusChanges$ = this._ngControl.statusChanges.subscribe(this.onStatusChanged.bind(this));
52✔
870
        }
871

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

890
    /** @hidden @internal */
891
    public ngOnChanges(changes: SimpleChanges): void {
892
        if (changes['displayFormat'] && this.hasProjectedInputs) {
118✔
893
            this.updateDisplayFormat();
11✔
894
        }
895
        if (changes['inputFormat'] && this.hasProjectedInputs) {
118✔
896
            this.updateInputFormat();
4✔
897
        }
898
        if (changes['disabled']) {
118✔
899
            this.updateDisabledState();
93✔
900
        }
901
    }
902

903
    /** @hidden @internal */
904
    public override ngOnDestroy(): void {
905
        super.ngOnDestroy();
79✔
906
        if (this._statusChanges$) {
79✔
907
            this._statusChanges$.unsubscribe();
32✔
908
        }
909
        if (this._overlayId) {
79!
UNCOV
910
            this._overlayService.detach(this._overlayId);
×
911
        }
912
    }
913

914
    /** @hidden @internal */
915
    public getEditElement(): HTMLInputElement {
916
        return this.inputDirective!.nativeElement;
76✔
917
    }
918

919
    protected onStatusChanged = () => {
133✔
920
        if (this.inputGroup) {
137✔
921
            this.setValidityState(this.inputDirective, this.inputGroup.isFocused);
7✔
922
        } else if (this.hasProjectedInputs) {
130✔
923
            this.projectedInputs
130✔
924
                .forEach((i) => {
925
                    this.setValidityState(i.inputDirective, i.isFocused);
260✔
926
                });
927
        }
928
        this.setRequiredToInputs();
137✔
929
    };
930

931
    private setValidityState(inputDirective: IgxInputDirective, isFocused: boolean) {
932
        if (this._ngControl && !this._ngControl.disabled && this.isTouchedOrDirty) {
267✔
933
            if (this.hasValidators && isFocused) {
237✔
934
                inputDirective.valid = this._ngControl.valid ? IgxInputState.VALID : IgxInputState.INVALID;
22✔
935
            } else {
936
                inputDirective.valid = this._ngControl.valid ? IgxInputState.INITIAL : IgxInputState.INVALID;
215✔
937
            }
938
        } else {
939
            inputDirective.valid = IgxInputState.INITIAL;
30✔
940
        }
941
    }
942

943
    private get isTouchedOrDirty(): boolean {
944
        return (this._ngControl.control.touched || this._ngControl.control.dirty);
257✔
945
    }
946

947
    private get hasValidators(): boolean {
948
        return (!!this._ngControl.control.validator || !!this._ngControl.control.asyncValidator);
237✔
949
    }
950

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

963
    private handleClosing(e: IBaseCancelableBrowserEventArgs): void {
964
        const args = { owner: this, cancel: e?.cancel, event: e?.event };
50✔
965
        this.closing.emit(args);
50✔
966
        e.cancel = args.cancel;
50✔
967
        if (args.cancel) {
50✔
968
            return;
3✔
969
        }
970

971
        if (this.isDropdown && e?.event && !this.isFocused) {
47!
972
            // outside click
973
            this.updateValidityOnBlur();
×
974
        } else {
975
            this.onTouchCallback();
47✔
976
            // input click
977
            if (this.hasProjectedInputs && this._focusedInput) {
47✔
978
                this._focusedInput.setFocus();
7✔
979
            }
980
            if (this.inputDirective) {
47✔
981
                this.inputDirective.focus();
18✔
982
            }
983
        }
984
    }
985

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

997
            this._initializeCalendarContainer(e.componentRef.instance);
61✔
998
            this._calendarContainer = e.componentRef.location.nativeElement;
61✔
999
            this._collapsed = false;
61✔
1000
            this.updateCalendar();
61✔
1001
        });
1002

1003
        this._overlayService.opened.pipe(...this._overlaySubFilter).subscribe(() => {
61✔
1004
            this.calendar.wrapper.nativeElement.focus();
47✔
1005
            this.opened.emit({ owner: this });
47✔
1006
        });
1007

1008
        this._overlayService.closing.pipe(...this._overlaySubFilter).subscribe((e: OverlayCancelableEventArgs) => {
61✔
1009
            const isEscape = e.event && (e.event as KeyboardEvent).key === this.platform.KEYMAP.ESCAPE;
50✔
1010
            if (this.isProjectedInputTarget(e.event) && !isEscape) {
50✔
1011
                e.cancel = true;
1✔
1012
            }
1013
            this.handleClosing(e as OverlayCancelableEventArgs);
50✔
1014
        });
1015

1016
        this._overlayService.closed.pipe(...this._overlaySubFilter).subscribe(() => {
61✔
1017
            this._overlayService.detach(this._overlayId);
19✔
1018
            this._collapsed = true;
19✔
1019
            this._overlayId = null;
19✔
1020
            this._calendar = null;
19✔
1021
            this._calendarContainer = undefined;
19✔
1022
            this.closed.emit({ owner: this });
19✔
1023
        });
1024
    }
1025

1026
    private isProjectedInputTarget(event: Event): boolean {
1027
        if (!this.hasProjectedInputs || !event) {
50✔
1028
            return false;
47✔
1029
        }
1030
        const path = event.composed ? event.composedPath() : [event.target];
3!
1031
        return this.projectedInputs.some(i =>
3✔
1032
            path.includes(i.dateTimeEditor.nativeElement)
5✔
1033
        );
1034
    }
1035

1036
    private updateValue(value: DateRange) {
1037
        this._value = value ? value : null;
230✔
1038
        this.updateInputs();
230✔
1039
        this.updateCalendar();
230✔
1040
    }
1041

1042
    private updateValidityOnBlur() {
1043
        this._focusedInput = null;
2✔
1044
        this.onTouchCallback();
2✔
1045
        if (this._ngControl) {
2✔
1046
            if (this.hasProjectedInputs) {
1✔
1047
                this.projectedInputs.forEach(i => {
1✔
1048
                    if (!this._ngControl.valid) {
2!
1049
                        i.updateInputValidity(IgxInputState.INVALID);
2✔
1050
                    } else {
1051
                        i.updateInputValidity(IgxInputState.INITIAL);
×
1052
                    }
1053
                });
1054
            }
1055

1056
            if (this.inputDirective) {
1!
1057
                if (!this._ngControl.valid) {
×
1058
                    this.inputDirective.valid = IgxInputState.INVALID;
×
1059
                } else {
1060
                    this.inputDirective.valid = IgxInputState.INITIAL;
×
1061
                }
1062
            }
1063
        }
1064
    }
1065

1066
    private updateDisabledState() {
1067
        if (this.hasProjectedInputs) {
219✔
1068
            const start = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
57✔
1069
            const end = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
114✔
1070
            start.inputDirective.disabled = this.disabled;
57✔
1071
            end.inputDirective.disabled = this.disabled;
57✔
1072
            return;
57✔
1073
        }
1074
    }
1075

1076
    private setRequiredToInputs(): void {
1077
        // workaround for igxInput setting required
1078
        Promise.resolve().then(() => {
263✔
1079
            const isRequired = this.required;
263✔
1080
            if (this.inputGroup && this.inputGroup.isRequired !== isRequired) {
263✔
1081
                this.inputGroup.isRequired = isRequired;
4✔
1082
            } else if (this.hasProjectedInputs && this._ngControl) {
259✔
1083
                this.projectedInputs.forEach(i => i.isRequired = isRequired);
358✔
1084
            }
1085
        });
1086
    }
1087

1088
    private parseMinValue(value: string | Date): Date | null {
1089
        let minValue: Date = parseDate(value);
268✔
1090
        if (!minValue && this.hasProjectedInputs) {
268✔
1091
            const start = this.projectedInputs.filter(i => i instanceof IgxDateRangeStartComponent)[0];
350✔
1092
            if (start) {
175✔
1093
                minValue = parseDate(start.dateTimeEditor.minValue);
175✔
1094
            }
1095
        }
1096

1097
        return minValue;
268✔
1098
    }
1099

1100
    private parseMaxValue(value: string | Date): Date | null {
1101
        let maxValue: Date = parseDate(value);
268✔
1102
        if (!maxValue && this.projectedInputs) {
268✔
1103
            const end = this.projectedInputs.filter(i => i instanceof IgxDateRangeEndComponent)[0];
350✔
1104
            if (end) {
258✔
1105
                maxValue = parseDate(end.dateTimeEditor.maxValue);
175✔
1106
            }
1107
        }
1108

1109
        return maxValue;
268✔
1110
    }
1111

1112
    private updateCalendar(): void {
1113
        if (!this.calendar) {
294✔
1114
            return;
172✔
1115
        }
1116
        this._setDisabledDates();
122✔
1117

1118
        const range: Date[] = [];
122✔
1119
        if (this.value) {
122✔
1120
            const _value = this.toRangeOfDates(this.value);
60✔
1121
            if (_value.start && _value.end) {
60✔
1122
                if (DateTimeUtil.greaterThanMaxValue(_value.start, _value.end)) {
57✔
1123
                    this.swapEditorDates();
2✔
1124
                }
1125
            }
1126
            if (_value.start) {
60✔
1127
                range.push(_value.start);
59✔
1128
            }
1129
            if (_value.end) {
60✔
1130
                range.push(_value.end);
58✔
1131
            }
1132
        }
1133

1134
        if (range.length > 0) {
122✔
1135
            this.calendar.selectDate(range);
60✔
1136
        } else if (range.length === 0 && this.calendar.monthViews) {
62✔
1137
            this.calendar.deselectDate();
6✔
1138
        }
1139
        this._setCalendarActiveDate();
122✔
1140
        this._cdr.detectChanges();
122✔
1141
    }
1142

1143
    private swapEditorDates(): void {
1144
        if (this.hasProjectedInputs) {
2✔
1145
            const start = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
2✔
1146
            const end = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
4✔
1147
            [start.dateTimeEditor.value, end.dateTimeEditor.value] = [end.dateTimeEditor.value, start.dateTimeEditor.value];
2✔
1148
            [this.value.start, this.value.end] = [this.value.end, this.value.start];
2✔
1149
        }
1150
    }
1151

1152
    private extractRange(selection: Date[]): DateRange {
1153
        return {
45✔
1154
            start: selection[0] || null,
45!
1155
            end: selection.length > 0 ? selection[selection.length - 1] : null
45!
1156
        };
1157
    }
1158

1159
    private toRangeOfDates(range: DateRange): { start: Date; end: Date } {
1160
        let start;
1161
        let end;
1162
        if (!isDate(range.start)) {
513✔
1163
            start = DateTimeUtil.parseIsoDate(range.start);
9✔
1164
        }
1165
        if (!isDate(range.end)) {
513✔
1166
            end = DateTimeUtil.parseIsoDate(range.end);
18✔
1167
        }
1168

1169
        if (start || end) {
513!
1170
            return { start, end };
×
1171
        }
1172

1173
        return { start: range.start as Date, end: range.end as Date };
513✔
1174
    }
1175

1176
    private subscribeToClick() {
1177
        const inputs = this.hasProjectedInputs
126✔
1178
            ? this.projectedInputs.map(i => i.inputDirective.nativeElement)
104✔
1179
            : [this.getEditElement()];
1180
        inputs.forEach(input => {
126✔
1181
            fromEvent(input, 'click')
178✔
1182
                .pipe(takeUntil(this._destroy$))
1183
                .subscribe(() => {
1184
                    if (!this.isDropdown) {
4✔
1185
                        this.toggle();
2✔
1186
                    }
1187
                });
1188
        });
1189
    }
1190

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

1226
    private attachOnTouched(): void {
1227
        if (this.hasProjectedInputs) {
126✔
1228
            this.projectedInputs.forEach(i => {
52✔
1229
                fromEvent(i.dateTimeEditor.nativeElement, 'blur')
104✔
1230
                    .pipe(takeUntil(this._destroy$))
1231
                    .subscribe(() => {
1232
                        if (this.collapsed) {
12✔
1233
                            this.updateValidityOnBlur();
1✔
1234
                        }
1235
                    });
1236
            });
1237
        } else {
1238
            fromEvent(this.inputDirective.nativeElement, 'blur')
74✔
1239
                .pipe(takeUntil(this._destroy$))
1240
                .subscribe(() => {
1241
                    if (this.collapsed) {
8!
1242
                        this.updateValidityOnBlur();
×
1243
                    }
1244
                });
1245
        }
1246
    }
1247

1248
    private cacheFocusedInput(): void {
1249
        if (this.hasProjectedInputs) {
126✔
1250
            this.projectedInputs.forEach(i => {
52✔
1251
                fromEvent(i.dateTimeEditor.nativeElement, 'focus')
104✔
1252
                    .pipe(takeUntil(this._destroy$))
1253
                    .subscribe(() => this._focusedInput = i);
16✔
1254
            });
1255
        }
1256
    }
1257

1258
    private configPositionStrategy(): void {
1259
        this._positionSettings = {
126✔
1260
            openAnimation: fadeIn,
1261
            closeAnimation: fadeOut,
1262
            offset: 1
1263
        };
1264
        this._dropDownOverlaySettings.positionStrategy = new AutoPositionStrategy(this._positionSettings);
126✔
1265

1266
        const bundle = this.hasProjectedInputs
126✔
1267
            ? this.projectedInputs.first?.nativeElement.querySelector('.igx-input-group__bundle')
1268
            : this.element.nativeElement.querySelector('.igx-input-group__bundle');
1269
        this._dropDownOverlaySettings.target = bundle || this.element.nativeElement;
126!
1270
    }
1271

1272
    private configOverlaySettings(): void {
1273
        if (this.overlaySettings !== null) {
126✔
1274
            this._dropDownOverlaySettings = Object.assign({}, this._dropDownOverlaySettings, this.overlaySettings);
126✔
1275
            this._dialogOverlaySettings = Object.assign({}, this._dialogOverlaySettings, this.overlaySettings);
126✔
1276
        }
1277
    }
1278

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

1292
    private updateInputs(): void {
1293
        const start = this.projectedInputs?.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
356✔
1294
        const end = this.projectedInputs?.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
364✔
1295
        if (start && end) {
356✔
1296
            const _value = this.value ? this.toRangeOfDates(this.value) : null;
182✔
1297
            start.updateInputValue(_value?.start || null);
182✔
1298
            end.updateInputValue(_value?.end || null);
182✔
1299
        }
1300
    }
1301

1302
    private updateDisplayFormat(): void {
1303
        this.projectedInputs.forEach(i => {
140✔
1304
            const input = i as IgxDateRangeInputsBaseComponent;
132✔
1305
            input.dateTimeEditor.displayFormat = this.displayFormat;
132✔
1306
        });
1307
    }
1308

1309
    private updateInputFormat(): void {
1310
        this.projectedInputs.forEach(i => {
130✔
1311
            const input = i as IgxDateRangeInputsBaseComponent;
112✔
1312
            if (input.dateTimeEditor.inputFormat !== this.inputFormat) {
112✔
1313
                input.dateTimeEditor.inputFormat = this.inputFormat;
8✔
1314
            }
1315
        });
1316
    }
1317

1318
    private updateInputLocale(): void {
1319
        this.projectedInputs.forEach(i => {
3✔
1320
            const input = i as IgxDateRangeInputsBaseComponent;
6✔
1321
            input.dateTimeEditor.locale = this.locale;
6✔
1322
            input.dateTimeEditor.updateMask();
6✔
1323
        });
1324
    }
1325

1326
    protected override onResourceChange(args: CustomEvent<IResourceChangeEventArgs>) {
1327
        super.onResourceChange(args);
757✔
1328
        if (args.detail.oldLocale !== args.detail.newLocale && this.hasProjectedInputs) {
757!
1329
            this.updateInputLocale();
×
1330
            this.updateDisplayFormat();
×
1331
        }
1332
    }
1333

1334
    protected override updateResources(): void {
1335
        this._defaultResourceStrings = getCurrentResourceStrings(DateRangePickerResourceStringsEN, false, this._locale);
764✔
1336
    }
1337

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

1355
        this._setDisabledDates();
61✔
1356
        this._setCalendarActiveDate();
61✔
1357

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

1377
            if (this.isDropdown) {
6✔
1378
            this.close();
6✔
1379
            }
1380
        });
1381
    }
1382

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

1397
    private _getMinMaxDates() {
1398
        const minValue = this.parseMinValue(this.minValue);
268✔
1399
        const maxValue = this.parseMaxValue(this.maxValue);
268✔
1400
        return { minValue, maxValue };
268✔
1401
    }
1402

1403
    private _isValueInDisabledRange(value: DateRange) {
1404
        if (value && value.start && value.end && this.disabledDates) {
85✔
1405
            const isOutsideDisabledRange = Array.from(
2✔
1406
                calendarRange({
1407
                    start: parseDate(this.value.start),
1408
                    end: parseDate(this.value.end),
1409
                    inclusive: true
1410
                })).every((date) => !isDateInRanges(date, this.disabledDates));
5✔
1411
            return !isOutsideDisabledRange;
2✔
1412
        }
1413
        return false;
83✔
1414
    }
1415

1416
    private _setCalendarActiveDate(value = null): void {
228✔
1417
        if (this._calendar) {
249✔
1418
            this._calendar.activeDate = value ?? this.activeDate;
225✔
1419
            this._calendar.viewDate = value ?? this.activeDate;
225✔
1420
        }
1421
    }
1422
}
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