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

IgniteUI / igniteui-angular / 13287533042

12 Feb 2025 02:22PM CUT coverage: 91.601%. Remained the same
13287533042

Pull #15360

github

web-flow
Merge 6191ebe49 into 77ba2ba27
Pull Request #15360: fix(time-picker): exclude from SSR toggle events #15135

12989 of 15225 branches covered (85.31%)

26327 of 28741 relevant lines covered (91.6%)

33995.2 hits per line

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

93.48
/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
    standalone: true,
95
    imports: [IgxInputGroupComponent, IgxInputDirective, IgxDateTimeEditorDirective, IgxTextSelectionDirective, NgIf, IgxPrefixDirective, IgxIconComponent, IgxSuffixDirective, IgxButtonDirective, IgxToggleDirective, NgClass, IgxItemListDirective, NgFor, IgxTimeItemDirective, NgTemplateOutlet, TimeFormatPipe, TimeItemPipe, IgxDividerDirective]
96
})
97
export class IgxTimePickerComponent extends PickerBaseDirective
2✔
98
    implements
99
    IgxTimePickerBase,
100
    ControlValueAccessor,
101
    OnInit,
102
    OnDestroy,
103
    AfterViewInit,
104
    Validator {
105
    /**
106
     * Sets the value of the `id` attribute.
107
     * ```html
108
     * <igx-time-picker [id]="'igx-time-picker-5'" [displayFormat]="h:mm tt" ></igx-time-picker>
109
     * ```
110
     */
111
    @HostBinding('attr.id')
112
    @Input()
113
    public id = `igx-time-picker-${NEXT_ID++}`;
80✔
114

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

336
    /** @hidden */
337
    public cleared = false;
80✔
338

339
    /** @hidden */
340
    public isNotEmpty = false;
80✔
341

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

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

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

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

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

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

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

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

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

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

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

404
        return this._dateMinValue;
4✔
405
    }
406

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

415
        return this._dateMaxValue;
×
416
    }
417

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

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

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

434
        return false;
11✔
435
    }
436

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

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

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

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

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

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

492

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

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

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

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

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

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

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

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

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

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

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

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

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

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

671
    // #region ControlValueAccessor
672

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

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

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

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

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

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

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

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

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

739
        if (this.platform.isBrowser) {
64✔
740
            this.subscribeToToggleDirectiveEvents();
64✔
741
        }
742

743
        this._defaultDropDownOverlaySettings.excludeFromOutsideClick = [this._inputGroup.element.nativeElement];
64✔
744

745
        fromEvent(this.inputDirective.nativeElement, 'blur')
64✔
746
            .pipe(takeUntil(this._destroy$))
747
            .subscribe(() => {
748
                if (this.collapsed) {
1✔
749
                    this.updateValidityOnBlur();
1✔
750
                }
751
            });
752

753
        this.subToIconsClicked(this.clearComponents, () => this.clear());
64✔
754
        this.clearComponents.changes.pipe(takeUntil(this._destroy$))
64✔
755
            .subscribe(() => this.subToIconsClicked(this.clearComponents, () => this.clear()));
4✔
756

757
        if (this._ngControl) {
64✔
758
            this._statusChanges$ = this._ngControl.statusChanges.subscribe(this.onStatusChanged.bind(this));
14✔
759
            this._inputGroup.isRequired = this.required;
14✔
760
            this.cdr.detectChanges();
14✔
761
        }
762
    }
763

764
    /** @hidden */
765
    public override ngOnDestroy(): void {
766
        super.ngOnDestroy();
65✔
767
        if (this._statusChanges$) {
65✔
768
            this._statusChanges$.unsubscribe();
14✔
769
        }
770
    }
771

772
    /** @hidden */
773
    public getEditElement(): HTMLInputElement {
774
        return this.dateTimeEditor.nativeElement;
24✔
775
    }
776

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

791
        this.setSelectedValue(this._dateValue);
33✔
792
        const overlaySettings = Object.assign({}, this.isDropdown
33✔
793
            ? this.dropDownOverlaySettings
794
            : this.dialogOverlaySettings
795
            , settings);
796

797
        this.toggleRef.open(overlaySettings);
33✔
798
    }
799

800
    /**
801
     * Closes the dropdown/dialog.
802
     * ```html
803
     * <igx-time-picker #timePicker></igx-time-picker>
804
     * ```
805
     * ```typescript
806
     * @ViewChild('timePicker', { read: IgxTimePickerComponent }) picker: IgxTimePickerComponent;
807
     * picker.close();
808
     * ```
809
     */
810
    public close(): void {
811
        this.toggleRef.close();
17✔
812
    }
813

814
    public toggle(settings?: OverlaySettings): void {
815
        if (this.toggleRef.collapsed) {
7✔
816
            this.open(settings);
3✔
817
        } else {
818
            this.close();
4✔
819
        }
820
    }
821

822
    /**
823
     * 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.
824
     *
825
     * @example
826
     * ```typescript
827
     * this.timePicker.clear();
828
     * ```
829
     */
830
    public clear(): void {
831
        if (this.disabled) {
5!
832
            return;
×
833
        }
834

835
        if (!this.toggleRef.collapsed) {
5!
836
            this.close();
×
837
        }
838

839
        if (DateTimeUtil.isValidDate(this.value)) {
5✔
840
            const oldValue = new Date(this.value);
2✔
841
            this.value.setHours(0, 0, 0, 0);
2✔
842
            if (this.value.getTime() !== oldValue.getTime()) {
2✔
843
                this.emitValueChange(oldValue, this.value);
1✔
844
                this._dateValue.setHours(0, 0, 0, 0);
1✔
845
                this.dateTimeEditor.value = new Date(this.value);
1✔
846
                this.setSelectedValue(this._dateValue);
1✔
847
            }
848
        } else {
849
            this.value = null;
3✔
850
        }
851
    }
852

853
    /**
854
     * Selects time from the igxTimePicker.
855
     *
856
     * @example
857
     * ```typescript
858
     * this.timePicker.select(date);
859
     *
860
     * @param date Date object containing the time to be selected.
861
     */
862
    public select(date: Date | string): void {
863
        this.value = date;
1✔
864
    }
865

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

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

894
    /** @hidden @internal */
895
    public cancelButtonClick(): void {
896
        this.setSelectedValue(this._dateValue);
3✔
897
        this.dateTimeEditor.value = this.parseToDate(this.value);
3✔
898
        this.close();
3✔
899
    }
900

901
    /** @hidden @internal */
902
    public okButtonClick(): void {
903
        this.updateValue(this._selectedDate);
3✔
904
        this.close();
3✔
905
    }
906

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

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

926
                date.setHours(hours);
1✔
927
                date = this.validateDropdownValue(date);
1✔
928

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1112
        const sign = type === 'min' ? 1 : -1;
164✔
1113

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

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

1141
        return time;
164✔
1142
    }
1143

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

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

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

1169
        return date;
34✔
1170
    }
1171

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

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

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

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

1206
        return true;
2✔
1207
    }
1208

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

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

1220
        return hour;
1✔
1221
    }
1222

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

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

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

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

1254
    private subscribeToToggleDirectiveEvents(): void {
1255
        if (this.toggleRef) {
64✔
1256
            if (this._inputGroup) {
62✔
1257
                this.toggleRef.element.style.width = this._inputGroup.element.nativeElement.getBoundingClientRect().width + 'px';
62✔
1258
            }
1259

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

1270
            this.toggleRef.opened.pipe(takeUntil(this._destroy$)).subscribe(() => {
62✔
1271
                this.opened.emit({ owner: this });
27✔
1272
            });
1273

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

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