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

IgniteUI / igniteui-angular / 30891705717

04 Aug 2026 08:22AM UTC coverage: 90.16% (-0.03%) from 90.187%
30891705717

Pull #15125

github

web-flow
Merge 19b57fb2e into 0a0755cbb
Pull Request #15125: refactor(*): bundle styles with components

14975 of 17448 branches covered (85.83%)

Branch coverage included in aggregate %.

30133 of 32583 relevant lines covered (92.48%)

37490.31 hits per line

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

92.86
/projects/igniteui-angular/date-picker/src/date-range-picker/date-range-picker.component.ts
1
import {
2
    AfterViewInit,
3
    booleanAttribute,
4
    ChangeDetectorRef,
5
    Component,
6
    ContentChild,
7
    ContentChildren,
8
    EventEmitter,
9
    HostBinding,
10
    HostListener,
11
    Injector,
12
    Input,
13
    OnChanges,
14
    OnDestroy,
15
    OnInit,
16
    Output,
17
    QueryList,
18
    SimpleChanges,
19
    TemplateRef,
20
    ViewChild,
21
    ViewContainerRef,
22
    inject,
23
    ChangeDetectionStrategy,
24
    ViewEncapsulation,
25
} from '@angular/core';
26
import { NgTemplateOutlet } from '@angular/common';
27
import {
28
    AbstractControl, ControlValueAccessor, NgControl,
29
    NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator
30
} from '@angular/forms';
31

32
import { fromEvent, merge, MonoTypeOperatorFunction, noop, Subscription } from 'rxjs';
33
import { filter, takeUntil } from 'rxjs/operators';
34

35
import {
36
    CalendarSelection,
37
    IgxCalendarComponent,
38
    IgxCalendarHeaderTemplateDirective,
39
    IgxCalendarHeaderTitleTemplateDirective,
40
    IgxCalendarSubheaderTemplateDirective
41
} from 'igniteui-angular/calendar';
42
import {
43
    DateRangeDescriptor,
44
    DateRangeType,
45
    DateRangePickerResourceStringsEN,
46
    IDateRangePickerResourceStrings,
47
    clamp,
48
    IBaseCancelableBrowserEventArgs,
49
    isDate,
50
    parseDate,
51
    PlatformUtil,
52
    getCurrentResourceStrings,
53
    AutoPositionStrategy,
54
    IgxOverlayService,
55
    OverlayCancelableEventArgs,
56
    OverlayEventArgs,
57
    OverlaySettings,
58
    PositionSettings,
59
    calendarRange,
60
    CustomDateRange,
61
    DateRange,
62
    DateTimeUtil,
63
    IgxPickerActionsDirective,
64
    isDateInRanges,
65
    PickerCalendarOrientation,
66
    THEME_TOKEN,
67
    ThemeToken
68
} from 'igniteui-angular/core';
69
import { IgxCalendarContainerComponent } from '../date-picker/calendar-container/calendar-container.component';
70
import { PickerBaseDirective } from '../date-picker/picker-base.directive';
71
import {
72
    IgxInputDirective,
73
    IgxInputGroupComponent,
74
    IgxInputState,
75
    IgxLabelDirective,
76
    IgxSuffixDirective,
77
    IgxPrefixDirective,
78
    IgxReadOnlyInputDirective,
79
    IgxHintDirective
80
} from 'igniteui-angular/input-group';
81
import {
82
    IgxDateRangeEndComponent,
83
    IgxDateRangeInputsBaseComponent,
84
    IgxDateRangeSeparatorDirective,
85
    IgxDateRangeStartComponent,
86
    DateRangePickerFormatPipe,
87
} from './date-range-picker-inputs.common';
88
import { IgxIconComponent } from 'igniteui-angular/icon';
89
import { fadeIn, fadeOut } from 'igniteui-angular/animations';
90
import { IResourceChangeEventArgs } from 'igniteui-i18n-core';
91

92
const SingleInputDatesConcatenationString = ' - ';
3✔
93

94
/**
95
 * Provides the ability to select a range of dates from a calendar UI or editable inputs.
96
 *
97
 * @igxModule IgxDateRangeModule
98
 *
99
 * @igxTheme igx-input-group-theme, igx-calendar-theme, igx-date-range-picker-theme
100
 *
101
 * @igxKeywords date, range, date range, date picker
102
 *
103
 * @igxGroup scheduling
104
 *
105
 * @remarks
106
 * It displays the range selection in a single or two input fields.
107
 * The default template displays a single *readonly* input field
108
 * while projecting `igx-date-range-start` and `igx-date-range-end`
109
 * displays two *editable* input fields.
110
 *
111
 * @example
112
 * ```html
113
 * <igx-date-range-picker mode="dropdown"></igx-date-range-picker>
114
 * ```
115
 */
