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

IgniteUI / igniteui-angular / 23331612584

20 Mar 2026 06:21AM UTC coverage: 89.281% (+0.02%) from 89.264%
23331612584

Pull #16989

github

web-flow
Merge ff99c625a into a4dc50177
Pull Request #16989: refactor(overlay): stop moving to body container & deprecate outlet

14417 of 16980 branches covered (84.91%)

Branch coverage included in aggregate %.

55 of 59 new or added lines in 6 files covered. (93.22%)

4 existing lines in 3 files now uncovered.

29087 of 31747 relevant lines covered (91.62%)

34511.0 hits per line

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

93.23
/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
    private get viewContainerRef(): ViewContainerRef {
445
        return this._viewContainerRef;
61✔
446
    }
447

448
    /** @hidden @internal */
449
    @ViewChild(IgxInputDirective)
450
    public inputDirective: IgxInputDirective;
451

452
    /** @hidden @internal */
453
    @ContentChildren(IgxDateRangeInputsBaseComponent)
454
    public projectedInputs: QueryList<IgxDateRangeInputsBaseComponent>;
455

456
    @ContentChild(IgxLabelDirective)
457
    public label: IgxLabelDirective;
458

459
    @ContentChild(IgxHintDirective)
460
    public hint: IgxHintDirective;
461

462
    @ContentChild(IgxPickerActionsDirective)
463
    public pickerActions: IgxPickerActionsDirective;
464

465
    /** @hidden @internal */
466
    @ContentChild(IgxDateRangeSeparatorDirective, { read: TemplateRef })
467
    public dateSeparatorTemplate: TemplateRef<any>;
468

469

470
    @ContentChild(IgxCalendarHeaderTitleTemplateDirective)
471
    private headerTitleTemplate: IgxCalendarHeaderTitleTemplateDirective;
472

473
    @ContentChild(IgxCalendarHeaderTemplateDirective)
474
    private headerTemplate: IgxCalendarHeaderTemplateDirective;
475

476
    @ContentChild(IgxCalendarSubheaderTemplateDirective)
477
    private subheaderTemplate: IgxCalendarSubheaderTemplateDirective;
478

479
    /** @hidden @internal */
480
    public get dateSeparator(): string {
481
        if (this._dateSeparator === null) {
87✔
482
            return this.resourceStrings.igx_date_range_picker_date_separator;
87✔
483
        }
484
        return this._dateSeparator;
×
485
    }
486

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

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

507
    public set activeDate(value: Date) {
508
        this._activeDate = value;
1✔
509
    }
510

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

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

539
    /** @hidden @internal */
540
    public get singleInputFormat(): string {
541
        if (this.placeholder !== '') {
512✔
542
            return this.placeholder;
2✔
543
        }
544

545
        const format = this.appliedFormat;
510✔
546
        return `${format}${SingleInputDatesConcatenationString}${format}`;
510✔
547
    }
548

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

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

576
    @Input()
577
    public set value(value: DateRange | null) {
578
        this.updateValue(value);
128✔
579
        this.onChangeCallback(value);
128✔
580
        this.valueChange.emit(value);
128✔
581
    }
582

583
    /** @hidden @internal */
584
    public get hasProjectedInputs(): boolean {
585
        return this.projectedInputs?.length > 0;
3,170✔
586
    }
587

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

595
    protected override get toggleContainer(): HTMLElement | undefined {
UNCOV
596
        return this._calendarContainer;
×
597
    }
598

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

605
        return false;
76✔
606
    }
607

608
    private get calendar(): IgxCalendarComponent {
609
        return this._calendar;
842✔
610
    }
611

612
    private get dropdownOverlaySettings(): OverlaySettings {
613
        return Object.assign({}, this._dropDownOverlaySettings, this.overlaySettings);
45✔
614
    }
615

616
    private get dialogOverlaySettings(): OverlaySettings {
617
        return Object.assign({}, this._dialogOverlaySettings, this.overlaySettings);
16✔
618
    }
619

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

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

665
    constructor() {
666
        super();
133✔
667
        this.initLocale();
133✔
668
    }
669

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

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

703
        this._originalValue = this._value
61✔
704
            ? { start: new Date(this._value.start), end: new Date(this._value.end) }
705
            : null;
706

707
        const settings = Object.assign({}, this.isDropdown
61✔
708
            ? this.dropdownOverlaySettings
709
            : this.dialogOverlaySettings
710
            , overlaySettings);
711

712
        this._overlayId = this._overlayService
61✔
713
            .attach(IgxCalendarContainerComponent, this.viewContainerRef, settings);
714
        this.subscribeToOverlayEvents();
61✔
715
        this._overlayService.show(this._overlayId);
61✔
716
    }
717

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

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

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

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

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

794
    /** @hidden @internal */
795
    public writeValue(value: DateRange): void {
796
        this.updateValue(value);
102✔
797
    }
798

799
    /** @hidden @internal */
800
    public registerOnChange(fn: any): void {
801
        this.onChangeCallback = fn;
61✔
802
    }
803

804
    /** @hidden @internal */
805
    public registerOnTouched(fn: any): void {
806
        this.onTouchCallback = fn;
59✔
807
    }
808

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

825
            if (this._isValueInDisabledRange(value)) {
85✔
826
                Object.assign(errors, { dateIsDisabled: true });
1✔
827
            }
828

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

842
        return Object.keys(errors).length > 0 ? errors : null;
363✔
843
    }
844

845
    /** @hidden @internal */
846
    public registerOnValidatorChange?(fn: any): void {
847
        this.onValidatorChange = fn;
60✔
848
    }
849

850
    /** @hidden @internal */
851
    public setDisabledState?(isDisabled: boolean): void {
852
        this.disabled = isDisabled;
60✔
853
    }
854

855
    /** @hidden */
856
    public ngOnInit(): void {
857
        this._ngControl = this._injector.get<NgControl>(NgControl, null);
130✔
858
    }
859

860
    /** @hidden */
861
    public override ngAfterViewInit(): void {
862
        super.ngAfterViewInit();
126✔
863
        this.subscribeToDateEditorEvents();
126✔
864
        this.subscribeToClick();
126✔
865
        this.configPositionStrategy();
126✔
866
        this.configOverlaySettings();
126✔
867
        this.cacheFocusedInput();
126✔
868
        this.attachOnTouched();
126✔
869

870
        this.setRequiredToInputs();
126✔
871

872
        if (this._ngControl) {
126✔
873
            this._statusChanges$ = this._ngControl.statusChanges.subscribe(this.onStatusChanged.bind(this));
52✔
874
        }
875

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

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

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

918
    /** @hidden @internal */
919
    public getEditElement(): HTMLInputElement {
920
        return this.inputDirective!.nativeElement;
76✔
921
    }
922

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

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

947
    private get isTouchedOrDirty(): boolean {
948
        return (this._ngControl.control.touched || this._ngControl.control.dirty);
257✔
949
    }
950

951
    private get hasValidators(): boolean {
952
        return (!!this._ngControl.control.validator || !!this._ngControl.control.asyncValidator);
237✔
953
    }
954

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

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

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

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

1001
            this._initializeCalendarContainer(e.componentRef.instance);
61✔
1002
            this._calendarContainer = e.componentRef.location.nativeElement;
61✔
1003
            this._collapsed = false;
61✔
1004
            this.updateCalendar();
61✔
1005
        });
1006

1007
        this._overlayService.opened.pipe(...this._overlaySubFilter).subscribe(() => {
61✔
1008
            this.calendar.wrapper.nativeElement.focus();
47✔
1009
            this.opened.emit({ owner: this });
47✔
1010
        });
1011

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

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

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

1040
    private updateValue(value: DateRange) {
1041
        this._value = value ? value : null;
230✔
1042
        this.updateInputs();
230✔
1043
        this.updateCalendar();
230✔
1044
    }
1045

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

1060
            if (this.inputDirective) {
1!
1061
                if (!this._ngControl.valid) {
×
1062
                    this.inputDirective.valid = IgxInputState.INVALID;
×
1063
                } else {
1064
                    this.inputDirective.valid = IgxInputState.INITIAL;
×
1065
                }
1066
            }
1067
        }
1068
    }
1069

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

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

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

1101
        return minValue;
268✔
1102
    }
1103

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

1113
        return maxValue;
268✔
1114
    }
1115

1116
    private updateCalendar(): void {
1117
        if (!this.calendar) {
294✔
1118
            return;
172✔
1119
        }
1120
        this._setDisabledDates();
122✔
1121

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

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

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

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

1163
    private toRangeOfDates(range: DateRange): { start: Date; end: Date } {
1164
        let start;
1165
        let end;
1166
        if (!isDate(range.start)) {
513✔
1167
            start = DateTimeUtil.parseIsoDate(range.start);
9✔
1168
        }
1169
        if (!isDate(range.end)) {
513✔
1170
            end = DateTimeUtil.parseIsoDate(range.end);
18✔
1171
        }
1172

1173
        if (start || end) {
513!
1174
            return { start, end };
×
1175
        }
1176

1177
        return { start: range.start as Date, end: range.end as Date };
513✔
1178
    }
1179

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

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

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

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

1262
    private configPositionStrategy(): void {
1263
        this._positionSettings = {
126✔
1264
            openAnimation: fadeIn,
1265
            closeAnimation: fadeOut,
1266
            offset: 1
1267
        };
1268
        this._dropDownOverlaySettings.positionStrategy = new AutoPositionStrategy(this._positionSettings);
126✔
1269

1270
        const bundle = this.hasProjectedInputs
126✔
1271
            ? this.projectedInputs.first?.nativeElement.querySelector('.igx-input-group__bundle')
1272
            : this.element.nativeElement.querySelector('.igx-input-group__bundle');
1273
        this._dropDownOverlaySettings.target = bundle || this.element.nativeElement;
126!
1274
    }
1275

1276
    private configOverlaySettings(): void {
1277
        if (this.overlaySettings !== null) {
126✔
1278
            this._dropDownOverlaySettings = Object.assign({}, this._dropDownOverlaySettings, this.overlaySettings);
126✔
1279
            this._dialogOverlaySettings = Object.assign({}, this._dialogOverlaySettings, this.overlaySettings);
126✔
1280
        }
1281
    }
1282

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

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

1306
    private updateDisplayFormat(): void {
1307
        this.projectedInputs.forEach(i => {
140✔
1308
            const input = i as IgxDateRangeInputsBaseComponent;
132✔
1309
            input.dateTimeEditor.displayFormat = this.displayFormat;
132✔
1310
        });
1311
    }
1312

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

1322
    private updateInputLocale(): void {
1323
        this.projectedInputs.forEach(i => {
3✔
1324
            const input = i as IgxDateRangeInputsBaseComponent;
6✔
1325
            input.dateTimeEditor.locale = this.locale;
6✔
1326
            input.dateTimeEditor.updateMask();
6✔
1327
        });
1328
    }
1329

1330
    protected override onResourceChange(args: CustomEvent<IResourceChangeEventArgs>) {
1331
        super.onResourceChange(args);
975✔
1332
        if (args.detail.oldLocale !== args.detail.newLocale && this.hasProjectedInputs) {
975!
1333
            this.updateInputLocale();
×
1334
            this.updateDisplayFormat();
×
1335
        }
1336
    }
1337

1338
    protected override updateResources(): void {
1339
        this._defaultResourceStrings = getCurrentResourceStrings(DateRangePickerResourceStringsEN, false, this._locale);
982✔
1340
    }
1341

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

1359
        this._setDisabledDates();
61✔
1360
        this._setCalendarActiveDate();
61✔
1361

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

1381
            if (this.isDropdown) {
6✔
1382
            this.close();
6✔
1383
            }
1384
        });
1385
    }
1386

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

1401
    private _getMinMaxDates() {
1402
        const minValue = this.parseMinValue(this.minValue);
268✔
1403
        const maxValue = this.parseMaxValue(this.maxValue);
268✔
1404
        return { minValue, maxValue };
268✔
1405
    }
1406

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

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