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

IgniteUI / igniteui-angular / 28358286454

29 Jun 2026 08:17AM UTC coverage: 90.127% (-0.05%) from 90.174%
28358286454

Pull #17246

github

web-flow
Merge 21687d939 into 07bdcd752
Pull Request #17246: fix(pivot-grid): fix date format based on the localization

14921 of 17392 branches covered (85.79%)

Branch coverage included in aggregate %.

29 of 32 new or added lines in 4 files covered. (90.63%)

735 existing lines in 76 files now uncovered.

29992 of 32441 relevant lines covered (92.45%)

34632.46 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, booleanAttribute, ChangeDetectorRef, Component, ContentChild, ContentChildren,
3
  EventEmitter, HostBinding, HostListener, Injector, Input,
4
  OnChanges, OnDestroy, OnInit, Output, QueryList,
5
  SimpleChanges, TemplateRef, ViewChild, ViewContainerRef, inject,
6
  ChangeDetectionStrategy
7
} from '@angular/core';
8
import { NgTemplateOutlet } from '@angular/common';
9
import {
10
    AbstractControl, ControlValueAccessor, NgControl,
11
    NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator
12
} from '@angular/forms';
13

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

17
import { CalendarSelection, IgxCalendarComponent, IgxCalendarHeaderTemplateDirective, IgxCalendarHeaderTitleTemplateDirective, IgxCalendarSubheaderTemplateDirective } from 'igniteui-angular/calendar';
18
import {
19
    DateRangeDescriptor,
20
    DateRangeType,
21
    DateRangePickerResourceStringsEN,
22
    IDateRangePickerResourceStrings,
23
    clamp,
24
    IBaseCancelableBrowserEventArgs,
25
    isDate,
26
    parseDate,
27
    PlatformUtil,
28
    getCurrentResourceStrings,
29
    AutoPositionStrategy,
30
    IgxOverlayService,
31
    OverlayCancelableEventArgs,
32
    OverlayEventArgs,
33
    OverlaySettings,
34
    PositionSettings,
35
    calendarRange,
36
    CustomDateRange,
37
    DateRange,
38
    DateTimeUtil,
39
    IgxPickerActionsDirective,
40
    isDateInRanges,
41
    PickerCalendarOrientation,
42
    THEME_TOKEN,
43
    ThemeToken
44
} from 'igniteui-angular/core';
45
import { IgxCalendarContainerComponent } from '../date-picker/calendar-container/calendar-container.component';
46
import { PickerBaseDirective } from '../date-picker/picker-base.directive';
47
import {
48
    IgxInputDirective,
49
    IgxInputGroupComponent,
50
    IgxInputState,
51
    IgxLabelDirective,
52
    IgxSuffixDirective,
53
    IgxPrefixDirective,
54
    IgxReadOnlyInputDirective,
55
    IgxHintDirective
56
} from 'igniteui-angular/input-group';
57
import {
58
    IgxDateRangeEndComponent,
59
    IgxDateRangeInputsBaseComponent,
60
    IgxDateRangeSeparatorDirective,
61
    IgxDateRangeStartComponent,
62
    DateRangePickerFormatPipe,
63
} from './date-range-picker-inputs.common';
64
import { IgxIconComponent } from 'igniteui-angular/icon';
65
import { fadeIn, fadeOut } from 'igniteui-angular/animations';
66
import { IResourceChangeEventArgs } from 'igniteui-i18n-core';
67

68
const SingleInputDatesConcatenationString = ' - ';
3✔
69

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

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

135
    public set displayMonthsCount(value: number) {
136
        this._displayMonthsCount = clamp(value, 1, 2);
2✔
137
    }
138

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

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

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

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

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

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

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

255
    public override get displayFormat(): string {
256
        return super.displayFormat;
2,324✔
257
    }
258

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

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

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

289
    public get minValue(): Date | string {
290
        return this._minValue;
274✔
291
    }
292

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

305
    public get maxValue(): Date | string {
306
        return this._maxValue;
274✔
307
    }
308

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

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

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

353
    /**
354
     * An accessor that returns the resource strings.
355
     */
356
    public get resourceStrings(): IDateRangePickerResourceStrings {
357
        return this._resourceStrings || this._defaultResourceStrings;
179✔
358
    }
359

360
    /**
361
     * Sets the `placeholder` for single-input date range picker.
362
     *
363
     *   @example
364
     * ```html
365
     * <igx-date-range-picker [placeholder]="'Choose your dates'"></igx-date-range-picker>
366
     * ```
367
     */
368
    @Input()
369
    public override placeholder = '';
134✔
370

371
    /**
372
     * Show/hide week numbers
373
     *
374
     * @remarks
375
     * Default is `false`.
376
     *
377
     * @example
378
     * ```html
379
     * <igx-date-range-picker [showWeekNumbers]="true"></igx-date-range-picker>
380
     * ``
381
     */
382
    @Input({ transform: booleanAttribute })
383
    public showWeekNumbers = false;
134✔
384

385
    /** @hidden @internal */
386
    @Input({ transform: booleanAttribute })
387
    public readOnly = false;
134✔
388

389
    /**
390
     * Emitted when the picker's value changes. Used for two-way binding.
391
     *
392
     * @example
393
     * ```html
394
     * <igx-date-range-picker [(value)]="date"></igx-date-range-picker>
395
     * ```
396
     */
397

398
     /**
399
      * Whether to render built-in predefined ranges.
400
      *
401
      * @example
402
      * ```html
403
      * <igx-date-range-picker [(usePredefinedRanges)]="true"></igx-date-range-picker>
404
      * ``
405
      *  */
406
    @Input() public usePredefinedRanges = false;
134✔
407

408
    /**
409
     *  Custom ranges rendered as chips.
410
     *
411
     * @example
412
     * ```html
413
     * <igx-date-range-picker [(usePredefinedRanges)]="true"></igx-date-range-picker>
414
     * ``
415
    */
416
    @Input() public customRanges: CustomDateRange[] = [];
134✔
417

418
    @Output()
419
    public valueChange = new EventEmitter<DateRange>();
134✔
420

421
    /** @hidden @internal */
422
    @HostBinding('class.igx-date-range-picker')
423
    public cssClass = 'igx-date-range-picker';
134✔
424

425
    @ViewChild("container", { read: ViewContainerRef })
426
    private viewContainerRef: ViewContainerRef;
427

428
    /** @hidden @internal */
429
    @ViewChild(IgxInputDirective)
430
    public inputDirective: IgxInputDirective;
431

432
    /** @hidden @internal */
433
    @ContentChildren(IgxDateRangeInputsBaseComponent)
434
    public projectedInputs: QueryList<IgxDateRangeInputsBaseComponent>;
435

436
    @ContentChild(IgxLabelDirective)
437
    public label: IgxLabelDirective;
438

439
    @ContentChild(IgxHintDirective)
440
    public hint: IgxHintDirective;
441

442
    @ContentChild(IgxPickerActionsDirective)
443
    public pickerActions: IgxPickerActionsDirective;
444

445
    /** @hidden @internal */
446
    @ContentChild(IgxDateRangeSeparatorDirective, { read: TemplateRef })
447
    public dateSeparatorTemplate: TemplateRef<any>;
448

449

450
    @ContentChild(IgxCalendarHeaderTitleTemplateDirective)
451
    private headerTitleTemplate: IgxCalendarHeaderTitleTemplateDirective;
452

453
    @ContentChild(IgxCalendarHeaderTemplateDirective)
454
    private headerTemplate: IgxCalendarHeaderTemplateDirective;
455

456
    @ContentChild(IgxCalendarSubheaderTemplateDirective)
457
    private subheaderTemplate: IgxCalendarSubheaderTemplateDirective;
458

459
    /** @hidden @internal */
460
    public get dateSeparator(): string {
461
        if (this._dateSeparator === null) {
87✔
462
            return this.resourceStrings.igx_date_range_picker_date_separator;
87✔
463
        }
UNCOV
464
        return this._dateSeparator;
×
465
    }
466

467
    /** @hidden @internal */
468
    public get appliedFormat(): string {
469
        // Resolve display format since it can be custom specified one like short, long, shortDate, longDate and etc.
470
        const formatOptions = this.i18nFormatter.getFormatOptions(this.displayFormat);
1,104✔
471
        return formatOptions
1,104✔
472
            ? this.i18nFormatter.getLocaleDateTimeFormat(this.locale, false, formatOptions)
473
            : this.displayFormat ?? this.inputFormat;
2,067✔
474
    }
475

476
    /**
477
     * Gets/Sets the date which is shown in the calendar picker and is highlighted.
478
     * By default it is the current date, or the value of the picker, if set.
479
    */
480
    @Input()
481
    public get activeDate(): Date {
482
        const today = new Date(new Date().setHours(0, 0, 0, 0));
417✔
483
        const dateValue = DateTimeUtil.isValidDate(this._firstDefinedInRange) ? new Date(new Date(this._firstDefinedInRange.getTime()).setHours(0, 0, 0, 0)) : null;
417✔
484
        return this._activeDate ?? dateValue ?? this._calendar?.activeDate ?? today;
417✔
485
    }
486

487
    public set activeDate(value: Date) {
488
        this._activeDate = value;
1✔
489
    }
490

491
    /**
492
     * @example
493
     * ```html
494
     * <igx-date-range-picker locale="jp"></igx-date-range-picker>
495
     * ```
496
     */
497
    /**
498
     * Gets the `locale` of the date-range-picker.
499
     * If not set, defaults to application's locale.
500
     */
501
    @Input()
502
    public override get locale(): string {
503
        return this._locale || this._defaultLocale;
1,834✔
504
    }
505

506
    /**
507
     * Sets the `locale` of the date-picker.
508
     * Expects a valid BCP 47 language tag.
509
     */
510
    public override set locale(value: string) {
511
        this._locale = this.i18nFormatter.verifyLocale(value);
7✔
512
        this.updateResources();
7✔
513
        if (this.hasProjectedInputs) {
7✔
514
            this.updateInputLocale();
3✔
515
            this.updateDisplayFormat();
3✔
516
        }
517
    }
518

519
    /** @hidden @internal */
520
    public get singleInputFormat(): string {
521
        if (this.placeholder !== '') {
512✔
522
            return this.placeholder;
2✔
523
        }
524

525
        const format = this.appliedFormat;
510✔
526
        return `${format}${SingleInputDatesConcatenationString}${format}`;
510✔
527
    }
528

529
    /**
530
     * Gets calendar state.
531
     *
532
     * ```typescript
533
     * let state = this.dateRange.collapsed;
534
     * ```
535
     */
536
    public override get collapsed(): boolean {
537
        return this._collapsed;
798✔
538
    }
539

540
    /**
541
     * The currently selected value / range from the calendar
542
     *
543
     * @remarks
544
     * The current value is of type `DateRange`
545
     *
546
     * @example
547
     * ```typescript
548
     * const newValue: DateRange = { start: new Date("2/2/2012"), end: new Date("3/3/2013")};
549
     * this.dateRangePicker.value = newValue;
550
     * ```
551
     */
552
    public get value(): DateRange | null {
553
        return this._value;
3,417✔
554
    }
555

556
    @Input()
557
    public set value(value: DateRange | null) {
558
        this.updateValue(value);
130✔
559
        this.onChangeCallback(value);
130✔
560
        this.valueChange.emit(value);
130✔
561
    }
562

563
    /** @hidden @internal */
564
    public get hasProjectedInputs(): boolean {
565
        return this.projectedInputs?.length > 0;
3,207✔
566
    }
567

568
    /** @hidden @internal */
569
    public get separatorClass(): string {
570
        const classes = ['igx-date-range-picker__label'];
539✔
571
        if (this.hint) classes.push('input-has-hint');
539!
572
        return classes.join(' ');
539✔
573
    }
574

575
    protected override get toggleContainer(): HTMLElement | undefined {
UNCOV
576
        return this._calendarContainer;
×
577
    }
578

579
    private get required(): boolean {
580
        if (this._ngControl && this._ngControl.control && this._ngControl.control.validator) {
268✔
581
            const error = this._ngControl.control.validator({} as AbstractControl);
192✔
582
            return (error && error.required) ? true : false;
192!
583
        }
584

585
        return false;
76✔
586
    }
587

588
    private get calendar(): IgxCalendarComponent {
589
        return this._calendar;
853✔
590
    }
591

592
    private get dropdownOverlaySettings(): OverlaySettings {
593
        return Object.assign({}, this._dropDownOverlaySettings, this.overlaySettings);
46✔
594
    }
595

596
    private get dialogOverlaySettings(): OverlaySettings {
597
        return Object.assign({}, this._dialogOverlaySettings, this.overlaySettings);
16✔
598
    }
599

600
    private get _firstDefinedInRange(): Date | null {
601
        if (!this.value) {
605✔
602
            return null;
229✔
603
        }
604
        const range = this.toRangeOfDates(this.value);
376✔
605
        return range?.start ?? range?.end ?? null;
376!
606
    }
607

608
    private _resourceStrings: IDateRangePickerResourceStrings = null;
134✔
609
    private _defaultResourceStrings = getCurrentResourceStrings(DateRangePickerResourceStringsEN);
134✔
610
    private _doneButtonText = null;
134✔
611
    private _cancelButtonText = null;
134✔
612
    private _dateSeparator = null;
134✔
613
    private _value: DateRange | null;
614
    private _originalValue: DateRange | null;
615
    private _overlayId: string;
616
    private _ngControl: NgControl;
617
    private _statusChanges$: Subscription;
618
    private _calendar: IgxCalendarComponent;
619
    private _calendarContainer?: HTMLElement;
620
    private _positionSettings: PositionSettings;
621
    private _focusedInput: IgxDateRangeInputsBaseComponent;
622
    private _displayMonthsCount = 2;
134✔
623
    private _specialDates: DateRangeDescriptor[] = null;
134✔
624
    private _disabledDates: DateRangeDescriptor[] = null;
134✔
625
    private _activeDate: Date | null = null;
134✔
626
    private _overlaySubFilter:
134✔
627
        [MonoTypeOperatorFunction<OverlayEventArgs>, MonoTypeOperatorFunction<OverlayEventArgs | OverlayCancelableEventArgs>] = [
628
            filter(x => x.id === this._overlayId),
181✔
629
            takeUntil(merge(this._destroy$, this.closed))
630
        ];
631
    private _dialogOverlaySettings: OverlaySettings = {
134✔
632
        closeOnOutsideClick: true,
633
        modal: true,
634
        closeOnEscape: true
635
    };
636
    private _dropDownOverlaySettings: OverlaySettings = {
134✔
637
        closeOnOutsideClick: true,
638
        modal: false,
639
        closeOnEscape: true
640
    };
641
    private onChangeCallback: (dateRange: DateRange) => void = noop;
134✔
642
    private onTouchCallback: () => void = noop;
134✔
643
    private onValidatorChange: () => void = noop;
134✔
644

645
    constructor() {
646
        super();
134✔
647
        this.initLocale();
134✔
648
    }
649

650
    /** @hidden @internal */
651
    @HostListener('keydown', ['$event'])
652
    /** @hidden @internal */
653
    public onKeyDown(event: KeyboardEvent): void {
654
        switch (event.key) {
9✔
655
            case this.platform.KEYMAP.ARROW_UP:
656
                if (event.altKey) {
1✔
657
                    this.close();
1✔
658
                }
659
                break;
1✔
660
            case this.platform.KEYMAP.ARROW_DOWN:
661
                if (event.altKey) {
5✔
662
                    this.open();
5✔
663
                }
664
                break;
5✔
665
        }
666
    }
667

668
    /**
669
     * Opens the date range picker's dropdown or dialog.
670
     *
671
     * @example
672
     * ```html
673
     * <igx-date-range-picker #dateRange></igx-date-range-picker>
674
     *
675
     * <button type="button" igxButton (click)="dateRange.open()">Open Dialog</button
676
     * ```
677
     */
678
    public open(overlaySettings?: OverlaySettings): void {
679
        if (!this.collapsed || this.disabled || this.readOnly) {
67✔
680
            return;
5✔
681
        }
682

683
        this._originalValue = this._value
62✔
684
            ? { start: new Date(this._value.start), end: new Date(this._value.end) }
685
            : null;
686

687
        const settings = Object.assign({}, this.isDropdown
62✔
688
            ? this.dropdownOverlaySettings
689
            : this.dialogOverlaySettings
690
            , overlaySettings);
691

692
        this._overlayId = this._overlayService
62✔
693
            .attach(IgxCalendarContainerComponent, this.viewContainerRef, settings);
694
        this.subscribeToOverlayEvents();
62✔
695
        this._overlayService.show(this._overlayId);
62✔
696
    }
697

698
    /**
699
     * Closes the date range picker's dropdown or dialog.
700
     *
701
     * @example
702
     * ```html
703
     * <igx-date-range-picker #dateRange></igx-date-range-picker>
704
     *
705
     * <button type="button" igxButton (click)="dateRange.close()">Close Dialog</button>
706
     * ```
707
     */
708
    public close(): void {
709
        if (!this.collapsed) {
64✔
710
            this._overlayService.hide(this._overlayId);
47✔
711
        }
712
    }
713

714
    /**
715
     * Toggles the date range picker's dropdown or dialog
716
     *
717
     * @example
718
     * ```html
719
     * <igx-date-range-picker #dateRange></igx-date-range-picker>
720
     *
721
     * <button type="button" igxButton (click)="dateRange.toggle()">Toggle Dialog</button>
722
     * ```
723
     */
724
    public toggle(overlaySettings?: OverlaySettings): void {
725
        if (!this.collapsed) {
13✔
726
            this.close();
2✔
727
        } else {
728
            this.open(overlaySettings);
11✔
729
        }
730
    }
731

732
    /**
733
     * Selects a range of dates. If no `endDate` is passed, range is 1 day (only `startDate`)
734
     *
735
     * @example
736
     * ```typescript
737
     * public selectFiveDayRange() {
738
     *  const today = new Date();
739
     *  const inFiveDays = new Date(new Date().setDate(today.getDate() + 5));
740
     *  this.dateRange.select(today, inFiveDays);
741
     * }
742
     * ```
743
     */
744
    public select(startDate: Date, endDate?: Date): void {
745
        endDate = endDate ?? startDate;
25✔
746
        const dateRange = [startDate, endDate];
25✔
747
        this.handleSelection(dateRange);
25✔
748
    }
749

750
    /**
751
     * Clears the input field(s) and the picker's value.
752
     *
753
     * @example
754
     * ```typescript
755
     * this.dateRangePicker.clear();
756
     * ```
757
     */
758
    public clear(): void {
759
        if (this.disabled) {
4!
UNCOV
760
            return;
×
761
        }
762

763
        this.value = null;
4✔
764
        this._calendar?.deselectDate();
4✔
765
        if (this.hasProjectedInputs) {
4✔
766
            this.projectedInputs.forEach((i) => {
2✔
767
                i.inputDirective.clear();
4✔
768
            });
769
        } else {
770
            this.inputDirective.clear();
2✔
771
        }
772
    }
773

774
    /** @hidden @internal */
775
    public writeValue(value: DateRange): void {
776
        this.updateValue(value);
104✔
777
    }
778

779
    /** @hidden @internal */
780
    public registerOnChange(fn: any): void {
781
        this.onChangeCallback = fn;
62✔
782
    }
783

784
    /** @hidden @internal */
785
    public registerOnTouched(fn: any): void {
786
        this.onTouchCallback = fn;
60✔
787
    }
788

789
    /** @hidden @internal */
790
    public validate(control: AbstractControl): ValidationErrors | null {
791
        const value: DateRange = control.value;
371✔
792
        const errors = {};
371✔
793
        if (value) {
371✔
794
            if (this.hasProjectedInputs) {
87✔
795
                const startInput = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
82✔
796
                const endInput = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
164✔
797
                if (!startInput.dateTimeEditor.value) {
82!
UNCOV
798
                    Object.assign(errors, { startValue: true });
×
799
                }
800
                if (!endInput.dateTimeEditor.value) {
82✔
801
                    Object.assign(errors, { endValue: true });
2✔
802
                }
803
            }
804

805
            if (this._isValueInDisabledRange(value)) {
87✔
806
                Object.assign(errors, { dateIsDisabled: true });
1✔
807
            }
808

809
            const { minValue, maxValue } = this._getMinMaxDates();
87✔
810
            const start = parseDate(value.start);
87✔
811
            const end = parseDate(value.end);
87✔
812
            if ((minValue && start && DateTimeUtil.lessThanMinValue(start, minValue, false))
87✔
813
                || (minValue && end && DateTimeUtil.lessThanMinValue(end, minValue, false))) {
814
                Object.assign(errors, { minValue: true });
2✔
815
            }
816
            if ((maxValue && start && DateTimeUtil.greaterThanMaxValue(start, maxValue, false))
87✔
817
                || (maxValue && end && DateTimeUtil.greaterThanMaxValue(end, maxValue, false))) {
818
                Object.assign(errors, { maxValue: true });
1✔
819
            }
820
        }
821

822
        return Object.keys(errors).length > 0 ? errors : null;
371✔
823
    }
824

825
    /** @hidden @internal */
826
    public registerOnValidatorChange?(fn: any): void {
827
        this.onValidatorChange = fn;
61✔
828
    }
829

830
    /** @hidden @internal */
831
    public setDisabledState?(isDisabled: boolean): void {
832
        this.disabled = isDisabled;
61✔
833
    }
834

835
    /** @hidden */
836
    public ngOnInit(): void {
837
        this._ngControl = this._injector.get<NgControl>(NgControl, null);
131✔
838
    }
839

840
    /** @hidden */
841
    public override ngAfterViewInit(): void {
842
        super.ngAfterViewInit();
127✔
843
        this.subscribeToDateEditorEvents();
127✔
844
        this.subscribeToClick();
127✔
845
        this.configPositionStrategy();
127✔
846
        this.configOverlaySettings();
127✔
847
        this.cacheFocusedInput();
127✔
848
        this.attachOnTouched();
127✔
849

850
        this.setRequiredToInputs();
127✔
851

852
        if (this._ngControl) {
127✔
853
            this._statusChanges$ = this._ngControl.statusChanges.subscribe(this.onStatusChanged.bind(this));
53✔
854
        }
855

856
        // delay invocations until the current change detection cycle has completed
857
        Promise.resolve().then(() => {
127✔
858
            this.updateDisabledState();
127✔
859
            this.initialSetValue();
127✔
860
            this.updateInputs();
127✔
861
            // B.P. 07 July 2021 - IgxDateRangePicker not showing initial disabled state with ChangeDetectionStrategy.OnPush #9776
862
            /**
863
             * if disabled is placed on the range picker element and there are projected inputs
864
             * run change detection since igxInput will initially set the projected inputs' disabled to false
865
             */
866
            if (this.hasProjectedInputs && this.disabled) {
127✔
867
                this._cdr.markForCheck();
3✔
868
            }
869
        });
870
        this.updateDisplayFormat();
127✔
871
        this.updateInputFormat();
127✔
872
    }
873

874
    /** @hidden @internal */
875
    public ngOnChanges(changes: SimpleChanges): void {
876
        if (changes['displayFormat'] && this.hasProjectedInputs) {
119✔
877
            this.updateDisplayFormat();
11✔
878
        }
879
        if (changes['inputFormat'] && this.hasProjectedInputs) {
119✔
880
            this.updateInputFormat();
4✔
881
        }
882
        if (changes['disabled']) {
119✔
883
            this.updateDisabledState();
94✔
884
        }
885
    }
886

887
    /** @hidden @internal */
888
    public override ngOnDestroy(): void {
889
        super.ngOnDestroy();
80✔
890
        if (this._statusChanges$) {
80✔
891
            this._statusChanges$.unsubscribe();
33✔
892
        }
893
        if (this._overlayId) {
80!
UNCOV
894
            this._overlayService.detach(this._overlayId);
×
895
        }
896
    }
897

898
    /** @hidden @internal */
899
    public getEditElement(): HTMLInputElement {
900
        return this.inputDirective!.nativeElement;
76✔
901
    }
902

903
    protected onStatusChanged = () => {
134✔
904
        if (this.inputGroup) {
140✔
905
            this.setValidityState(this.inputDirective, this.inputGroup.isFocused);
7✔
906
        } else if (this.hasProjectedInputs) {
133✔
907
            this.projectedInputs
133✔
908
                .forEach((i) => {
909
                    this.setValidityState(i.inputDirective, i.isFocused);
266✔
910
                });
911
        }
912
        this.setRequiredToInputs();
140✔
913
    };
914

915
    private setValidityState(inputDirective: IgxInputDirective, isFocused: boolean) {
916
        if (this._ngControl && !this._ngControl.disabled && this.isTouchedOrDirty) {
273✔
917
            if (this.hasValidators && isFocused) {
243✔
918
                inputDirective.valid = this._ngControl.valid ? IgxInputState.VALID : IgxInputState.INVALID;
22✔
919
            } else {
920
                inputDirective.valid = this._ngControl.valid ? IgxInputState.INITIAL : IgxInputState.INVALID;
221✔
921
            }
922
        } else {
923
            inputDirective.valid = IgxInputState.INITIAL;
30✔
924
        }
925
    }
926

927
    private get isTouchedOrDirty(): boolean {
928
        return (this._ngControl.control.touched || this._ngControl.control.dirty);
263✔
929
    }
930

931
    private get hasValidators(): boolean {
932
        return (!!this._ngControl.control.validator || !!this._ngControl.control.asyncValidator);
243✔
933
    }
934

935
    private handleSelection(selectionData: Date[]): void {
936
        let newValue = this.extractRange(selectionData);
45✔
937
        if (!newValue.start && !newValue.end) {
45!
UNCOV
938
            newValue = null;
×
939
        }
940
        this.value = newValue;
45✔
941
        if (this.isDropdown && selectionData?.length > 1) {
45✔
942
            this.close();
32✔
943
        }
944
        this._setCalendarActiveDate();
45✔
945
    }
946

947
    private handleClosing(e: IBaseCancelableBrowserEventArgs): void {
948
        const args = { owner: this, cancel: e?.cancel, event: e?.event };
51✔
949
        this.closing.emit(args);
51✔
950
        e.cancel = args.cancel;
51✔
951
        if (args.cancel) {
51✔
952
            return;
3✔
953
        }
954

955
        if (this.isDropdown && e?.event && !this.isFocused) {
48!
956
            // outside click
UNCOV
957
            this.updateValidityOnBlur();
×
958
        } else {
959
            this.onTouchCallback();
48✔
960
            // input click
961
            if (this.hasProjectedInputs && this._focusedInput) {
48✔
962
                this._focusedInput.setFocus();
7✔
963
            }
964
            if (this.inputDirective) {
48✔
965
                this.inputDirective.focus();
18✔
966
            }
967
        }
968
    }
969

970
    private subscribeToOverlayEvents() {
971
        this._overlayService.opening.pipe(...this._overlaySubFilter).subscribe((e) => {
62✔
972
            const overlayEvent = e as OverlayCancelableEventArgs;
62✔
973
            const args = { owner: this, cancel: overlayEvent?.cancel, event: e.event };
62✔
974
            this.opening.emit(args);
62✔
975
            if (args.cancel) {
62!
UNCOV
976
                this._overlayService.detach(this._overlayId);
×
UNCOV
977
                overlayEvent.cancel = true;
×
UNCOV
978
                return;
×
979
            }
980

981
            this._initializeCalendarContainer(e.componentRef.instance);
62✔
982
            this._calendarContainer = e.componentRef.location.nativeElement;
62✔
983
            this._collapsed = false;
62✔
984
            this.updateCalendar();
62✔
985
        });
986

987
        this._overlayService.opened.pipe(...this._overlaySubFilter).subscribe(() => {
62✔
988
            this.calendar.wrapper.nativeElement.focus();
48✔
989
            this.opened.emit({ owner: this });
48✔
990
        });
991

992
        this._overlayService.closing.pipe(...this._overlaySubFilter).subscribe((e: OverlayCancelableEventArgs) => {
62✔
993
            const isEscape = e.event && (e.event as KeyboardEvent).key === this.platform.KEYMAP.ESCAPE;
51✔
994
            if (this.isProjectedInputTarget(e.event) && !isEscape) {
51✔
995
                e.cancel = true;
1✔
996
            }
997
            this.handleClosing(e as OverlayCancelableEventArgs);
51✔
998
        });
999

1000
        this._overlayService.closed.pipe(...this._overlaySubFilter).subscribe(() => {
62✔
1001
            this._overlayService.detach(this._overlayId);
20✔
1002
            this._collapsed = true;
20✔
1003
            this._overlayId = null;
20✔
1004
            this._calendar = null;
20✔
1005
            this._calendarContainer = undefined;
20✔
1006
            this.closed.emit({ owner: this });
20✔
1007
        });
1008
    }
1009

1010
    private isProjectedInputTarget(event: Event): boolean {
1011
        if (!this.hasProjectedInputs || !event) {
51✔
1012
            return false;
48✔
1013
        }
1014
        const path = event.composed ? event.composedPath() : [event.target];
3!
1015
        return this.projectedInputs.some(i =>
3✔
1016
            path.includes(i.dateTimeEditor.nativeElement)
5✔
1017
        );
1018
    }
1019

1020
    private updateValue(value: DateRange) {
1021
        this._value = value ? value : null;
234✔
1022
        this.updateInputs();
234✔
1023
        this.updateCalendar();
234✔
1024
    }
1025

1026
    private updateValidityOnBlur() {
1027
        this._focusedInput = null;
2✔
1028
        this.onTouchCallback();
2✔
1029
        if (this._ngControl) {
2✔
1030
            if (this.hasProjectedInputs) {
1✔
1031
                this.projectedInputs.forEach(i => {
1✔
1032
                    if (!this._ngControl.valid) {
2!
1033
                        i.updateInputValidity(IgxInputState.INVALID);
2✔
1034
                    } else {
UNCOV
1035
                        i.updateInputValidity(IgxInputState.INITIAL);
×
1036
                    }
1037
                });
1038
            }
1039

1040
            if (this.inputDirective) {
1!
UNCOV
1041
                if (!this._ngControl.valid) {
×
UNCOV
1042
                    this.inputDirective.valid = IgxInputState.INVALID;
×
1043
                } else {
UNCOV
1044
                    this.inputDirective.valid = IgxInputState.INITIAL;
×
1045
                }
1046
            }
1047
        }
1048
    }
1049

1050
    private updateDisabledState() {
1051
        if (this.hasProjectedInputs) {
221✔
1052
            const start = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
58✔
1053
            const end = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
116✔
1054
            start.inputDirective.disabled = this.disabled;
58✔
1055
            end.inputDirective.disabled = this.disabled;
58✔
1056
            return;
58✔
1057
        }
1058
    }
1059

1060
    private setRequiredToInputs(): void {
1061
        // workaround for igxInput setting required
1062
        Promise.resolve().then(() => {
267✔
1063
            const isRequired = this.required;
267✔
1064
            if (this.inputGroup && this.inputGroup.isRequired !== isRequired) {
267✔
1065
                this.inputGroup.isRequired = isRequired;
4✔
1066
            } else if (this.hasProjectedInputs && this._ngControl) {
263✔
1067
                this.projectedInputs.forEach(i => i.isRequired = isRequired);
366✔
1068
            }
1069
        });
1070
    }
1071

1072
    private parseMinValue(value: string | Date): Date | null {
1073
        let minValue: Date = parseDate(value);
272✔
1074
        if (!minValue && this.hasProjectedInputs) {
272✔
1075
            const start = this.projectedInputs.filter(i => i instanceof IgxDateRangeStartComponent)[0];
358✔
1076
            if (start) {
179✔
1077
                minValue = parseDate(start.dateTimeEditor.minValue);
179✔
1078
            }
1079
        }
1080

1081
        return minValue;
272✔
1082
    }
1083

1084
    private parseMaxValue(value: string | Date): Date | null {
1085
        let maxValue: Date = parseDate(value);
272✔
1086
        if (!maxValue && this.projectedInputs) {
272✔
1087
            const end = this.projectedInputs.filter(i => i instanceof IgxDateRangeEndComponent)[0];
358✔
1088
            if (end) {
262✔
1089
                maxValue = parseDate(end.dateTimeEditor.maxValue);
179✔
1090
            }
1091
        }
1092

1093
        return maxValue;
272✔
1094
    }
1095

1096
    private updateCalendar(): void {
1097
        if (!this.calendar) {
299✔
1098
            return;
176✔
1099
        }
1100
        this._setDisabledDates();
123✔
1101

1102
        const range: Date[] = [];
123✔
1103
        if (this.value) {
123✔
1104
            const _value = this.toRangeOfDates(this.value);
61✔
1105
            if (_value.start && _value.end) {
61✔
1106
                if (DateTimeUtil.greaterThanMaxValue(_value.start, _value.end)) {
58✔
1107
                    this.swapEditorDates();
2✔
1108
                }
1109
            }
1110
            if (_value.start) {
61✔
1111
                range.push(_value.start);
60✔
1112
            }
1113
            if (_value.end) {
61✔
1114
                range.push(_value.end);
59✔
1115
            }
1116
        }
1117

1118
        if (range.length > 0) {
123✔
1119
            this.calendar.selectDate(range);
61✔
1120
        } else if (range.length === 0 && this.calendar.monthViews) {
62✔
1121
            this.calendar.deselectDate();
6✔
1122
        }
1123
        this._setCalendarActiveDate();
123✔
1124
        this._cdr.detectChanges();
123✔
1125
    }
1126

1127
    private swapEditorDates(): void {
1128
        if (this.hasProjectedInputs) {
2✔
1129
            const start = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
2✔
1130
            const end = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
4✔
1131
            [start.dateTimeEditor.value, end.dateTimeEditor.value] = [end.dateTimeEditor.value, start.dateTimeEditor.value];
2✔
1132
            [this.value.start, this.value.end] = [this.value.end, this.value.start];
2✔
1133
        }
1134
    }
1135

1136
    private extractRange(selection: Date[]): DateRange {
1137
        return {
45✔
1138
            start: selection[0] || null,
45!
1139
            end: selection.length > 0 ? selection[selection.length - 1] : null
45!
1140
        };
1141
    }
1142

1143
    private toRangeOfDates(range: DateRange): { start: Date; end: Date } {
1144
        let start;
1145
        let end;
1146
        if (!isDate(range.start)) {
524✔
1147
            start = DateTimeUtil.parseIsoDate(range.start);
9✔
1148
        }
1149
        if (!isDate(range.end)) {
524✔
1150
            end = DateTimeUtil.parseIsoDate(range.end);
18✔
1151
        }
1152

1153
        if (start || end) {
524!
UNCOV
1154
            return { start, end };
×
1155
        }
1156

1157
        return { start: range.start as Date, end: range.end as Date };
524✔
1158
    }
1159

1160
    private subscribeToClick() {
1161
        const inputs = this.hasProjectedInputs
127✔
1162
            ? this.projectedInputs.map(i => i.inputDirective.nativeElement)
106✔
1163
            : [this.getEditElement()];
1164
        inputs.forEach(input => {
127✔
1165
            fromEvent(input, 'click')
180✔
1166
                .pipe(takeUntil(this._destroy$))
1167
                .subscribe(() => {
1168
                    if (!this.isDropdown) {
4✔
1169
                        this.toggle();
2✔
1170
                    }
1171
                });
1172
        });
1173
    }
1174

1175
    private subscribeToDateEditorEvents(): void {
1176
        if (this.hasProjectedInputs) {
127✔
1177
            const start = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
53✔
1178
            const end = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
106✔
1179
            if (start && end) {
53✔
1180
                start.dateTimeEditor.valueChange
53✔
1181
                    .pipe(takeUntil(this._destroy$))
1182
                    .subscribe(value => {
1183
                        if (this.value) {
22✔
1184
                            this.value = { start: value, end: this.value.end };
20✔
1185
                        } else {
1186
                            this.value = { start: value, end: null };
2✔
1187
                        }
1188
                        if (this.calendar) {
22✔
1189
                            this._setCalendarActiveDate(parseDate(value));
21✔
1190
                            this._cdr.detectChanges();
21✔
1191
                        }
1192
                    });
1193
                end.dateTimeEditor.valueChange
53✔
1194
                    .pipe(takeUntil(this._destroy$))
1195
                    .subscribe(value => {
1196
                        if (this.value) {
1!
1197
                            this.value = { start: this.value.start, end: value as Date };
1✔
1198
                        } else {
UNCOV
1199
                            this.value = { start: null, end: value as Date };
×
1200
                        }
1201
                        if (this.calendar) {
1!
UNCOV
1202
                            this._setCalendarActiveDate(parseDate(value));
×
UNCOV
1203
                            this._cdr.detectChanges();
×
1204
                        }
1205
                    });
1206
            }
1207
        }
1208
    }
1209

1210
    private attachOnTouched(): void {
1211
        if (this.hasProjectedInputs) {
127✔
1212
            this.projectedInputs.forEach(i => {
53✔
1213
                fromEvent(i.dateTimeEditor.nativeElement, 'blur')
106✔
1214
                    .pipe(takeUntil(this._destroy$))
1215
                    .subscribe(() => {
1216
                        if (this.collapsed) {
12✔
1217
                            this.updateValidityOnBlur();
1✔
1218
                        }
1219
                    });
1220
            });
1221
        } else {
1222
            fromEvent(this.inputDirective.nativeElement, 'blur')
74✔
1223
                .pipe(takeUntil(this._destroy$))
1224
                .subscribe(() => {
1225
                    if (this.collapsed) {
8!
UNCOV
1226
                        this.updateValidityOnBlur();
×
1227
                    }
1228
                });
1229
        }
1230
    }
1231

1232
    private cacheFocusedInput(): void {
1233
        if (this.hasProjectedInputs) {
127✔
1234
            this.projectedInputs.forEach(i => {
53✔
1235
                fromEvent(i.dateTimeEditor.nativeElement, 'focus')
106✔
1236
                    .pipe(takeUntil(this._destroy$))
1237
                    .subscribe(() => this._focusedInput = i);
16✔
1238
            });
1239
        }
1240
    }
1241

1242
    private configPositionStrategy(): void {
1243
        this._positionSettings = {
127✔
1244
            openAnimation: fadeIn,
1245
            closeAnimation: fadeOut,
1246
            offset: 1
1247
        };
1248
        this._dropDownOverlaySettings.positionStrategy = new AutoPositionStrategy(this._positionSettings);
127✔
1249

1250
        const bundle = this.hasProjectedInputs
127✔
1251
            ? this.projectedInputs.first?.nativeElement.querySelector('.igx-input-group__bundle')
1252
            : this.element.nativeElement.querySelector('.igx-input-group__bundle');
1253
        this._dropDownOverlaySettings.target = bundle || this.element.nativeElement;
127!
1254
    }
1255

1256
    private configOverlaySettings(): void {
1257
        if (this.overlaySettings !== null) {
127✔
1258
            this._dropDownOverlaySettings = Object.assign({}, this._dropDownOverlaySettings, this.overlaySettings);
127✔
1259
            this._dialogOverlaySettings = Object.assign({}, this._dialogOverlaySettings, this.overlaySettings);
127✔
1260
        }
1261
    }
1262

1263
    private initialSetValue() {
1264
        // if there is no value and no ngControl on the picker but we have inputs we may have value set through
1265
        // their ngModels - we should generate our initial control value
1266
        if ((!this.value || (!this.value.start && !this.value.end)) && this.hasProjectedInputs && !this._ngControl) {
127✔
1267
            const start = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent);
3✔
1268
            const end = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent);
6✔
1269
            this._value = {
3✔
1270
                start: start.dateTimeEditor.value as Date,
1271
                end: end.dateTimeEditor.value as Date
1272
            };
1273
        }
1274
    }
1275

1276
    private updateInputs(): void {
1277
        const start = this.projectedInputs?.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
361✔
1278
        const end = this.projectedInputs?.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
372✔
1279
        if (start && end) {
361✔
1280
            const _value = this.value ? this.toRangeOfDates(this.value) : null;
186✔
1281
            start.updateInputValue(_value?.start || null);
186✔
1282
            end.updateInputValue(_value?.end || null);
186✔
1283
        }
1284
    }
1285

1286
    private updateDisplayFormat(): void {
1287
        this.projectedInputs.forEach(i => {
141✔
1288
            const input = i as IgxDateRangeInputsBaseComponent;
134✔
1289
            input.dateTimeEditor.displayFormat = this.displayFormat;
134✔
1290
        });
1291
    }
1292

1293
    private updateInputFormat(): void {
1294
        this.projectedInputs.forEach(i => {
131✔
1295
            const input = i as IgxDateRangeInputsBaseComponent;
114✔
1296
            if (input.dateTimeEditor.inputFormat !== this.inputFormat) {
114✔
1297
                input.dateTimeEditor.inputFormat = this.inputFormat;
8✔
1298
            }
1299
        });
1300
    }
1301

1302
    private updateInputLocale(): void {
1303
        this.projectedInputs.forEach(i => {
3✔
1304
            const input = i as IgxDateRangeInputsBaseComponent;
6✔
1305
            input.dateTimeEditor.locale = this.locale;
6✔
1306
            input.dateTimeEditor.updateMask();
6✔
1307
        });
1308
    }
1309

1310
    protected override onResourceChange(args: CustomEvent<IResourceChangeEventArgs>) {
1311
        super.onResourceChange(args);
867✔
1312
        if (args.detail.oldLocale !== args.detail.newLocale && this.hasProjectedInputs) {
867!
UNCOV
1313
            this.updateInputLocale();
×
UNCOV
1314
            this.updateDisplayFormat();
×
1315
        }
1316
    }
1317

1318
    protected override updateResources(): void {
1319
        this._defaultResourceStrings = getCurrentResourceStrings(DateRangePickerResourceStringsEN, false, this._locale);
874✔
1320
    }
1321

1322
    private _initializeCalendarContainer(componentInstance: IgxCalendarContainerComponent) {
1323
        this._calendar = componentInstance.calendar;
62✔
1324
        this._calendar.hasHeader = !this.isDropdown && !this.hideHeader;
62✔
1325
        this._calendar.locale = this.locale;
62✔
1326
        this._calendar.selection = CalendarSelection.RANGE;
62✔
1327
        this._calendar.weekStart = this.weekStart;
62✔
1328
        this._calendar.hideOutsideDays = this.hideOutsideDays;
62✔
1329
        this._calendar.monthsViewNumber = this._displayMonthsCount;
62✔
1330
        this._calendar.showWeekNumbers = this.showWeekNumbers;
62✔
1331
        this._calendar.headerTitleTemplate = this.headerTitleTemplate;
62✔
1332
        this._calendar.headerTemplate = this.headerTemplate;
62✔
1333
        this._calendar.subheaderTemplate = this.subheaderTemplate;
62✔
1334
        this._calendar.headerOrientation = this.headerOrientation;
62✔
1335
        this._calendar.orientation = this.orientation;
62✔
1336
        this._calendar.specialDates = this.specialDates;
62✔
1337
        this._calendar.selected.pipe(takeUntil(this._destroy$)).subscribe((ev: Date[]) => this.handleSelection(ev));
62✔
1338

1339
        this._setDisabledDates();
62✔
1340
        this._setCalendarActiveDate();
62✔
1341

1342
        componentInstance.mode = this.mode;
62✔
1343
        componentInstance.closeButtonLabel = !this.isDropdown ? this.doneButtonText : null;
62✔
1344
        componentInstance.cancelButtonLabel = !this.isDropdown ? this.cancelButtonText : null;
62✔
1345
        if (!this.isDropdown && this.themeToken.theme === 'indigo') {
62!
UNCOV
1346
            componentInstance.closeButtonType = 'contained';
×
UNCOV
1347
            componentInstance.cancelButtonType = 'outlined';
×
1348
        }
1349
        componentInstance.pickerActions = this.pickerActions;
62✔
1350
        componentInstance.usePredefinedRanges = this.usePredefinedRanges;
62✔
1351
        componentInstance.customRanges = this.customRanges;
62✔
1352
        componentInstance.resourceStrings = this.resourceStrings;
62✔
1353
        componentInstance.calendarClose.pipe(takeUntil(this._destroy$)).subscribe(() => this.close());
62✔
1354
        componentInstance.calendarCancel.pipe(takeUntil(this._destroy$)).subscribe(() => {
62✔
1355
            this._value = this._originalValue;
2✔
1356
            this.close()
2✔
1357
        });
1358
        componentInstance.rangeSelected
62✔
1359
        .pipe(takeUntil(this._destroy$))
1360
        .subscribe((r: DateRange) => {
1361
            if (r?.start && r?.end) {
6✔
1362
            this.select(new Date(r.start), new Date(r.end));
6✔
1363
            }
1364

1365
            if (this.isDropdown) {
6✔
1366
            this.close();
6✔
1367
            }
1368
        });
1369
    }
1370

1371
    private _setDisabledDates(): void {
1372
        if (!this.calendar) {
185!
UNCOV
1373
            return;
×
1374
        }
1375
        this.calendar.disabledDates = this.disabledDates ? [...this.disabledDates] : [];
185✔
1376
        const { minValue, maxValue } = this._getMinMaxDates();
185✔
1377
        if (minValue) {
185✔
1378
            this.calendar.disabledDates.push({ type: DateRangeType.Before, dateRange: [minValue] });
2✔
1379
        }
1380
        if (maxValue) {
185✔
1381
            this.calendar.disabledDates.push({ type: DateRangeType.After, dateRange: [maxValue] });
2✔
1382
        }
1383
    }
1384

1385
    private _getMinMaxDates() {
1386
        const minValue = this.parseMinValue(this.minValue);
272✔
1387
        const maxValue = this.parseMaxValue(this.maxValue);
272✔
1388
        return { minValue, maxValue };
272✔
1389
    }
1390

1391
    private _isValueInDisabledRange(value: DateRange) {
1392
        if (value && value.start && value.end && this.disabledDates) {
87✔
1393
            const isOutsideDisabledRange = Array.from(
2✔
1394
                calendarRange({
1395
                    start: parseDate(this.value.start),
1396
                    end: parseDate(this.value.end),
1397
                    inclusive: true
1398
                })).every((date) => !isDateInRanges(date, this.disabledDates));
5✔
1399
            return !isOutsideDisabledRange;
2✔
1400
        }
1401
        return false;
85✔
1402
    }
1403

1404
    private _setCalendarActiveDate(value = null): void {
230✔
1405
        if (this._calendar) {
251✔
1406
            this._calendar.activeDate = value ?? this.activeDate;
227✔
1407
            this._calendar.viewDate = value ?? this.activeDate;
227✔
1408
        }
1409
    }
1410
}
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