116
@Component({
117
    selector: 'igx-date-range-picker',
118
    templateUrl: './date-range-picker.component.html',
119
    styleUrls: ['../date-picker/date-picker.component.css', 'date-range-picker.component.css'],
120
    encapsulation: ViewEncapsulation.None,
121
    providers: [
122
        { provide: NG_VALUE_ACCESSOR, useExisting: IgxDateRangePickerComponent, multi: true },
123
        { provide: NG_VALIDATORS, useExisting: IgxDateRangePickerComponent, multi: true }
124
    ],
125
    changeDetection: ChangeDetectionStrategy.Eager,
126
    imports: [
127
        NgTemplateOutlet,
128
        IgxIconComponent,
129
        IgxInputGroupComponent,
130
        IgxInputDirective,
131
        IgxPrefixDirective,
132
        IgxSuffixDirective,
133
        IgxReadOnlyInputDirective,
134
        DateRangePickerFormatPipe
135
    ]
136
})
137
export class IgxDateRangePickerComponent extends PickerBaseDirective
3✔
138
    implements OnChanges, OnInit, AfterViewInit, OnDestroy, ControlValueAccessor, Validator {
139
    protected platform = inject(PlatformUtil);
134✔
140
    private themeToken = inject<ThemeToken>(THEME_TOKEN);
134✔
141
    private _injector = inject(Injector);
134✔
142
    private _cdr = inject(ChangeDetectorRef);
134✔
143
    private _overlayService = inject<IgxOverlayService>(IgxOverlayService);
134✔
144

145
    /**
146
     * The number of displayed month views.
147
     *
148
     * @remarks
149
     * Default is `2`.
150
     *
151
     * @example
152
     * ```html
153
     * <igx-date-range-picker [displayMonthsCount]="3"></igx-date-range-picker>
154
     * ```
155
     */
156
    @Input()
157
    public get displayMonthsCount(): number {
158
        return this._displayMonthsCount;
2✔
159
    }
160

161
    public set displayMonthsCount(value: number) {
162
        this._displayMonthsCount = clamp(value, 1, 2);
2✔
163
    }
164

165
    /**
166
     * Gets/Sets the orientation of the multiple months displayed in the picker's calendar's days view.
167
     *
168
     * @example
169
     * <igx-date-range-picker orientation="vertical"></igx-date-range-picker>
170
     */
171
    @Input()
172
    public orientation: PickerCalendarOrientation = PickerCalendarOrientation.Horizontal;
134✔
173

174
    /**
175
     * Gets/Sets whether dates that are not part of the current month will be displayed.
176
     *
177
     * @remarks
178
     * Default value is `false`.
179
     *
180
     * @example
181
     * ```html
182
     * <igx-date-range-picker [hideOutsideDays]="true"></igx-date-range-picker>
183
     * ```
184
     */
185
    @Input({ transform: booleanAttribute })
186
    public hideOutsideDays: boolean;
187

188
    /**
189
     * A custom formatter function, applied on the selected or passed in date.
190
     *
191
     * @example
192
     * ```typescript
193
     * private dayFormatter = new Intl.DateTimeFormat("en", { weekday: "long" });
194
     * private monthFormatter = new Intl.DateTimeFormat("en", { month: "long" });
195
     *
196
     * public formatter(date: Date): string {
197
     *  return `${this.dayFormatter.format(date)} - ${this.monthFormatter.format(date)} - ${date.getFullYear()}`;
198
     * }
199
     * ```
200
     * ```html
201
     * <igx-date-range-picker [formatter]="formatter"></igx-date-range-picker>
202
     * ```
203
     */
204
    @Input()
205
    public formatter: (val: DateRange) => string;
206

207
    /**
208
     * Overrides the default text of the calendar dialog **Done** button.
209
     *
210
     * @remarks
211
     * Defaults to the value from resource strings, `"Done"` for the built-in EN.
212
     * The button will only show up in `dialog` mode.
213
     *
214
     * @example
215
     * ```html
216
     * <igx-date-range-picker doneButtonText="完了"></igx-date-range-picker>
217
     * ```
218
     */
219
    @Input()
220
    public set doneButtonText(value: string) {
221
        this._doneButtonText = value;
1✔
222
    }
223

224
    public get doneButtonText(): string {
225
        if (this._doneButtonText === null) {
16✔
226
            return this.resourceStrings.igx_date_range_picker_done_button;
15✔
227
        }
228
        return this._doneButtonText;
1✔
229
    }
230
    /**
231
     * Overrides the default text of the calendar dialog **Cancel** button.
232
     *
233
     * @remarks
234
     * Defaults to the value from resource strings, `"Cancel"` for the built-in EN.
235
     * The button will only show up in `dialog` mode.
236
     *
237
     * @example
238
     * ```html
239
     * <igx-date-range-picker cancelButtonText="取消"></igx-date-range-picker>
240
     * ```
241
     */
242
    @Input()
243
    public set cancelButtonText(value: string) {
244
        this._cancelButtonText = value;
1✔
245
    }
246

247
    public get cancelButtonText(): string {
248
        if (this._cancelButtonText === null) {
16✔
249
            return this.resourceStrings.igx_date_range_picker_cancel_button;
15✔
250
        }
251
        return this._cancelButtonText;
1✔
252
    }
253
    /**
254
     * Custom overlay settings that should be used to display the calendar.
255
     *
256
     * @example
257
     * ```html
258
     * <igx-date-range-picker [overlaySettings]="customOverlaySettings"></igx-date-range-picker>
259
     * ```
260
     */
261
    @Input()
262
    public override overlaySettings: OverlaySettings;
263

264
    /**
265
     * The format used when editable inputs are not focused.
266
     *
267
     * @remarks
268
     * Uses Angular's DatePipe.
269
     *
270
     * @example
271
     * ```html
272
     * <igx-date-range-picker displayFormat="EE/M/yy"></igx-date-range-picker>
273
     * ```
274
     *
275
     */
276
    @Input()
277
    public override set displayFormat(value: string) {
278
        super.displayFormat = value;
63✔
279
    }
280

281
    public override get displayFormat(): string {
282
        return super.displayFormat;
2,324✔
283
    }
284

285
    /**
286
     * The expected user input format and placeholder.
287
     *
288
     * @example
289
     * ```html
290
     * <igx-date-range-picker inputFormat="dd/MM/yy"></igx-date-range-picker>
291
     * ```
292
     */
293
    @Input()
294
    public override set inputFormat(value: string) {
295
        super.inputFormat = value;
46✔
296
    };
297

298
    public override get inputFormat(): string {
299
        // We need to get default input format because igxDateRangePicker is not using igxDateTimeEditor, but a plain input ???
300
        return this._inputFormat ?? DateTimeUtil.getDefaultInputFormat(this.locale, this.i18nFormatter);
1,103✔
301
    }
302

303
    /**
304
     * The minimum value in a valid range.
305
     *
306
     * @example
307
     * <igx-date-range-picker [minValue]="minDate"></igx-date-range-picker>
308
     */
309
    @Input()
310
    public set minValue(value: Date | string) {
311
        this._minValue = value;
44✔
312
        this.onValidatorChange();
44✔
313
    }
314

315
    public get minValue(): Date | string {
316
        return this._minValue;
274✔
317
    }
318

319
    /**
320
     * The maximum value in a valid range.
321
     *
322
     * @example
323
     * <igx-date-range-picker [maxValue]="maxDate"></igx-date-range-picker>
324
     */
325
    @Input()
326
    public set maxValue(value: Date | string) {
327
        this._maxValue = value;
44✔
328
        this.onValidatorChange();
44✔
329
    }
330

331
    public get maxValue(): Date | string {
332
        return this._maxValue;
274✔
333
    }
334

335
    /**
336
     * Gets/Sets the disabled dates descriptors.
337
     *
338
     * @example
339
     * ```typescript
340
     * let disabledDates = this.dateRangePicker.disabledDates;
341
     * this.dateRangePicker.disabledDates = [ {type: DateRangeType.Weekends}, ...];
342
     * ```
343
     */
344
    @Input()
345
    public get disabledDates(): DateRangeDescriptor[] {
346
        return this._disabledDates;
280✔
347
    }
348
    public set disabledDates(value: DateRangeDescriptor[]) {
349
        this._disabledDates = value;
2✔
350
        this.onValidatorChange();
2✔
351
    }
352

353
    /**
354
     * Gets/Sets the special dates descriptors.
355
     *
356
     * @example
357
     * ```typescript
358
     * let specialDates = this.dateRangePicker.specialDates;
359
     * this.dateRangePicker.specialDates = [ {type: DateRangeType.Weekends}, ... ];
360
     * ```
361
     */
362
    @Input()
363
    public get specialDates(): DateRangeDescriptor[] {
364
        return this._specialDates;
62✔
365
    }
366
    public set specialDates(value: DateRangeDescriptor[]) {
367
        this._specialDates = value;
1✔
368
    }
369

370
    /**
371
     * An accessor that sets the resource strings.
372
     * By default it uses EN resources.
373
     */
374
    @Input()
375
    public set resourceStrings(value: IDateRangePickerResourceStrings) {
376
        this._resourceStrings = Object.assign({}, this._resourceStrings, value);
1✔
377
    }
378

379
    /**
380
     * An accessor that returns the resource strings.
381
     */
382
    public get resourceStrings(): IDateRangePickerResourceStrings {
383
        return this._resourceStrings || this._defaultResourceStrings;
179✔
384
    }
385

386
    /**
387
     * Sets the `placeholder` for single-input date range picker.
388
     *
389
     *   @example
390
     * ```html
391
     * <igx-date-range-picker [placeholder]="'Choose your dates'"></igx-date-range-picker>
392
     * ```
393
     */
394
    @Input()
395
    public override placeholder = '';
134✔
396

397
    /**
398
     * Show/hide week numbers
399
     *
400
     * @remarks
401
     * Default is `false`.
402
     *
403
     * @example
404
     * ```html
405
     * <igx-date-range-picker [showWeekNumbers]="true"></igx-date-range-picker>
406
     * ``
407
     */
408
    @Input({ transform: booleanAttribute })
409
    public showWeekNumbers = false;
134✔
410

411
    /** @hidden @internal */
412
    @Input({ transform: booleanAttribute })
413
    public readOnly = false;
134✔
414

415
    /**
416
     * Emitted when the picker's value changes. Used for two-way binding.
417
     *
418
     * @example
419
     * ```html
420
     * <igx-date-range-picker [(value)]="date"></igx-date-range-picker>
421
     * ```
422
     */
423

424
     /**
425
      * Whether to render built-in predefined ranges.
426
      *
427
      * @example
428
      * ```html
429
      * <igx-date-range-picker [(usePredefinedRanges)]="true"></igx-date-range-picker>
430
      * ``
431
      *  */
432
    @Input() public usePredefinedRanges = false;
134✔
433

434
    /**
435
     *  Custom ranges rendered as chips.
436
     *
437
     * @example
438
     * ```html
439
     * <igx-date-range-picker [(usePredefinedRanges)]="true"></igx-date-range-picker>
440
     * ``
441
    */
442
    @Input() public customRanges: CustomDateRange[] = [];
134✔
443

444
    @Output()
445
    public valueChange = new EventEmitter<DateRange>();
134✔
446

447
    /** @hidden @internal */
448
    @HostBinding('class.igx-date-range-picker')
449
    public cssClass = 'igx-date-range-picker';
134✔
450

451
    @ViewChild("container", { read: ViewContainerRef })
452
    private viewContainerRef: ViewContainerRef;
453

454
    /** @hidden @internal */
455
    @ViewChild(IgxInputDirective)
456
    public inputDirective: IgxInputDirective;
457

458
    /** @hidden @internal */
459
    @ContentChildren(IgxDateRangeInputsBaseComponent)
460
    public projectedInputs: QueryList<IgxDateRangeInputsBaseComponent>;
461

462
    @ContentChild(IgxLabelDirective)
463
    public label: IgxLabelDirective;
464

465
    @ContentChild(IgxHintDirective)
466
    public hint: IgxHintDirective;
467

468
    @ContentChild(IgxPickerActionsDirective)
469
    public pickerActions: IgxPickerActionsDirective;
470

471
    /** @hidden @internal */
472
    @ContentChild(IgxDateRangeSeparatorDirective, { read: TemplateRef })
473
    public dateSeparatorTemplate: TemplateRef<any>;
474

475

476
    @ContentChild(IgxCalendarHeaderTitleTemplateDirective)
477
    private headerTitleTemplate: IgxCalendarHeaderTitleTemplateDirective;
478

479
    @ContentChild(IgxCalendarHeaderTemplateDirective)
480
    private headerTemplate: IgxCalendarHeaderTemplateDirective;
481

482
    @ContentChild(IgxCalendarSubheaderTemplateDirective)
483
    private subheaderTemplate: IgxCalendarSubheaderTemplateDirective;
484

485
    /** @hidden @internal */
486
    public get dateSeparator(): string {
487
        if (this._dateSeparator === null) {
87✔
488
            return this.resourceStrings.igx_date_range_picker_date_separator;
87✔
489
        }
490
        return this._dateSeparator;
×
491
    }
492

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

502
    /**
503
     * Gets/Sets the date which is shown in the calendar picker and is highlighted.
504
     * By default it is the current date, or the value of the picker, if set.
505
    */
506
    @Input()
507
    public get activeDate(): Date {
508
        const today = new Date(new Date().setHours(0, 0, 0, 0));
417✔
509
        const dateValue = DateTimeUtil.isValidDate(this._firstDefinedInRange) ? new Date(new Date(this._firstDefinedInRange.getTime()).setHours(0, 0, 0, 0)) : null;
417✔
510
        return this._activeDate ?? dateValue ?? this._calendar?.activeDate ?? today;
417✔
511
    }
512

513
    public set activeDate(value: Date) {
514
        this._activeDate = value;
1✔
515
    }
516

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

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

545
    /** @hidden @internal */
546
    public get singleInputFormat(): string {
547
        if (this.placeholder !== '') {
512✔
548
            return this.placeholder;
2✔
549
        }
550

551
        const format = this.appliedFormat;
510✔
552
        return `${format}${SingleInputDatesConcatenationString}${format}`;
510✔
553
    }
554

555
    /**
556
     * Gets calendar state.
557
     *
558
     * ```typescript
559
     * let state = this.dateRange.collapsed;
560
     * ```
561
     */
562
    public override get collapsed(): boolean {
563
        return this._collapsed;
798✔
564
    }
565

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

582
    @Input()
583
    public set value(value: DateRange | null) {
584
        this.updateValue(value);
130✔
585
        this.onChangeCallback(value);
130✔
586
        this.valueChange.emit(value);
130✔
587
    }
588

589
    /** @hidden @internal */
590
    public get hasProjectedInputs(): boolean {
591
        return this.projectedInputs?.length > 0;
3,207✔
592
    }
593

594
    /** @hidden @internal */
595
    public get separatorClass(): string {
596
        const classes = ['igx-date-range-picker__label'];
539✔
597
        if (this.hint) classes.push('input-has-hint');
539!
598
        return classes.join(' ');
539✔
599
    }
600

601
    protected override get toggleContainer(): HTMLElement | undefined {
602
        return this._calendarContainer;
×
603
    }
604

605
    private get required(): boolean {
606
        if (this._ngControl && this._ngControl.control && this._ngControl.control.validator) {
268✔
607
            const error = this._ngControl.control.validator({} as AbstractControl);
192✔
608
            return (error && error.required) ? true : false;
192!
609
        }
610

611
        return false;
76✔
612
    }
613

614
    private get calendar(): IgxCalendarComponent {
615
        return this._calendar;
853✔
616
    }
617

618
    private get dropdownOverlaySettings(): OverlaySettings {
619
        return Object.assign({}, this._dropDownOverlaySettings, this.overlaySettings);
46✔
620
    }
621

622
    private get dialogOverlaySettings(): OverlaySettings {
623
        return Object.assign({}, this._dialogOverlaySettings, this.overlaySettings);
16✔
624
    }
625

626
    private get _firstDefinedInRange(): Date | null {
627
        if (!this.value) {
605✔
628
            return null;
229✔
629
        }
630
        const range = this.toRangeOfDates(this.value);
376✔
631
        return range?.start ?? range?.end ?? null;
376!
632
    }
633

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

671
    constructor() {
672
        super();
134✔
673
        this.initLocale();
134✔
674
    }
675

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

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

709
        this._originalValue = this._value
62✔
710
            ? { start: new Date(this._value.start), end: new Date(this._value.end) }
711
            : null;
712

713
        const settings = Object.assign({}, this.isDropdown
62✔
714
            ? this.dropdownOverlaySettings
715
            : this.dialogOverlaySettings
716
            , overlaySettings);
717

718
        this._overlayId = this._overlayService
62✔
719
            .attach(IgxCalendarContainerComponent, this.viewContainerRef, settings);
720
        this.subscribeToOverlayEvents();
62✔
721
        this._overlayService.show(this._overlayId);
62✔
722
    }
