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

IgniteUI / igniteui-angular / 6586761169

20 Oct 2023 10:52AM UTC coverage: 92.288% (+0.02%) from 92.264%
6586761169

push

github

web-flow
fix(buttons): adding missing type=button #13567 (#13573)

15314 of 17987 branches covered (0.0%)

15 of 15 new or added lines in 6 files covered. (100.0%)

26855 of 29099 relevant lines covered (92.29%)

29746.73 hits per line

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

91.44
/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.ts
1
import {
2
  AfterViewInit, ChangeDetectorRef, Component, ContentChild, ContentChildren, ElementRef,
3
  EventEmitter, HostBinding, HostListener, Inject, Injector, Input, LOCALE_ID,
4
  OnChanges, OnDestroy, OnInit, Optional, Output, QueryList,
5
  SimpleChanges, TemplateRef, ViewChild, ViewContainerRef
6
} from '@angular/core';
7
import {
8
  AbstractControl, ControlValueAccessor, NgControl,
9
  NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator
10
} from '@angular/forms';
11
import { fromEvent, merge, MonoTypeOperatorFunction, noop, Subscription } from 'rxjs';
12
import { filter, takeUntil } from 'rxjs/operators';
13
import { fadeIn, fadeOut } from '../animations/fade';
14
import { CalendarSelection, IgxCalendarComponent } from '../calendar/public_api';
15
import { DateRangeType } from '../core/dates';
16
import { DisplayDensityToken, IDisplayDensityOptions } from '../core/density';
17
import { IDateRangePickerResourceStrings } from '../core/i18n/date-range-picker-resources';
18
import { CurrentResourceStrings } from '../core/i18n/resources';
19
import { IBaseCancelableBrowserEventArgs, isDate, parseDate, PlatformUtil } from '../core/utils';
20
import { IgxCalendarContainerComponent } from '../date-common/calendar-container/calendar-container.component';
21
import { PickerBaseDirective } from '../date-common/picker-base.directive';
2✔
22
import { IgxPickerActionsDirective } from '../date-common/picker-icons.common';
23
import { DateTimeUtil } from '../date-common/util/date-time.util';
24
import { IgxOverlayOutletDirective } from '../directives/toggle/toggle.directive';
25
import {
26
  IgxInputDirective, IgxInputGroupComponent, IgxInputGroupType, IgxInputState,
27
  IgxLabelDirective, IGX_INPUT_GROUP_TYPE
28
} from '../input-group/public_api';
29
import {
30
  AutoPositionStrategy, IgxOverlayService, OverlayCancelableEventArgs, OverlayEventArgs,
31
  OverlaySettings, PositionSettings
32
} from '../services/public_api';
33
import {
34
  DateRange, IgxDateRangeEndComponent, IgxDateRangeInputsBaseComponent,
35
  IgxDateRangeSeparatorDirective, IgxDateRangeStartComponent
36
} from './date-range-picker-inputs.common';
37

38
const SingleInputDatesConcatenationString = ' - ';
39

40
/**
41
 * Provides the ability to select a range of dates from a calendar UI or editable inputs.
42
 *
43
 * @igxModule IgxDateRangeModule
44
 *
2✔
45
 * @igxTheme igx-input-group-theme, igx-calendar-theme, igx-date-range-picker-theme
46
 *
1✔
47
 * @igxKeywords date, range, date range, date picker
48
 *
49
 * @igxGroup scheduling
6✔
50
 *
5✔
51
 * @remarks
52
 * It displays the range selection in a single or two input fields.
1✔
53
 * The default template displays a single *readonly* input field
54
 * while projecting `igx-date-range-start` and `igx-date-range-end`
55
 * displays two *editable* input fields.
27✔
56
 *
27✔
57
 * @example
58
 * ```html
59
 * <igx-date-range-picker mode="dropdown"></igx-date-range-picker>
99✔
60
 * ```
61
 */
62
@Component({
27✔
63
    selector: 'igx-date-range-picker',
27✔
64
    templateUrl: './date-range-picker.component.html',
65
    providers: [
66
        { provide: NG_VALUE_ACCESSOR, useExisting: IgxDateRangePickerComponent, multi: true },
99✔
67
        { provide: NG_VALIDATORS, useExisting: IgxDateRangePickerComponent, multi: true }
68
    ]
69
})
×
70
export class IgxDateRangePickerComponent extends PickerBaseDirective
71
    implements OnChanges, OnInit, AfterViewInit, OnDestroy, ControlValueAccessor, Validator {
72

73
    /**
74
     * The number of displayed month views.
75
     *
45✔
76
     * @remarks
77
     * Default is `2`.
78
     *
79
     * @example
40!
80
     * ```html
40✔
81
     * <igx-date-range-picker [displayMonthsCount]="3"></igx-date-range-picker>
82
     * ```
×
83
     */
84
    @Input()
85
    public displayMonthsCount = 2;
86

506✔
87
    /**
88
     * Gets/Sets whether dates that are not part of the current month will be displayed.
89
     *
90
     * @remarks
91
     * Default value is `false`.
236✔
92
     *
2✔
93
     * @example
94
     * ```html
234✔
95
     * <igx-date-range-picker [hideOutsideDays]="true"></igx-date-range-picker>
234✔
96
     * ```
97
     */
98
    @Input()
99
    public hideOutsideDays: boolean;
100

101
    /**
102
     * A custom formatter function, applied on the selected or passed in date.
103
     *
104
     * @example
105
     * ```typescript
405✔
106
     * private dayFormatter = new Intl.DateTimeFormat("en", { weekday: "long" });
107
     * private monthFormatter = new Intl.DateTimeFormat("en", { month: "long" });
108
     *
109
     * public formatter(date: Date): string {
110
     *  return `${this.dayFormatter.format(date)} - ${this.monthFormatter.format(date)} - ${date.getFullYear()}`;
111
     * }
112
     * ```
113
     * ```html
114
     * <igx-date-range-picker [formatter]="formatter"></igx-date-range-picker>
115
     * ```
116
     */
117
    @Input()
118
    public formatter: (val: DateRange) => string;
119

120
    /**
900✔
121
     * The default text of the calendar dialog `done` button.
122
     *
123
     * @remarks
70✔
124
     * Default value is `Done`.
70✔
125
     * An @Input property that renders Done button with custom text. By default `doneButtonText` is set to Done.
70✔
126
     * The button will only show up in `dialog` mode.
127
     *
128
     * @example
129
     * ```html
1,316✔
130
     * <igx-date-range-picker doneButtonText="完了"></igx-date-range-picker>
131
     * ```
132
     */
133
    @Input()
240✔
134
    public set doneButtonText(value: string) {
135
        this._doneButtonText = value;
136
    }
135✔
137

98✔
138
    public get doneButtonText(): string {
98!
139
        if (this._doneButtonText === null) {
140
            return this.resourceStrings.igx_date_range_picker_done_button;
37✔
141
        }
142
        return this._doneButtonText;
143
    }
565✔
144
    /**
145
     * Custom overlay settings that should be used to display the calendar.
146
     *
27✔
147
     * @example
148
     * ```html
149
     * <igx-date-range-picker [overlaySettings]="customOverlaySettings"></igx-date-range-picker>
6✔
150
     * ```
151
     */
152
    @Input()
69✔
153
    public override overlaySettings: OverlaySettings;
69✔
154

69✔
155
    /**
69✔
156
     * The format used when editable inputs are not focused.
69✔
157
     *
69✔
158
     * @remarks
69✔
159
     * Uses Angular's DatePipe.
69✔
160
     *
69✔
161
     * @example
69✔
162
     * ```html
69✔
163
     * <igx-date-range-picker displayFormat="EE/M/yy"></igx-date-range-picker>
69✔
164
     * ```
69✔
165
     *
104✔
166
     */
167
    @Input()
168
    public override displayFormat: string;
69✔
169

170
    /**
171
     * The expected user input format and placeholder.
172
     *
173
     * @remarks
69✔
174
     * Default is `"'MM/dd/yyyy'"`
175
     *
176
     * @example
177
     * ```html
178
     * <igx-date-range-picker inputFormat="dd/MM/yy"></igx-date-range-picker>
69✔
179
     * ```
69✔
180
     */
69✔
181
    @Input()
69✔
182
    public override inputFormat: string;
71✔
183

7✔
184
    /**
185
     * The minimum value in a valid range.
64!
186
     *
64✔
187
     * @example
188
     * <igx-date-range-picker [minValue]="minDate"></igx-date-range-picker>
128✔
189
     */
190
    @Input()
191
    public set minValue(value: Date | string) {
71✔
192
        this._minValue = value;
193
        this.onValidatorChange();
69!
194
    }
195

196
    public get minValue(): Date | string {
4!
197
        return this._minValue;
198
    }
×
199

×
200
    /**
201
     * The maximum value in a valid range.
×
202
     *
203
     * @example
4!
204
     * <igx-date-range-picker [maxValue]="maxDate"></igx-date-range-picker>
4✔
205
     */
206
    @Input()
4✔
207
    public set maxValue(value: Date | string) {
208
        this._maxValue = value;
209
        this.onValidatorChange();
210
    }
211

212
    public get maxValue(): Date | string {
213
        return this._maxValue;
214
    }
215

216
    /**
217
     * An accessor that sets the resource strings.
218
     * By default it uses EN resources.
219
     */
220
    @Input()
37✔
221
    public set resourceStrings(value: IDateRangePickerResourceStrings) {
4✔
222
        this._resourceStrings = Object.assign({}, this._resourceStrings, value);
223
    }
33✔
224

225
    /**
226
     * An accessor that returns the resource strings.
33✔
227
     */
228
    public get resourceStrings(): IDateRangePickerResourceStrings {
33✔
229
        return this._resourceStrings;
33✔
230
    }
231

232
    /**
233
     * Sets the `placeholder` for single-input `IgxDateRangePickerComponent`.
234
     *
235
     *   @example
236
     * ```html
237
     * <igx-date-range-picker [placeholder]="'Choose your dates'"></igx-date-range-picker>
238
     * ```
239
     */
240
    @Input()
241
    public override placeholder = '';
242

46✔
243
    /**
29✔
244
     * Gets/Sets the container used for the popup element.
245
     *
246
     * @remarks
247
     *  `outlet` is an instance of `IgxOverlayOutletDirective` or an `ElementRef`.
248
     * @example
249
     * ```html
250
     * <div igxOverlayOutlet #outlet="overlay-outlet"></div>
251
     * //..
252
     * <igx-date-range-picker [outlet]="outlet"></igx-date-range-picker>
253
     * //..
254
     * ```
255
     */
256
    @Input()
257
    public override outlet: IgxOverlayOutletDirective | ElementRef<any>;
7✔
258

2✔
259
    /**
260
     * Emitted when the picker's value changes. Used for two-way binding.
261
     *
5✔
262
     * @example
263
     * ```html
264
     * <igx-date-range-picker [(value)]="date"></igx-date-range-picker>
265
     * ```
266
     */
267
    @Output()
268
    public valueChange = new EventEmitter<DateRange>();
269

270
    /** @hidden @internal */
271
    @HostBinding('class.igx-date-range-picker')
272
    public cssClass = 'igx-date-range-picker';
273

274
    @ViewChild(IgxInputGroupComponent, { read: ViewContainerRef })
275
    private viewContainerRef: ViewContainerRef;
276

277
    /** @hidden @internal */
19✔
278
    @ViewChild(IgxInputDirective)
19✔
279
    public inputDirective: IgxInputDirective;
19✔
280

281
    /** @hidden @internal */
282
    @ContentChildren(IgxDateRangeInputsBaseComponent)
283
    public projectedInputs: QueryList<IgxDateRangeInputsBaseComponent>;
52✔
284

285
    @ContentChild(IgxLabelDirective)
286
    public label: IgxLabelDirective;
287

36✔
288
    @ContentChild(IgxPickerActionsDirective)
289
    public pickerActions: IgxPickerActionsDirective;
290

291
    /** @hidden @internal */
35✔
292
    @ContentChild(IgxDateRangeSeparatorDirective, { read: TemplateRef })
293
    public dateSeparatorTemplate: TemplateRef<any>;
294

295
    /** @hidden @internal */
181✔
296
    public get dateSeparator(): string {
181✔
297
        if (this._dateSeparator === null) {
181✔
298
            return this.resourceStrings.igx_date_range_picker_date_separator;
41✔
299
        }
38✔
300
        return this._dateSeparator;
76✔
301
    }
38!
302

×
303
    /** @hidden @internal */
304
    public get appliedFormat(): string {
38✔
305
        return DateTimeUtil.getLocaleDateFormat(this.locale, this.displayFormat)
1✔
306
            || DateTimeUtil.DEFAULT_INPUT_FORMAT;
307
    }
308

41✔
309
    /** @hidden @internal */
41✔
310
    public get singleInputFormat(): string {
41✔
311
        if (this.placeholder !== '') {
41✔
312
            return this.placeholder;
41✔
313
        }
314

2✔
315
        const format = this.appliedFormat;
316
        return `${format}${SingleInputDatesConcatenationString}${format}`;
41✔
317
    }
318

1✔
319
    /**
320
     * Gets calendar state.
321
     *
181✔
322
     * ```typescript
323
     * let state = this.dateRange.collapsed;
324
     * ```
325
     */
35✔
326
    public override get collapsed(): boolean {
327
        return this._collapsed;
328
    }
329

36✔
330
    /**
331
     * The currently selected value / range from the calendar
332
     *
333
     * @remarks
66✔
334
     * The current value is of type `DateRange`
66!
335
     *
66✔
336
     * @example
337
     * ```typescript
338
     * const newValue: DateRange = { start: new Date("2/2/2012"), end: new Date("3/3/2013")};
339
     * this.dateRangePicker.value = newValue;
63✔
340
     * ```
63✔
341
     */
63✔
342
    public get value(): DateRange | null {
63✔
343
        return this._value;
63✔
344
    }
63✔
345

63✔
346
    @Input()
63✔
347
    public set value(value: DateRange | null) {
28✔
348
        this.updateValue(value);
349
        this.onChangeCallback(value);
350
        this.valueChange.emit(value);
63✔
351
    }
63✔
352

63✔
353
    /** @hidden @internal */
63✔
354
    public get hasProjectedInputs(): boolean {
355
        return this.projectedInputs?.length > 0;
356
    }
357

358
    /** @hidden @internal */
359
    public get separatorClass(): string {
63✔
360
        return this.getComponentDensityClass('igx-date-range-picker__label');
3✔
361
    }
362

363
    private get required(): boolean {
63✔
364
        if (this._ngControl && this._ngControl.control && this._ngControl.control.validator) {
63✔
365
            const error = this._ngControl.control.validator({} as AbstractControl);
366
            return (error && error.required) ? true : false;
367
        }
368

76✔
369
        return false;
7✔
370
    }
371

76✔
372
    private get calendar(): IgxCalendarComponent {
4✔
373
        return this._calendar;
374
    }
76✔
375

57✔
376
    private get dropdownOverlaySettings(): OverlaySettings {
377
        return Object.assign({}, this._dropDownOverlaySettings, this.overlaySettings);
378
    }
379

380
    private get dialogOverlaySettings(): OverlaySettings {
53✔
381
        return Object.assign({}, this._dialogOverlaySettings, this.overlaySettings);
53✔
382
    }
28✔
383

384
    private _resourceStrings = CurrentResourceStrings.DateRangePickerResStrings;
53✔
385
    private _doneButtonText = null;
8✔
386
    private _dateSeparator = null;
387
    private _value: DateRange | null;
388
    private _overlayId: string;
389
    private _ngControl: NgControl;
390
    private _statusChanges$: Subscription;
×
391
    private _calendar: IgxCalendarComponent;
392
    private _positionSettings: PositionSettings;
393
    private _focusedInput: IgxDateRangeInputsBaseComponent;
135✔
394
    private _overlaySubFilter:
125✔
395
        [MonoTypeOperatorFunction<OverlayEventArgs>, MonoTypeOperatorFunction<OverlayEventArgs | OverlayCancelableEventArgs>] = [
2✔
396
            filter(x => x.id === this._overlayId),
397
            takeUntil(merge(this._destroy$, this.closed))
398
        ];
123✔
399
    private _dialogOverlaySettings: OverlaySettings = {
400
        closeOnOutsideClick: true,
401
        modal: true,
402
        closeOnEscape: true
10✔
403
    };
404
    private _dropDownOverlaySettings: OverlaySettings = {
405
        closeOnOutsideClick: true,
406
        modal: false,
125✔
407
        closeOnEscape: true
408
    };
409
    private onChangeCallback: (dateRange: DateRange) => void = noop;
125✔
410
    private onTouchCallback: () => void = noop;
411
    private onValidatorChange: () => void = noop;
412

37✔
413
    constructor(element: ElementRef,
37!
414
        @Inject(LOCALE_ID) _localeId: string,
×
415
        protected platform: PlatformUtil,
416
        private _injector: Injector,
37✔
417
        private _cdr: ChangeDetectorRef,
37✔
418
        @Inject(IgxOverlayService) private _overlayService: IgxOverlayService,
26✔
419
        @Optional() @Inject(DisplayDensityToken) _displayDensityOptions?: IDisplayDensityOptions,
420
        @Optional() @Inject(IGX_INPUT_GROUP_TYPE) _inputGroupType?: IgxInputGroupType) {
421
        super(element, _localeId, _displayDensityOptions, _inputGroupType);
422
        this.locale = this.locale || this._localeId;
32✔
423
    }
32✔
424

32✔
425
    /** @hidden @internal */
32✔
426
    @HostListener('keydown', ['$event'])
2✔
427
    /** @hidden @internal */
428
    public onKeyDown(event: KeyboardEvent): void {
30✔
429
        switch (event.key) {
430
            case this.platform.KEYMAP.ARROW_UP:
3✔
431
                if (event.altKey) {
432
                    this.close();
433
                }
27✔
434
                break;
435
            case this.platform.KEYMAP.ARROW_DOWN:
27✔
436
                if (event.altKey) {
2✔
437
                    this.open();
2✔
438
                }
439
                break;
27✔
440
        }
14✔
441
    }
442

443
    /**
444
     * Opens the date range picker's dropdown or dialog.
445
     *
33✔
446
     * @example
33✔
447
     * ```html
33✔
448
     * <igx-date-range-picker #dateRange></igx-date-range-picker>
33✔
449
     *
33!
450
     * <button type="button" igxButton (click)="dateRange.open()">Open Dialog</button
×
451
     * ```
×
452
     */
×
453
    public open(overlaySettings?: OverlaySettings): void {
454
        if (!this.collapsed || this.disabled) {
33✔
455
            return;
33✔
456
        }
33✔
457

458
        const settings = Object.assign({}, this.isDropdown
33✔
459
            ? this.dropdownOverlaySettings
25✔
460
            : this.dialogOverlaySettings
25✔
461
            , overlaySettings);
462

33✔
463
        this._overlayId = this._overlayService
32✔
464
            .attach(IgxCalendarContainerComponent, this.viewContainerRef, settings);
465
        this.subscribeToOverlayEvents();
33✔
466
        this._overlayService.show(this._overlayId);
14✔
467
    }
14✔
468

14✔
469
    /**
14✔
470
     * Closes the date range picker's dropdown or dialog.
471
     *
472
     * @example
473
     * ```html
122✔
474
     * <igx-date-range-picker #dateRange></igx-date-range-picker>
122✔
475
     *
122✔
476
     * <button type="button" igxButton (click)="dateRange.close()">Close Dialog</button>
477
     * ```
478
     */
5✔
479
    public close(): void {
5✔
480
        if (!this.collapsed) {
2!
481
            this._overlayService.hide(this._overlayId);
2✔
482
        }
4!
483
    }
4✔
484

485
    /**
486
     * Toggles the date range picker's dropdown or dialog
×
487
     *
488
     * @example
489
     * ```html
490
     * <igx-date-range-picker #dateRange></igx-date-range-picker>
2!
491
     *
×
492
     * <button type="button" igxButton (click)="dateRange.toggle()">Toggle Dialog</button>
×
493
     * ```
494
     */
495
    public toggle(overlaySettings?: OverlaySettings): void {
×
496
        if (!this.collapsed) {
497
            this.close();
498
        } else {
499
            this.open(overlaySettings);
500
        }
501
    }
120✔
502

32✔
503
    /**
64✔
504
     * Selects a range of dates. If no `endDate` is passed, range is 1 day (only `startDate`)
32✔
505
     *
32✔
506
     * @example
32✔
507
     * ```typescript
508
     * public selectFiveDayRange() {
509
     *  const today = new Date();
510
     *  const inFiveDays = new Date(new Date().setDate(today.getDate() + 5));
511
     *  this.dateRange.select(today, inFiveDays);
134✔
512
     * }
134✔
513
     * ```
134✔
514
     */
4✔
515
    public select(startDate: Date, endDate?: Date): void {
516
        endDate = endDate ?? startDate;
130✔
517
        const dateRange = [startDate, endDate];
178✔
518
        this.handleSelection(dateRange);
519
    }
520

521
    /** @hidden @internal */
522
    public writeValue(value: DateRange): void {
56✔
523
        this.updateValue(value);
56✔
524
    }
50✔
525

25!
526
    /** @hidden @internal */
25✔
527
    public registerOnChange(fn: any): void {
528
        this.onChangeCallback = fn;
529
    }
56✔
530

531
    /** @hidden @internal */
532
    public registerOnTouched(fn: any): void {
56✔
533
        this.onTouchCallback = fn;
56✔
534
    }
53✔
535

53✔
536
    /** @hidden @internal */
25✔
537
    public validate(control: AbstractControl): ValidationErrors | null {
538
        const value: DateRange = control.value;
539
        const errors = {};
56✔
540
        if (value) {
541
            if (this.hasProjectedInputs) {
542
                const startInput = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
157✔
543
                const endInput = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
101✔
544
                if (!startInput.dateTimeEditor.value) {
545
                    Object.assign(errors, { startValue: true });
56✔
546
                }
56✔
547
                if (!endInput.dateTimeEditor.value) {
56✔
548
                    Object.assign(errors, { endValue: true });
3✔
549
                }
550
            }
56✔
551

56✔
552
            const min = parseDate(this.minValue);
3✔
553
            const max = parseDate(this.maxValue);
554
            const start = parseDate(value.start);
56✔
555
            const end = parseDate(value.end);
56✔
556
            if ((min && start && DateTimeUtil.lessThanMinValue(start, min, false))
22✔
557
                || (min && end && DateTimeUtil.lessThanMinValue(end, min, false))) {
22!
558
                Object.assign(errors, { minValue: true });
×
559
            }
560
            if ((max && start && DateTimeUtil.greaterThanMaxValue(start, max, false))
22!
561
                || (max && end && DateTimeUtil.greaterThanMaxValue(end, max, false))) {
22✔
562
                Object.assign(errors, { maxValue: true });
563
            }
564
        }
56✔
565

22✔
566
        return Object.keys(errors).length > 0 ? errors : null;
567
    }
34!
568

×
569
    /** @hidden @internal */
570
    public registerOnValidatorChange?(fn: any): void {
56✔
571
        this.onValidatorChange = fn;
572
    }
573

×
574
    /** @hidden @internal */
×
575
    public setDisabledState?(isDisabled: boolean): void {
×
576
        this.disabled = isDisabled;
×
577
    }
×
578

579
    /** @hidden */
580
    public override ngOnInit(): void {
581
        this._ngControl = this._injector.get<NgControl>(NgControl, null);
22✔
582

22!
583
        this.locale = this.locale || this._localeId;
×
584
        super.ngOnInit();
585
    }
22!
586

×
587
    /** @hidden */
588
    public override ngAfterViewInit(): void {
22✔
589
        super.ngAfterViewInit();
590
        this.subscribeToDateEditorEvents();
591
        this.configPositionStrategy();
37✔
592
        this.configOverlaySettings();
37!
593
        this.cacheFocusedInput();
37!
594
        this.attachOnTouched();
595

596
        this.setRequiredToInputs();
597

598
        if (this._ngControl) {
599
            this._statusChanges$ = this._ngControl.statusChanges.subscribe(this.onStatusChanged.bind(this));
84✔
600
        }
1✔
601

602
        // delay invocations until the current change detection cycle has completed
84✔
603
        Promise.resolve().then(() => {
2✔
604
            this.updateDisabledState();
605
            this.initialSetValue();
84!
606
            this.updateInputs();
×
607
            // B.P. 07 July 2021 - IgxDateRangePicker not showing initial disabled state with ChangeDetectionStrategy.OnPush #9776
608
            /**
84✔
609
             * if disabled is placed on the range picker element and there are projected inputs
610
             * run change detection since igxInput will initially set the projected inputs' disabled to false
611
             */
63✔
612
            if (this.hasProjectedInputs && this.disabled) {
27✔
613
                this._cdr.markForCheck();
54✔
614
            }
27!
615
        });
27✔
616
        this.updateDisplayFormat();
617
        this.updateInputFormat();
618
    }
1!
619

×
620
    /** @hidden @internal */
621
    public ngOnChanges(changes: SimpleChanges): void {
622
        if (changes['displayFormat'] && this.hasProjectedInputs) {
1✔
623
            this.updateDisplayFormat();
624
        }
625
        if (changes['inputFormat'] && this.hasProjectedInputs) {
27✔
626
            this.updateInputFormat();
627
        }
628
        if (changes['disabled']) {
1!
629
            this.updateDisabledState();
1✔
630
        }
631
    }
632

×
633
    /** @hidden @internal */
634
    public override ngOnDestroy(): void {
635
        super.ngOnDestroy();
636
        if (this._statusChanges$) {
637
            this._statusChanges$.unsubscribe();
638
        }
639
        if (this._overlayId) {
63✔
640
            this._overlayService.detach(this._overlayId);
27✔
641
        }
54✔
642
    }
643

644
    /** @hidden @internal */
4!
645
    public getEditElement() {
×
646
        return this.inputDirective.nativeElement;
647
    }
648

649
    protected onStatusChanged = () => {
650
        if (this.inputGroup) {
651
            this.setValidityState(this.inputDirective, this.inputGroup.isFocused);
36✔
652
        } else if (this.hasProjectedInputs) {
653
            this.projectedInputs
654
                .forEach((i) => {
8✔
655
                    this.setValidityState(i.inputDirective, i.isFocused);
1✔
656
                });
657
        }
658
        this.setRequiredToInputs();
659
    };
660

661
    private setValidityState(inputDirective: IgxInputDirective, isFocused: boolean) {
63✔
662
        if (this._ngControl && !this._ngControl.disabled && this.isTouchedOrDirty) {
27✔
663
            if (this.hasValidators && isFocused) {
54✔
664
                inputDirective.valid = this._ngControl.valid ? IgxInputState.VALID : IgxInputState.INVALID;
665
            } else {
6✔
666
                inputDirective.valid = this._ngControl.valid ? IgxInputState.INITIAL : IgxInputState.INVALID;
667
            }
668
        } else {
669
            inputDirective.valid = IgxInputState.INITIAL;
670
        }
63✔
671
    }
672

673
    private get isTouchedOrDirty(): boolean {
674
        return (this._ngControl.control.touched || this._ngControl.control.dirty);
63✔
675
    }
63✔
676

677
    private get hasValidators(): boolean {
678
        return (!!this._ngControl.control.validator || !!this._ngControl.control.asyncValidator);
63!
679
    }
63✔
680

63✔
681
    private handleSelection(selectionData: Date[]): void {
682
        let newValue = this.extractRange(selectionData);
683
        if (!newValue.start && !newValue.end) {
684
            newValue = null;
685
        }
686
        this.value = newValue;
63!
687
        if (this.isDropdown && selectionData?.length > 1) {
2✔
688
            this.close();
4✔
689
        }
2✔
690
    }
691

692
    private handleClosing(e: IBaseCancelableBrowserEventArgs): void {
693
        const args = { owner: this, cancel: e?.cancel, event: e?.event };
694
        this.closing.emit(args);
695
        e.cancel = args.cancel;
696
        if (args.cancel) {
185✔
697
            return;
185✔
698
        }
185✔
699

87✔
700
        if (this.isDropdown && e?.event && !this.element.nativeElement.contains(e.event.target)) {
87✔
701
            // outside click
87✔
702
            this.updateValidityOnBlur();
703
        } else {
704
            this.onTouchCallback();
705
            // input click
70✔
706
            if (this.hasProjectedInputs && this._focusedInput) {
68✔
707
                this._focusedInput.setFocus();
68✔
708
                this._focusedInput = null;
709
            }
710
            if (this.inputDirective) {
711
                this.inputDirective.focus();
67✔
712
            }
62✔
713
        }
62!
714
    }
62✔
715

716
    private subscribeToOverlayEvents() {
717
        this._overlayService.opening.pipe(...this._overlaySubFilter).subscribe((e) => {
718
            const overlayEvent = e as OverlayCancelableEventArgs;
719
            const args = { owner: this, cancel: overlayEvent?.cancel, event: e.event };
33✔
720
            this.opening.emit(args);
33✔
721
            if (args.cancel) {
33✔
722
                this._overlayService.detach(this._overlayId);
33✔
723
                overlayEvent.cancel = true;
33✔
724
                return;
33✔
725
            }
33✔
726

33✔
727
            this._initializeCalendarContainer(e.componentRef.instance);
33✔
728
            this._collapsed = false;
33✔
729
            this.updateCalendar();
33✔
730
        });
33✔
731

732
        this._overlayService.opened.pipe(...this._overlaySubFilter).subscribe(() => {
2✔
733
            this.calendar?.daysView?.focusActiveDate();
734
            this.opened.emit({ owner: this });
735
        });
736

737
        this._overlayService.closing.pipe(...this._overlaySubFilter).subscribe((e) => {
738
            this.handleClosing(e as OverlayCancelableEventArgs);
739
        });
740

741
        this._overlayService.closed.pipe(...this._overlaySubFilter).subscribe(() => {
742
            this._overlayService.detach(this._overlayId);
2✔
743
            this._collapsed = true;
744
            this._overlayId = null;
745
            this.closed.emit({ owner: this });
746
        });
747
    }
748

749
    private updateValue(value: DateRange) {
750
        this._value = value ? value : null;
751
        this.updateInputs();
752
        this.updateCalendar();
753
    }
754

755
    private updateValidityOnBlur() {
756
        this.onTouchCallback();
757
        if (this._ngControl) {
758
            if (this.hasProjectedInputs) {
759
                this.projectedInputs.forEach(i => {
760
                    if (!this._ngControl.valid) {
761
                        i.updateInputValidity(IgxInputState.INVALID);
762
                    } else {
763
                        i.updateInputValidity(IgxInputState.INITIAL);
764
                    }
765
                });
766
            }
767

2✔
768
            if (this.inputDirective) {
769
                if (!this._ngControl.valid) {
770
                    this.inputDirective.valid = IgxInputState.INVALID;
771
                } else {
772
                    this.inputDirective.valid = IgxInputState.INITIAL;
773
                }
774
            }
775
        }
776
    }
777

778
    private updateDisabledState() {
779
        if (this.hasProjectedInputs) {
780
            const start = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
781
            const end = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
782
            start.inputDirective.disabled = this.disabled;
783
            end.inputDirective.disabled = this.disabled;
784
            return;
785
        }
786
    }
787

788
    private setRequiredToInputs(): void {
789
        // workaround for igxInput setting required
790
        Promise.resolve().then(() => {
791
            const isRequired = this.required;
792
            if (this.inputGroup && this.inputGroup.isRequired !== isRequired) {
793
                this.inputGroup.isRequired = isRequired;
794
            } else if (this.hasProjectedInputs && this._ngControl) {
795
                this.projectedInputs.forEach(i => i.isRequired = isRequired);
796
            }
797
        });
798
    }
799

800
    private parseMinValue(value: string | Date): Date | null {
801
        let minValue: Date = parseDate(value);
802
        if (!minValue && this.hasProjectedInputs) {
803
            const start = this.projectedInputs.filter(i => i instanceof IgxDateRangeStartComponent)[0];
804
            if (start) {
805
                minValue = parseDate(start.dateTimeEditor.minValue);
806
            }
807
        }
808

809
        return minValue;
810
    }
811

812
    private parseMaxValue(value: string | Date): Date | null {
813
        let maxValue: Date = parseDate(value);
814
        if (!maxValue && this.projectedInputs) {
815
            const end = this.projectedInputs.filter(i => i instanceof IgxDateRangeEndComponent)[0];
816
            if (end) {
817
                maxValue = parseDate(end.dateTimeEditor.maxValue);
818
            }
819
        }
820

821
        return maxValue;
822
    }
823

824
    private updateCalendar(): void {
825
        if (!this.calendar) {
826
             return;
827
        }
828
        this.calendar.disabledDates = [];
829
        const minValue = this.parseMinValue(this.minValue);
830
        if (minValue) {
831
            this.calendar.disabledDates.push({ type: DateRangeType.Before, dateRange: [minValue] });
832
        }
833
        const maxValue = this.parseMaxValue(this.maxValue);
834
        if (maxValue) {
835
            this.calendar.disabledDates.push({ type: DateRangeType.After, dateRange: [maxValue] });
836
        }
837

838
        const range: Date[] = [];
839
        if (this.value?.start && this.value?.end) {
840
            const _value = this.toRangeOfDates(this.value);
841
            if (DateTimeUtil.greaterThanMaxValue(_value.start, _value.end)) {
842
                this.swapEditorDates();
843
            }
844
            if (this.valueInRange(this.value, minValue, maxValue)) {
845
                range.push(_value.start, _value.end);
846
            }
847
        }
848

849
        if (range.length > 0) {
850
            this.calendar.selectDate(range);
851
        } else if (range.length === 0 && this.calendar.monthViews) {
852
            this.calendar.deselectDate();
853
        }
854
        this.calendar.viewDate = range[0] || new Date();
855
    }
856

857
    private swapEditorDates(): void {
858
        if (this.hasProjectedInputs) {
859
            const start = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
860
            const end = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
861
            [start.dateTimeEditor.value, end.dateTimeEditor.value] = [end.dateTimeEditor.value, start.dateTimeEditor.value];
862
            [this.value.start, this.value.end] = [this.value.end, this.value.start];
863
        }
864
    }
865

866
    private valueInRange(value: DateRange, minValue?: Date, maxValue?: Date): boolean {
867
        const _value = this.toRangeOfDates(value);
868
        if (minValue && DateTimeUtil.lessThanMinValue(_value.start, minValue, false)) {
869
            return false;
870
        }
871
        if (maxValue && DateTimeUtil.greaterThanMaxValue(_value.end, maxValue, false)) {
872
            return false;
873
        }
874

875
        return true;
876
    }
877

878
    private extractRange(selection: Date[]): DateRange {
879
        return {
880
            start: selection[0] || null,
881
            end: selection.length > 0 ? selection[selection.length - 1] : null
882
        };
883
    }
884

885
    private toRangeOfDates(range: DateRange): { start: Date; end: Date } {
886
        let start;
887
        let end;
888
        if (!isDate(range.start)) {
889
            start = DateTimeUtil.parseIsoDate(range.start);
890
        }
891
        if (!isDate(range.end)) {
892
            end = DateTimeUtil.parseIsoDate(range.end);
893
        }
894

895
        if (start || end) {
896
            return { start, end };
897
        }
898

899
        return { start: range.start as Date, end: range.end as Date };
900
    }
901

902
    private subscribeToDateEditorEvents(): void {
903
        if (this.hasProjectedInputs) {
904
            const start = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
905
            const end = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
906
            if (start && end) {
907
                start.dateTimeEditor.valueChange
908
                    .pipe(takeUntil(this._destroy$))
909
                    .subscribe(value => {
910
                        if (this.value) {
911
                            this.value = { start: value, end: this.value.end };
912
                        } else {
913
                            this.value = { start: value, end: null };
914
                        }
915
                    });
916
                end.dateTimeEditor.valueChange
917
                    .pipe(takeUntil(this._destroy$))
918
                    .subscribe(value => {
919
                        if (this.value) {
920
                            this.value = { start: this.value.start, end: value as Date };
921
                        } else {
922
                            this.value = { start: null, end: value as Date };
923
                        }
924
                    });
925
            }
926
        }
927
    }
928

929
    private attachOnTouched(): void {
930
        if (this.hasProjectedInputs) {
931
            this.projectedInputs.forEach(i => {
932
                fromEvent(i.dateTimeEditor.nativeElement, 'blur')
933
                    .pipe(takeUntil(this._destroy$))
934
                    .subscribe(() => {
935
                        if (this.collapsed) {
936
                            this.updateValidityOnBlur();
937
                        }
938
                    });
939
            });
940
        } else {
941
            fromEvent(this.inputDirective.nativeElement, 'blur')
942
                .pipe(takeUntil(this._destroy$))
943
                .subscribe(() => {
944
                    if (this.collapsed) {
945
                        this.updateValidityOnBlur();
946
                    }
947
                });
948
        }
949
    }
950

951
    private cacheFocusedInput(): void {
952
        if (this.hasProjectedInputs) {
953
            this.projectedInputs.forEach(i => {
954
                fromEvent(i.dateTimeEditor.nativeElement, 'focus')
955
                    .pipe(takeUntil(this._destroy$))
956
                    .subscribe(() => this._focusedInput = i);
957
            });
958
        }
959
    }
960

961
    private configPositionStrategy(): void {
962
        this._positionSettings = {
963
            openAnimation: fadeIn,
964
            closeAnimation: fadeOut
965
        };
966
        this._dropDownOverlaySettings.positionStrategy = new AutoPositionStrategy(this._positionSettings);
967
        this._dropDownOverlaySettings.target = this.element.nativeElement;
968
    }
969

970
    private configOverlaySettings(): void {
971
        if (this.overlaySettings !== null) {
972
            this._dropDownOverlaySettings = Object.assign({}, this._dropDownOverlaySettings, this.overlaySettings);
973
            this._dialogOverlaySettings = Object.assign({}, this._dialogOverlaySettings, this.overlaySettings);
974
        }
975
    }
976

977
    private initialSetValue() {
978
        // if there is no value and no ngControl on the picker but we have inputs we may have value set through
979
        // their ngModels - we should generate our initial control value
980
        if ((!this.value || (!this.value.start && !this.value.end)) && this.hasProjectedInputs && !this._ngControl) {
981
            const start = this.projectedInputs.find(i => i instanceof IgxDateRangeStartComponent);
982
            const end = this.projectedInputs.find(i => i instanceof IgxDateRangeEndComponent);
983
            this._value = {
984
                start: start.dateTimeEditor.value as Date,
985
                end: end.dateTimeEditor.value as Date
986
            };
987
        }
988
    }
989

990
    private updateInputs(): void {
991
        const start = this.projectedInputs?.find(i => i instanceof IgxDateRangeStartComponent) as IgxDateRangeStartComponent;
992
        const end = this.projectedInputs?.find(i => i instanceof IgxDateRangeEndComponent) as IgxDateRangeEndComponent;
993
        if (start && end) {
994
            const _value = this.value ? this.toRangeOfDates(this.value) : null;
995
            start.updateInputValue(_value?.start || null);
996
            end.updateInputValue(_value?.end || null);
997
        }
998
    }
999

1000
    private updateDisplayFormat(): void {
1001
        this.projectedInputs.forEach(i => {
1002
            const input = i as IgxDateRangeInputsBaseComponent;
1003
            input.dateTimeEditor.displayFormat = this.displayFormat;
1004
        });
1005
    }
1006

1007
    private updateInputFormat(): void {
1008
        this.projectedInputs.forEach(i => {
1009
            const input = i as IgxDateRangeInputsBaseComponent;
1010
            if (input.dateTimeEditor.inputFormat !== this.inputFormat) {
1011
                input.dateTimeEditor.inputFormat = this.inputFormat;
1012
            }
1013
        });
1014
    }
1015

1016
    private _initializeCalendarContainer(componentInstance: IgxCalendarContainerComponent) {
1017
        this._calendar = componentInstance.calendar;
1018
        this.calendar.hasHeader = false;
1019
        this.calendar.locale = this.locale;
1020
        this.calendar.selection = CalendarSelection.RANGE;
1021
        this.calendar.weekStart = this.weekStart;
1022
        this.calendar.hideOutsideDays = this.hideOutsideDays;
1023
        this.calendar.monthsViewNumber = this.displayMonthsCount;
1024
        this.calendar.selected.pipe(takeUntil(this._destroy$)).subscribe((ev: Date[]) => this.handleSelection(ev));
1025

1026
        componentInstance.mode = this.mode;
1027
        componentInstance.closeButtonLabel = !this.isDropdown ? this.doneButtonText : null;
1028
        componentInstance.pickerActions = this.pickerActions;
1029
        componentInstance.calendarClose.pipe(takeUntil(this._destroy$)).subscribe(() => this.close());
1030
    }
1031
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc