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

IgniteUI / igniteui-angular / 13548823408

26 Feb 2025 04:36PM CUT coverage: 91.555% (-0.04%) from 91.599%
13548823408

Pull #15223

github

web-flow
Merge 3ac8087aa into 786685675
Pull Request #15223: fix(pivot-grid): added createRow method for grid based events - 19.0

12997 of 15250 branches covered (85.23%)

0 of 18 new or added lines in 2 files covered. (0.0%)

135 existing lines in 23 files now uncovered.

26344 of 28774 relevant lines covered (91.55%)

34046.37 hits per line

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

93.47
/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++}`;
81✔
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;
81✔
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) {
167
        this._minValue = value;
54✔
168
        const date = this.parseToDate(value);
54✔
169
        if (date) {
54✔
170
            this._dateMinValue = new Date();
14✔
171
            this._dateMinValue.setHours(date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
14✔
172
            this.minDropdownValue = this.setMinMaxDropdownValue('min', this._dateMinValue);
14✔
173
        }
174
        this.setSelectedValue(this._selectedDate);
54✔
175
        this._onValidatorChange();
54✔
176
    }
177

178
    public get minValue(): Date | string {
179
        return this._minValue;
858✔
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 {
190
        return this.toggleRef?.collapsed;
39✔
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) {
206
        this._maxValue = value;
50✔
207
        const date = this.parseToDate(value);
50✔
208
        if (date) {
50✔
209
            this._dateMaxValue = new Date();
10✔
210
            this._dateMaxValue.setHours(date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
10✔
211
            this.maxDropdownValue = this.setMinMaxDropdownValue('max', this._dateMaxValue);
10✔
212
        }
213
        this.setSelectedValue(this._selectedDate);
50✔
214
        this._onValidatorChange();
50✔
215
    }
216

217
    public get maxValue(): Date | string {
218
        return this._maxValue;
850✔
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;
81✔
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;
81✔
254

255
    /** @hidden @internal */
256
    @Input({ transform: booleanAttribute })
257
    public readOnly = false;
81✔
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>();
81✔
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>();
81✔
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>();
81✔
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;
81✔
337

338
    /** @hidden */
339
    public isNotEmpty = false;
81✔
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) {
557✔
350
            return false;
37✔
351
        }
352
        if (DateTimeUtil.isValidDate(this.value)) {
520✔
353
            // TODO: Update w/ clear behavior
354
            return this.value.getHours() !== 0 || this.value.getMinutes() !== 0 ||
447!
355
                   this.value.getSeconds() !== 0 || this.value.getMilliseconds() !== 0;
356
        }
357
        return !!this.dateTimeEditor.value;
73✔
358
    }
359

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

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

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

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

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

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

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

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

403
        return this._dateMinValue;
4✔
404
    }
405

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

414
        return this._dateMaxValue;
×
415
    }
416

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

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

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

433
        return false;
11✔
434
    }
435

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

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

444
    /** @hidden @internal */
445
    public displayValue: PipeTransform = { transform: (date: Date) => this.formatter(date) };
81✔
446
    /** @hidden @internal */
447
    public minDropdownValue: Date;
448
    /** @hidden @internal */
449
    public maxDropdownValue: Date;
450
    /** @hidden @internal */
451
    public hourItems = [];
81✔
452
    /** @hidden @internal */
453
    public minuteItems = [];
81✔
454
    /** @hidden @internal */
455
    public secondsItems = [];
81✔
456
    /** @hidden @internal */
457
    public ampmItems = [];
81✔
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);
81✔
465
    private _okButtonLabel = null;
81✔
466
    private _cancelButtonLabel = null;
81✔
467
    private _itemsDelta: Pick<DatePartDeltas, 'hours' | 'minutes' | 'seconds' | 'fractionalSeconds'> =
81✔
468
                                             { hours: 1, minutes: 1, seconds: 1, fractionalSeconds: 1 };
469

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

476
    private _defaultDialogOverlaySettings: OverlaySettings = {
81✔
477
        closeOnOutsideClick: true,
478
        modal: true,
479
        closeOnEscape: true,
480
        outlet: this.outlet
481
    };
482
    private _defaultDropDownOverlaySettings: OverlaySettings = {
81✔
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 {
505
        return this._value;
1,604✔
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;
95✔
519
        this._value = value;
95✔
520
        const date = this.parseToDate(value);
95✔
521
        if (date) {
95✔
522
            this._dateValue = new Date();
80✔
523
            this._dateValue.setHours(date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
80✔
524
            this.setSelectedValue(this._dateValue);
80✔
525
        } else {
526
            this._dateValue = null;
15✔
527
            this.setSelectedValue(null);
15✔
528
        }
529
        if (this.dateTimeEditor) {
95✔
530
            this.dateTimeEditor.value = date;
95✔
531
        }
532
        this.emitValueChange(oldValue, this._value);
95✔
533
        this._onChangeCallback(this._value);
95✔
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;
3,307✔
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) {
1,114✔
572
            return this.resourceStrings.igx_time_picker_ok;
1,114✔
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) {
1,671✔
595
            return this.resourceStrings.igx_time_picker_cancel;
1,671✔
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'>) {
610
        Object.assign(this._itemsDelta, value);
11✔
611
    }
612

613
    public get itemsDelta(): Pick<DatePartDeltas, 'hours' | 'minutes' | 'seconds' | 'fractionalSeconds'> {
614
        return this._itemsDelta;
24,163✔
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,
81✔
622
        private platform: PlatformUtil,
81✔
623
        private cdr: ChangeDetectorRef,
81✔
624
    ) {
625
        super(element, _localeId, _inputGroupType);
81✔
626
        this.locale = this.locale || this._localeId;
81!
627
    }
628

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

653
    /** @hidden @internal */
654
    public getPartValue(value: Date, type: string): string {
655
        const inputDateParts = DateTimeUtil.parseDateTimeFormat(this.appliedFormat);
154✔
656
        const part = inputDateParts.find(element => element.type === type);
814✔
657
        return DateTimeUtil.getPartValue(value, part, part.format?.length);
154✔
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) {
674
        this._value = value;
21✔
675
        const date = this.parseToDate(value);
21✔
676
        if (date) {
21✔
677
            this._dateValue = new Date();
13✔
678
            this._dateValue.setHours(date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
13✔
679
            this.setSelectedValue(this._dateValue);
13✔
680
        } else {
681
            this.setSelectedValue(null);
8✔
682
        }
683
        if (this.dateTimeEditor) {
21✔
684
            this.dateTimeEditor.value = date;
21✔
685
        }
686
    }
687

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

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

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

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

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

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

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

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

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

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

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

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

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

768
    /** @hidden */
769
    public getEditElement(): HTMLInputElement {
770
        return this.dateTimeEditor.nativeElement;
25✔
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 {
783
        if (this.disabled || !this.toggleRef.collapsed) {
34!
784
            return;
×
785
        }
786

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

793
        this.toggleRef.open(overlaySettings);
34✔
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 {
807
        this.toggleRef.close();
17✔
808
    }
809

810
    public toggle(settings?: OverlaySettings): void {
811
        if (this.toggleRef.collapsed) {
7✔
812
            this.open(settings);
3✔
813
        } else {
814
            this.close();
4✔
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 {
827
        if (this.disabled) {
5!
828
            return;
×
829
        }
830

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

835
        if (DateTimeUtil.isValidDate(this.value)) {
5✔
836
            const oldValue = new Date(this.value);
2✔
837
            this.value.setHours(0, 0, 0, 0);
2✔
838
            if (this.value.getTime() !== oldValue.getTime()) {
2✔
839
                this.emitValueChange(oldValue, this.value);
1✔
840
                this._dateValue.setHours(0, 0, 0, 0);
1✔
841
                this.dateTimeEditor.value = new Date(this.value);
1✔
842
                this.setSelectedValue(this._dateValue);
1✔
843
            }
844
        } else {
845
            this.value = null;
3✔
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 {
859
        this.value = date;
1✔
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 {
873
        this.dateTimeEditor.increment(datePart, delta);
3✔
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 {
887
        this.dateTimeEditor.decrement(datePart, delta);
2✔
888
    }
889

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

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

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

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

922
                date.setHours(hours);
1✔
923
                date = this.validateDropdownValue(date);
1✔
924

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

951
                date.setHours(hour);
5✔
952
                date = this.validateDropdownValue(date, true);
5✔
953
                this.setSelectedValue(date);
5✔
954
                break;
5✔
955
            }
956
        }
957
        this.updateEditorValue();
8✔
958
    }
959

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

972
        this._selectedDate.setHours(hours);
17✔
973
        this._selectedDate = this.validateDropdownValue(this._selectedDate);
17✔
974
        this._selectedDate = new Date(this._selectedDate);
17✔
975
        this.updateEditorValue();
17✔
976
    }
977

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

990
        if ((delta < 0 && minutes === minMinutes) || (delta > 0 && minutes === maxMinutes)) {
7✔
991
            minutes = this.spinLoop && minutes === minMinutes ? maxMinutes : this.spinLoop && minutes === maxMinutes ? minMinutes : minutes;
3!
992
        } else {
993
            minutes = minutes + delta * this.itemsDelta.minutes;
4✔
994
        }
995

996
        this._selectedDate.setMinutes(minutes);
7✔
997
        this._selectedDate = this.validateDropdownValue(this._selectedDate);
7✔
998
        this._selectedDate = new Date(this._selectedDate);
7✔
999
        this.updateEditorValue();
7✔
1000
    }
1001

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

1017
        if ((delta < 0 && seconds === minSeconds) || (delta > 0 && seconds === maxSeconds)) {
2!
UNCOV
1018
            seconds = this.spinLoop && seconds === minSeconds ? maxSeconds : this.spinLoop && seconds === maxSeconds ? minSeconds : seconds;
×
1019
        } else {
1020
            seconds = seconds + delta * this.itemsDelta.seconds;
2✔
1021
        }
1022

1023
        this._selectedDate.setSeconds(seconds);
2✔
1024
        this._selectedDate = this.validateDropdownValue(this._selectedDate);
2✔
1025
        this._selectedDate = new Date(this._selectedDate);
2✔
1026
        this.updateEditorValue();
2✔
1027
    }
1028

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

1044
    /** @hidden @internal */
1045
    public setSelectedValue(value: Date) {
1046
        this._selectedDate = value ? new Date(value) : null;
337✔
1047
        if (!DateTimeUtil.isValidDate(this._selectedDate)) {
337✔
1048
            this._selectedDate = new Date(this.minDropdownValue);
54✔
1049
            return;
54✔
1050
        }
1051
        if (this.minValue && DateTimeUtil.lessThanMinValue(this._selectedDate, this.minDropdownValue, true, false)) {
283✔
1052
            this._selectedDate = new Date(this.minDropdownValue);
8✔
1053
            return;
8✔
1054
        }
1055
        if (this.maxValue && DateTimeUtil.greaterThanMaxValue(this._selectedDate, this.maxDropdownValue, true, false)) {
275✔
1056
            this._selectedDate = new Date(this.maxDropdownValue);
1✔
1057
            return;
1✔
1058
        }
1059

1060
        if (this._selectedDate.getHours() % this.itemsDelta.hours > 0) {
274✔
1061
            this._selectedDate.setHours(
6✔
1062
                this._selectedDate.getHours() + this.itemsDelta.hours - this._selectedDate.getHours() % this.itemsDelta.hours,
1063
                0,
1064
                0
1065
            );
1066
        }
1067

1068
        if (this._selectedDate.getMinutes() % this.itemsDelta.minutes > 0) {
274✔
1069
            this._selectedDate.setHours(
4✔
1070
                this._selectedDate.getHours(),
1071
                this._selectedDate.getMinutes() + this.itemsDelta.minutes - this._selectedDate.getMinutes() % this.itemsDelta.minutes,
1072
                0
1073
            );
1074
        }
1075

1076
        if (this._selectedDate.getSeconds() % this.itemsDelta.seconds > 0) {
274!
UNCOV
1077
            this._selectedDate.setSeconds(
×
1078
                this._selectedDate.getSeconds() + this.itemsDelta.seconds - this._selectedDate.getSeconds() % this.itemsDelta.seconds
1079
            );
1080
        }
1081
    }
1082

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

1095
        if (this._inputGroup && this._inputGroup.isRequired !== this.required) {
27✔
1096
            this._inputGroup.isRequired = this.required;
8✔
1097
        }
1098
    }
1099

1100
    private get isTouchedOrDirty(): boolean {
1101
        return (this._ngControl.control.touched || this._ngControl.control.dirty);
26✔
1102
    }
1103

1104
    private get hasValidators(): boolean {
1105
        return (!!this._ngControl.control.validator || !!this._ngControl.control.asyncValidator);
20✔
1106
    }
1107

1108
    private setMinMaxDropdownValue(type: string, time: Date): Date {
1109
        let delta: number;
1110

1111
        const sign = type === 'min' ? 1 : -1;
166✔
1112

1113
        const hours = time.getHours();
166✔
1114
        let minutes = time.getMinutes();
166✔
1115
        let seconds = time.getSeconds();
166✔
1116

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

1140
        return time;
166✔
1141
    }
1142

1143
    private initializeContainer() {
1144
        requestAnimationFrame(() => {
31✔
1145
            if (this.hourList) {
31!
1146
                this.hourList.nativeElement.focus();
31✔
1147
            } else if (this.minuteList) {
×
UNCOV
1148
                this.minuteList.nativeElement.focus();
×
UNCOV
1149
            } else if (this.secondsList) {
×
UNCOV
1150
                this.secondsList.nativeElement.focus();
×
1151
            }
1152
        });
1153
    }
1154

1155
    private validateDropdownValue(date: Date, isAmPm = false): Date {
28✔
1156
        if (date > this.maxDropdownValue) {
37✔
1157
            if (isAmPm && date.getHours() !== this.maxDropdownValue.getHours()) {
2!
UNCOV
1158
                date.setHours(12);
×
1159
            } else {
1160
                date = new Date(this.maxDropdownValue);
2✔
1161
            }
1162
        }
1163

1164
        if (date < this.minDropdownValue) {
37✔
1165
            date = new Date(this.minDropdownValue);
1✔
1166
        }
1167

1168
        return date;
37✔
1169
    }
1170

1171
    private emitValueChange(oldValue: Date | string, newValue: Date | string) {
1172
        if (!isEqual(oldValue, newValue)) {
96✔
1173
            this.valueChange.emit(newValue);
92✔
1174
        }
1175
    }
1176

1177
    private emitValidationFailedEvent(previousValue: Date | string) {
1178
        const args: IgxTimePickerValidationFailedEventArgs = {
1✔
1179
            owner: this,
1180
            previousValue,
1181
            currentValue: this.value
1182
        };
1183
        this.validationFailed.emit(args);
1✔
1184
    }
1185

1186
    private updateValidityOnBlur() {
1187
        this._onTouchedCallback();
5✔
1188
        if (this._ngControl) {
5✔
1189
            if (!this._ngControl.valid) {
2✔
1190
                this.inputDirective.valid = IgxInputState.INVALID;
1✔
1191
            } else {
1192
                this.inputDirective.valid = IgxInputState.INITIAL;
1✔
1193
            }
1194
        }
1195
    }
1196

1197
    private valueInRange(value: Date, minValue: Date, maxValue: Date): boolean {
1198
        if (minValue && DateTimeUtil.lessThanMinValue(value, minValue, true, false)) {
2!
1199
            return false;
×
1200
        }
1201
        if (maxValue && DateTimeUtil.greaterThanMaxValue(value, maxValue, true, false)) {
2!
UNCOV
1202
            return false;
×
1203
        }
1204

1205
        return true;
2✔
1206
    }
1207

1208
    private parseToDate(value: Date | string): Date | null {
1209
        return DateTimeUtil.isValidDate(value) ? value : DateTimeUtil.parseIsoDate(value);
240✔
1210
    }
1211

1212
    private toTwentyFourHourFormat(hour: number, ampm: string): number {
1213
        if (DateTimeUtil.isPm(ampm) && hour < 12) {
1!
UNCOV
1214
            hour += 12;
×
1215
        } else if (DateTimeUtil.isAm(ampm) && hour === 12) {
1!
UNCOV
1216
            hour = 0;
×
1217
        }
1218

1219
        return hour;
1✔
1220
    }
1221

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

1234
    private updateEditorValue(): void {
1235
        const date = this.dateTimeEditor.value ? new Date(this.dateTimeEditor.value) : new Date();
38✔
1236
        date.setHours(this._selectedDate.getHours(), this._selectedDate.getMinutes(), this._selectedDate.getSeconds(), this._selectedDate.getMilliseconds());
38✔
1237
        this.dateTimeEditor.value = date;
38✔
1238
    }
1239

1240
    private subscribeToDateEditorEvents(): void {
1241
        this.dateTimeEditor.valueChange.pipe(
65✔
1242
            // internal date editor directive is only used w/ Date object values:
1243
            takeUntil(this._destroy$)).subscribe((date: Date | null) => {
1244
                this.updateValue(date);
7✔
1245
            });
1246

1247
        this.dateTimeEditor.validationFailed.pipe(
65✔
1248
            takeUntil(this._destroy$)).subscribe((event) => {
1249
                this.emitValidationFailedEvent(event.oldValue);
1✔
1250
            });
1251
    }
1252

1253
    private subscribeToToggleDirectiveEvents(): void {
1254
        if (this.toggleRef) {
65✔
1255
            if (this._inputGroup && this.platform.isBrowser) {
63✔
1256
                this.toggleRef.element.style.width = this._inputGroup.element.nativeElement.getBoundingClientRect().width + 'px';
63✔
1257
            }
1258

1259
            this.toggleRef.opening.pipe(takeUntil(this._destroy$)).subscribe((e) => {
63✔
1260
                const args: IBaseCancelableBrowserEventArgs = { owner: this, event: e.event, cancel: false };
32✔
1261
                this.opening.emit(args);
32✔
1262
                e.cancel = args.cancel;
32✔
1263
                if (args.cancel) {
32✔
1264
                    return;
1✔
1265
                }
1266
                this.initializeContainer();
31✔
1267
            });
1268

1269
            this.toggleRef.opened.pipe(takeUntil(this._destroy$)).subscribe(() => {
63✔
1270
                this.opened.emit({ owner: this });
28✔
1271
            });
1272

1273
            this.toggleRef.closed.pipe(takeUntil(this._destroy$)).subscribe(() => {
63✔
1274
                this.closed.emit({ owner: this });
17✔
1275
            });
1276

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