723

724
    /**
725
     * Closes the date range picker's dropdown or dialog.
726
     *
727
     * @example
728
     * ```html
729
     * <igx-date-range-picker #dateRange></igx-date-range-picker>
730
     *
731
     * <button type="button" igxButton (click)="dateRange.close()">Close Dialog</button>
732
     * ```
733
     */
734
    public close(): void {
735
        if (!this.collapsed) {
64✔
736
            this._overlayService.hide(this._overlayId);
47✔
737
        }
738
    }
739

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

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

776
    /**
777
     * Clears the input field(s) and the picker's value.
778
     *
779
     * @example
780
     * ```typescript
781
     * this.dateRangePicker.clear();
782
     * ```
783
     */
784
    public clear(): void {
785
        if (this.disabled) {
4!
786
            return;
×
787
        }
788

789
        this.value = null;
4✔
790
        this._calendar?.deselectDate();
4✔
791
        if (this.hasProjectedInputs) {
4✔
792
            this.projectedInputs.forEach((i) => {
2✔
793
                i.inputDirective.clear();
4✔
794
            });
795
        } else {
796
            this.inputDirective.clear();
2✔
797
        }
798
    }
799

800
    /** @hidden @internal */
801
    public writeValue(value: DateRange): void {
802
        this.updateValue(value);
104✔
803
    }
804

805
    /** @hidden @internal */
806
    public registerOnChange(fn: any): void {
807
        this.onChangeCallback = fn;
62✔
808
    }
809

810
    /** @hidden @internal */
811
    public registerOnTouched(fn: any): void {
812
        this.onTouchCallback = fn;
60✔
813
    }
814

815
    /** @hidden @internal */
816
    public validate(control: AbstractControl): ValidationErrors | null {
817
        const value: DateRange = control.value;
371✔
818
        const errors = {};
371✔
819
        if (value) {
371✔
820
            if (this.hasProjectedInputs) {
87✔
821
                const startInput = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
82✔
822
                const endInput = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
164✔
823
                if (!startInput.dateTimeEditor.value) {
82!
824
                    Object.assign(errors, { startValue: true });
×
825
                }
826
                if (!endInput.dateTimeEditor.value) {
82✔
827
                    Object.assign(errors, { endValue: true });
2✔
828
                }
829
            }
830

831
            if (this._isValueInDisabledRange(value)) {
87✔
832
                Object.assign(errors, { dateIsDisabled: true });
1✔
833
            }
834

835
            const { minValue, maxValue } = this._getMinMaxDates();
87✔
836
            const start = parseDate(value.start);
87✔
837
            const end = parseDate(value.end);
87✔
838
            if ((minValue && start && DateTimeUtil.lessThanMinValue(start, minValue, false))
87✔
839
                || (minValue && end && DateTimeUtil.lessThanMinValue(end, minValue, false))) {
840
                Object.assign(errors, { minValue: true });
2✔
841
            }
842
            if ((maxValue && start && DateTimeUtil.greaterThanMaxValue(start, maxValue, false))
87✔
843
                || (maxValue && end && DateTimeUtil.greaterThanMaxValue(end, maxValue, false))) {
844
                Object.assign(errors, { maxValue: true });
1✔
845
            }
846
        }
847

848
        return Object.keys(errors).length > 0 ? errors : null;
371✔
849
    }
850

851
    /** @hidden @internal */
852
    public registerOnValidatorChange?(fn: any): void {
853
        this.onValidatorChange = fn;
61✔
854
    }
855

856
    /** @hidden @internal */
857
    public setDisabledState?(isDisabled: boolean): void {
858
        this.disabled = isDisabled;
61✔
859
    }
860

861
    /** @hidden */
862
    public ngOnInit(): void {
863
        this._ngControl = this._injector.get<NgControl>(NgControl, null);
131✔
864
    }
865

866
    /** @hidden */
867
    public override ngAfterViewInit(): void {
868
        super.ngAfterViewInit();
127✔
869
        this.subscribeToDateEditorEvents();
127✔
870
        this.subscribeToClick();
127✔
871
        this.configPositionStrategy();
127✔
872
        this.configOverlaySettings();
127✔
873
        this.cacheFocusedInput();
127✔
874
        this.attachOnTouched();
127✔
875

876
        this.setRequiredToInputs();
127✔
877

878
        if (this._ngControl) {
127✔
879
            this._statusChanges$ = this._ngControl.statusChanges.subscribe(this.onStatusChanged.bind(this));
53✔
880
        }
881

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

900
    /** @hidden @internal */
901
    public ngOnChanges(changes: SimpleChanges): void {
902
        if (changes['displayFormat'] && this.hasProjectedInputs) {
119✔
903
            this.updateDisplayFormat();
11✔
904
        }
905
        if (changes['inputFormat'] && this.hasProjectedInputs) {
119✔
906
            this.updateInputFormat();
4✔
907
        }
908
        if (changes['disabled']) {
119✔
909
            this.updateDisabledState();
94✔
910
        }
911
    }
912

913
    /** @hidden @internal */
914
    public override ngOnDestroy(): void {
915
        super.ngOnDestroy();
80✔
916
        if (this._statusChanges$) {
80✔
917
            this._statusChanges$.unsubscribe();
33✔
918
        }
919
        if (this._overlayId) {
80!
920
            this._overlayService.detach(this._overlayId);
×
921
        }
922
    }
923

924
    /** @hidden @internal */
925
    public getEditElement(): HTMLInputElement {
926
        return this.inputDirective!.nativeElement;
76✔
927
    }
928

929
    protected onStatusChanged = () => {
134✔
930
        if (this.inputGroup) {
140✔
931
            this.setValidityState(this.inputDirective, this.inputGroup.isFocused);
7✔
932
        } else if (this.hasProjectedInputs) {
133✔
933
            this.projectedInputs
133✔
934
                .forEach((i) => {
935
                    this.setValidityState(i.inputDirective, i.isFocused);
266✔
936
                });
937
        }
938
        this.setRequiredToInputs();
140✔
939
    };
940

941
    private setValidityState(inputDirective: IgxInputDirective, isFocused: boolean) {
942
        if (this._ngControl && !this._ngControl.disabled && this.isTouchedOrDirty) {
273✔
943
            if (this.hasValidators && isFocused) {
243✔
944
                inputDirective.valid = this._ngControl.valid ? IgxInputState.VALID : IgxInputState.INVALID;
22✔
945
            } else {
946
                inputDirective.valid = this._ngControl.valid ? IgxInputState.INITIAL : IgxInputState.INVALID;
221✔
947
            }
948
        } else {
949
            inputDirective.valid = IgxInputState.INITIAL;
30✔
950
        }
951
    }
952

953
    private get isTouchedOrDirty(): boolean {
954
        return (this._ngControl.control.touched || this._ngControl.control.dirty);
263✔
955
    }
956

957
    private get hasValidators(): boolean {
958
        return (!!this._ngControl.control.validator || !!this._ngControl.control.asyncValidator);
243✔
959
    }
960

961
    private handleSelection(selectionData: Date[]): void {
962
        let newValue = this.extractRange(selectionData);
45✔
963
        if (!newValue.start && !newValue.end) {
45!
964
            newValue = null;
×
965
        }
966
        this.value = newValue;
45✔
967
        if (this.isDropdown && selectionData?.length > 1) {
45✔
968
            this.close();
32✔
969
        }
970
        this._setCalendarActiveDate();
45✔
971
    }
972

973
    private handleClosing(e: IBaseCancelableBrowserEventArgs): void {
974
        const args = { owner: this, cancel: e?.cancel, event: e?.event };
51✔
975
        this.closing.emit(args);
51✔
976
        e.cancel = args.cancel;
51✔
977
        if (args.cancel) {
51✔
978
            return;
3✔
979
        }
980

981
        if (this.isDropdown && e?.event && !this.isFocused) {
48!
982
            // outside click
983
            this.updateValidityOnBlur();
×
984
        } else {
985
            this.onTouchCallback();
48✔
986
            // input click
987
            if (this.hasProjectedInputs && this._focusedInput) {
48✔
988
                this._focusedInput.setFocus();
7✔
989
            }
990
            if (this.inputDirective) {
48✔
991
                this.inputDirective.focus();
18✔
992
            }
993
        }
994
    }
995

996
    private subscribeToOverlayEvents() {
997
        this._overlayService.opening.pipe(...this._overlaySubFilter).subscribe((e) => {
62✔
998
            const overlayEvent = e as OverlayCancelableEventArgs;
62✔
999
            const args = { owner: this, cancel: overlayEvent?.cancel, event: e.event };
62✔
1000
            this.opening.emit(args);
62✔
1001
            if (args.cancel) {
62!
1002
                this._overlayService.detach(this._overlayId);
×
1003
                overlayEvent.cancel = true;
×
1004
                return;
×
1005
            }
1006

1007
            this._initializeCalendarContainer(e.componentRef.instance);
62✔
1008
            this._calendarContainer = e.componentRef.location.nativeElement;
62✔
1009
            this._collapsed = false;
62✔
1010
            this.updateCalendar();
62✔
1011
        });
1012

1013
        this._overlayService.opened.pipe(...this._overlaySubFilter).subscribe(() => {
62✔
1014
            this.calendar.wrapper.nativeElement.focus();
48✔
1015
            this.opened.emit({ owner: this });
48✔
1016
        });
1017

1018
        this._overlayService.closing.pipe(...this._overlaySubFilter).subscribe((e: OverlayCancelableEventArgs) => {
62✔
1019
            const isEscape = e.event && (e.event as KeyboardEvent).key === this.platform.KEYMAP.ESCAPE;
51✔
1020
            if (this.isProjectedInputTarget(e.event) && !isEscape) {
51✔
1021
                e.cancel = true;
1✔
1022
            }
1023
            this.handleClosing(e as OverlayCancelableEventArgs);
51✔
1024
        });
1025

1026
        this._overlayService.closed.pipe(...this._overlaySubFilter).subscribe(() => {
62✔
1027
            this._overlayService.detach(this._overlayId);
20✔
1028
            this._collapsed = true;
20✔
1029
            this._overlayId = null;
20✔
1030
            this._calendar = null;
20✔
1031
            this._calendarContainer = undefined;
20✔
1032
            this.closed.emit({ owner: this });
20✔
1033
        });
1034
    }
1035

1036
    private isProjectedInputTarget(event: Event): boolean {
1037
        if (!this.hasProjectedInputs || !event) {
51✔
1038
            return false;
48✔
1039
        }
1040
        const path = event.composed ? event.composedPath() : [event.target];
3!
1041
        return this.projectedInputs.some(i =>
3✔
1042
            path.includes(i.dateTimeEditor.nativeElement)
5✔
1043
        );
1044
    }
1045

1046
    private updateValue(value: DateRange) {
1047
        this._value = value ? value : null;
234✔
1048
        this.updateInputs();
234✔
1049
        this.updateCalendar();
234✔
1050
    }
1051

1052
    private updateValidityOnBlur() {
1053
        this._focusedInput = null;
2✔
1054
        this.onTouchCallback();
2✔
1055
        if (this._ngControl) {
2✔
1056
            if (this.hasProjectedInputs) {
1✔
1057
                this.projectedInputs.forEach(i => {
1✔
1058
                    if (!this._ngControl.valid) {
2!
1059
                        i.updateInputValidity(IgxInputState.INVALID);
2✔
1060
                    } else {
1061
                        i.updateInputValidity(IgxInputState.INITIAL);
×
1062
                    }
1063
                });
1064
            }
1065

1066
            if (this.inputDirective) {
1!
1067
                if (!this._ngControl.valid) {
×
1068
                    this.inputDirective.valid = IgxInputState.INVALID;
×
1069
                } else {
1070
                    this.inputDirective.valid = IgxInputState.INITIAL;
×
1071
                }
1072
            }
1073
        }
1074
    }
1075

1076
    private updateDisabledState() {
1077
        if (this.hasProjectedInputs) {
221✔
1078
            const start = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
58✔
1079
            const end = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
116✔
1080
            start.inputDirective.disabled = this.disabled;
58✔
1081
            end.inputDirective.disabled = this.disabled;
58✔
1082
            return;
58✔
1083
        }
1084
    }
1085

1086
    private setRequiredToInputs(): void {
1087
        // workaround for igxInput setting required
1088
        Promise.resolve().then(() => {
267✔
1089
            const isRequired = this.required;
267✔
1090
            if (this.inputGroup && this.inputGroup.isRequired !== isRequired) {
267✔
1091
                this.inputGroup.isRequired = isRequired;
4✔
1092
            } else if (this.hasProjectedInputs && this._ngControl) {
263✔
1093
                this.projectedInputs.forEach(i => i.isRequired = isRequired);
366✔
1094
            }
1095
        });
1096
    }
1097

1098
    private parseMinValue(value: string | Date): Date | null {
1099
        let minValue: Date = parseDate(value);
272✔
1100
        if (!minValue && this.hasProjectedInputs) {
272✔
1101
            const start = this.projectedInputs.filter(i => i instanceof IgxDateRangeStartComponent)[0];
358✔
1102
            if (start) {
179✔
1103
                minValue = parseDate(start.dateTimeEditor.minValue);
179✔
1104
            }
1105
        }
1106

1107
        return minValue;
272✔
1108
    }
1109

1110
    private parseMaxValue(value: string | Date): Date | null {
1111
        let maxValue: Date = parseDate(value);
272✔
1112
        if (!maxValue && this.projectedInputs) {
272✔
1113
            const end = this.projectedInputs.filter(i => i instanceof IgxDateRangeEndComponent)[0];
358✔
1114
            if (end) {
262✔
1115
                maxValue = parseDate(end.dateTimeEditor.maxValue);
179✔
1116
            }
1117
        }
1118

1119
        return maxValue;
272✔
1120
    }
1121

1122
    private updateCalendar(): void {
1123
        if (!this.calendar) {
299✔
1124
            return;
176✔
1125
        }
1126
        this._setDisabledDates();
123✔
1127

1128
        const range: Date[] = [];
123✔
1129
        if (this.value) {
123✔
1130
            const _value = this.toRangeOfDates(this.value);
61✔
1131
            if (_value.start && _value.end) {
61✔
1132
                if (DateTimeUtil.greaterThanMaxValue(_value.start, _value.end)) {
58✔
1133
                    this.swapEditorDates();
2✔
1134
                }
1135
            }
1136
            if (_value.start) {
61✔
1137
                range.push(_value.start);
60✔
1138
            }
1139
            if (_value.end) {
61✔
1140
                range.push(_value.end);
59✔
1141
            }
1142
        }
1143

1144
        if (range.length > 0) {
123✔
1145
            this.calendar.selectDate(range);
61✔
1146
        } else if (range.length === 0 && this.calendar.monthViews) {
62✔
1147
            this.calendar.deselectDate();
6✔
1148
        }
1149
        this._setCalendarActiveDate();
123✔
1150
        this._cdr.detectChanges();
123✔
1151
    }
1152

1153
    private swapEditorDates(): void {
1154
        if (this.hasProjectedInputs) {
2✔
1155
            const start = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
2✔
1156
            const end = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
4✔
1157
            [start.dateTimeEditor.value, end.dateTimeEditor.value] = [end.dateTimeEditor.value, start.dateTimeEditor.value];
2✔
1158
            [this.value.start, this.value.end] = [this.value.end, this.value.start];
2✔
1159
        }
1160
    }
1161

1162
    private extractRange(selection: Date[]): DateRange {
1163
        return {
45✔
1164
            start: selection[0] || null,
45!
1165
            end: selection.length > 0 ? selection[selection.length - 1] : null
45!
1166
        };
1167
    }
1168

1169
    private toRangeOfDates(range: DateRange): { start: Date; end: Date } {
1170
        let start: Date;
1171
        let end: Date;
1172

1173
        if (!isDate(range.start)) {
524✔
1174
            start = DateTimeUtil.parseIsoDate(range.start);
9✔
1175
        }
1176
        if (!isDate(range.end)) {
524✔
1177
            end = DateTimeUtil.parseIsoDate(range.end);
18✔
1178
        }
1179

1180
        if (start || end) {
524!
1181
            return { start, end };
×
1182
        }
1183

1184
        return { start: range.start as Date, end: range.end as Date };
524✔
1185
    }
1186

1187
    private subscribeToClick() {
1188
        const inputs = this.hasProjectedInputs
127✔
1189
            ? this.projectedInputs.map(i => i.inputDirective.nativeElement)
106✔
1190
            : [this.getEditElement()];
1191
        inputs.forEach(input => {
127✔
1192
            fromEvent(input, 'click')
180✔
1193
                .pipe(takeUntil(this._destroy$))
1194
                .subscribe(() => {
1195
                    if (!this.isDropdown) {
4✔
1196
                        this.toggle();
2✔
1197
                    }
1198
                });
1199
        });
1200
    }
1201

1202
    private subscribeToDateEditorEvents(): void {
1203
        if (this.hasProjectedInputs) {
127✔
1204
            const start = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
53✔
1205
            const end = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
106✔
1206
            if (start && end) {
53✔
1207
                start.dateTimeEditor.valueChange
53✔
1208
                    .pipe(takeUntil(this._destroy$))
1209
                    .subscribe(value => {
1210
                        if (this.value) {
22✔
1211
                            this.value = { start: value, end: this.value.end };
20✔
1212
                        } else {
1213
                            this.value = { start: value, end: null };
2✔
1214
                        }
1215
                        if (this.calendar) {
22✔
1216
                            this._setCalendarActiveDate(parseDate(value));
21✔
1217
                            this._cdr.detectChanges();
21✔
1218
                        }
1219
                    });
1220
                end.dateTimeEditor.valueChange
53✔
1221
                    .pipe(takeUntil(this._destroy$))
1222
                    .subscribe(value => {
1223
                        if (this.value) {
1!
1224
                            this.value = { start: this.value.start, end: value as Date };
1✔
1225
                        } else {
1226
                            this.value = { start: null, end: value as Date };
×
1227
                        }
1228
                        if (this.calendar) {
1!
1229
                            this._setCalendarActiveDate(parseDate(value));
×
1230
                            this._cdr.detectChanges();
×
1231
                        }
1232
                    });
1233
            }
1234
        }
1235
    }
1236

1237
    private attachOnTouched(): void {
1238
        if (this.hasProjectedInputs) {
127✔
1239
            this.projectedInputs.forEach(i => {
53✔
1240
                fromEvent(i.dateTimeEditor.nativeElement, 'blur')
106✔
1241
                    .pipe(takeUntil(this._destroy$))
1242
                    .subscribe(() => {
1243
                        if (this.collapsed) {
12✔
1244
                            this.updateValidityOnBlur();
1✔
1245
                        }
1246
                    });
1247
            });
1248
        } else {
1249
            fromEvent(this.inputDirective.nativeElement, 'blur')
74✔
1250
                .pipe(takeUntil(this._destroy$))
1251
                .subscribe(() => {
1252
                    if (this.collapsed) {
8!
1253
                        this.updateValidityOnBlur();
×
1254
                    }
1255
                });
1256
        }
1257
    }
1258

1259
    private cacheFocusedInput(): void {
1260
        if (this.hasProjectedInputs) {
127✔
1261
            this.projectedInputs.forEach(i => {
53✔
1262
                fromEvent(i.dateTimeEditor.nativeElement, 'focus')
106✔
1263
                    .pipe(takeUntil(this._destroy$))
1264
                    .subscribe(() => this._focusedInput = i);
16✔
1265
            });
1266
        }
1267
    }
1268

1269
    private configPositionStrategy(): void {
1270
        this._positionSettings = {
127✔
1271
            openAnimation: fadeIn,
1272
            closeAnimation: fadeOut,
1273
            offset: 1
1274
        };
1275
        this._dropDownOverlaySettings.positionStrategy = new AutoPositionStrategy(this._positionSettings);
127✔
1276

1277
        const bundle = this.hasProjectedInputs
127✔
1278
            ? this.projectedInputs.first?.nativeElement.querySelector('.igx-input-group__bundle')
1279
            : this.element.nativeElement.querySelector('.igx-input-group__bundle');
1280
        this._dropDownOverlaySettings.target = bundle || this.element.nativeElement;
127!
1281
    }
1282

1283
    private configOverlaySettings(): void {
1284
        if (this.overlaySettings !== null) {
127✔
1285
            this._dropDownOverlaySettings = Object.assign({}, this._dropDownOverlaySettings, this.overlaySettings);
127✔
1286
            this._dialogOverlaySettings = Object.assign({}, this._dialogOverlaySettings, this.overlaySettings);
127✔
1287
        }
1288
    }
1289

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

1303
    private updateInputs(): void {
1304
        const start = this.projectedInputs?.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
361✔
1305
        const end = this.projectedInputs?.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
372✔
1306
        if (start && end) {
361✔
1307
            const _value = this.value ? this.toRangeOfDates(this.value) : null;
186✔
1308
            start.updateInputValue(_value?.start || null);
186✔
1309
            end.updateInputValue(_value?.end || null);
186✔
1310
        }
1311
    }
1312

1313
    private updateDisplayFormat(): void {
1314
        this.projectedInputs.forEach(i => {
141✔
1315
            const input = i as IgxDateRangeInputsBaseComponent;
134✔
1316
            input.dateTimeEditor.displayFormat = this.displayFormat;
134✔
1317
        });
1318
    }
1319

1320
    private updateInputFormat(): void {
1321
        this.projectedInputs.forEach(i => {
131✔
1322
            const input = i as IgxDateRangeInputsBaseComponent;
114✔
1323
            if (input.dateTimeEditor.inputFormat !== this.inputFormat) {
114✔
1324
                input.dateTimeEditor.inputFormat = this.inputFormat;
8✔
1325
            }
1326
        });
1327
    }
1328

1329
    private updateInputLocale(): void {
1330
        this.projectedInputs.forEach(i => {
3✔
1331
            const input = i as IgxDateRangeInputsBaseComponent;
6✔
1332
            input.dateTimeEditor.locale = this.locale;
6✔
1333
            input.dateTimeEditor.updateMask();
6✔
1334
        });
1335
    }
1336

1337
    protected override onResourceChange(args: CustomEvent<IResourceChangeEventArgs>) {
1338
        super.onResourceChange(args);
975✔
1339
        if (args.detail.oldLocale !== args.detail.newLocale && this.hasProjectedInputs) {
975!
1340
            this.updateInputLocale();
×
1341
            this.updateDisplayFormat();
×
1342
        }
1343
    }
1344

1345
    protected override updateResources(): void {
1346
        this._defaultResourceStrings = getCurrentResourceStrings(DateRangePickerResourceStringsEN, false, this._locale);
982✔
1347
    }
1348

1349
    private _initializeCalendarContainer(componentInstance: IgxCalendarContainerComponent) {
1350
        this._calendar = componentInstance.calendar;
62✔
1351
        this._calendar.hasHeader = !this.isDropdown && !this.hideHeader;
62✔
1352
        this._calendar.locale = this.locale;
62✔
1353
        this._calendar.selection = CalendarSelection.RANGE;
62✔
1354
        this._calendar.weekStart = this.weekStart;
62✔
1355
        this._calendar.hideOutsideDays = this.hideOutsideDays;
62✔
1356
        this._calendar.monthsViewNumber = this._displayMonthsCount;
62✔
1357
        this._calendar.showWeekNumbers = this.showWeekNumbers;
62✔
1358
        this._calendar.headerTitleTemplate = this.headerTitleTemplate;
62✔
1359
        this._calendar.headerTemplate = this.headerTemplate;
62✔
1360
        this._calendar.subheaderTemplate = this.subheaderTemplate;
62✔
1361
        this._calendar.headerOrientation = this.headerOrientation;
62✔
1362
        this._calendar.orientation = this.orientation;
62✔
1363
        this._calendar.specialDates = this.specialDates;
62✔
1364
        this._calendar.selected.pipe(takeUntil(this._destroy$)).subscribe((ev: Date[]) => this.handleSelection(ev));
62✔
1365

1366
        this._setDisabledDates();
62✔
1367
        this._setCalendarActiveDate();
62✔
1368

1369
        componentInstance.mode = this.mode;
62✔
1370
        componentInstance.closeButtonLabel = !this.isDropdown ? this.doneButtonText : null;
62✔
1371
        componentInstance.cancelButtonLabel = !this.isDropdown ? this.cancelButtonText : null;
62✔
1372
        if (!this.isDropdown && this.themeToken.theme === 'indigo') {
62!
1373
            componentInstance.closeButtonType = 'contained';
×
1374
            componentInstance.cancelButtonType = 'outlined';
×
1375
        }
1376
        componentInstance.pickerActions = this.pickerActions;
62✔
1377
        componentInstance.usePredefinedRanges = this.usePredefinedRanges;
62✔
1378
        componentInstance.customRanges = this.customRanges;
62✔
1379
        componentInstance.resourceStrings = this.resourceStrings;
62✔
1380
        componentInstance.calendarClose.pipe(takeUntil(this._destroy$)).subscribe(() => this.close());
62✔
1381
        componentInstance.calendarCancel.pipe(takeUntil(this._destroy$)).subscribe(() => {
62✔
1382
            this._value = this._originalValue;
2✔
1383
            this.close()
2✔
1384
        });
1385
        componentInstance.rangeSelected
62✔
1386
        .pipe(takeUntil(this._destroy$))
1387
        .subscribe((r: DateRange) => {
1388
            if (r?.start && r?.end) {
6✔
1389
            this.select(new Date(r.start), new Date(r.end));
6✔
1390
            }
1391

1392
            if (this.isDropdown) {
6✔
1393
            this.close();
6✔
1394
            }
1395
        });
1396
    }
1397

1398
    private _setDisabledDates(): void {
1399
        if (!this.calendar) {
185!
1400
            return;
×
1401
        }
1402
        this.calendar.disabledDates = this.disabledDates ? [...this.disabledDates] : [];
185✔
1403
        const { minValue, maxValue } = this._getMinMaxDates();
185✔
1404
        if (minValue) {
185✔
1405
            this.calendar.disabledDates.push({ type: DateRangeType.Before, dateRange: [minValue] });
2✔
1406
        }
1407
        if (maxValue) {
185✔
1408
            this.calendar.disabledDates.push({ type: DateRangeType.After, dateRange: [maxValue] });
2✔
1409
        }
1410
    }
1411

1412
    private _getMinMaxDates() {
1413
        const minValue = this.parseMinValue(this.minValue);
272✔
1414
        const maxValue = this.parseMaxValue(this.maxValue);
272✔
1415
        return { minValue, maxValue };
272✔
1416
    }
1417

1418
    private _isValueInDisabledRange(value: DateRange) {
1419
        if (value && value.start && value.end && this.disabledDates) {
87✔
1420
            const isOutsideDisabledRange = Array.from(
2✔
1421
                calendarRange({
1422
                    start: parseDate(this.value.start),
1423
                    end: parseDate(this.value.end),
1424
                    inclusive: true
1425
                })).every((date) => !isDateInRanges(date, this.disabledDates));
5✔
1426
            return !isOutsideDisabledRange;
2✔
1427
        }
1428
        return false;
85✔
1429
    }
1430

1431
    private _setCalendarActiveDate(value = null): void {
230✔
1432
        if (this._calendar) {
251✔
1433
            this._calendar.activeDate = value ?? this.activeDate;
227✔
1434
            this._calendar.viewDate = value ?? this.activeDate;
227✔
1435
        }
1436
    }
1437
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc