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

IgniteUI / igniteui-angular / 13331632524

14 Feb 2025 02:51PM CUT coverage: 22.015% (-69.6%) from 91.622%
13331632524

Pull #15372

github

web-flow
Merge d52d57714 into bcb78ae0a
Pull Request #15372: chore(*): test ci passing

1990 of 15592 branches covered (12.76%)

431 of 964 new or added lines in 18 files covered. (44.71%)

19956 existing lines in 307 files now uncovered.

6452 of 29307 relevant lines covered (22.02%)

249.17 hits per line

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

26.63
/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts
1
import { NgIf, NgClass, NgFor, NgTemplateOutlet } from '@angular/common';
2
import {
3
    Component,
4
    ElementRef,
5
    EventEmitter,
6
    HostBinding,
7
    Input,
8
    OnDestroy,
9
    OnInit,
10
    Output,
11
    ViewChild,
12
    ContentChild,
13
    Inject,
14
    AfterViewInit,
15
    Injector,
16
    PipeTransform,
17
    ChangeDetectorRef,
18
    LOCALE_ID, Optional, ContentChildren, QueryList, HostListener, booleanAttribute
19
} from '@angular/core';
20
import {
21
    ControlValueAccessor,
22
    NG_VALUE_ACCESSOR,
23
    NgControl,
24
    AbstractControl,
25
    ValidationErrors,
26
    Validator,
27
    NG_VALIDATORS
28
} from '@angular/forms';
29

30
import { IgxInputGroupComponent } from '../input-group/input-group.component';
31
import { IgxInputDirective, IgxInputState } from '../directives/input/input.directive';
32
import { IgxInputGroupType, IGX_INPUT_GROUP_TYPE } from '../input-group/public_api';
33
import {
34
    IgxItemListDirective,
35
    IgxTimeItemDirective
36
} from './time-picker.directives';
37
import { Subscription, noop, fromEvent } from 'rxjs';
38
import { IgxTimePickerBase, IGX_TIME_PICKER_COMPONENT } from './time-picker.common';
39
import { AbsoluteScrollStrategy } from '../services/overlay/scroll';
40
import { AutoPositionStrategy } from '../services/overlay/position';
41
import { OverlaySettings } from '../services/overlay/utilities';
42
import { takeUntil } from 'rxjs/operators';
43
import { IgxButtonDirective } from '../directives/button/button.directive';
44

45
import { IgxDateTimeEditorDirective } from '../directives/date-time-editor/date-time-editor.directive';
46
import { IgxToggleDirective } from '../directives/toggle/toggle.directive';
47
import { ITimePickerResourceStrings, TimePickerResourceStringsEN } from '../core/i18n/time-picker-resources';
48
import { IBaseEventArgs, isEqual, isDate, PlatformUtil, IBaseCancelableBrowserEventArgs } from '../core/utils';
49
import { PickerInteractionMode } from '../date-common/types';
50
import { IgxTextSelectionDirective } from '../directives/text-selection/text-selection.directive';
51
import { IgxLabelDirective } from '../directives/label/label.directive';
52
import { PickerBaseDirective } from '../date-common/picker-base.directive';
53
import { DateTimeUtil } from '../date-common/util/date-time.util';
54
import { DatePart, DatePartDeltas } from '../directives/date-time-editor/public_api';
55
import { PickerHeaderOrientation } from '../date-common/types';
56
import { IgxPickerActionsDirective, IgxPickerClearComponent } from '../date-common/picker-icons.common';
57
import { TimeFormatPipe, TimeItemPipe } from './time-picker.pipes';
58
import { IgxSuffixDirective } from '../directives/suffix/suffix.directive';
59
import { IgxIconComponent } from '../icon/icon.component';
60
import { IgxPrefixDirective } from '../directives/prefix/prefix.directive';
61
import { getCurrentResourceStrings } from '../core/i18n/resources';
62
import { IgxDividerDirective } from '../directives/divider/divider.directive';
63

64
let NEXT_ID = 0;
2✔
65
export interface IgxTimePickerValidationFailedEventArgs extends IBaseEventArgs {
66
    previousValue: Date | string;
67
    currentValue: Date | string;
68
}
69

70
@Component({
71
    providers: [
72
        {
73
            provide: NG_VALUE_ACCESSOR,
74
            useExisting: IgxTimePickerComponent,
75
            multi: true
76
        },
77
        {
78
            provide: IGX_TIME_PICKER_COMPONENT,
79
            useExisting: IgxTimePickerComponent
80
        },
81
        {
82
            provide: NG_VALIDATORS,
83
            useExisting: IgxTimePickerComponent,
84
            multi: true
85
        }
86
    ],
87
    selector: 'igx-time-picker',
88
    templateUrl: 'time-picker.component.html',
89
    styles: [
90
        `:host {
91
            display: block;
92
        }`
93
    ],
94
    imports: [IgxInputGroupComponent, IgxInputDirective, IgxDateTimeEditorDirective, IgxTextSelectionDirective, NgIf, IgxPrefixDirective, IgxIconComponent, IgxSuffixDirective, IgxButtonDirective, IgxToggleDirective, NgClass, IgxItemListDirective, NgFor, IgxTimeItemDirective, NgTemplateOutlet, TimeFormatPipe, TimeItemPipe, IgxDividerDirective]
95
})
96
export class IgxTimePickerComponent extends PickerBaseDirective
2✔
97
    implements
98
    IgxTimePickerBase,
99
    ControlValueAccessor,
100
    OnInit,
101
    OnDestroy,
102
    AfterViewInit,
103
    Validator {
104
    /**
105
     * Sets the value of the `id` attribute.
106
     * ```html
107
     * <igx-time-picker [id]="'igx-time-picker-5'" [displayFormat]="h:mm tt" ></igx-time-picker>
108
     * ```
109
     */
110
    @HostBinding('attr.id')
111
    @Input()
112
    public id = `igx-time-picker-${NEXT_ID++}`;
2✔
113

114
    /**
115
     * The format used when editable input is not focused. Defaults to the `inputFormat` if not set.
116
     *
117
     * @remarks
118
     * Uses Angular's `DatePipe`.
119
     *
120
     * @example
121
     * ```html
122
     * <igx-time-picker displayFormat="mm:ss"></igx-time-picker>
123
     * ```
124
     *
125
     */
126
    @Input()
127
    public override displayFormat: string;
128

129
    /**
130
     * The expected user input format and placeholder.
131
     *
132
     * @remarks
133
     * Default is `hh:mm tt`
134
     *
135
     * @example
136
     * ```html
137
     * <igx-time-picker inputFormat="HH:mm"></igx-time-picker>
138
     * ```
139
     */
140
    @Input()
141
    public override inputFormat: string;
142

143
    /**
144
     * Gets/Sets the interaction mode - dialog or drop down.
145
     *
146
     * @example
147
     * ```html
148
     * <igx-time-picker mode="dialog"></igx-time-picker>
149
     * ```
150
     */
151
    @Input()
152
    public override mode: PickerInteractionMode = PickerInteractionMode.DropDown;
2✔
153

154
    /**
155
     * The minimum value the picker will accept.
156
     *
157
     * @remarks
158
     * If a `string` value is passed in, it must be in ISO format.
159
     *
160
     * @example
161
     * ```html
162
     * <igx-time-picker [minValue]="18:00:00"></igx-time-picker>
163
     * ```
164
     */
165
    @Input()
166
    public set minValue(value: Date | string) {
UNCOV
167
        this._minValue = value;
×
UNCOV
168
        const date = this.parseToDate(value);
×
UNCOV
169
        if (date) {
×
UNCOV
170
            this._dateMinValue = new Date();
×
UNCOV
171
            this._dateMinValue.setHours(date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
×
UNCOV
172
            this.minDropdownValue = this.setMinMaxDropdownValue('min', this._dateMinValue);
×
173
        }
UNCOV
174
        this.setSelectedValue(this._selectedDate);
×
UNCOV
175
        this._onValidatorChange();
×
176
    }
177

178
    public get minValue(): Date | string {
179
        return this._minValue;
20✔
180
    }
181

182
    /**
183
     * Gets if the dropdown/dialog is collapsed
184
     *
185
     * ```typescript
186
     * let isCollapsed = this.timePicker.collapsed;
187
     * ```
188
     */
189
    public override get collapsed(): boolean {
UNCOV
190
        return this.toggleRef?.collapsed;
×
191
    }
192

193
    /**
194
     * The maximum value the picker will accept.
195
     *
196
     * @remarks
197
     * If a `string` value is passed in, it must be in ISO format.
198
     *
199
     * @example
200
     * ```html
201
     * <igx-time-picker [maxValue]="20:30:00"></igx-time-picker>
202
     * ```
203
     */
204
    @Input()
205
    public set maxValue(value: Date | string) {
UNCOV
206
        this._maxValue = value;
×
UNCOV
207
        const date = this.parseToDate(value);
×
UNCOV
208
        if (date) {
×
UNCOV
209
            this._dateMaxValue = new Date();
×
UNCOV
210
            this._dateMaxValue.setHours(date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
×
UNCOV
211
            this.maxDropdownValue = this.setMinMaxDropdownValue('max', this._dateMaxValue);
×
212
        }
UNCOV
213
        this.setSelectedValue(this._selectedDate);
×
UNCOV
214
        this._onValidatorChange();
×
215
    }
216

217
    public get maxValue(): Date | string {
218
        return this._maxValue;
20✔
219
    }
220

221
    /**
222
     * Sets whether the seconds, minutes and hour spinning will loop back around when end value is reached.
223
     * By default it's set to true.
224
     * ```html
225
     * <igx-time-picker [spinLoop]="false"></igx-time-picker>
226
     * ```
227
     */
228
    @Input({ transform: booleanAttribute })
229
    public spinLoop = true;
2✔
230

231
    /**
232
     * Gets/Sets a custom formatter function on the selected or passed date.
233
     *
234
     * @example
235
     * ```html
236
     * <igx-time-picker [value]="date" [formatter]="formatter"></igx-time-picker>
237
     * ```
238
     */
239
    @Input()
240
    public formatter: (val: Date) => string;
241

242
    /**
243
     * Sets the orientation of the picker's header.
244
     *
245
     * @remarks
246
     * Available in dialog mode only. Default value is `horizontal`.
247
     *
248
     * ```html
249
     * <igx-time-picker [headerOrientation]="'vertical'"></igx-time-picker>
250
     * ```
251
     */
252
    @Input()
253
    public headerOrientation: PickerHeaderOrientation = PickerHeaderOrientation.Horizontal;
2✔
254

255
    /** @hidden @internal */
256
    @Input({ transform: booleanAttribute })
257
    public readOnly = false;
2✔
258

259
    /**
260
     * Emitted after a selection has been done.
261
     *
262
     * @example
263
     * ```html
264
     * <igx-time-picker (selected)="onSelection($event)"></igx-time-picker>
265
     * ```
266
     */
267
    @Output()
268
    public selected = new EventEmitter<Date>();
2✔
269

270
    /**
271
     * Emitted when the picker's value changes.
272
     *
273
     * @remarks
274
     * Used for `two-way` bindings.
275
     *
276
     * @example
277
     * ```html
278
     * <igx-time-picker [(value)]="date"></igx-time-picker>
279
     * ```
280
     */
281
    @Output()
282
    public valueChange = new EventEmitter<Date | string>();
2✔
283

284
    /**
285
     * Emitted when the user types/spins invalid time in the time-picker editor.
286
     *
287
     *  @example
288
     * ```html
289
     * <igx-time-picker (validationFailed)="onValidationFailed($event)"></igx-time-picker>
290
     * ```
291
     */
292
    @Output()
293
    public validationFailed = new EventEmitter<IgxTimePickerValidationFailedEventArgs>();
2✔
294

295
    /** @hidden */
296
    @ViewChild('hourList')
297
    public hourList: ElementRef;
298

299
    /** @hidden */
300
    @ViewChild('minuteList')
301
    public minuteList: ElementRef;
302

303
    /** @hidden */
304
    @ViewChild('secondsList')
305
    public secondsList: ElementRef;
306

307
    /** @hidden */
308
    @ViewChild('ampmList')
309
    public ampmList: ElementRef;
310

311
    /** @hidden @internal */
312
    @ContentChildren(IgxPickerClearComponent)
313
    public clearComponents: QueryList<IgxPickerClearComponent>;
314

315
    /** @hidden @internal */
316
    @ContentChild(IgxLabelDirective)
317
    public label: IgxLabelDirective;
318

319
    /** @hidden @internal */
320
    @ContentChild(IgxPickerActionsDirective)
321
    public timePickerActionsDirective: IgxPickerActionsDirective;
322

323
    @ViewChild(IgxInputDirective, { read: IgxInputDirective })
324
    private inputDirective: IgxInputDirective;
325

326
    @ViewChild('inputGroup', { read: IgxInputGroupComponent, static: true })
327
    private _inputGroup: IgxInputGroupComponent;
328

329
    @ViewChild(IgxDateTimeEditorDirective, { static: true })
330
    private dateTimeEditor: IgxDateTimeEditorDirective;
331

332
    @ViewChild(IgxToggleDirective)
333
    private toggleRef: IgxToggleDirective;
334

335
    /** @hidden */
336
    public cleared = false;
2✔
337

338
    /** @hidden */
339
    public isNotEmpty = false;
2✔
340

341
    /** @hidden */
342
    public currentHour: number;
343

344
    /** @hidden */
345
    public currentMinutes: number;
346

347
    /** @hidden */
348
    public get showClearButton(): boolean {
349
        if (this.clearComponents.length) {
20✔
350
            return false;
20✔
351
        }
UNCOV
352
        if (DateTimeUtil.isValidDate(this.value)) {
×
353
            // TODO: Update w/ clear behavior
UNCOV
354
            return this.value.getHours() !== 0 || this.value.getMinutes() !== 0 ||
×
355
                   this.value.getSeconds() !== 0 || this.value.getMilliseconds() !== 0;
356
        }
UNCOV
357
        return !!this.dateTimeEditor.value;
×
358
    }
359

360
    /** @hidden */
361
    public get showHoursList(): boolean {
362
        return this.appliedFormat?.indexOf('h') !== - 1 || this.appliedFormat?.indexOf('H') !== - 1;
24!
363
    }
364

365
    /** @hidden */
366
    public get showMinutesList(): boolean {
367
        return this.appliedFormat?.indexOf('m') !== - 1;
24✔
368
    }
369

370
    /** @hidden */
371
    public get showSecondsList(): boolean {
372
        return this.appliedFormat?.indexOf('s') !== - 1;
24✔
373
    }
374

375
    /** @hidden */
376
    public get showAmPmList(): boolean {
377
        return this.appliedFormat?.indexOf('t') !== - 1 || this.appliedFormat?.indexOf('a') !== - 1;
20✔
378
    }
379

380
    /** @hidden */
381
    public get isTwelveHourFormat(): boolean {
382
        return this.appliedFormat?.indexOf('h') !== - 1;
52✔
383
    }
384

385
    /** @hidden @internal */
386
    public get isVertical(): boolean {
387
        return this.headerOrientation === PickerHeaderOrientation.Vertical;
20✔
388
    }
389

390
    /** @hidden @internal */
391
    public get selectedDate(): Date {
392
        return this._selectedDate;
1,800✔
393
    }
394

395
    /** @hidden @internal */
396
    public get minDateValue(): Date {
397
        if (!this._dateMinValue) {
2✔
398
            const minDate = new Date();
2✔
399
            minDate.setHours(0, 0, 0, 0);
2✔
400
            return minDate;
2✔
401
        }
402

UNCOV
403
        return this._dateMinValue;
×
404
    }
405

406
    /** @hidden @internal */
407
    public get maxDateValue(): Date {
408
        if (!this._dateMaxValue) {
2✔
409
            const maxDate = new Date();
2✔
410
            maxDate.setHours(23, 59, 59, 999);
2✔
411
            return maxDate;
2✔
412
        }
413

414
        return this._dateMaxValue;
×
415
    }
416

417
    /** @hidden @internal */
418
    public get appliedFormat(): string {
419
        return this.inputFormat || this.dateTimeEditor?.inputFormat;
2,032✔
420
    }
421

422
    protected override get toggleContainer(): HTMLElement | undefined {
UNCOV
423
        return this.toggleRef?.element;
×
424
    }
425

426
    private get required(): boolean {
UNCOV
427
        if (this._ngControl && this._ngControl.control && this._ngControl.control.validator) {
×
428
            // Run the validation with empty object to check if required is enabled.
UNCOV
429
            const error = this._ngControl.control.validator({} as AbstractControl);
×
UNCOV
430
            return !!(error && error.required);
×
431
        }
432

UNCOV
433
        return false;
×
434
    }
435

436
    private get dialogOverlaySettings(): OverlaySettings {
UNCOV
437
        return Object.assign({}, this._defaultDialogOverlaySettings, this.overlaySettings);
×
438
    }
439

440
    private get dropDownOverlaySettings(): OverlaySettings {
UNCOV
441
        return Object.assign({}, this._defaultDropDownOverlaySettings, this.overlaySettings);
×
442
    }
443

444
    /** @hidden @internal */
445
    public displayValue: PipeTransform = { transform: (date: Date) => this.formatter(date) };
2✔
446
    /** @hidden @internal */
447
    public minDropdownValue: Date;
448
    /** @hidden @internal */
449
    public maxDropdownValue: Date;
450
    /** @hidden @internal */
451
    public hourItems = [];
2✔
452
    /** @hidden @internal */
453
    public minuteItems = [];
2✔
454
    /** @hidden @internal */
455
    public secondsItems = [];
2✔
456
    /** @hidden @internal */
457
    public ampmItems = [];
2✔
458

459
    private _value: Date | string;
460
    private _dateValue: Date;
461
    private _dateMinValue: Date;
462
    private _dateMaxValue: Date;
463
    private _selectedDate: Date;
464
    private _resourceStrings = getCurrentResourceStrings(TimePickerResourceStringsEN);
2✔
465
    private _okButtonLabel = null;
2✔
466
    private _cancelButtonLabel = null;
2✔
467
    private _itemsDelta: Pick<DatePartDeltas, 'hours' | 'minutes' | 'seconds' | 'fractionalSeconds'> =
2✔
468
                                             { hours: 1, minutes: 1, seconds: 1, fractionalSeconds: 1 };
469

470
    private _statusChanges$: Subscription;
471
    private _ngControl: NgControl = null;
2✔
472
    private _onChangeCallback: (_: Date | string) => void = noop;
2✔
473
    private _onTouchedCallback: () => void = noop;
2✔
474
    private _onValidatorChange: () => void = noop;
2✔
475

476
    private _defaultDialogOverlaySettings: OverlaySettings = {
2✔
477
        closeOnOutsideClick: true,
478
        modal: true,
479
        closeOnEscape: true,
480
        outlet: this.outlet
481
    };
482
    private _defaultDropDownOverlaySettings: OverlaySettings = {
2✔
483
        target: this.element.nativeElement,
484
        modal: false,
485
        closeOnOutsideClick: true,
486
        scrollStrategy: new AbsoluteScrollStrategy(),
487
        positionStrategy: new AutoPositionStrategy(),
488
        outlet: this.outlet
489
    };
490

491

492
    /**
493
     * The currently selected value / time from the drop-down/dialog
494
     *
495
     * @remarks
496
     * The current value is of type `Date`
497
     *
498
     * @example
499
     * ```typescript
500
     * const newValue: Date = new Date(2000, 2, 2, 10, 15, 15);
501
     * this.timePicker.value = newValue;
502
     * ```
503
     */
504
    public get value(): Date | string {
UNCOV
505
        return this._value;
×
506
    }
507

508
    /**
509
     * An accessor that allows you to set a time using the `value` input.
510
     * ```html
511
     * public date: Date = new Date(Date.now());
512
     *  //...
513
     * <igx-time-picker [value]="date" format="h:mm tt"></igx-time-picker>
514
     * ```
515
     */
516
    @Input()
517
    public set value(value: Date | string) {
518
        const oldValue = this._value;
2✔
519
        this._value = value;
2✔
520
        const date = this.parseToDate(value);
2✔
521
        if (date) {
2!
UNCOV
522
            this._dateValue = new Date();
×
UNCOV
523
            this._dateValue.setHours(date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
×
UNCOV
524
            this.setSelectedValue(this._dateValue);
×
525
        } else {
526
            this._dateValue = null;
2✔
527
            this.setSelectedValue(null);
2✔
528
        }
529
        if (this.dateTimeEditor) {
2✔
530
            this.dateTimeEditor.value = date;
2✔
531
        }
532
        this.emitValueChange(oldValue, this._value);
2✔
533
        this._onChangeCallback(this._value);
2✔
534
    }
535

536
    /**
537
     * An accessor that sets the resource strings.
538
     * By default it uses EN resources.
539
     */
540
    @Input()
541
    public set resourceStrings(value: ITimePickerResourceStrings) {
542
        this._resourceStrings = Object.assign({}, this._resourceStrings, value);
×
543
    }
544

545
    /**
546
     * An accessor that returns the resource strings.
547
     */
548
    public get resourceStrings(): ITimePickerResourceStrings {
549
        return this._resourceStrings;
100✔
550
    }
551

552
    /**
553
     * Overrides the default text of the **OK** button.
554
     *
555
     * @remarks
556
     * Defaults to the value from resource strings, `"OK"` for the built-in EN.
557
     *
558
     * ```html
559
     * <igx-time-picker okButtonLabel='SET' [value]="date" format="h:mm tt"></igx-time-picker>
560
     * ```
561
     */
562
    @Input()
563
    public set okButtonLabel(value: string) {
564
        this._okButtonLabel = value;
×
565
    }
566

567
    /**
568
     * An accessor that returns the label of ok button.
569
     */
570
    public get okButtonLabel(): string {
571
        if (this._okButtonLabel === null) {
40✔
572
            return this.resourceStrings.igx_time_picker_ok;
40✔
573
        }
574
        return this._okButtonLabel;
×
575
    }
576

577
    /**
578
     * Overrides the default text of the **Cancel** button.
579
     * @remarks
580
     * Defaults to the value from resource strings, `"Cancel"` for the built-in EN.
581
     * ```html
582
     * <igx-time-picker cancelButtonLabel='Exit' [value]="date" format="h:mm tt"></igx-time-picker>
583
     * ```
584
     */
585
    @Input()
586
    public set cancelButtonLabel(value: string) {
587
        this._cancelButtonLabel = value;
×
588
    }
589

590
    /**
591
     * An accessor that returns the label of cancel button.
592
     */
593
    public get cancelButtonLabel(): string {
594
        if (this._cancelButtonLabel === null) {
60✔
595
            return this.resourceStrings.igx_time_picker_cancel;
60✔
596
        }
597
        return this._cancelButtonLabel;
×
598
    }
599

600
    /**
601
     * Delta values used to increment or decrement each editor date part on spin actions and
602
     * to display time portions in the dropdown/dialog.
603
     * By default `itemsDelta` is set to `{hour: 1, minute: 1, second: 1}`
604
     * ```html
605
     * <igx-time-picker [itemsDelta]="{hour:3, minute:5, second:10}" id="time-picker"></igx-time-picker>
606
     * ```
607
     */
608
    @Input()
609
    public set itemsDelta(value: Pick<DatePartDeltas, 'hours' | 'minutes' | 'seconds' | 'fractionalSeconds'>) {
UNCOV
610
        Object.assign(this._itemsDelta, value);
×
611
    }
612

613
    public get itemsDelta(): Pick<DatePartDeltas, 'hours' | 'minutes' | 'seconds' | 'fractionalSeconds'> {
614
        return this._itemsDelta;
342✔
615
    }
616

617
    constructor(
618
        element: ElementRef,
619
        @Inject(LOCALE_ID) _localeId: string,
620
        @Optional() @Inject(IGX_INPUT_GROUP_TYPE) _inputGroupType: IgxInputGroupType,
621
        private _injector: Injector,
2✔
622
        private platform: PlatformUtil,
2✔
623
        private cdr: ChangeDetectorRef,
2✔
624
    ) {
625
        super(element, _localeId, _inputGroupType);
2✔
626
        this.locale = this.locale || this._localeId;
2!
627
    }
628

629
    /** @hidden @internal */
630
    @HostListener('keydown', ['$event'])
631
    public onKeyDown(event: KeyboardEvent): void {
UNCOV
632
        switch (event.key) {
×
633
            case this.platform.KEYMAP.ARROW_UP:
UNCOV
634
                if (event.altKey && this.isDropdown) {
×
UNCOV
635
                    this.close();
×
636
                }
UNCOV
637
                break;
×
638
            case this.platform.KEYMAP.ARROW_DOWN:
UNCOV
639
                if (event.altKey && this.isDropdown) {
×
UNCOV
640
                    this.open();
×
641
                }
UNCOV
642
                break;
×
643
            case this.platform.KEYMAP.ESCAPE:
UNCOV
644
                this.cancelButtonClick();
×
UNCOV
645
                break;
×
646
            case this.platform.KEYMAP.SPACE:
UNCOV
647
                this.open();
×
UNCOV
648
                event.preventDefault();
×
UNCOV
649
                break;
×
650
        }
651
    }
652

653
    /** @hidden @internal */
654
    public getPartValue(value: Date, type: string): string {
UNCOV
655
        const inputDateParts = DateTimeUtil.parseDateTimeFormat(this.appliedFormat);
×
UNCOV
656
        const part = inputDateParts.find(element => element.type === type);
×
UNCOV
657
        return DateTimeUtil.getPartValue(value, part, part.format?.length);
×
658
    }
659

660
    /** @hidden @internal */
661
    public toISOString(value: Date): string {
662
        return value.toLocaleTimeString('en-GB', {
×
663
            hour: '2-digit',
664
            minute: '2-digit',
665
            second: '2-digit',
666
            fractionalSecondDigits: 3
667
        });
668
    }
669

670
    // #region ControlValueAccessor
671

672
    /** @hidden @internal */
673
    public writeValue(value: Date | string) {
UNCOV
674
        this._value = value;
×
UNCOV
675
        const date = this.parseToDate(value);
×
UNCOV
676
        if (date) {
×
UNCOV
677
            this._dateValue = new Date();
×
UNCOV
678
            this._dateValue.setHours(date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
×
UNCOV
679
            this.setSelectedValue(this._dateValue);
×
680
        } else {
UNCOV
681
            this.setSelectedValue(null);
×
682
        }
UNCOV
683
        if (this.dateTimeEditor) {
×
UNCOV
684
            this.dateTimeEditor.value = date;
×
685
        }
686
    }
687

688
    /** @hidden @internal */
689
    public registerOnChange(fn: (_: Date | string) => void) {
UNCOV
690
        this._onChangeCallback = fn;
×
691
    }
692

693
    /** @hidden @internal */
694
    public registerOnTouched(fn: () => void) {
UNCOV
695
        this._onTouchedCallback = fn;
×
696
    }
697

698
    /** @hidden @internal */
699
    public registerOnValidatorChange(fn: any) {
UNCOV
700
        this._onValidatorChange = fn;
×
701
    }
702

703
    /** @hidden @internal */
704
    public validate(control: AbstractControl): ValidationErrors | null {
UNCOV
705
        if (!control.value) {
×
UNCOV
706
            return null;
×
707
        }
708
        // InvalidDate handling
UNCOV
709
        if (isDate(control.value) && !DateTimeUtil.isValidDate(control.value)) {
×
710
            return { value: true };
×
711
        }
712

UNCOV
713
        const errors = {};
×
UNCOV
714
        const value = DateTimeUtil.isValidDate(control.value) ? control.value : DateTimeUtil.parseIsoDate(control.value);
×
UNCOV
715
        Object.assign(errors, DateTimeUtil.validateMinMax(value, this.minValue, this.maxValue, true, false));
×
UNCOV
716
        return Object.keys(errors).length > 0 ? errors : null;
×
717
    }
718

719
    /** @hidden @internal */
720
    public setDisabledState(isDisabled: boolean): void {
UNCOV
721
        this.disabled = isDisabled;
×
722
    }
723
    //#endregion
724

725
    /** @hidden */
726
    public ngOnInit(): void {
727
        this._ngControl = this._injector.get<NgControl>(NgControl, null);
2✔
728
        this.minDropdownValue = this.setMinMaxDropdownValue('min', this.minDateValue);
2✔
729
        this.maxDropdownValue = this.setMinMaxDropdownValue('max', this.maxDateValue);
2✔
730
        this.setSelectedValue(this._dateValue);
2✔
731
    }
732

733
    /** @hidden */
734
    public override ngAfterViewInit(): void {
735
        super.ngAfterViewInit();
2✔
736
        this.subscribeToDateEditorEvents();
2✔
737
        this.subscribeToToggleDirectiveEvents();
2✔
738

739
        this._defaultDropDownOverlaySettings.excludeFromOutsideClick = [this._inputGroup.element.nativeElement];
2✔
740

741
        fromEvent(this.inputDirective.nativeElement, 'blur')
2✔
742
            .pipe(takeUntil(this._destroy$))
743
            .subscribe(() => {
UNCOV
744
                if (this.collapsed) {
×
UNCOV
745
                    this.updateValidityOnBlur();
×
746
                }
747
            });
748

749
        this.subToIconsClicked(this.clearComponents, () => this.clear());
2✔
750
        this.clearComponents.changes.pipe(takeUntil(this._destroy$))
2✔
UNCOV
751
            .subscribe(() => this.subToIconsClicked(this.clearComponents, () => this.clear()));
×
752

753
        if (this._ngControl) {
2!
UNCOV
754
            this._statusChanges$ = this._ngControl.statusChanges.subscribe(this.onStatusChanged.bind(this));
×
UNCOV
755
            this._inputGroup.isRequired = this.required;
×
UNCOV
756
            this.cdr.detectChanges();
×
757
        }
758
    }
759

760
    /** @hidden */
761
    public override ngOnDestroy(): void {
762
        super.ngOnDestroy();
2✔
763
        if (this._statusChanges$) {
2!
UNCOV
764
            this._statusChanges$.unsubscribe();
×
765
        }
766
    }
767

768
    /** @hidden */
769
    public getEditElement(): HTMLInputElement {
UNCOV
770
        return this.dateTimeEditor.nativeElement;
×
771
    }
772

773
    /**
774
     * Opens the picker's dialog UI.
775
     *
776
     * @param settings OverlaySettings - the overlay settings to use for positioning the drop down or dialog container according to
777
     * ```html
778
     * <igx-time-picker #picker [value]="date"></igx-time-picker>
779
     * <button type="button" igxButton (click)="picker.open()">Open Dialog</button>
780
     * ```
781
     */
782
    public open(settings?: OverlaySettings): void {
UNCOV
783
        if (this.disabled || !this.toggleRef.collapsed) {
×
784
            return;
×
785
        }
786

UNCOV
787
        this.setSelectedValue(this._dateValue);
×
UNCOV
788
        const overlaySettings = Object.assign({}, this.isDropdown
×
789
            ? this.dropDownOverlaySettings
790
            : this.dialogOverlaySettings
791
            , settings);
792

UNCOV
793
        this.toggleRef.open(overlaySettings);
×
794
    }
795

796
    /**
797
     * Closes the dropdown/dialog.
798
     * ```html
799
     * <igx-time-picker #timePicker></igx-time-picker>
800
     * ```
801
     * ```typescript
802
     * @ViewChild('timePicker', { read: IgxTimePickerComponent }) picker: IgxTimePickerComponent;
803
     * picker.close();
804
     * ```
805
     */
806
    public close(): void {
UNCOV
807
        this.toggleRef.close();
×
808
    }
809

810
    public toggle(settings?: OverlaySettings): void {
UNCOV
811
        if (this.toggleRef.collapsed) {
×
UNCOV
812
            this.open(settings);
×
813
        } else {
UNCOV
814
            this.close();
×
815
        }
816
    }
817

818
    /**
819
     * Clears the time picker value if it is a `string` or resets the time to `00:00:00` if the value is a Date object.
820
     *
821
     * @example
822
     * ```typescript
823
     * this.timePicker.clear();
824
     * ```
825
     */
826
    public clear(): void {
UNCOV
827
        if (this.disabled) {
×
828
            return;
×
829
        }
830

UNCOV
831
        if (!this.toggleRef.collapsed) {
×
832
            this.close();
×
833
        }
834

UNCOV
835
        if (DateTimeUtil.isValidDate(this.value)) {
×
UNCOV
836
            const oldValue = new Date(this.value);
×
UNCOV
837
            this.value.setHours(0, 0, 0, 0);
×
UNCOV
838
            if (this.value.getTime() !== oldValue.getTime()) {
×
UNCOV
839
                this.emitValueChange(oldValue, this.value);
×
UNCOV
840
                this._dateValue.setHours(0, 0, 0, 0);
×
UNCOV
841
                this.dateTimeEditor.value = new Date(this.value);
×
UNCOV
842
                this.setSelectedValue(this._dateValue);
×
843
            }
844
        } else {
UNCOV
845
            this.value = null;
×
846
        }
847
    }
848

849
    /**
850
     * Selects time from the igxTimePicker.
851
     *
852
     * @example
853
     * ```typescript
854
     * this.timePicker.select(date);
855
     *
856
     * @param date Date object containing the time to be selected.
857
     */
858
    public select(date: Date | string): void {
UNCOV
859
        this.value = date;
×
860
    }
861

862
    /**
863
     * Increment a specified `DatePart`.
864
     *
865
     * @param datePart The optional DatePart to increment. Defaults to Hour.
866
     * @param delta The optional delta to increment by. Overrides `itemsDelta`.
867
     * @example
868
     * ```typescript
869
     * this.timePicker.increment(DatePart.Hours);
870
     * ```
871
     */
872
    public increment(datePart?: DatePart, delta?: number): void {
UNCOV
873
        this.dateTimeEditor.increment(datePart, delta);
×
874
    }
875

876
    /**
877
     * Decrement a specified `DatePart`
878
     *
879
     * @param datePart The optional DatePart to decrement. Defaults to Hour.
880
     * @param delta The optional delta to decrement by. Overrides `itemsDelta`.
881
     * @example
882
     * ```typescript
883
     * this.timePicker.decrement(DatePart.Seconds);
884
     * ```
885
     */
886
    public decrement(datePart?: DatePart, delta?: number): void {
UNCOV
887
        this.dateTimeEditor.decrement(datePart, delta);
×
888
    }
889

890
    /** @hidden @internal */
891
    public cancelButtonClick(): void {
UNCOV
892
        this.setSelectedValue(this._dateValue);
×
UNCOV
893
        this.dateTimeEditor.value = this.parseToDate(this.value);
×
UNCOV
894
        this.close();
×
895
    }
896

897
    /** @hidden @internal */
898
    public okButtonClick(): void {
UNCOV
899
        this.updateValue(this._selectedDate);
×
UNCOV
900
        this.close();
×
901
    }
902

903
    /** @hidden @internal */
904
    public onItemClick(item: string, dateType: string): void {
UNCOV
905
        let date = new Date(this._selectedDate);
×
UNCOV
906
        switch (dateType) {
×
907
            case 'hourList': {
908
                let ampm: string;
UNCOV
909
                const selectedHour = parseInt(item, 10);
×
UNCOV
910
                let hours = selectedHour;
×
911

UNCOV
912
                if (this.showAmPmList) {
×
UNCOV
913
                    ampm = this.getPartValue(date, 'ampm');
×
UNCOV
914
                    hours = this.toTwentyFourHourFormat(hours, ampm);
×
UNCOV
915
                    const minHours = this.minDropdownValue?.getHours() || 0;
×
UNCOV
916
                    const maxHours = this.maxDropdownValue?.getHours() || 24;
×
UNCOV
917
                    if (hours < minHours || hours > maxHours) {
×
918
                        hours = hours < 12 ? hours + 12 : hours - 12;
×
919
                    }
920
                }
921

UNCOV
922
                date.setHours(hours);
×
UNCOV
923
                date = this.validateDropdownValue(date);
×
924

UNCOV
925
                if (this.valueInRange(date, this.minDropdownValue, this.maxDropdownValue)) {
×
UNCOV
926
                    this.setSelectedValue(date);
×
927
                }
UNCOV
928
                break;
×
929
            }
930
            case 'minuteList': {
UNCOV
931
                const minutes = parseInt(item, 10);
×
UNCOV
932
                date.setMinutes(minutes);
×
UNCOV
933
                date = this.validateDropdownValue(date);
×
UNCOV
934
                this.setSelectedValue(date);
×
UNCOV
935
                break;
×
936
            }
937
            case 'secondsList': {
UNCOV
938
                const seconds = parseInt(item, 10);
×
UNCOV
939
                date.setSeconds(seconds);
×
UNCOV
940
                if (this.valueInRange(date, this.minDropdownValue, this.maxDropdownValue)) {
×
UNCOV
941
                    this.setSelectedValue(date);
×
942
                }
UNCOV
943
                break;
×
944
            }
945
            case 'ampmList': {
UNCOV
946
                let hour = this._selectedDate.getHours();
×
UNCOV
947
                hour = DateTimeUtil.isAm(item) ? hour - 12 : hour + 12;
×
UNCOV
948
                date.setHours(hour);
×
UNCOV
949
                date = this.validateDropdownValue(date, true);
×
UNCOV
950
                this.setSelectedValue(date);
×
UNCOV
951
                break;
×
952
            }
953
        }
UNCOV
954
        this.updateEditorValue();
×
955
    }
956

957
    /** @hidden @internal */
958
    public nextHour(delta: number) {
UNCOV
959
        delta = delta > 0 ? 1 : -1;
×
UNCOV
960
        const previousDate = new Date(this._selectedDate);
×
UNCOV
961
        const minHours = this.minDropdownValue?.getHours();
×
UNCOV
962
        const maxHours = this.maxDropdownValue?.getHours();
×
UNCOV
963
        const previousHours = previousDate.getHours();
×
UNCOV
964
        let hours = previousHours + delta * this.itemsDelta.hours;
×
UNCOV
965
        if ((previousHours === maxHours && delta > 0) || (previousHours === minHours && delta < 0)) {
×
UNCOV
966
            hours = !this.spinLoop ? previousHours : delta > 0 ? minHours : maxHours;
×
967
        }
968

UNCOV
969
        this._selectedDate.setHours(hours);
×
UNCOV
970
        this._selectedDate = this.validateDropdownValue(this._selectedDate);
×
UNCOV
971
        this._selectedDate = new Date(this._selectedDate);
×
UNCOV
972
        this.updateEditorValue();
×
973
    }
974

975
    /** @hidden @internal */
976
    public nextMinute(delta: number) {
UNCOV
977
        delta = delta > 0 ? 1 : -1;
×
UNCOV
978
        const minHours = this.minDropdownValue.getHours();
×
UNCOV
979
        const maxHours = this.maxDropdownValue.getHours();
×
UNCOV
980
        const hours = this._selectedDate.getHours();
×
UNCOV
981
        let minutes = this._selectedDate.getMinutes();
×
UNCOV
982
        const minMinutes = hours === minHours ? this.minDropdownValue.getMinutes() : 0;
×
UNCOV
983
        const maxMinutes = hours === maxHours ? this.maxDropdownValue.getMinutes() :
×
984
            60 % this.itemsDelta.minutes > 0 ? 60 - (60 % this.itemsDelta.minutes) :
×
985
                60 - this.itemsDelta.minutes;
986

UNCOV
987
        if ((delta < 0 && minutes === minMinutes) || (delta > 0 && minutes === maxMinutes)) {
×
UNCOV
988
            minutes = this.spinLoop && minutes === minMinutes ? maxMinutes : this.spinLoop && minutes === maxMinutes ? minMinutes : minutes;
×
989
        } else {
UNCOV
990
            minutes = minutes + delta * this.itemsDelta.minutes;
×
991
        }
992

UNCOV
993
        this._selectedDate.setMinutes(minutes);
×
UNCOV
994
        this._selectedDate = this.validateDropdownValue(this._selectedDate);
×
UNCOV
995
        this._selectedDate = new Date(this._selectedDate);
×
UNCOV
996
        this.updateEditorValue();
×
997
    }
998

999
    /** @hidden @internal */
1000
    public nextSeconds(delta: number) {
UNCOV
1001
        delta = delta > 0 ? 1 : -1;
×
UNCOV
1002
        const minHours = this.minDropdownValue.getHours();
×
UNCOV
1003
        const maxHours = this.maxDropdownValue.getHours();
×
UNCOV
1004
        const hours = this._selectedDate.getHours();
×
UNCOV
1005
        const minutes = this._selectedDate.getMinutes();
×
UNCOV
1006
        const minMinutes = this.minDropdownValue.getMinutes();
×
UNCOV
1007
        const maxMinutes = this.maxDropdownValue.getMinutes();
×
UNCOV
1008
        let seconds = this._selectedDate.getSeconds();
×
UNCOV
1009
        const minSeconds = (hours === minHours && minutes === minMinutes) ? this.minDropdownValue.getSeconds() : 0;
×
UNCOV
1010
        const maxSeconds = (hours === maxHours && minutes === maxMinutes) ? this.maxDropdownValue.getSeconds() :
×
1011
            60 % this.itemsDelta.seconds > 0 ? 60 - (60 % this.itemsDelta.seconds) :
×
1012
                60 - this.itemsDelta.seconds;
1013

UNCOV
1014
        if ((delta < 0 && seconds === minSeconds) || (delta > 0 && seconds === maxSeconds)) {
×
1015
            seconds = this.spinLoop && seconds === minSeconds ? maxSeconds : this.spinLoop && seconds === maxSeconds ? minSeconds : seconds;
×
1016
        } else {
UNCOV
1017
            seconds = seconds + delta * this.itemsDelta.seconds;
×
1018
        }
1019

UNCOV
1020
        this._selectedDate.setSeconds(seconds);
×
UNCOV
1021
        this._selectedDate = this.validateDropdownValue(this._selectedDate);
×
UNCOV
1022
        this._selectedDate = new Date(this._selectedDate);
×
UNCOV
1023
        this.updateEditorValue();
×
1024
    }
1025

1026
    /** @hidden @internal */
1027
    public nextAmPm(delta?: number) {
UNCOV
1028
        const ampm = this.getPartValue(this._selectedDate, 'ampm');
×
UNCOV
1029
        if (!delta || (DateTimeUtil.isAm(ampm) && delta > 0)
×
1030
                   || (DateTimeUtil.isPm(ampm) && delta < 0)) {
UNCOV
1031
            let hours = this._selectedDate.getHours();
×
UNCOV
1032
            const sign = hours < 12 ? 1 : -1;
×
UNCOV
1033
            hours = hours + sign * 12;
×
UNCOV
1034
            this._selectedDate.setHours(hours);
×
UNCOV
1035
            this._selectedDate = this.validateDropdownValue(this._selectedDate, true);
×
UNCOV
1036
            this._selectedDate = new Date(this._selectedDate);
×
UNCOV
1037
            this.updateEditorValue();
×
1038
        }
1039
    }
1040

1041
    /** @hidden @internal */
1042
    public setSelectedValue(value: Date) {
1043
        this._selectedDate = value ? new Date(value) : null;
4!
1044
        if (!DateTimeUtil.isValidDate(this._selectedDate)) {
4✔
1045
            this._selectedDate = new Date(this.minDropdownValue);
4✔
1046
            return;
4✔
1047
        }
UNCOV
1048
        if (this.minValue && DateTimeUtil.lessThanMinValue(this._selectedDate, this.minDropdownValue, true, false)) {
×
UNCOV
1049
            this._selectedDate = new Date(this.minDropdownValue);
×
UNCOV
1050
            return;
×
1051
        }
UNCOV
1052
        if (this.maxValue && DateTimeUtil.greaterThanMaxValue(this._selectedDate, this.maxDropdownValue, true, false)) {
×
UNCOV
1053
            this._selectedDate = new Date(this.maxDropdownValue);
×
UNCOV
1054
            return;
×
1055
        }
1056

UNCOV
1057
        if (this._selectedDate.getHours() % this.itemsDelta.hours > 0) {
×
UNCOV
1058
            this._selectedDate.setHours(
×
1059
                this._selectedDate.getHours() + this.itemsDelta.hours - this._selectedDate.getHours() % this.itemsDelta.hours,
1060
                0,
1061
                0
1062
            );
1063
        }
1064

UNCOV
1065
        if (this._selectedDate.getMinutes() % this.itemsDelta.minutes > 0) {
×
UNCOV
1066
            this._selectedDate.setHours(
×
1067
                this._selectedDate.getHours(),
1068
                this._selectedDate.getMinutes() + this.itemsDelta.minutes - this._selectedDate.getMinutes() % this.itemsDelta.minutes,
1069
                0
1070
            );
1071
        }
1072

UNCOV
1073
        if (this._selectedDate.getSeconds() % this.itemsDelta.seconds > 0) {
×
1074
            this._selectedDate.setSeconds(
×
1075
                this._selectedDate.getSeconds() + this.itemsDelta.seconds - this._selectedDate.getSeconds() % this.itemsDelta.seconds
1076
            );
1077
        }
1078
    }
1079

1080
    protected onStatusChanged() {
UNCOV
1081
        if (this._ngControl && !this._ngControl.disabled && this.isTouchedOrDirty) {
×
UNCOV
1082
            if (this.hasValidators && this._inputGroup.isFocused) {
×
UNCOV
1083
                this.inputDirective.valid = this._ngControl.valid ? IgxInputState.VALID : IgxInputState.INVALID;
×
1084
            } else {
UNCOV
1085
                this.inputDirective.valid = this._ngControl.valid ? IgxInputState.INITIAL : IgxInputState.INVALID;
×
1086
            }
1087
        } else {
1088
            // B.P. 18 May 2021: IgxDatePicker does not reset its state upon resetForm #9526
UNCOV
1089
            this.inputDirective.valid = IgxInputState.INITIAL;
×
1090
        }
1091

UNCOV
1092
        if (this._inputGroup && this._inputGroup.isRequired !== this.required) {
×
UNCOV
1093
            this._inputGroup.isRequired = this.required;
×
1094
        }
1095
    }
1096

1097
    private get isTouchedOrDirty(): boolean {
UNCOV
1098
        return (this._ngControl.control.touched || this._ngControl.control.dirty);
×
1099
    }
1100

1101
    private get hasValidators(): boolean {
UNCOV
1102
        return (!!this._ngControl.control.validator || !!this._ngControl.control.asyncValidator);
×
1103
    }
1104

1105
    private setMinMaxDropdownValue(type: string, time: Date): Date {
1106
        let delta: number;
1107

1108
        const sign = type === 'min' ? 1 : -1;
4✔
1109

1110
        const hours = time.getHours();
4✔
1111
        let minutes = time.getMinutes();
4✔
1112
        let seconds = time.getSeconds();
4✔
1113

1114
        if (this.showHoursList && hours % this.itemsDelta.hours > 0) {
4!
UNCOV
1115
            delta = type === 'min' ? this.itemsDelta.hours - hours % this.itemsDelta.hours
×
1116
                : hours % this.itemsDelta.hours;
UNCOV
1117
            minutes = type === 'min' ? 0
×
1118
                : 60 % this.itemsDelta.minutes > 0 ? 60 - 60 % this.itemsDelta.minutes
×
1119
                    : 60 - this.itemsDelta.minutes;
UNCOV
1120
            seconds = type === 'min' ? 0
×
1121
                : 60 % this.itemsDelta.seconds > 0 ? 60 - 60 % this.itemsDelta.seconds
×
1122
                    : 60 - this.itemsDelta.seconds;
UNCOV
1123
            time.setHours(hours + sign * delta, minutes, seconds);
×
1124
        } else if (this.showMinutesList && minutes % this.itemsDelta.minutes > 0) {
4!
UNCOV
1125
            delta = type === 'min' ? this.itemsDelta.minutes - minutes % this.itemsDelta.minutes
×
1126
                : minutes % this.itemsDelta.minutes;
UNCOV
1127
            seconds = type === 'min' ? 0
×
1128
                : 60 % this.itemsDelta.seconds > 0 ? 60 - 60 % this.itemsDelta.seconds
×
1129
                    : 60 - this.itemsDelta.seconds;
UNCOV
1130
            time.setHours(hours, minutes + sign * delta, seconds);
×
1131
        } else if (this.showSecondsList && seconds % this.itemsDelta.seconds > 0) {
4!
1132
            delta = type === 'min' ? this.itemsDelta.seconds - seconds % this.itemsDelta.seconds
×
1133
                : seconds % this.itemsDelta.seconds;
1134
            time.setHours(hours, minutes, seconds + sign * delta);
×
1135
        }
1136

1137
        return time;
4✔
1138
    }
1139

1140
    private initializeContainer() {
UNCOV
1141
        requestAnimationFrame(() => {
×
UNCOV
1142
            if (this.hourList) {
×
UNCOV
1143
                this.hourList.nativeElement.focus();
×
1144
            } else if (this.minuteList) {
×
1145
                this.minuteList.nativeElement.focus();
×
1146
            } else if (this.secondsList) {
×
1147
                this.secondsList.nativeElement.focus();
×
1148
            }
1149
        });
1150
    }
1151

1152
    private validateDropdownValue(date: Date, isAmPm = false): Date {
×
UNCOV
1153
        if (date > this.maxDropdownValue) {
×
UNCOV
1154
            if (isAmPm && date.getHours() !== this.maxDropdownValue.getHours()) {
×
1155
                date.setHours(12);
×
1156
            } else {
UNCOV
1157
                date = new Date(this.maxDropdownValue);
×
1158
            }
1159
        }
1160

UNCOV
1161
        if (date < this.minDropdownValue) {
×
UNCOV
1162
            date = new Date(this.minDropdownValue);
×
1163
        }
1164

UNCOV
1165
        return date;
×
1166
    }
1167

1168
    private emitValueChange(oldValue: Date | string, newValue: Date | string) {
1169
        if (!isEqual(oldValue, newValue)) {
2✔
1170
            this.valueChange.emit(newValue);
2✔
1171
        }
1172
    }
1173

1174
    private emitValidationFailedEvent(previousValue: Date | string) {
UNCOV
1175
        const args: IgxTimePickerValidationFailedEventArgs = {
×
1176
            owner: this,
1177
            previousValue,
1178
            currentValue: this.value
1179
        };
UNCOV
1180
        this.validationFailed.emit(args);
×
1181
    }
1182

1183
    private updateValidityOnBlur() {
UNCOV
1184
        this._onTouchedCallback();
×
UNCOV
1185
        if (this._ngControl) {
×
UNCOV
1186
            if (!this._ngControl.valid) {
×
UNCOV
1187
                this.inputDirective.valid = IgxInputState.INVALID;
×
1188
            } else {
UNCOV
1189
                this.inputDirective.valid = IgxInputState.INITIAL;
×
1190
            }
1191
        }
1192
    }
1193

1194
    private valueInRange(value: Date, minValue: Date, maxValue: Date): boolean {
UNCOV
1195
        if (minValue && DateTimeUtil.lessThanMinValue(value, minValue, true, false)) {
×
1196
            return false;
×
1197
        }
UNCOV
1198
        if (maxValue && DateTimeUtil.greaterThanMaxValue(value, maxValue, true, false)) {
×
1199
            return false;
×
1200
        }
1201

UNCOV
1202
        return true;
×
1203
    }
1204

1205
    private parseToDate(value: Date | string): Date | null {
1206
        return DateTimeUtil.isValidDate(value) ? value : DateTimeUtil.parseIsoDate(value);
2!
1207
    }
1208

1209
    private toTwentyFourHourFormat(hour: number, ampm: string): number {
UNCOV
1210
        if (DateTimeUtil.isPm(ampm) && hour < 12) {
×
1211
            hour += 12;
×
UNCOV
1212
        } else if (DateTimeUtil.isAm(ampm) && hour === 12) {
×
1213
            hour = 0;
×
1214
        }
1215

UNCOV
1216
        return hour;
×
1217
    }
1218

1219
    private updateValue(newValue: Date | null): void {
UNCOV
1220
        if (!this.value) {
×
UNCOV
1221
            this.value = newValue ? new Date(newValue) : newValue;
×
UNCOV
1222
        } else if (isDate(this.value)) {
×
UNCOV
1223
            const date = new Date(this.value);
×
UNCOV
1224
            date.setHours(newValue?.getHours() || 0, newValue?.getMinutes() || 0, newValue?.getSeconds() || 0, newValue?.getMilliseconds() || 0);
×
UNCOV
1225
            this.value = date;
×
1226
        } else {
1227
            this.value = newValue ? this.toISOString(newValue) : newValue;
×
1228
        }
1229
    }
1230

1231
    private updateEditorValue(): void {
UNCOV
1232
        const date = this.dateTimeEditor.value ? new Date(this.dateTimeEditor.value) : new Date();
×
UNCOV
1233
        date.setHours(this._selectedDate.getHours(), this._selectedDate.getMinutes(), this._selectedDate.getSeconds(), this._selectedDate.getMilliseconds());
×
UNCOV
1234
        this.dateTimeEditor.value = date;
×
1235
    }
1236

1237
    private subscribeToDateEditorEvents(): void {
1238
        this.dateTimeEditor.valueChange.pipe(
2✔
1239
            // internal date editor directive is only used w/ Date object values:
1240
            takeUntil(this._destroy$)).subscribe((date: Date | null) => {
UNCOV
1241
                this.updateValue(date);
×
1242
            });
1243

1244
        this.dateTimeEditor.validationFailed.pipe(
2✔
1245
            takeUntil(this._destroy$)).subscribe((event) => {
UNCOV
1246
                this.emitValidationFailedEvent(event.oldValue);
×
1247
            });
1248
    }
1249

1250
    private subscribeToToggleDirectiveEvents(): void {
1251
        if (this.toggleRef) {
2✔
1252
            if (this._inputGroup) {
2✔
1253
                this.toggleRef.element.style.width = this._inputGroup.element.nativeElement.getBoundingClientRect().width + 'px';
2✔
1254
            }
1255

1256
            this.toggleRef.opening.pipe(takeUntil(this._destroy$)).subscribe((e) => {
2✔
UNCOV
1257
                const args: IBaseCancelableBrowserEventArgs = { owner: this, event: e.event, cancel: false };
×
UNCOV
1258
                this.opening.emit(args);
×
UNCOV
1259
                e.cancel = args.cancel;
×
UNCOV
1260
                if (args.cancel) {
×
UNCOV
1261
                    return;
×
1262
                }
UNCOV
1263
                this.initializeContainer();
×
1264
            });
1265

1266
            this.toggleRef.opened.pipe(takeUntil(this._destroy$)).subscribe(() => {
2✔
UNCOV
1267
                this.opened.emit({ owner: this });
×
1268
            });
1269

1270
            this.toggleRef.closed.pipe(takeUntil(this._destroy$)).subscribe(() => {
2✔
UNCOV
1271
                this.closed.emit({ owner: this });
×
1272
            });
1273

1274
            this.toggleRef.closing.pipe(takeUntil(this._destroy$)).subscribe((e) => {
2✔
UNCOV
1275
                const args: IBaseCancelableBrowserEventArgs = { owner: this, event: e.event, cancel: false };
×
UNCOV
1276
                this.closing.emit(args);
×
UNCOV
1277
                e.cancel = args.cancel;
×
UNCOV
1278
                if (args.cancel) {
×
UNCOV
1279
                    return;
×
1280
                }
UNCOV
1281
                const value = this.parseToDate(this.value);
×
UNCOV
1282
                if ((this.dateTimeEditor.value as Date)?.getTime() !== value?.getTime()) {
×
UNCOV
1283
                    this.updateValue(this._selectedDate);
×
1284
                }
1285
                // Do not focus the input if clicking outside in dropdown mode
UNCOV
1286
                const input = this.getEditElement();
×
UNCOV
1287
                if (input && !(e.event && this.isDropdown)) {
×
UNCOV
1288
                    input.focus();
×
1289
                } else {
UNCOV
1290
                    this.updateValidityOnBlur();
×
1291
                }
1292
            });
1293
        }
1294
    }
1295
}
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

© 2025 Coveralls, Inc