From ea08d7092b5c531ffa862c7ea8b91fe826ff3e42 Mon Sep 17 00:00:00 2001 From: SAndreeva Date: Thu, 22 Nov 2018 21:59:56 +0200 Subject: [PATCH 01/34] feat(time-picker): initial implementation of removing dialog #2337 --- .../src/lib/directives/mask/mask.directive.ts | 32 ++- .../src/lib/services/overlay/overlay.ts | 2 + .../time-picker/time-picker.component.html | 78 +++--- .../lib/time-picker/time-picker.component.ts | 226 +++++++++++++++--- src/app/time-picker/time-picker.sample.html | 14 +- src/app/time-picker/time-picker.sample.ts | 10 + 6 files changed, 291 insertions(+), 71 deletions(-) diff --git a/projects/igniteui-angular/src/lib/directives/mask/mask.directive.ts b/projects/igniteui-angular/src/lib/directives/mask/mask.directive.ts index 1c1fa4913ba..36507e442e9 100644 --- a/projects/igniteui-angular/src/lib/directives/mask/mask.directive.ts +++ b/projects/igniteui-angular/src/lib/directives/mask/mask.directive.ts @@ -7,7 +7,8 @@ import { Input, NgModule, OnInit, - Output + Output, + PipeTransform } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { KEYS, MaskHelper } from './mask-helper'; @@ -47,6 +48,14 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { */ @Input() public includeLiterals: boolean; + + @Input() + public placeholder: string; + @Input() + public displayValuePipe: PipeTransform; + @Input() + public inputValuePipe: PipeTransform; + /** *@hidden */ @@ -152,7 +161,7 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { this._maskOptions.format = this.mask ? this.mask : 'CCCCCCCCCC'; this._maskOptions.promptChar = this.promptChar ? this.promptChar : '_'; - this.nativeElement.setAttribute('placeholder', this.mask); + this.nativeElement.setAttribute('placeholder', this.placeholder ? this.placeholder : this.mask); } /** *@hidden @@ -228,9 +237,22 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { /** *@hidden */ - @HostListener('focus', ['$event']) - public onFocus(event) { - this.value = this.maskHelper.parseValueByMaskOnInit(this.value, this._maskOptions); + @HostListener('focus', ['$event.target.value']) + public onFocus(value) { + if (this.inputValuePipe) { + this.value = this.inputValuePipe.transform(value); + } else { + this.value = this.maskHelper.parseValueByMaskOnInit(this.value, this._maskOptions); + } + } + /** + *@hidden + */ + @HostListener('blur', ['$event.target.value']) + public onBlur(value) { + if (this.displayValuePipe) { + this.value = this.displayValuePipe.transform(value); + } } /** *@hidden diff --git a/projects/igniteui-angular/src/lib/services/overlay/overlay.ts b/projects/igniteui-angular/src/lib/services/overlay/overlay.ts index 57e10ec361b..748712016a9 100644 --- a/projects/igniteui-angular/src/lib/services/overlay/overlay.ts +++ b/projects/igniteui-angular/src/lib/services/overlay/overlay.ts @@ -20,6 +20,7 @@ import { AnimationBuilder, AnimationReferenceMetadata, AnimationMetadataType, An import { fromEvent, Subject } from 'rxjs'; import { take, filter, takeUntil } from 'rxjs/operators'; import { IAnimationParams } from '../../animations/main'; +import { DebugRenderer2 } from '@angular/core/src/view/services'; /** * [Documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/overlay_main.html) @@ -119,6 +120,7 @@ export class IgxOverlayService implements OnDestroy { show(compOrId: string | ElementRef | Type<{}> , settings?: OverlaySettings): string { let info: OverlayInfo; let id: string; + debugger; if (typeof compOrId === 'string') { id = compOrId; info = this.getOverlayById(compOrId); diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html index 20da254c3a0..6d3ee89798a 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html @@ -1,3 +1,25 @@ + + + + + + clear + + + access_time + + + @@ -8,30 +30,32 @@ - - - - -
-
-
{{ selectedAmPm }}
-

- {{ selectedHour }}:{{ selectedMinute }} -

-
-
-
- {{ hour }} -
-
- {{ minute }} -
-
- {{ ampm }} -
-
-
-
-
-
+ + +
+
+
{{ selectedAmPm }}
+

+ {{ selectedHour }}:{{ selectedMinute }} +

+
+
+
+ {{ hour }} +
+
+ {{ minute }} +
+
+ {{ ampm }} +
+
+
+ + +
+
diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts index 5aba3d73a72..75beaf1c880 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts @@ -16,13 +16,16 @@ import { ViewChild, AfterViewInit, DoCheck, - ContentChild + ContentChild, + Inject, + Pipe, + PipeTransform } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { HAMMER_GESTURE_CONFIG, HammerGestureConfig } from '@angular/platform-browser'; import { IgxDialogComponent, IgxDialogModule } from '../dialog/dialog.component'; import { IgxIconModule } from '../icon/index'; -import { IgxInputGroupModule } from '../input-group/input-group.component'; +import { IgxInputGroupModule, IgxInputGroupComponent } from '../input-group/input-group.component'; import { IgxInputDirective } from '../directives/input/input.directive'; import { IgxAmPmItemDirective, @@ -31,10 +34,24 @@ import { IgxMinuteItemDirective, IgxTimePickerTemplateDirective } from './time-picker.directives'; -import { Subscription } from 'rxjs'; +import { Subject } from 'rxjs'; import { EditorProvider } from '../core/edit-provider'; +import { IgxOverlayService } from '../services/overlay/overlay'; +import { NoOpScrollStrategy } from '../services/overlay/scroll'; +import { ConnectedPositioningStrategy, GlobalPositionStrategy } from '../services/overlay/position'; +import { HorizontalAlignment, VerticalAlignment, PositionSettings, OverlaySettings } from '../services/overlay/utilities'; +import { takeUntil } from 'rxjs/operators'; +import { IgxButtonModule } from '../directives/button/button.directive'; +import { IgxMaskModule } from '../directives/mask/mask.directive'; + let NEXT_ID = 0; + +export enum InteractionMode { + dialog, + dropdown +} + export class TimePickerHammerConfig extends HammerGestureConfig { public overrides = { pan: { direction: Hammer.DIRECTION_VERTICAL, threshold: 1 } @@ -68,7 +85,7 @@ export interface IgxTimePickerValidationFailedEventArgs { styles: [':host {display: block;}'], templateUrl: 'time-picker.component.html' }) -export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvider, OnInit, OnDestroy, DoCheck, AfterViewInit { +export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvider, OnInit, OnDestroy, AfterViewInit { private _value: Date; @@ -215,7 +232,14 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi * ``` */ @Input() - public format = 'hh:mm tt'; + get format() { + return this._format ? this._format : 'hh:mm tt'; + } + + set format(formatValue: string) { + this._format = formatValue; + this.mask = this._format.indexOf('tt') !== -1 ? '00:00 LL' : '00:00'; + } /** * Emitted when selection is made. The event contains the selected value. Returns {`oldValue`: `Date`, `newValue`: `Date`}. @@ -358,7 +382,7 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi private _prevSelectedMinute: string; private _prevSelectedAmPm: string; - protected dialogClosed = new Subscription(); + // protected dialogClosed = new Subscription(); /** * Returns the current time formatted as string using the `format` option. @@ -411,6 +435,19 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi * ``` */ public openDialog(timePicker: IgxTimePickerComponent = this): void { + + this.showContainer = true; + requestAnimationFrame(()=> { + if (this.mode === InteractionMode.dialog) { + this.overlayId = this.overlayService.show(this.container); + } else { + if (this._collapsed) { + this._overlaySettings.positionStrategy.settings.target = this.group.element.nativeElement; + this.overlayId = this.overlayService.show(this.container, this._overlaySettings); + } + } + }); + if (this.value) { const foramttedTime = this._formatTime(this.value, this.format); const sections = foramttedTime.split(/[\s:]+/); @@ -437,7 +474,7 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi this._prevSelectedMinute = this.selectedMinute; this._prevSelectedAmPm = this.selectedAmPm; - this._alert.open(); + // this._alert.open(); this._onTouchedCallback(); this._updateHourView(0, 7); @@ -454,11 +491,11 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi this.scrollAmPmIntoView(this.selectedAmPm); } - setTimeout(() => { + requestAnimationFrame(() => { this.hourList.nativeElement.focus(); }); - this.onOpen.emit(this); + // this.onOpen.emit(this); } /** @@ -470,42 +507,62 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi if (this.format.indexOf('tt') !== -1) { this._generateAmPm(); } + + this._positionSettings = { + horizontalStartPoint: HorizontalAlignment.Left, + verticalStartPoint: VerticalAlignment.Bottom + }; + + this._overlaySettings = { + modal: false, + closeOnOutsideClick: true, + scrollStrategy: new NoOpScrollStrategy(), + positionStrategy: new ConnectedPositioningStrategy(this._positionSettings) + }; } /** * @hidden */ public ngAfterViewInit(): void { - this.dialogClosed = this._alert.toggleRef.onClosed.pipe().subscribe((ev) => this.handleDialogCloseAction()); + // this.dialogClosed = this._alert.toggleRef.onClosed.pipe().subscribe((ev) => this.handleDialogCloseAction()); + if (this.group) { + this.container.nativeElement.style.width = this.group.element.nativeElement.getBoundingClientRect().width + 'px'; + } } /** * @hidden */ public ngOnDestroy(): void { - this.dialogClosed.unsubscribe(); - } + // this.dialogClosed.unsubscribe(); + this.overlayService.hide(this.overlayId); - // XXX - temporary fix related with issue #1660 - public ngDoCheck(): void { - if (this.vertical && this._alert) { - this._alert.toggleRef.element.classList.remove('igx-time-picker'); - this._alert.toggleRef.element.classList.add('igx-time-picker--vertical'); - } else if (!this.vertical && this._alert) { - this._alert.toggleRef.element.classList.add('igx-time-picker'); - this._alert.toggleRef.element.classList.remove('igx-time-picker--vertical'); - } + this.destroy$.next(true); + this.destroy$.complete(); } - /** - * @hidden - */ - public handleDialogCloseAction() { - if (this._input) { - this._input.nativeElement.focus(); - } - this.onClose.emit(this); - } + // XXX - temporary fix related with issue #1660 + // public ngDoCheck(): void { + // debugger; + // if (this.vertical && this._alert) { + // this._alert.toggleRef.element.classList.remove('igx-time-picker'); + // this._alert.toggleRef.element.classList.add('igx-time-picker--vertical'); + // } else if (!this.vertical && this._alert) { + // this._alert.toggleRef.element.classList.add('igx-time-picker'); + // this._alert.toggleRef.element.classList.remove('igx-time-picker--vertical'); + // } + // } + + // /** + // * @hidden + // */ + // public handleDialogCloseAction() { + // if (this._input) { + // this._input.nativeElement.focus(); + // } + // this.onClose.emit(this); + // } /** * @hidden @@ -933,7 +990,8 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi */ public okButtonClick(): boolean { if (this._isValueValid(this._getSelectedTime())) { - this._alert.close(); + // this._alert.close(); + this.overlayService.hide(this.overlayId); const oldValue = this.value; this.value = this._getSelectedTime(); const args: IgxTimePickerValueChangedEventArgs = { @@ -962,7 +1020,8 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi * ``` */ public cancelButtonClick(): void { - this._alert.close(); + // this._alert.close(); + this.overlayService.hide(this.overlayId); this.selectedHour = this._prevSelectedHour; this.selectedMinute = this._prevSelectedMinute; this.selectedAmPm = this._prevSelectedAmPm; @@ -1031,7 +1090,7 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi if (this.timePickerTemplateDirective) { return this.timePickerTemplateDirective.template; } - return this.defaultTimePickerTemplate; + return this.mode === InteractionMode.dialog ? this.defaultTimePickerTemplate : this.dropdownInputTemplate; } /** @@ -1045,6 +1104,95 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi openDialog: () => { this.openDialog(); } }; } + + + + + + constructor(@Inject(IgxOverlayService) private overlayService: IgxOverlayService) { + + this.overlayService.onClosed.pipe(takeUntil(this.destroy$)).subscribe(() => { + this.showContainer = false; + this._collapsed = true; + if (this._input) { + this._input.nativeElement.focus(); + } + this.onClose.emit(this); + }); + + this.overlayService.onOpened.pipe(takeUntil(this.destroy$)).subscribe(() => { + this._collapsed = false; + this.onOpen.emit(this); + }); + } + + @Input() + public mode = InteractionMode.dialog; + + @ViewChild('container') + public container: ElementRef; + + @ViewChild('group', { read: IgxInputGroupComponent }) + private group: IgxInputGroupComponent; + + @ViewChild('dropdownInputTemplate', { read: TemplateRef }) + private dropdownInputTemplate: TemplateRef; + + + public showContainer = false; + public buttonType = 'flat'; + public mask: string; + public displayValue = ''; + public promptChar = '-'; + public displayFormat = new TimeDisplayFormatPipe(this); + public inputFormat = new TimeInputFormatPipe(this); + + private _format: string; + private overlayId: string; + private destroy$ = new Subject(); + private _collapsed = true; + private _positionSettings: PositionSettings; + private _overlaySettings: OverlaySettings; + + get isModal(): boolean { + return this.mode === InteractionMode.dialog; + } + + public clear() { + + } + public onKeydown(event) { + + } + + public onBlur(event) { + + } + + public spinOnEdit(event) { + + } + +} + +@Pipe({ name: "displayFormat" }) +export class TimeDisplayFormatPipe implements PipeTransform { + + constructor(public timePicker: IgxTimePickerComponent) { } + + transform(value: any): string { + return ''; + } +} + +@Pipe({ name: "inputFormat" }) +export class TimeInputFormatPipe implements PipeTransform { + + constructor(public timePicker: IgxTimePickerComponent) { } + + transform(value: any): string { + return ''; + } } /** @@ -1057,17 +1205,23 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi IgxItemListDirective, IgxMinuteItemDirective, IgxAmPmItemDirective, - IgxTimePickerTemplateDirective + IgxTimePickerTemplateDirective, + TimeDisplayFormatPipe, + TimeInputFormatPipe ], exports: [ IgxTimePickerComponent, - IgxTimePickerTemplateDirective + IgxTimePickerTemplateDirective, + TimeDisplayFormatPipe, + TimeInputFormatPipe ], imports: [ CommonModule, IgxInputGroupModule, IgxDialogModule, - IgxIconModule + IgxIconModule, + IgxButtonModule, + IgxMaskModule ], providers: [] }) diff --git a/src/app/time-picker/time-picker.sample.html b/src/app/time-picker/time-picker.sample.html index 9be8fb7f3a5..c6046f69fce 100644 --- a/src/app/time-picker/time-picker.sample.html +++ b/src/app/time-picker/time-picker.sample.html @@ -7,10 +7,18 @@

Default Time Picker.

Detailed description to be added.

- + +
-
+ diff --git a/src/app/time-picker/time-picker.sample.ts b/src/app/time-picker/time-picker.sample.ts index 6e79e77d1a2..ad67b9f7478 100644 --- a/src/app/time-picker/time-picker.sample.ts +++ b/src/app/time-picker/time-picker.sample.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; +import { InteractionMode } from 'igniteui-angular'; @Component({ selector: 'app-time-picker-sample', @@ -6,4 +7,13 @@ import { Component } from '@angular/core'; templateUrl: 'time-picker.sample.html' }) export class TimePickerSampleComponent { + max = "19:00"; + min = "09:00"; + itemsDelta = { hours: 1, minutes: 15 }; + format="hh:mm tt"; + isSpinLoop = true; + isVertical = false; + mode = InteractionMode.dropdown; + + date = new Date(); } From b47ffb4dde7938a631e457cd1e0fce52dde8f20e Mon Sep 17 00:00:00 2001 From: SAndreeva Date: Mon, 26 Nov 2018 11:23:18 +0200 Subject: [PATCH 02/34] feat(time picker): spin on edit functionality #2337 --- .../src/lib/services/overlay/overlay.ts | 1 - .../time-picker/time-picker.component.html | 7 +- .../lib/time-picker/time-picker.component.ts | 294 ++++++++++++++++-- src/app/time-picker/time-picker.sample.html | 13 +- src/app/time-picker/time-picker.sample.ts | 14 +- 5 files changed, 283 insertions(+), 46 deletions(-) diff --git a/projects/igniteui-angular/src/lib/services/overlay/overlay.ts b/projects/igniteui-angular/src/lib/services/overlay/overlay.ts index 748712016a9..0eae45e7940 100644 --- a/projects/igniteui-angular/src/lib/services/overlay/overlay.ts +++ b/projects/igniteui-angular/src/lib/services/overlay/overlay.ts @@ -120,7 +120,6 @@ export class IgxOverlayService implements OnDestroy { show(compOrId: string | ElementRef | Type<{}> , settings?: OverlaySettings): string { let info: OverlayInfo; let id: string; - debugger; if (typeof compOrId === 'string') { id = compOrId; info = this.getOverlayById(compOrId); diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html index 6d3ee89798a..9a9471b862a 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html @@ -9,10 +9,11 @@ [promptChar]="promptChar" [value]="displayValue" (keydown)="onKeydown($event)" + (keyup)="onKeyup($event)" (blur)="onBlur($event)" (wheel)="spinOnEdit($event)" [disabled]="disabled"/> - + clear @@ -32,7 +33,9 @@ -
+
{{ selectedAmPm }}

diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts index 75beaf1c880..117760ae8b6 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts @@ -15,15 +15,15 @@ import { TemplateRef, ViewChild, AfterViewInit, - DoCheck, ContentChild, Inject, Pipe, - PipeTransform + PipeTransform, + ChangeDetectorRef } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { HAMMER_GESTURE_CONFIG, HammerGestureConfig } from '@angular/platform-browser'; -import { IgxDialogComponent, IgxDialogModule } from '../dialog/dialog.component'; +// import { IgxDialogComponent, IgxDialogModule } from '../dialog/dialog.component'; import { IgxIconModule } from '../icon/index'; import { IgxInputGroupModule, IgxInputGroupComponent } from '../input-group/input-group.component'; import { IgxInputDirective } from '../directives/input/input.directive'; @@ -40,13 +40,17 @@ import { IgxOverlayService } from '../services/overlay/overlay'; import { NoOpScrollStrategy } from '../services/overlay/scroll'; import { ConnectedPositioningStrategy, GlobalPositionStrategy } from '../services/overlay/position'; import { HorizontalAlignment, VerticalAlignment, PositionSettings, OverlaySettings } from '../services/overlay/utilities'; -import { takeUntil } from 'rxjs/operators'; +import { takeUntil, filter } from 'rxjs/operators'; import { IgxButtonModule } from '../directives/button/button.directive'; import { IgxMaskModule } from '../directives/mask/mask.directive'; let NEXT_ID = 0; +const HOURS_POS = [0, 1, 2]; +const MINUTES_POS = [3, 4, 5]; +const AMPM_POS = [6, 7, 8]; + export enum InteractionMode { dialog, dropdown @@ -339,11 +343,11 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi @ViewChild(IgxInputDirective, { read: ElementRef }) private _input: ElementRef; - /** - * @hidden - */ - @ViewChild(IgxDialogComponent) - private _alert: IgxDialogComponent; + // /** + // * @hidden + // */ + // @ViewChild(IgxDialogComponent) + // private _alert: IgxDialogComponent; /** * @hidden @@ -439,12 +443,10 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi this.showContainer = true; requestAnimationFrame(()=> { if (this.mode === InteractionMode.dialog) { - this.overlayId = this.overlayService.show(this.container); - } else { - if (this._collapsed) { - this._overlaySettings.positionStrategy.settings.target = this.group.element.nativeElement; - this.overlayId = this.overlayService.show(this.container, this._overlaySettings); - } + this._overlayId = this.overlayService.show(this.container); + } else if (this._collapsed) { + this._overlaySettings.positionStrategy.settings.target = this.group.element.nativeElement; + this._overlayId = this.overlayService.show(this.container, this._overlaySettings); } }); @@ -527,7 +529,7 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi public ngAfterViewInit(): void { // this.dialogClosed = this._alert.toggleRef.onClosed.pipe().subscribe((ev) => this.handleDialogCloseAction()); if (this.group) { - this.container.nativeElement.style.width = this.group.element.nativeElement.getBoundingClientRect().width + 'px'; + this.dropdownWidth = this.group.element.nativeElement.getBoundingClientRect().width + 'px'; } } @@ -536,10 +538,10 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi */ public ngOnDestroy(): void { // this.dialogClosed.unsubscribe(); - this.overlayService.hide(this.overlayId); + this.overlayService.hide(this._overlayId); - this.destroy$.next(true); - this.destroy$.complete(); + this._destroy$.next(true); + this._destroy$.complete(); } // XXX - temporary fix related with issue #1660 @@ -569,6 +571,10 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi */ public writeValue(value: Date) { this.value = value; + + if (this.mode === InteractionMode.dropdown) { + this.displayValue = this._formatTime(value, this.format); + } } /** @@ -833,8 +839,8 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi return date; } - private _convertMinMaxValue(value: string): Date { - const date = this.value ? new Date(this.value) : new Date(); + private _convertMinMaxValue(value: string, dateVal = null): Date { + const date = dateVal ? new Date(dateVal) : this.value ? new Date(this.value) : new Date(); const sections = value.split(/[\s:]+/); date.setHours(parseInt(sections[0], 10)); @@ -851,6 +857,9 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi } private _isValueValid(value: Date): boolean { + if (!value && this.mode === InteractionMode.dropdown) { + return true; + } if (this.maxValue && value > this._convertMinMaxValue(this.maxValue)) { return false; } else if (this.minValue && value < this._convertMinMaxValue(this.minValue)) { @@ -991,7 +1000,7 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi public okButtonClick(): boolean { if (this._isValueValid(this._getSelectedTime())) { // this._alert.close(); - this.overlayService.hide(this.overlayId); + this.overlayService.hide(this._overlayId); const oldValue = this.value; this.value = this._getSelectedTime(); const args: IgxTimePickerValueChangedEventArgs = { @@ -1021,7 +1030,8 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi */ public cancelButtonClick(): void { // this._alert.close(); - this.overlayService.hide(this.overlayId); + this.overlayService.hide(this._overlayId); + this.selectedHour = this._prevSelectedHour; this.selectedMinute = this._prevSelectedMinute; this.selectedAmPm = this._prevSelectedAmPm; @@ -1109,9 +1119,13 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi - constructor(@Inject(IgxOverlayService) private overlayService: IgxOverlayService) { + constructor(@Inject(IgxOverlayService) private overlayService: IgxOverlayService, + private cdr: ChangeDetectorRef) { + + this.overlayService.onClosed.pipe( + filter(event => event.id === this._overlayId), + takeUntil(this._destroy$)).subscribe(() => { - this.overlayService.onClosed.pipe(takeUntil(this.destroy$)).subscribe(() => { this.showContainer = false; this._collapsed = true; if (this._input) { @@ -1120,7 +1134,10 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi this.onClose.emit(this); }); - this.overlayService.onOpened.pipe(takeUntil(this.destroy$)).subscribe(() => { + this.overlayService.onOpened.pipe( + filter(event => event.id === this._overlayId), + takeUntil(this._destroy$)).subscribe(() => { + this._collapsed = false; this.onOpen.emit(this); }); @@ -1132,6 +1149,9 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi @ViewChild('container') public container: ElementRef; + @ViewChild('input', { read: ElementRef }) + private input: ElementRef; + @ViewChild('group', { read: IgxInputGroupComponent }) private group: IgxInputGroupComponent; @@ -1144,13 +1164,17 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi public mask: string; public displayValue = ''; public promptChar = '-'; + public dropdownWidth; + public cleared = false; + public isNotEmpty = false; public displayFormat = new TimeDisplayFormatPipe(this); public inputFormat = new TimeInputFormatPipe(this); private _format: string; - private overlayId: string; - private destroy$ = new Subject(); + private _overlayId: string; + private _destroy$ = new Subject(); private _collapsed = true; + private _currentVal = new Date(); private _positionSettings: PositionSettings; private _overlaySettings: OverlaySettings; @@ -1158,19 +1182,155 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi return this.mode === InteractionMode.dialog; } + private getCursorPosition(): number { + return this.input.nativeElement.selectionStart; + } + private setCursorPosition(start: number, end: number = start): void { + this.input.nativeElement.setSelectionRange(start, end); + } + + public parseMask(preserveAmPm = true): string { + const prompts = this.promptChar + this.promptChar; + let amPm = preserveAmPm ? (new Date(Date.now())).getHours() > 11 ? 'PM' : 'AM' : prompts; + + return this.format.indexOf('tt') !== -1 ? `${prompts}:${prompts} ${amPm}` : `${prompts}:${prompts}`; + } + public clear() { + this.cleared = true; + this.displayValue = ''; + this.value = null; } + public onKeydown(event) { + switch (event.key.toLowerCase()) { + case 'arrowup': + case 'up': + case 'arrowdown': + case 'down': + this.spinOnEdit(event); + break; + case 'backspace': + case 'delete': + case 'del': + this.updateValueOnDelete(event); + break; + default: + return; + } + } + + public onKeyup(event) { + const val = event.target.value; + + if (val.indexOf(this.promptChar) === -1) { + this.value = this._convertMinMaxValue(val); + } + this.isNotEmpty = val !== this.parseMask(false); } - public onBlur(event) { + public updateValueOnDelete(event) { + requestAnimationFrame(() => { + const value = event.target.value; + if (!this.value || !value || value === this.parseMask(false)) { + this.value = null; + } + }); + } + public onBlur(event) { + const value = event.target.value; + this.displayValue = value; + this.value = value && value !== this.parseMask() ? this._convertMinMaxValue(value) : null; } public spinOnEdit(event) { + event.preventDefault(); + const cursor = this.getCursorPosition(); + + let sign = -1; + if (event.key.toLowerCase() === 'arrowup' || (event.wheelDelta && event.wheelDelta === 120)) { + sign = 1; + } + + let min = this.minValue ? this._convertMinMaxValue(this.minValue): this._convertMinMaxValue('00:00', this._currentVal); + let max = this.maxValue ? this._convertMinMaxValue(this.maxValue) : this._convertMinMaxValue('24:00',this._currentVal); + + let displayVal; + if (!this.value) { + this.value = sign > 0 ? min : max; + displayVal = this._formatTime(this.value, this.format); + } else { + this._currentVal = Object.assign(this._currentVal, this.value); + + const hDelta = this.itemsDelta.hours * 60 + (sign * this.value.getMinutes()); + const mDelta = this.itemsDelta.minutes; + + if (HOURS_POS.indexOf(cursor) !== -1) { + this.value.setMinutes(sign * hDelta); + } + + if (MINUTES_POS.indexOf(cursor) !== -1) { + this.value.setMinutes(this.value.getMinutes() + (sign * mDelta)); + } + + displayVal = this.displayValue = this._formatTime(this.value, this.format); + if (this.value.getTime() > max.getTime()) { + if (this.isSpinLoop && this.maxValue) { + this.value = min; + this.value.setHours(this.value.getHours() + 24); + displayVal = this._formatTime(this.value, this.format); + } + + if (!this.isSpinLoop) { + this.value = max; + displayVal = this._formatTime(this.value, this.format); + } + } + if (this.value.getTime() < min.getTime()) { + if (this.isSpinLoop && this.minValue) { + this.value = max; + this.value.setHours(this.value.getHours() - 24); + displayVal = this._formatTime(this.value, this.format); + } + + if (!this.isSpinLoop) { + this.value = min; + displayVal = this._formatTime(this.value, this.format); + } + } + + if (AMPM_POS.indexOf(cursor) !== -1 && this.format.indexOf('tt') !== -1) { + const sections = this.displayValue.split(/[\s:]+/); + + const hour = sections[0]; + const minutes = sections[1]; + let amPM = sections[2]; + + amPM = amPM === 'AM' ? 'PM' :'AM'; + + displayVal = `${hour}:${minutes} ${amPM}`; + } + + if ((this.format.indexOf('hh') !== -1 || this.format.indexOf('h') !== -1) && this.format.indexOf('tt') !== -1) { + + if (displayVal.split(/[\s:]+/)[2] === 'PM' && this.value.getHours() < 11) { + this.value.setHours(this.value.getHours() + 12); + } + + displayVal = this._formatTime(this.value, this.format); + } + } + + this.displayValue = this.inputFormat.transform(displayVal); + this.value = this._convertMinMaxValue(this.displayValue); + + requestAnimationFrame(() => { + this.setCursorPosition(cursor); + }); } } @@ -1181,17 +1341,86 @@ export class TimeDisplayFormatPipe implements PipeTransform { constructor(public timePicker: IgxTimePickerComponent) { } transform(value: any): string { - return ''; - } + + const maskAmPM = this.timePicker.parseMask(); + const mask = this.timePicker.parseMask(false); + if (!value || value === mask || value === maskAmPM) { + return ''; + } + + const sections = value.split(/[\s:]+/); + + let hour = sections[0]; + let minutes = sections[1]; + let amPM = sections[2]; + + const format = this.timePicker.format; + const prompt = this.timePicker.promptChar; + const regExp = new RegExp(this.timePicker.promptChar,"g"); + + if (format.indexOf('hh') !== -1 || format.indexOf('HH') !== -1 && hour.indexOf(prompt) !== -1) { + hour = hour === prompt + prompt ? '00' : hour.replace(regExp, '0'); + } + + if (format.indexOf('mm') !== -1 && minutes.indexOf(prompt) !== -1) { + minutes = minutes === prompt + prompt ? '00' : minutes.replace(regExp, '0'); + } + + if (format.indexOf('hh') === -1 && format.indexOf('HH') === -1) { + hour = hour.indexOf(prompt) !== -1 ? hour.replace(regExp, '') : hour; + let hourVal = parseInt(hour, 10); + hour = !hourVal ? '0' : hourVal < 10 && hourVal !== 0 ? hour.replace('0', '') : hour; + } + + if (format.indexOf('mm') === -1) { + minutes = minutes.indexOf(prompt) !== -1 ? minutes.replace(regExp, '') : minutes; + let minutesVal = parseInt(minutes, 10); + minutes = !minutesVal ? '0' : minutesVal < 10 && minutesVal !== 0 ? minutes.replace('0', '') : minutes; + } + + if (format.indexOf('tt') !== -1 && (amPM !== 'AM' ||amPM !== 'PM')) { + amPM = amPM.indexOf('p') !== -1 || amPM.indexOf('P') !== -1 ? 'PM' : 'AM'; + } + + return amPM ? `${hour}:${minutes} ${amPM}` : `${hour}:${minutes}`; + } } + @Pipe({ name: "inputFormat" }) export class TimeInputFormatPipe implements PipeTransform { constructor(public timePicker: IgxTimePickerComponent) { } transform(value: any): string { - return ''; + const prompt = this.timePicker.promptChar; + const regExp = new RegExp(prompt,"g"); + + let mask: string; + if (this.timePicker.cleared) { + this.timePicker.cleared = false; + mask = this.timePicker.parseMask(false); + } else { + mask = this.timePicker.parseMask(); + } + + if (!value || value === mask) { + return mask; + } + + const sections = value.split(/[\s:]+/); + + let hour = sections[0].replace(regExp, ''); + let minutes = sections[1].replace(regExp, ''); + let amPM = sections[2]; + + const leadZeroHour = (parseInt(hour, 10) < 10 && !hour.startsWith('0')) || hour === '0'; + const leadZeroMinutes = (parseInt(minutes, 10) < 10 && !minutes.startsWith('0')) || minutes === '0'; + + hour = leadZeroHour ? '0' + hour : hour; + minutes = leadZeroMinutes ? '0' + minutes : minutes; + + return amPM ? `${hour}:${minutes} ${amPM}` : `${hour}:${minutes}`; } } @@ -1218,7 +1447,6 @@ export class TimeInputFormatPipe implements PipeTransform { imports: [ CommonModule, IgxInputGroupModule, - IgxDialogModule, IgxIconModule, IgxButtonModule, IgxMaskModule diff --git a/src/app/time-picker/time-picker.sample.html b/src/app/time-picker/time-picker.sample.html index c6046f69fce..6afce90f6da 100644 --- a/src/app/time-picker/time-picker.sample.html +++ b/src/app/time-picker/time-picker.sample.html @@ -7,25 +7,25 @@

Default Time Picker.

Detailed description to be added.

- + [format]="format">

- +
{{tp.value}}
diff --git a/src/app/time-picker/time-picker.sample.ts b/src/app/time-picker/time-picker.sample.ts index ad67b9f7478..e482cdf1ce1 100644 --- a/src/app/time-picker/time-picker.sample.ts +++ b/src/app/time-picker/time-picker.sample.ts @@ -1,5 +1,5 @@ -import { Component } from '@angular/core'; -import { InteractionMode } from 'igniteui-angular'; +import { Component, ViewChild } from '@angular/core'; +import { InteractionMode, IgxTimePickerComponent } from 'igniteui-angular'; @Component({ selector: 'app-time-picker-sample', @@ -8,12 +8,18 @@ import { InteractionMode } from 'igniteui-angular'; }) export class TimePickerSampleComponent { max = "19:00"; - min = "09:00"; + min = "10:00"; itemsDelta = { hours: 1, minutes: 15 }; - format="hh:mm tt"; + format="H:mm"; isSpinLoop = true; isVertical = false; mode = InteractionMode.dropdown; date = new Date(); + date1 = new Date(); + + @ViewChild('tp', { read: IgxTimePickerComponent }) + public tp: IgxTimePickerComponent; + + } From 3052a9e25637e1ec829a602e49240ebc427e93b9 Mon Sep 17 00:00:00 2001 From: SAndreeva Date: Tue, 27 Nov 2018 14:56:19 +0200 Subject: [PATCH 03/34] feat(time picker): editable input implementation #2337 --- .../navigation-drawer.directives.ts | 6 +- .../src/lib/services/overlay/overlay.ts | 1 - .../time-picker/time-picker.component.html | 4 +- .../lib/time-picker/time-picker.component.ts | 137 ++++++++++-------- src/app/calendar/calendar.sample.html | 2 +- src/app/time-picker/time-picker.sample.html | 5 +- src/app/time-picker/time-picker.sample.ts | 11 +- 7 files changed, 96 insertions(+), 70 deletions(-) diff --git a/projects/igniteui-angular/src/lib/navigation-drawer/navigation-drawer.directives.ts b/projects/igniteui-angular/src/lib/navigation-drawer/navigation-drawer.directives.ts index 90c20e34ce2..b57ffbcbfb6 100644 --- a/projects/igniteui-angular/src/lib/navigation-drawer/navigation-drawer.directives.ts +++ b/projects/igniteui-angular/src/lib/navigation-drawer/navigation-drawer.directives.ts @@ -9,17 +9,17 @@ export class IgxNavDrawerItemDirective { /** * @hidden */ - @Input('active') public active = false; + @Input('active') public active;; /** * @hidden */ - @Input('isHeader') public isHeader = false; + @Input('isHeader') public isHeader; /** * @hidden */ - public readonly activeClass = 'igx-nav-drawer__item--active'; + public readonly activeClass; /** * @hidden diff --git a/projects/igniteui-angular/src/lib/services/overlay/overlay.ts b/projects/igniteui-angular/src/lib/services/overlay/overlay.ts index 0eae45e7940..57e10ec361b 100644 --- a/projects/igniteui-angular/src/lib/services/overlay/overlay.ts +++ b/projects/igniteui-angular/src/lib/services/overlay/overlay.ts @@ -20,7 +20,6 @@ import { AnimationBuilder, AnimationReferenceMetadata, AnimationMetadataType, An import { fromEvent, Subject } from 'rxjs'; import { take, filter, takeUntil } from 'rxjs/operators'; import { IAnimationParams } from '../../animations/main'; -import { DebugRenderer2 } from '@angular/core/src/view/services'; /** * [Documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/overlay_main.html) diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html index 9a9471b862a..d45536f6246 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html @@ -11,9 +11,10 @@ (keydown)="onKeydown($event)" (keyup)="onKeyup($event)" (blur)="onBlur($event)" + (focus)="onFocus($event)" (wheel)="spinOnEdit($event)" [disabled]="disabled"/> - + clear @@ -62,3 +63,4 @@

+
diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts index 117760ae8b6..23d582f2526 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts @@ -18,8 +18,7 @@ import { ContentChild, Inject, Pipe, - PipeTransform, - ChangeDetectorRef + PipeTransform } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { HAMMER_GESTURE_CONFIG, HammerGestureConfig } from '@angular/platform-browser'; @@ -38,11 +37,12 @@ import { Subject } from 'rxjs'; import { EditorProvider } from '../core/edit-provider'; import { IgxOverlayService } from '../services/overlay/overlay'; import { NoOpScrollStrategy } from '../services/overlay/scroll'; -import { ConnectedPositioningStrategy, GlobalPositionStrategy } from '../services/overlay/position'; +import { ConnectedPositioningStrategy } from '../services/overlay/position'; import { HorizontalAlignment, VerticalAlignment, PositionSettings, OverlaySettings } from '../services/overlay/utilities'; import { takeUntil, filter } from 'rxjs/operators'; import { IgxButtonModule } from '../directives/button/button.directive'; import { IgxMaskModule } from '../directives/mask/mask.directive'; +import { IgxOverlayOutletDirective } from '../directives/toggle/toggle.directive'; let NEXT_ID = 0; @@ -443,7 +443,7 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi this.showContainer = true; requestAnimationFrame(()=> { if (this.mode === InteractionMode.dialog) { - this._overlayId = this.overlayService.show(this.container); + this._overlayId = this.overlayService.show(this.container, this._dialogOverlaySettings); } else if (this._collapsed) { this._overlaySettings.positionStrategy.settings.target = this.group.element.nativeElement; this._overlayId = this.overlayService.show(this.container, this._overlaySettings); @@ -518,9 +518,13 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi this._overlaySettings = { modal: false, closeOnOutsideClick: true, + outlet: this.outlet, scrollStrategy: new NoOpScrollStrategy(), positionStrategy: new ConnectedPositioningStrategy(this._positionSettings) }; + this._dialogOverlaySettings = { + outlet: this.outlet + }; } /** @@ -839,8 +843,8 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi return date; } - private _convertMinMaxValue(value: string, dateVal = null): Date { - const date = dateVal ? new Date(dateVal) : this.value ? new Date(this.value) : new Date(); + private _convertMinMaxValue(value: string): Date { + const date = this.value ? new Date(this.value) : new Date(); const sections = value.split(/[\s:]+/); date.setHours(parseInt(sections[0], 10)); @@ -849,7 +853,7 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi if (sections[2] && sections[2] === 'PM' && sections[0] !== '12') { date.setHours(date.getHours() + 12); } - if (sections[2] && sections[2] && sections[0] === '12') { + if (sections[0] === '12' && sections[2] && sections[2] === 'AM') { date.setHours(0); } @@ -860,9 +864,10 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi if (!value && this.mode === InteractionMode.dropdown) { return true; } - if (this.maxValue && value > this._convertMinMaxValue(this.maxValue)) { + + if (this.value && this.maxValue && value > this._convertMinMaxValue(this.maxValue)) { return false; - } else if (this.minValue && value < this._convertMinMaxValue(this.minValue)) { + } else if (this.value && this.minValue && value < this._convertMinMaxValue(this.minValue)) { return false; } else { return true; @@ -1119,8 +1124,7 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi - constructor(@Inject(IgxOverlayService) private overlayService: IgxOverlayService, - private cdr: ChangeDetectorRef) { + constructor(@Inject(IgxOverlayService) private overlayService: IgxOverlayService) { this.overlayService.onClosed.pipe( filter(event => event.id === this._overlayId), @@ -1158,6 +1162,9 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi @ViewChild('dropdownInputTemplate', { read: TemplateRef }) private dropdownInputTemplate: TemplateRef; + @ViewChild('outlet', { read: IgxOverlayOutletDirective }) + private outlet: IgxOverlayOutletDirective; + public showContainer = false; public buttonType = 'flat'; @@ -1174,9 +1181,9 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi private _overlayId: string; private _destroy$ = new Subject(); private _collapsed = true; - private _currentVal = new Date(); private _positionSettings: PositionSettings; private _overlaySettings: OverlaySettings; + private _dialogOverlaySettings: OverlaySettings; get isModal(): boolean { return this.mode === InteractionMode.dialog; @@ -1185,19 +1192,21 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi private getCursorPosition(): number { return this.input.nativeElement.selectionStart; } - private setCursorPosition(start: number, end: number = start): void { + + private setCursorPosition(start: number, end: number = start): void { this.input.nativeElement.setSelectionRange(start, end); } public parseMask(preserveAmPm = true): string { const prompts = this.promptChar + this.promptChar; - let amPm = preserveAmPm ? (new Date(Date.now())).getHours() > 11 ? 'PM' : 'AM' : prompts; + let amPm = preserveAmPm ? 'AM' : prompts; return this.format.indexOf('tt') !== -1 ? `${prompts}:${prompts} ${amPm}` : `${prompts}:${prompts}`; } public clear() { this.cleared = true; + this.isNotEmpty = false; this.displayValue = ''; this.value = null; @@ -1223,12 +1232,17 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi public onKeyup(event) { const val = event.target.value; + const key = event.key.toLowerCase(); + + this.isNotEmpty = val !== this.parseMask(false); + + if (key === 'arrowdown' || key === 'arrowup') { + return; + } if (val.indexOf(this.promptChar) === -1) { this.value = this._convertMinMaxValue(val); } - - this.isNotEmpty = val !== this.parseMask(false); } public updateValueOnDelete(event) { @@ -1240,93 +1254,100 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi }); } + public onFocus(event) { + this.isNotEmpty = event.target.value !== this.parseMask(false); + } + public onBlur(event) { const value = event.target.value; + this.displayValue = value; this.value = value && value !== this.parseMask() ? this._convertMinMaxValue(value) : null; + + this.isNotEmpty = value === this.format; } + public spinOnEdit(event) { event.preventDefault(); + + let sign: number; + let displayVal: string; + let min = this.minValue ? this._convertMinMaxValue(this.minValue): this._convertMinMaxValue('00:00'); + let max = this.maxValue ? this._convertMinMaxValue(this.maxValue) : this._convertMinMaxValue('24:00'); + const cursor = this.getCursorPosition(); - let sign = -1; - if (event.key.toLowerCase() === 'arrowup' || (event.wheelDelta && event.wheelDelta === 120)) { - sign = 1; + if (event.key) { + const key = event.key.toLowerCase(); + sign = key === 'arrowdown' || key === 'down' ? -1 : 1; } - let min = this.minValue ? this._convertMinMaxValue(this.minValue): this._convertMinMaxValue('00:00', this._currentVal); - let max = this.maxValue ? this._convertMinMaxValue(this.maxValue) : this._convertMinMaxValue('24:00',this._currentVal); + if (event.wheelDelta) { + sign = event.wheelDelta === 120 ? 1 : -1; + } - let displayVal; if (!this.value) { this.value = sign > 0 ? min : max; displayVal = this._formatTime(this.value, this.format); } else { - this._currentVal = Object.assign(this._currentVal, this.value); + let currentVal = new Date(this.value); const hDelta = this.itemsDelta.hours * 60 + (sign * this.value.getMinutes()); const mDelta = this.itemsDelta.minutes; + const sections = this.displayValue.split(/[\s:]+/); if (HOURS_POS.indexOf(cursor) !== -1) { - this.value.setMinutes(sign * hDelta); + let val = new Date(this.value); + + val.setMinutes(sign * hDelta); + if (this.format.indexOf('tt') !== -1 && sections[2] && sections[2] === 'PM' && val.getHours() < 11) { + val.setHours(val.getHours() + 12); + } + + this.value = val; } if (MINUTES_POS.indexOf(cursor) !== -1) { - this.value.setMinutes(this.value.getMinutes() + (sign * mDelta)); + let val = new Date(this.value); + val.setMinutes(this.value.getMinutes() + (sign * mDelta)); + + this.value = val; } - displayVal = this.displayValue = this._formatTime(this.value, this.format); if (this.value.getTime() > max.getTime()) { - if (this.isSpinLoop && this.maxValue) { + if (this.isSpinLoop) { + min.setMinutes(0); + min.setMinutes(this.value.getMinutes()); this.value = min; - this.value.setHours(this.value.getHours() + 24); - displayVal = this._formatTime(this.value, this.format); - } - - if (!this.isSpinLoop) { - this.value = max; - displayVal = this._formatTime(this.value, this.format); + } else { + this.value = currentVal; } } if (this.value.getTime() < min.getTime()) { - if (this.isSpinLoop && this.minValue) { + if (this.isSpinLoop) { + max.setMinutes(0); + max.setMinutes(-60 + this.value.getMinutes()); this.value = max; - this.value.setHours(this.value.getHours() - 24); - displayVal = this._formatTime(this.value, this.format); - } - - if (!this.isSpinLoop) { - this.value = min; - displayVal = this._formatTime(this.value, this.format); + } else { + this.value = currentVal; } } if (AMPM_POS.indexOf(cursor) !== -1 && this.format.indexOf('tt') !== -1) { - const sections = this.displayValue.split(/[\s:]+/); + let val = new Date(this.value); - const hour = sections[0]; - const minutes = sections[1]; - let amPM = sections[2]; + sign = sections[2] && sections[2] === 'AM' ? 1 : -1; + val.setHours(val.getHours() + (sign * 12)); - amPM = amPM === 'AM' ? 'PM' :'AM'; - - displayVal = `${hour}:${minutes} ${amPM}`; + this.value = val; } - if ((this.format.indexOf('hh') !== -1 || this.format.indexOf('h') !== -1) && this.format.indexOf('tt') !== -1) { - - if (displayVal.split(/[\s:]+/)[2] === 'PM' && this.value.getHours() < 11) { - this.value.setHours(this.value.getHours() + 12); - } - - displayVal = this._formatTime(this.value, this.format); - } + displayVal = this._formatTime(this.value, this.format); } this.displayValue = this.inputFormat.transform(displayVal); - this.value = this._convertMinMaxValue(this.displayValue); requestAnimationFrame(() => { this.setCursorPosition(cursor); diff --git a/src/app/calendar/calendar.sample.html b/src/app/calendar/calendar.sample.html index 7d840e265d3..e38187b27c8 100644 --- a/src/app/calendar/calendar.sample.html +++ b/src/app/calendar/calendar.sample.html @@ -11,7 +11,7 @@

Default Calendar

- + diff --git a/src/app/time-picker/time-picker.sample.html b/src/app/time-picker/time-picker.sample.html index 6afce90f6da..de757949590 100644 --- a/src/app/time-picker/time-picker.sample.html +++ b/src/app/time-picker/time-picker.sample.html @@ -8,7 +8,8 @@

Default Time Picker.

Detailed description to be added.

Default Time Picker.

Default Time Picker.

Detailed description to be added.

- +
-
{{tp.value}}
+
{{date}}
diff --git a/src/app/time-picker/time-picker.sample.ts b/src/app/time-picker/time-picker.sample.ts index e2685b8aaf6..a2a865421d6 100644 --- a/src/app/time-picker/time-picker.sample.ts +++ b/src/app/time-picker/time-picker.sample.ts @@ -9,8 +9,8 @@ import { InteractionMode, IgxTimePickerComponent } from 'igniteui-angular'; export class TimePickerSampleComponent { max = "19:00"; min = "09:00"; - itemsDelta = { hours: 1, minutes: 1 }; - format="hh:mm tt"; + itemsDelta = { hours: 1, minutes: 5 }; + format="h:mm tt"; isSpinLoop = true; isVertical = false; mode = InteractionMode.dropdown; From 30616beee13a3d7d29a5cf9a2ca71d32dfb4fd73 Mon Sep 17 00:00:00 2001 From: SAndreeva Date: Thu, 29 Nov 2018 17:45:41 +0200 Subject: [PATCH 05/34] feat(time-picker): sync dropdwwon navigation and input display #2337 --- .../time-picker/time-picker.component.html | 6 +- .../lib/time-picker/time-picker.component.ts | 162 +++++++++--------- src/app/time-picker/time-picker.sample.html | 8 +- src/app/time-picker/time-picker.sample.ts | 6 +- 4 files changed, 92 insertions(+), 90 deletions(-) diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html index ccd9c6374a9..5dd879db25a 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html @@ -1,6 +1,9 @@ + + access_time + clear
- - access_time - diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts index 43d3a166897..32dc9059873 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts @@ -14,7 +14,6 @@ import { Output, TemplateRef, ViewChild, - AfterViewInit, ContentChild, Inject, Pipe, @@ -22,7 +21,6 @@ import { } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { HAMMER_GESTURE_CONFIG, HammerGestureConfig } from '@angular/platform-browser'; -// import { IgxDialogComponent, IgxDialogModule } from '../dialog/dialog.component'; import { IgxIconModule } from '../icon/index'; import { IgxInputGroupModule, IgxInputGroupComponent } from '../input-group/input-group.component'; import { IgxInputDirective } from '../directives/input/input.directive'; @@ -343,12 +341,6 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi @ViewChild(IgxInputDirective, { read: ElementRef }) private _input: ElementRef; - // /** - // * @hidden - // */ - // @ViewChild(IgxDialogComponent) - // private _alert: IgxDialogComponent; - /** * @hidden */ @@ -386,8 +378,6 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi private _prevSelectedMinute: string; private _prevSelectedAmPm: string; - // protected dialogClosed = new Subscription(); - /** * Returns the current time formatted as string using the `format` option. * If there is no set time the return is an empty string. @@ -475,7 +465,6 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi this._prevSelectedMinute = this.selectedMinute; this._prevSelectedAmPm = this.selectedAmPm; - // this._alert.open(); this._onTouchedCallback(); this._updateHourView(0, 7); @@ -495,8 +484,6 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi requestAnimationFrame(() => { this.hourList.nativeElement.focus(); }); - - // this.onOpen.emit(this); } /** @@ -526,46 +513,16 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi }; } - // /** - // * @hidden - // */ - // public ngAfterViewInit(): void { - // // this.dialogClosed = this._alert.toggleRef.onClosed.pipe().subscribe((ev) => this.handleDialogCloseAction()); - // } - /** * @hidden */ public ngOnDestroy(): void { - // this.dialogClosed.unsubscribe(); this.overlayService.hide(this._overlayId); this._destroy$.next(true); this._destroy$.complete(); } - // XXX - temporary fix related with issue #1660 - // public ngDoCheck(): void { - // debugger; - // if (this.vertical && this._alert) { - // this._alert.toggleRef.element.classList.remove('igx-time-picker'); - // this._alert.toggleRef.element.classList.add('igx-time-picker--vertical'); - // } else if (!this.vertical && this._alert) { - // this._alert.toggleRef.element.classList.add('igx-time-picker'); - // this._alert.toggleRef.element.classList.remove('igx-time-picker--vertical'); - // } - // } - - // /** - // * @hidden - // */ - // public handleDialogCloseAction() { - // if (this._input) { - // this._input.nativeElement.focus(); - // } - // this.onClose.emit(this); - // } - /** * @hidden */ @@ -887,6 +844,7 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi if (hourIntoView) { this._hourView = hourIntoView.view; this.selectedHour = hourIntoView.selectedItem; + this.updateEditableInput(); } } @@ -907,6 +865,7 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi if (minuteIntoView) { this._minuteView = minuteIntoView.view; this.selectedMinute = minuteIntoView.selectedItem; + this.updateEditableInput(); } } @@ -927,6 +886,7 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi if (ampmIntoView) { this._ampmView = ampmIntoView.view; this.selectedAmPm = ampmIntoView.selectedItem; + this.updateEditableInput(); } } @@ -934,14 +894,11 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi * @hidden */ public nextHour() { - debugger; const nextHour = this._nextItem(this._hourItems, this.selectedHour, this._isHourListLoop, 'hour'); this._hourView = nextHour.view; this.selectedHour = nextHour.selectedItem; - if (this.mode === InteractionMode.dropdown) { - // this.displayValue = this._getSelectedTime(); - } + this.updateEditableInput(); } /** @@ -951,6 +908,8 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi const prevHour = this._prevItem(this._hourItems, this.selectedHour, this._isHourListLoop, 'hour'); this._hourView = prevHour.view; this.selectedHour = prevHour.selectedItem; + + this.updateEditableInput(); } /** @@ -960,6 +919,8 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi const nextMinute = this._nextItem(this._minuteItems, this.selectedMinute, this._isMinuteListLoop, 'minute'); this._minuteView = nextMinute.view; this.selectedMinute = nextMinute.selectedItem; + + this.updateEditableInput(); } /** @@ -969,6 +930,8 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi const prevMinute = this._prevItem(this._minuteItems, this.selectedMinute, this._isMinuteListLoop, 'minute'); this._minuteView = prevMinute.view; this.selectedMinute = prevMinute.selectedItem; + + this.updateEditableInput(); } /** @@ -980,6 +943,8 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi if (selectedIndex + 1 < this._ampmItems.length - 3) { this._updateAmPmView(selectedIndex - 2, selectedIndex + 5); this.selectedAmPm = this._ampmItems[selectedIndex + 1]; + + this.updateEditableInput(); } } @@ -992,6 +957,8 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi if (selectedIndex > 3) { this._updateAmPmView(selectedIndex - 4, selectedIndex + 3); this.selectedAmPm = this._ampmItems[selectedIndex - 1]; + + this.updateEditableInput(); } } @@ -1005,7 +972,6 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi */ public okButtonClick(): boolean { if (this._isValueValid(this._getSelectedTime())) { - // this._alert.close(); this.overlayService.hide(this._overlayId); const oldValue = this.value; this.value = this._getSelectedTime(); @@ -1035,7 +1001,6 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi * ``` */ public cancelButtonClick(): void { - // this._alert.close(); this.overlayService.hide(this._overlayId); this.selectedHour = this._prevSelectedHour; @@ -1138,12 +1103,15 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi filter(event => event.id === this._overlayId), takeUntil(this._destroy$)).subscribe(() => { - this.container.nativeElement.style.display = "none"; this._collapsed = true; if (this._input) { this._input.nativeElement.focus(); } this.onClose.emit(this); + + if (this.mode === InteractionMode.dropdown) { + this.onDropDownClosed(); + } }); this.overlayService.onOpened.pipe( @@ -1203,6 +1171,36 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi this.input.nativeElement.setSelectionRange(start, end); } + private updateEditableInput() { + if (this.mode === InteractionMode.dropdown) { + this.displayValue = this._formatTime(this._getSelectedTime(), this.format); + } + } + + private onDropDownClosed() { + const oldValue = this.value; + const currentVal = this._convertMinMaxValue(this.displayValue); + + if (this._isValueValid(currentVal)) { + this.value = currentVal; + + const args: IgxTimePickerValueChangedEventArgs = { + oldValue, + newValue: this.value + }; + this.onValueChanged.emit(args); + } else { + this.displayValue = this._formatTime(oldValue, this.format); + + const args: IgxTimePickerValidationFailedEventArgs = { + timePicker: this, + currentValue: currentVal, + setThroughUI: true + }; + this.onValidationFailed.emit(args); + } + } + public parseMask(preserveAmPm = true): string { const prompts = this.promptChar + this.promptChar; let amPm = preserveAmPm ? 'AM' : prompts; @@ -1219,11 +1217,17 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi } public onKeydown(event) { + const alt = event.altKey; + switch (event.key.toLowerCase()) { case 'arrowup': case 'up': case 'arrowdown': case 'down': + if (alt) { + this.openDialog(); + break; + } this.spinOnEdit(event); break; case 'backspace': @@ -1237,18 +1241,18 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi } public onKeyup(event) { - const val = event.target.value; - const key = event.key.toLowerCase(); + // const val = event.target.value; + //const key = event.key.toLowerCase(); - this.isNotEmpty = val !== this.parseMask(false); + this.isNotEmpty = event.target.value !== this.parseMask(false); - if (key === 'arrowdown' || key === 'arrowup') { - return; - } + // if (key === 'arrowdown' || key === 'arrowup') { + // return; + // } - if (val.indexOf(this.promptChar) === -1) { - this.value = this._convertMinMaxValue(val); - } + // if (val.indexOf(this.promptChar) === -1) { + // this.value = this._convertMinMaxValue(val); + // } } public updateValueOnDelete(event) { @@ -1275,7 +1279,6 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi public spinOnEdit(event) { - debugger; event.preventDefault(); let sign: number; @@ -1313,7 +1316,24 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi val.setHours(val.getHours() + 12); } - this.value = val; + if (val.getTime() > max.getTime()) { + if (this.isSpinLoop) { + min.setMinutes(val.getMinutes()); + this.value = min; + } else { + this.value = currentVal; + } + } else if (val.getTime() < min.getTime()) { + if (this.isSpinLoop) { + let minutes = val.getMinutes() === 0 ? 0 : 60 -val.getMinutes(); + max.setMinutes(sign * minutes); + this.value = max; + } else { + this.value = currentVal; + } + } else { + this.value = val; + } } if (MINUTES_POS.indexOf(cursor) !== -1) { @@ -1330,26 +1350,6 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi this.value = val; } - if (this.value.getTime() >= max.getTime()) { - if (this.isSpinLoop) { - min.setMinutes(0); - min.setMinutes(this.value.getMinutes()); - this.value = min; - } else { - this.value = currentVal; - } - } - - if (this.value.getTime() < min.getTime()) { - if (this.isSpinLoop) { - max.setMinutes(0); - max.setMinutes(-60 + this.value.getMinutes()); - this.value = max; - } else { - this.value = currentVal; - } - } - if (AMPM_POS.indexOf(cursor) !== -1 && this.format.indexOf('tt') !== -1) { let val = new Date(this.value); diff --git a/src/app/time-picker/time-picker.sample.html b/src/app/time-picker/time-picker.sample.html index 066aba58bfb..07653f942d7 100644 --- a/src/app/time-picker/time-picker.sample.html +++ b/src/app/time-picker/time-picker.sample.html @@ -8,7 +8,8 @@

Default Time Picker.

Detailed description to be added.

Default Time Picker.

Default Time Picker.

Detailed description to be added.

- +
-
{{date}}
+
{{tp.value}}
diff --git a/src/app/time-picker/time-picker.sample.ts b/src/app/time-picker/time-picker.sample.ts index a2a865421d6..153386bf336 100644 --- a/src/app/time-picker/time-picker.sample.ts +++ b/src/app/time-picker/time-picker.sample.ts @@ -10,8 +10,8 @@ export class TimePickerSampleComponent { max = "19:00"; min = "09:00"; itemsDelta = { hours: 1, minutes: 5 }; - format="h:mm tt"; - isSpinLoop = true; + format="HH:mm"; + isSpinLoop = false; isVertical = false; mode = InteractionMode.dropdown; @@ -21,7 +21,7 @@ export class TimePickerSampleComponent { @ViewChild('tp', { read: IgxTimePickerComponent }) public tp: IgxTimePickerComponent; - @ViewChild('tp', { read: IgxTimePickerComponent }) + @ViewChild('tp1', { read: IgxTimePickerComponent }) public tp1: IgxTimePickerComponent; From 47f8cdbf2b361bd6dfc26a9d26c4d1f5865360db Mon Sep 17 00:00:00 2001 From: SAndreeva Date: Fri, 30 Nov 2018 11:34:34 +0200 Subject: [PATCH 06/34] feat(time picker): emit events when necessary #2337 --- .../time-picker/time-picker.component.html | 6 +- .../lib/time-picker/time-picker.component.ts | 97 ++++++++++++++----- .../lib/time-picker/time-picker.directives.ts | 7 +- src/app/time-picker/time-picker.sample.html | 31 +++--- src/app/time-picker/time-picker.sample.ts | 6 +- 5 files changed, 99 insertions(+), 48 deletions(-) diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html index 5dd879db25a..ac9e64bcda2 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html @@ -34,7 +34,7 @@ -
- -
diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts index 32dc9059873..cfe0eac80fa 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts @@ -433,7 +433,8 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi if (this.mode === InteractionMode.dialog) { this.container.nativeElement.style.display = "inline"; this._overlayId = this.overlayService.show(this.container, this._dialogOverlaySettings); - } else if (this._collapsed) { + } else if (this.mode === InteractionMode.dropdown && this._collapsed) { + this._collapsed = false; this.container.nativeElement.style.display = "inline"; this._overlaySettings.positionStrategy.settings.target = this.group.element.nativeElement; this._overlayId = this.overlayService.show(this.container, this._overlaySettings); @@ -1090,7 +1091,7 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi - constructor(@Inject(IgxOverlayService) private overlayService: IgxOverlayService) { + constructor(@Inject(IgxOverlayService) public overlayService: IgxOverlayService) { this.overlayService.onClosing.pipe( filter(event => event.id === this._overlayId), @@ -1145,13 +1146,13 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi public mask: string; public displayValue = ''; public promptChar = '-'; - public dropdownWidth; public cleared = false; public isNotEmpty = false; public displayFormat = new TimeDisplayFormatPipe(this); public inputFormat = new TimeInputFormatPipe(this); private _format: string; + private _oldValue: Date; private _overlayId: string; private _destroy$ = new Subject(); private _collapsed = true; @@ -1177,6 +1178,12 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi } } + private resetDropdownItems() { + this.selectedHour = undefined; + this.selectedMinute = undefined; + this.selectedAmPm = undefined; + } + private onDropDownClosed() { const oldValue = this.value; const currentVal = this._convertMinMaxValue(this.displayValue); @@ -1201,6 +1208,10 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi } } + public hideDropDownOverlay() { + this.overlayService.hide(this._overlayId); + } + public parseMask(preserveAmPm = true): string { const prompts = this.promptChar + this.promptChar; let amPm = preserveAmPm ? 'AM' : prompts; @@ -1214,6 +1225,13 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi this.displayValue = ''; this.value = null; + this.resetDropdownItems(); + + const args: IgxTimePickerValueChangedEventArgs = { + oldValue: this._oldValue, + newValue: this.value + }; + this.onValueChanged.emit(args); } public onKeydown(event) { @@ -1241,18 +1259,26 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi } public onKeyup(event) { - // const val = event.target.value; - //const key = event.key.toLowerCase(); + const val = event.target.value; + const key = event.key.toLowerCase(); this.isNotEmpty = event.target.value !== this.parseMask(false); - // if (key === 'arrowdown' || key === 'arrowup') { - // return; - // } + if (key === 'arrowdown' || key === 'arrowup') { + return; + } + + if (val.indexOf(this.promptChar) === -1) { + this.value = this._convertMinMaxValue(val); - // if (val.indexOf(this.promptChar) === -1) { - // this.value = this._convertMinMaxValue(val); - // } + if (this._isValueValid(this.value) && this._oldValue !== this.value) { + const args: IgxTimePickerValueChangedEventArgs = { + oldValue: this._oldValue, + newValue: this.value + }; + this.onValueChanged.emit(args); + } + } } public updateValueOnDelete(event) { @@ -1260,21 +1286,39 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi const value = event.target.value; if (!this.value || !value || value === this.parseMask(false)) { this.value = null; + this.resetDropdownItems(); + + const args: IgxTimePickerValueChangedEventArgs = { + oldValue: this._oldValue, + newValue: this.value + }; + this.onValueChanged.emit(args); } }); } public onFocus(event) { + this._oldValue = this.value; this.isNotEmpty = event.target.value !== this.parseMask(false); } public onBlur(event) { - const value = event.target.value; + if (this._collapsed) { + const value = event.target.value; + + this.isNotEmpty = value === this.format; - this.displayValue = value; - this.value = value && value !== this.parseMask() ? this._convertMinMaxValue(value) : null; + this.displayValue = value; + this.value = value && value !== this.parseMask() ? this._convertMinMaxValue(value) : null; - this.isNotEmpty = value === this.format; + if (this._isValueValid(this.value) && this._oldValue !== this.value) { + const args: IgxTimePickerValueChangedEventArgs = { + oldValue: this._oldValue, + newValue: this.value + }; + this.onValueChanged.emit(args); + } + } } @@ -1297,19 +1341,20 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi sign = event.deltaY < 0 ? 1 : -1; } + let currentVal = new Date(this.value); + if (!this.value) { this.value = sign > 0 ? min : max; displayVal = this._formatTime(this.value, this.format); - } else { - let currentVal = new Date(this.value); + } + if (this.value) { const hDelta = this.itemsDelta.hours * 60 + (sign * this.value.getMinutes()); const mDelta = this.itemsDelta.minutes; const sections = this.displayValue.split(/[\s:]+/); + let val = new Date(this.value); if (HOURS_POS.indexOf(cursor) !== -1) { - let val = new Date(this.value); - val.setMinutes(sign * hDelta); if (this.format.indexOf('tt') !== -1 && sections[2] && sections[2] === 'PM' && val.getHours() < 11) { @@ -1318,14 +1363,14 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi if (val.getTime() > max.getTime()) { if (this.isSpinLoop) { - min.setMinutes(val.getMinutes()); + min.setMinutes(sign * val.getMinutes()); this.value = min; } else { this.value = currentVal; } } else if (val.getTime() < min.getTime()) { if (this.isSpinLoop) { - let minutes = val.getMinutes() === 0 ? 0 : 60 -val.getMinutes(); + let minutes = val.getMinutes() === 0 ? 0 : 60 - val.getMinutes(); max.setMinutes(sign * minutes); this.value = max; } else { @@ -1337,8 +1382,6 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi } if (MINUTES_POS.indexOf(cursor) !== -1) { - let val = new Date(this.value); - let minutes = this.value.getMinutes() + (sign * mDelta); if (minutes >= 60) { minutes = this.isSpinLoop ? minutes - 60 : val.getMinutes(); @@ -1351,8 +1394,6 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi } if (AMPM_POS.indexOf(cursor) !== -1 && this.format.indexOf('tt') !== -1) { - let val = new Date(this.value); - sign = sections[2] && sections[2] === 'AM' ? 1 : -1; val.setHours(val.getHours() + (sign * 12)); @@ -1364,6 +1405,12 @@ export class IgxTimePickerComponent implements ControlValueAccessor, EditorProvi this.displayValue = this.inputFormat.transform(displayVal); + const args: IgxTimePickerValueChangedEventArgs = { + oldValue: currentVal, + newValue: this.value + }; + this.onValueChanged.emit(args); + requestAnimationFrame(() => { this.setCursorPosition(cursor); }); diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.directives.ts b/projects/igniteui-angular/src/lib/time-picker/time-picker.directives.ts index 1c9ac0f8d9c..920ba6a2e78 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.directives.ts +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.directives.ts @@ -16,7 +16,7 @@ import { Output, TemplateRef } from '@angular/core'; -import { IgxTimePickerComponent } from './time-picker.component'; +import { IgxTimePickerComponent, InteractionMode } from './time-picker.component'; /** @hidden */ @Directive({ @@ -159,6 +159,11 @@ export class IgxItemListDirective { public onKeydownEnter(event: KeyboardEvent) { event.preventDefault(); + if (this.timePicker.mode === InteractionMode.dropdown) { + + this.timePicker.hideDropDownOverlay(); + return; + } this.timePicker.okButtonClick(); } diff --git a/src/app/time-picker/time-picker.sample.html b/src/app/time-picker/time-picker.sample.html index 07653f942d7..c1faa579ff4 100644 --- a/src/app/time-picker/time-picker.sample.html +++ b/src/app/time-picker/time-picker.sample.html @@ -4,27 +4,26 @@
-

Default Time Picker.

-

Detailed description to be added.

+
- - +
-

Default Time Picker.

-

Detailed description to be added.

+
- + + +
+

Time Picker with Dropdown.

+
{{date.toLocaleString()}}
- + +
- +

Time Picker with Dialog.

+

Vertical dialog.

+

- - - + +
+
+
+

Time Picker with Dialog.

+

AM/PM Time format.

+
+
- +
-
{{tp.value}}
diff --git a/src/app/time-picker/time-picker.sample.ts b/src/app/time-picker/time-picker.sample.ts index 648481b097e..07aecdad342 100644 --- a/src/app/time-picker/time-picker.sample.ts +++ b/src/app/time-picker/time-picker.sample.ts @@ -9,20 +9,14 @@ import { InteractionMode, IgxTimePickerComponent } from 'igniteui-angular'; export class TimePickerSampleComponent { max = "19:00"; min = "09:00"; + itemsDelta = { hours: 1, minutes: 5 }; - format="h:mm tt"; + format="hh:mm tt"; isSpinLoop = true; - isVertical = false; mode = InteractionMode.dropdown; date = new Date(2018, 10, 27, 17, 45, 0, 0); - date1 = new Date(); @ViewChild('tp', { read: IgxTimePickerComponent }) public tp: IgxTimePickerComponent; - - @ViewChild('tp1', { read: IgxTimePickerComponent }) - public tp1: IgxTimePickerComponent; - - } From 6f1b64468ae08b13476a9c37a88de60583e10326 Mon Sep 17 00:00:00 2001 From: SAndreeva Date: Fri, 30 Nov 2018 19:17:37 +0200 Subject: [PATCH 08/34] feat(time picker): fix broken sample #2337 --- src/app/time-picker/time-picker.sample.html | 2 +- src/app/time-picker/time-picker.sample.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/time-picker/time-picker.sample.html b/src/app/time-picker/time-picker.sample.html index be939e92b56..4259407cd6e 100644 --- a/src/app/time-picker/time-picker.sample.html +++ b/src/app/time-picker/time-picker.sample.html @@ -5,7 +5,7 @@

Time Picker with Dropdown.

-
{{date.toLocaleString()}}
+
{{showDate()}}
Date: Sat, 1 Dec 2018 22:45:57 +0200 Subject: [PATCH 09/34] feat(time picker): fix test failures and styles #2337 --- .../directives/mask/mask.directive.spec.ts | 147 +++++++++------- .../src/lib/time-picker/time-picker.common.ts | 1 + .../time-picker/time-picker.component.html | 9 +- .../time-picker/time-picker.component.scss | 26 +++ .../lib/time-picker/time-picker.component.ts | 162 +++++++++--------- .../lib/time-picker/time-picker.directives.ts | 20 ++- src/app/time-picker/time-picker.sample.html | 10 +- src/app/time-picker/time-picker.sample.ts | 1 + 8 files changed, 225 insertions(+), 151 deletions(-) create mode 100644 projects/igniteui-angular/src/lib/time-picker/time-picker.component.scss diff --git a/projects/igniteui-angular/src/lib/directives/mask/mask.directive.spec.ts b/projects/igniteui-angular/src/lib/directives/mask/mask.directive.spec.ts index 32958009981..c56e2de17fb 100644 --- a/projects/igniteui-angular/src/lib/directives/mask/mask.directive.spec.ts +++ b/projects/igniteui-angular/src/lib/directives/mask/mask.directive.spec.ts @@ -1,4 +1,4 @@ -import { Component, Input, ViewChild } from '@angular/core'; +import { Component, Input, ViewChild, OnInit, ElementRef } from '@angular/core'; import { async, fakeAsync, @@ -41,21 +41,21 @@ describe('igxMask', () => { const fixture = TestBed.createComponent(DefMaskComponent); fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input')); + const input = fixture.componentInstance.input; expect(input.nativeElement.value).toEqual('__________'); - input.triggerEventHandler('click', {}); + input.nativeElement.dispatchEvent(new Event('click')); tick(); input.nativeElement.value = '@#$YUA123'; input.nativeElement.dispatchEvent(new Event('input')); tick(); - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); - fixture.detectChanges(); + expect(input.nativeElement.value).toEqual('@#$YUA123_'); })); @@ -63,10 +63,11 @@ describe('igxMask', () => { const fixture = TestBed.createComponent(DigitSpaceMaskComponent); fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input')); + const input = fixture.componentInstance.input; - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); + fixture.detectChanges(); expect(input.nativeElement.value).toEqual('555 55'); @@ -76,10 +77,11 @@ describe('igxMask', () => { const fixture = TestBed.createComponent(DigitPlusMinusMaskComponent); fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input')); + const input = fixture.componentInstance.input; - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); + fixture.detectChanges(); expect(input.nativeElement.value).toEqual('+359-884 19 08 54'); })); @@ -88,9 +90,9 @@ describe('igxMask', () => { const fixture = TestBed.createComponent(LetterSpaceMaskComponent); fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input')); + const input = fixture.componentInstance.input; - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); expect(input.nativeElement.value).toEqual('AB _CD E'); @@ -100,9 +102,9 @@ describe('igxMask', () => { const fixture = TestBed.createComponent(AlphanumSpaceMaskComponent); fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input')); + const input = fixture.componentInstance.input; - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); expect(input.nativeElement.value).toEqual('7c_ 8u'); @@ -112,9 +114,9 @@ describe('igxMask', () => { const fixture = TestBed.createComponent(AnyCharMaskComponent); fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input')); + const input = fixture.componentInstance.input; - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); expect(input.nativeElement.value).toEqual('_=%. p]'); @@ -125,58 +127,56 @@ describe('igxMask', () => { fixture.detectChanges(); const comp = fixture.componentInstance; - const input = fixture.debugElement.query(By.css('input')); + const input = comp.input; - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('input')); tick(); fixture.detectChanges(); expect(input.nativeElement.value).toEqual('(123) 4567-890'); expect(comp.value).toEqual('1234567890'); - input.nativeElement.value = '7777'; - input.nativeElement.dispatchEvent(new Event('input')); - tick(); + comp.value = '7777'; + fixture.detectChanges(); - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('input')); tick(); - fixture.detectChanges(); + expect(input.nativeElement.value).toEqual('(777) 7___-___'); expect(comp.value).toEqual('7777'); - })); it('Enter incorrect value with a preset mask', fakeAsync(() => { const fixture = TestBed.createComponent(MaskComponent); fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input[type=text]')); + const input = fixture.componentInstance.input; - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); input.nativeElement.value = 'abc4569d12'; input.nativeElement.dispatchEvent(new Event('input')); tick(); - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); - fixture.detectChanges(); + expect(input.nativeElement.value).toEqual('(___) 4569-_12'); - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); input.nativeElement.value = '1111111111111111111'; input.nativeElement.dispatchEvent(new Event('input')); tick(); - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); - fixture.detectChanges(); + expect(input.nativeElement.value).toEqual('(111) 1111-111'); })); @@ -184,29 +184,28 @@ describe('igxMask', () => { const fixture = TestBed.createComponent(IncludeLiteralsComponent); fixture.detectChanges(); - const comp = fixture.componentInstance; - const inputs = fixture.debugElement.queryAll(By.css('input')); + const input = fixture.componentInstance.input; - inputs[0].triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); - expect(inputs[0].nativeElement.value).toEqual('(555) 55__-___'); + expect(input.nativeElement.value).toEqual('(555) 55__-___'); })); it('Correct event firing', fakeAsync(() => { const fixture = TestBed.createComponent(EventFiringComponent); fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input[type=text]')); + const input = fixture.componentInstance.input; - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); input.nativeElement.value = '123'; input.nativeElement.dispatchEvent(new Event('input')); tick(); - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); fixture.detectChanges(); @@ -220,13 +219,12 @@ describe('igxMask', () => { fixture.detectChanges(); const comp = fixture.componentInstance; - const input = fixture.debugElement.query(By.css('input')); + const input = comp.input; expect(input.nativeElement.value).toEqual('3456'); - input.triggerEventHandler('focus', null); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); - fixture.detectChanges(); expect(input.nativeElement.value).toEqual('3456****'); @@ -236,7 +234,7 @@ describe('igxMask', () => { input.nativeElement.dispatchEvent(new Event('input')); tick(); - input.triggerEventHandler('focus', null); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); expect(input.nativeElement.value).toEqual('A*******'); @@ -246,7 +244,7 @@ describe('igxMask', () => { const fixture = TestBed.createComponent(MaskComponent); fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input')); + const input = fixture.componentInstance.input; input.nativeElement.focus(); tick(); @@ -277,7 +275,7 @@ describe('igxMask', () => { const fixture = TestBed.createComponent(MaskComponent); fixture.detectChanges(); - const input = fixture.debugElement.query(By.css('input')); + const input = fixture.componentInstance.input; input.nativeElement.focus(); tick(); @@ -306,83 +304,110 @@ describe('igxMask', () => { input.nativeElement.dispatchEvent(new Event('input')); tick(); - input.triggerEventHandler('focus', {}); + input.nativeElement.dispatchEvent(new Event('focus')); tick(); - fixture.detectChanges(); + expect(input.nativeElement.value).toEqual('(666) 6___-___'); })); }); @Component({ template: ` - + ` }) class DefMaskComponent { public mask; public value; + + @ViewChild('input') + public input: ElementRef; } @Component({ template: ` - + ` }) class MaskComponent { - public mask = '(000) 0000-000'; - public value = '1234567890'; + mask = '(000) 0000-000'; + value = '1234567890'; + + @ViewChild('input') + public input: ElementRef; } @Component({ template: ` - + - + ` }) class IncludeLiteralsComponent { public mask = '(000) 0000-000'; @Input() public value = '55555'; + + @ViewChild('input') + public input: ElementRef; + + @ViewChild('input1') + public input1: ElementRef; } @Component({ template: ` - + ` }) class DigitSpaceMaskComponent { public mask = '999999'; public value = '555 555'; + + @ViewChild('input') + public input: ElementRef; } @Component({ template: ` - + ` }) class DigitPlusMinusMaskComponent { public mask = '####-### ## ## ##'; public value = '+359884190854'; + + @ViewChild('input') + public input: ElementRef; } @Component({ template: ` - + ` }) class LetterSpaceMaskComponent { public mask = 'LL??LL??'; public value = 'AB 2CD E'; + + @ViewChild('input') + public input: ElementRef; } @Component({ template: ` - + ` }) class AlphanumSpaceMaskComponent { public mask = 'AAAaaa'; public value = '7c 8u'; + + @ViewChild('input') + public input: ElementRef; } @Component({ template: ` - + ` }) class AnyCharMaskComponent { public mask = '&&&.CCC'; public value = ' =% p]'; + + @ViewChild('input') + public input: ElementRef; } @Component({ template: ` - ` }) class EventFiringComponent { @@ -391,6 +416,9 @@ class EventFiringComponent { raw: string; formatted: string; + @ViewChild('input') + public input: ElementRef; + handleValueChange(event) { this.raw = event.rawValue; this.formatted = event.formattedValue; @@ -398,9 +426,12 @@ class EventFiringComponent { } @Component({ template: ` - + ` }) class OneWayBindComponent { myMask = 'AAAAAAAA'; value = 3456; + + @ViewChild('input') + public input: ElementRef; } diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.common.ts b/projects/igniteui-angular/src/lib/time-picker/time-picker.common.ts index c7a02bf28db..717e1a8b63c 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.common.ts +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.common.ts @@ -29,6 +29,7 @@ export interface IgxTimePickerBase { scrollAmPmIntoView(item: string): void; hideDropDownOverlay(): void; parseMask(preserveAmPm?: boolean): string; + collapsed: boolean; } export enum InteractionMode { diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html index ac9e64bcda2..95d8871af0e 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html @@ -33,9 +33,9 @@ - -
+
+
+

Mask Using Pipes

+
+ + + + model value: {{value}} + +
+
+
diff --git a/src/app/mask/mask.sample.ts b/src/app/mask/mask.sample.ts index 2a818815e3a..226820198fd 100644 --- a/src/app/mask/mask.sample.ts +++ b/src/app/mask/mask.sample.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, Pipe, PipeTransform } from '@angular/core'; interface IPerson { name: string; @@ -15,6 +15,12 @@ interface IPerson { export class MaskSampleComponent { person: IPerson; + value = '1255'; + mask = '##.##'; + placeholder = '-##.## %'; + displayFormat = new DisplayFormatPipe(); + inputFormat = new InputFormatPipe(); + constructor() { this.person = { birthday: null, @@ -52,3 +58,44 @@ export class MaskSampleComponent { } } +@Pipe({ name: 'displayFormat' }) +export class DisplayFormatPipe implements PipeTransform { + transform(value: any): string { + let val = value; + + if (val === '__.__') { + val = ''; + } + + if (val && val.indexOf('%') === -1) { + val += ' %'; + } + + if (val && val.indexOf('-') === -1) { + val = val.substring(0, 0) + '-' + val.substring(0); + } + + return val; + } +} + +@Pipe({ name: 'inputFormat' }) +export class InputFormatPipe implements PipeTransform { + transform(value: any): string { + let val = value; + + if (!val) { + val = '__.__'; + } + + if (val.indexOf(' %') !== -1) { + val = val.replace(new RegExp(' %', 'g'), ''); + } + + if (val.indexOf('-') !== -1) { + val = val.replace(new RegExp('-', 'g'), ''); + } + + return val; + } +} From 093761cb61df0e179b7dcd89fedfe0bd325e06fc Mon Sep 17 00:00:00 2001 From: SAndreeva Date: Thu, 13 Dec 2018 10:21:03 +0200 Subject: [PATCH 27/34] chore(*): mask demo enhancement #2337 --- src/app/mask/mask.sample.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/mask/mask.sample.ts b/src/app/mask/mask.sample.ts index 226820198fd..26c2d467a0e 100644 --- a/src/app/mask/mask.sample.ts +++ b/src/app/mask/mask.sample.ts @@ -67,6 +67,10 @@ export class DisplayFormatPipe implements PipeTransform { val = ''; } + if (val && val.indexOf('_') !== -1) { + val = val.replace(new RegExp('_', 'g'), '0'); + } + if (val && val.indexOf('%') === -1) { val += ' %'; } From 7de7f879cfea666faaccc2f7d391980370ca08b8 Mon Sep 17 00:00:00 2001 From: SAndreeva Date: Tue, 18 Dec 2018 11:48:20 +0200 Subject: [PATCH 28/34] chore(*): address review comments #2337 --- CHANGELOG.md | 2 +- .../src/lib/time-picker/README.md | 6 +- .../time-picker/time-picker.component.spec.ts | 2 +- .../lib/time-picker/time-picker.component.ts | 112 ++++++++++-------- 4 files changed, 66 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72f9e9a400d..c63c6365879 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes for each version of this project will be documented in this ## 7.1.1 ### Features -- `IgxTimePickerComponent`: in addition to the current dialog interaction mode, the user now select or edit a time value, using an editable masked input with a dropdown. +- `IgxTimePickerComponent`: in addition to the current dialog interaction mode, now the user can select or edit a time value, using an editable masked input with a dropdown. ## 7.1.0 ### Features diff --git a/projects/igniteui-angular/src/lib/time-picker/README.md b/projects/igniteui-angular/src/lib/time-picker/README.md index 5c934c92970..e3320be44ab 100644 --- a/projects/igniteui-angular/src/lib/time-picker/README.md +++ b/projects/igniteui-angular/src/lib/time-picker/README.md @@ -58,7 +58,7 @@ The TimePicker input group could be retemplated.
``` -The TimePicker now supports one more interaction mode - and editable masked input and a dropdown. The user can enter or edit the time value that inside the text input or select a vlaue from a dropdown that is therefore applied on the text input. +The TimePicker supports another interaction mode - an editable masked input and a dropdown. The user can enter or edit the time value inside the text input or select a vlaue from a dropdown, that will be applied on the text input. ```typescript mode = InteractionMode.dropdown; ``` @@ -92,8 +92,8 @@ List of time-flags: "mm": minutes field with leading zero "tt": 2 characters of string which represents AM/PM field | | `isSpinLoop` | boolean | Determines the spin behavior. By default `isSpinLoop` is set to true. | -| `mode` | InteractionMode | Determines the interaction mode - a dialog picker or dropdown with editable masked input. Deafult is dialog picker.| -| `promptChar` | string | Sets the character representing a fillable spot in the editable masked input. Only applicable for dropdown mode. +| `mode` | InteractionMode | Determines the interaction mode - a dialog picker or a dropdown with editable masked input. Default is dialog picker.| +| `promptChar` | string | Sets the character used to prompt the user for input. The default is a dash. Only applicable for dropdown mode. ### Outputs | Name | Description | diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.spec.ts b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.spec.ts index 3d9fe71e9d5..41943ced010 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.spec.ts +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.spec.ts @@ -1238,7 +1238,7 @@ describe('IgxTimePicker', () => { const dropDown = dom.query(By.css('.igx-time-picker--dropdown')); // should open dropdown on alt + arrow down - input.nativeElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'down', altKey: true })); + input.nativeElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', altKey: true })); tick(); fixture.detectChanges(); diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts index 7b7169944da..0d8d71a0b0d 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts @@ -44,6 +44,7 @@ import { IgxOverlayOutletDirective } from '../directives/toggle/toggle.directive import { TimeDisplayFormatPipe, TimeInputFormatPipe } from './time-picker.pipes'; import { ITimePickerResourceStrings, TimePickerResourceStringsEN } from '../core/i18n/time-picker-resources'; import { CurrentResourceStrings } from '../core/i18n/resources'; +import { KEYS } from '../core/utils'; let NEXT_ID = 0; @@ -290,7 +291,7 @@ export class IgxTimePickerComponent implements } /** - * Sets the character representing a fillable spot in the editable input mask. + * Sets the character used to prompt the user for input. * Default value is "'-'". * ```html * @@ -1078,6 +1079,53 @@ export class IgxTimePickerComponent implements } } + private _spinHours(currentVal: Date, minVal: Date, maxVal: Date, hDelta: number, sign: number): Date { + let value = new Date(); + const oldVal = new Date(currentVal); + + currentVal.setMinutes(sign * hDelta); + if (currentVal.getDate() !== oldVal.getDate() && this.isSpinLoop) { + currentVal.setDate(oldVal.getDate()); + } + + let minutes = currentVal.getMinutes(); + if (currentVal.getTime() > maxVal.getTime()) { + if (this.isSpinLoop) { + minutes = minutes < minVal.getMinutes() ? 60 + minutes : minutes; + minVal.setMinutes(sign * minutes); + value = minVal; + } else { + value = oldVal; + } + } else if (currentVal.getTime() < minVal.getTime()) { + if (this.isSpinLoop) { + minutes = minutes <= maxVal.getMinutes() ? minutes : minutes - 60; + maxVal.setMinutes(minutes); + value = maxVal; + } else { + value = oldVal; + } + } else { + value = currentVal; + } + + return value; + } + + private _spinMinutes(currentVal: Date, mDelta: number, sign: number) { + let value = new Date(); + let minutes = currentVal.getMinutes() + (sign * mDelta); + + if (minutes < 0 || minutes >= 60) { + minutes = this.isSpinLoop ? minutes - (sign * 60) : currentVal.getMinutes(); + } + + currentVal.setMinutes(minutes); + value = currentVal; + + return value; + } + private _onDropDownClosed(): void { const oldValue = this.value; const newVal = this._convertMinMaxValue(this.displayValue); @@ -1370,16 +1418,14 @@ export class IgxTimePickerComponent implements * @hidden */ public onKeydown(event): void { - const alt = event.altKey; - - switch (event.key.toLowerCase()) { - case 'arrowup': - case 'up': + switch (event.key) { + case KEYS.UP_ARROW: + case KEYS.UP_ARROW_IE: this.spinOnEdit(event); break; - case 'arrowdown': - case 'down': - if (alt) { + case KEYS.DOWN_ARROW: + case KEYS.DOWN_ARROW_IE: + if (event.altKey) { this.openDialog(); } else { this.spinOnEdit(event); @@ -1421,6 +1467,7 @@ export class IgxTimePickerComponent implements if (!this.value || !val || val === this.parseMask(false)) { this.isNotEmpty = false; this.value.setHours(0, 0); + this.displayValue = val; newVal = new Date(this.value); if (oldVal.getTime() !== newVal.getTime()) { @@ -1475,23 +1522,21 @@ export class IgxTimePickerComponent implements let sign: number; let displayVal: string; + const currentVal = new Date(this.value); const min = this.minValue ? this._convertMinMaxValue(this.minValue) : this._convertMinMaxValue('00:00'); const max = this.maxValue ? this._convertMinMaxValue(this.maxValue) : this._convertMinMaxValue('24:00'); const cursor = this._getCursorPosition(); if (event.key) { - const key = event.key.toLowerCase(); - sign = key === 'arrowdown' || key === 'down' ? -1 : 1; + const key = event.key; + sign = key === KEYS.DOWN_ARROW || key === KEYS.DOWN_ARROW_IE ? -1 : 1; } if (event.deltaY) { sign = event.deltaY < 0 ? 1 : -1; } - const oldVal = new Date(this.value); - const currentVal = new Date(this.value); - if (!this.displayValue) { this.value = min; displayVal = this._formatTime(this.value, this.format); @@ -1501,46 +1546,11 @@ export class IgxTimePickerComponent implements const sections = this.displayValue.split(/[\s:]+/); if (HOURS_POS.indexOf(cursor) !== -1) { - - currentVal.setMinutes(sign * hDelta); - - if (currentVal.getDate() !== oldVal.getDate() && this.isSpinLoop) { - currentVal.setDate(oldVal.getDate()); - } - - if (currentVal.getTime() >= max.getTime()) { - if (this.isSpinLoop) { - const minutes = currentVal.getMinutes() < min.getMinutes() ? - 60 + currentVal.getMinutes() : currentVal.getMinutes(); - min.setMinutes(sign * minutes); - this.value = min; - } else { - this.value = oldVal; - } - } else if (currentVal.getTime() < min.getTime()) { - if (this.isSpinLoop) { - const minutes = currentVal.getMinutes() <= max.getMinutes() ? - currentVal.getMinutes() : currentVal.getMinutes() - 60; - max.setMinutes(minutes); - this.value = max; - } else { - this.value = oldVal; - } - } else { - this.value = currentVal; - } + this.value = this._spinHours(currentVal, min, max, hDelta, sign); } if (MINUTES_POS.indexOf(cursor) !== -1) { - let minutes = this.value.getMinutes() + (sign * mDelta); - if (minutes >= 60) { - minutes = this.isSpinLoop ? minutes - 60 : currentVal.getMinutes(); - } else if (minutes < 0 ) { - minutes = this.isSpinLoop ? minutes + 60 : currentVal.getMinutes(); - } - - currentVal.setMinutes(minutes); - this.value = currentVal; + this.value = this._spinMinutes(currentVal, mDelta, sign); } if (AMPM_POS.indexOf(cursor) !== -1 && this.format.indexOf('tt') !== -1) { From b769f3fb4166761040c3b3e7cb81ae32dd2037cb Mon Sep 17 00:00:00 2001 From: SAndreeva Date: Tue, 18 Dec 2018 14:42:37 +0200 Subject: [PATCH 29/34] chore(*): more refinements #2337 --- .../lib/time-picker/time-picker.component.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts index 0d8d71a0b0d..e714fd19249 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts @@ -1080,7 +1080,6 @@ export class IgxTimePickerComponent implements } private _spinHours(currentVal: Date, minVal: Date, maxVal: Date, hDelta: number, sign: number): Date { - let value = new Date(); const oldVal = new Date(currentVal); currentVal.setMinutes(sign * hDelta); @@ -1093,27 +1092,24 @@ export class IgxTimePickerComponent implements if (this.isSpinLoop) { minutes = minutes < minVal.getMinutes() ? 60 + minutes : minutes; minVal.setMinutes(sign * minutes); - value = minVal; + return minVal; } else { - value = oldVal; + return oldVal; } } else if (currentVal.getTime() < minVal.getTime()) { if (this.isSpinLoop) { minutes = minutes <= maxVal.getMinutes() ? minutes : minutes - 60; maxVal.setMinutes(minutes); - value = maxVal; + return maxVal; } else { - value = oldVal; + return oldVal; } } else { - value = currentVal; + return currentVal; } - - return value; } private _spinMinutes(currentVal: Date, mDelta: number, sign: number) { - let value = new Date(); let minutes = currentVal.getMinutes() + (sign * mDelta); if (minutes < 0 || minutes >= 60) { @@ -1121,9 +1117,7 @@ export class IgxTimePickerComponent implements } currentVal.setMinutes(minutes); - value = currentVal; - - return value; + return currentVal; } private _onDropDownClosed(): void { From 127249925a58b08e51ab99226cf6c41ea912035a Mon Sep 17 00:00:00 2001 From: SAndreeva Date: Thu, 20 Dec 2018 16:12:24 +0200 Subject: [PATCH 30/34] feat(time picker): address comments form review #2337 --- CHANGELOG.md | 4 +- .../src/lib/time-picker/time-picker.common.ts | 11 +- .../time-picker/time-picker.component.spec.ts | 17 +- .../lib/time-picker/time-picker.component.ts | 391 +++++++++--------- .../lib/time-picker/time-picker.directives.ts | 4 +- .../src/lib/time-picker/time-picker.pipes.ts | 14 +- projects/igniteui-angular/src/public_api.ts | 1 - src/app/time-picker/time-picker.sample.ts | 4 +- 8 files changed, 221 insertions(+), 225 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c63c6365879..6850deb27d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes for each version of this project will be documented in this file. -## 7.1.1 +## 7.1.2 ### Features - `IgxTimePickerComponent`: in addition to the current dialog interaction mode, now the user can select or edit a time value, using an editable masked input with a dropdown. @@ -26,8 +26,6 @@ All notable changes for each version of this project will be documented in this - `focusedValuePipe` input property is provided that allows developers to additionally transform the value on focus; - `IgxTreeGrid`: - Batch editing - an injectable transaction provider accumulates pending changes, which are not directly applied to the grid's data source. Those can later be inspected, manipulated and submitted at once. Changes are collected for individual cells or rows, depending on editing mode, and accumulated per data row/record. - - You can now export the tree grid both to CSV and Excel. - - The hierarchy and the records' expanded states would be reflected in the exported Excel worksheet. - You can now export the tree grid both to CSV and Excel. The hierarchy and the records' expanded states would be reflected in the exported Excel worksheet. - Summaries feature is now supported in the tree grid. Summary results are calculated and displayed for the root level and each child level by default. diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.common.ts b/projects/igniteui-angular/src/lib/time-picker/time-picker.common.ts index c78001f8bcd..b1951891cd1 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.common.ts +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.common.ts @@ -12,11 +12,11 @@ export interface IgxTimePickerBase { selectedHour: string; selectedMinute: string; selectedAmPm: string; - mode: InteractionMode; format: string; promptChar: string; cleared: boolean; collapsed: boolean; + isModal: boolean; nextHour(); prevHour(); nextMinute(); @@ -31,12 +31,3 @@ export interface IgxTimePickerBase { hideOverlay(): void; parseMask(preserveAmPm?: boolean): string; } - -/** - * Defines the posible values of the igxTimePicker's time slelection mode. - */ -export enum InteractionMode { - dialog, - dropdown -} - diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.spec.ts b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.spec.ts index 41943ced010..5193143ab4c 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.spec.ts +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.spec.ts @@ -4,8 +4,7 @@ import { FormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { IgxInputDirective } from '../directives/input/input.directive'; -import { IgxTimePickerComponent, IgxTimePickerModule } from './time-picker.component'; -import { InteractionMode } from './time-picker.common'; +import { IgxTimePickerComponent, IgxTimePickerModule, TimePickerInteractionMode } from './time-picker.component'; import { UIInteractions } from '../test-utils/ui-interactions.spec'; import { IgxInputGroupModule } from '../input-group'; import { configureTestSuite } from '../test-utils/configure-suite'; @@ -1111,7 +1110,7 @@ describe('IgxTimePicker', () => { expect(input.nativeElement.placeholder).toBe('hh:mm tt'); })); - it('should break on spinloop witn min and max value on arrow down', (() => { + it('should break on spinloop with min and max value on arrow down', (() => { fixture.detectChanges(); const customValue = '07:07 AM'; @@ -1146,7 +1145,7 @@ describe('IgxTimePicker', () => { expect(input.nativeElement.value).toBe(customValue, 'SpinLoop did not stop on minutes'); })); - it('should break on spinloop witn min and max value on arrow up', (() => { + it('should break on spinloop with min and max value on arrow up', (() => { fixture.detectChanges(); const customValue = '08:07 AM'; @@ -1369,7 +1368,7 @@ describe('IgxTimePicker', () => { expect(timePicker.onValidationFailed.emit).toHaveBeenCalled(); - // ininitial value should not be changed + // initial value should not be changed expect(fixture.componentInstance.date).toEqual(initVal); })); @@ -1384,7 +1383,7 @@ describe('IgxTimePicker', () => { expect(dom.query(By.css('.igx-time-picker--dropdown'))).toBeDefined(); - fixture.componentInstance.timePicker.mode = InteractionMode.dialog; + fixture.componentInstance.timePicker.mode = TimePickerInteractionMode.dialog; fixture.detectChanges(); UIInteractions.clickElement(iconTime); @@ -1540,7 +1539,7 @@ export class IgxTimePickerDropDownComponent { format = 'hh:mm tt'; isSpinLoop = true; isVertical = true; - mode = InteractionMode; + mode = TimePickerInteractionMode; date = new Date(2018, 10, 27, 17, 45, 0, 0); @ViewChild(IgxTimePickerComponent) public timePicker: IgxTimePickerComponent; @@ -1556,7 +1555,7 @@ export class IgxTimePickerDropDownComponent { }) export class IgxTimePickerDropDownSingleHourComponent { customDate = new Date(2018, 10, 27, 4, 5); - mode = InteractionMode.dropdown; + mode = TimePickerInteractionMode.dropdown; @ViewChild(IgxTimePickerComponent) public timePicker: IgxTimePickerComponent; } @@ -1569,7 +1568,7 @@ export class IgxTimePickerDropDownSingleHourComponent { ` }) export class IgxTimePickerDropDownNoValueComponent { - mode = InteractionMode.dropdown; + mode = TimePickerInteractionMode.dropdown; @ViewChild(IgxTimePickerComponent) public timePicker: IgxTimePickerComponent; } diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts index e714fd19249..2aca611ffcb 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts @@ -32,7 +32,7 @@ import { } from './time-picker.directives'; import { Subject } from 'rxjs'; import { EditorProvider } from '../core/edit-provider'; -import { IgxTimePickerBase, IGX_TIME_PICKER_COMPONENT, InteractionMode } from './time-picker.common'; +import { IgxTimePickerBase, IGX_TIME_PICKER_COMPONENT } from './time-picker.common'; import { IgxOverlayService } from '../services/overlay/overlay'; import { NoOpScrollStrategy } from '../services/overlay/scroll'; import { ConnectedPositioningStrategy } from '../services/overlay/position'; @@ -52,6 +52,14 @@ const HOURS_POS = [0, 1, 2]; const MINUTES_POS = [3, 4, 5]; const AMPM_POS = [6, 7, 8]; +/** + * Defines the posible values of the igxTimePicker's time selection mode. + */ +export enum TimePickerInteractionMode { + dialog, + dropdown +} + @Injectable() export class TimePickerHammerConfig extends HammerGestureConfig { public overrides = { @@ -96,12 +104,6 @@ export class IgxTimePickerComponent implements OnInit, OnDestroy { - private _value: Date; - private _resourceStrings = CurrentResourceStrings.TimePickerResStrings; - private _okButtonLabel = null; - private _cancelButtonLabel = null; - private _format: string; - /** * An @Input property that sets the value of the `id` attribute. * ```html @@ -123,15 +125,16 @@ export class IgxTimePickerComponent implements @Input() set value(value: Date) { if (this._isValueValid(value)) { - const args: IgxTimePickerValueChangedEventArgs = { - oldValue: this._value, - newValue: value - }; - this.onValueChanged.emit(args); + const oldVal = this._value; this._value = value; this._onChangeCallback(value); + const args: IgxTimePickerValueChangedEventArgs = { + oldValue: oldVal, + newValue: value + }; + this.onValueChanged.emit(args); } else { const args: IgxTimePickerValidationFailedEventArgs = { timePicker: this, @@ -287,7 +290,12 @@ export class IgxTimePickerComponent implements */ @Input() get format() { - return this._format ? this._format : 'hh:mm tt'; + return this._format || 'hh:mm tt'; + } + + set format(formatValue: string) { + this._format = formatValue; + this.mask = this._format.indexOf('tt') !== -1 ? '00:00 LL' : '00:00'; } /** @@ -306,19 +314,14 @@ export class IgxTimePickerComponent implements * a dialog picker or dropdown with editable masked input. * Deafult is dialog picker. *```html - *public mode = InteractionMode.dropdown; + *public mode = TimePickerInteractionMode.dropdown; * //.. * *``` * @memberof IgxTimePickerComponent */ @Input() - public mode = InteractionMode.dialog; - - set format(formatValue: string) { - this._format = formatValue; - this.mask = this._format.indexOf('tt') !== -1 ? '00:00 LL' : '00:00'; - } + public mode = TimePickerInteractionMode.dialog; /** *@hidden @@ -490,6 +493,25 @@ export class IgxTimePickerComponent implements */ public inputFormat = new TimeInputFormatPipe(this); + /** + * @hidden + */ + public selectedHour: string; + /** + * @hidden + */ + public selectedMinute: string; + /** + * @hidden + */ + public selectedAmPm: string; + + private _value: Date; + private _resourceStrings = CurrentResourceStrings.TimePickerResStrings; + private _okButtonLabel = null; + private _cancelButtonLabel = null; + private _format: string; + private _isHourListLoop = this.isSpinLoop; private _isMinuteListLoop = this.isSpinLoop; @@ -504,23 +526,13 @@ export class IgxTimePickerComponent implements private _dropDownOverlaySettings: OverlaySettings; private _dialogOverlaySettings: OverlaySettings; - /** - * @hidden - */ - public selectedHour: string; - /** - * @hidden - */ - public selectedMinute: string; - /** - * @hidden - */ - public selectedAmPm: string; - private _prevSelectedHour: string; private _prevSelectedMinute: string; private _prevSelectedAmPm: string; + private _onTouchedCallback: () => void = () => { }; + private _onChangeCallback: (_: Date) => void = () => { }; + /** * Returns the current time formatted as string using the `format` option. * If there is no set time the return is an empty string. @@ -565,7 +577,7 @@ export class IgxTimePickerComponent implements * @hidden */ get isModal(): boolean { - return this.mode === InteractionMode.dialog; + return this.mode === TimePickerInteractionMode.dialog; } /** @@ -608,78 +620,30 @@ export class IgxTimePickerComponent implements } /** - * opens the dialog. - * ```html - * - * ``` + * Gets the input group template. * ```typescript - * @ViewChild('tp', { read: IgxTimePickerComponent }) tp: IgxTimePickerComponent; - * tp.openDialog(); + * let template = this.template(); * ``` + * @memberof IgxTimePickerComponent */ - public openDialog(timePicker: IgxTimePickerComponent = this): void { - if (this.mode === InteractionMode.dialog) { - this.collapsed = false; - if (this.outlet) { - this._dialogOverlaySettings.outlet = this.outlet; - } - this._overlayId = this.overlayService.show(this.container, this._dialogOverlaySettings); - } - - if (this.mode === InteractionMode.dropdown && this.collapsed) { - this.collapsed = false; - if (this.outlet) { - this._dropDownOverlaySettings.outlet = this.outlet; - } - this._dropDownOverlaySettings.positionStrategy.settings.target = this.group.element.nativeElement; - this._overlayId = this.overlayService.show(this.container, this._dropDownOverlaySettings); - } - - if (this.value) { - const foramttedTime = this._formatTime(this.value, this.format); - const sections = foramttedTime.split(/[\s:]+/); - - this.selectedHour = sections[0]; - this.selectedMinute = sections[1]; - - if (this._ampmItems !== null) { - this.selectedAmPm = sections[2]; - } - } - - if (this.selectedHour === undefined) { - this.selectedHour = `${this._hourItems[3]}`; - } - if (this.selectedMinute === undefined) { - this.selectedMinute = '0'; - } - if (this.selectedAmPm === undefined && this._ampmItems !== null) { - this.selectedAmPm = this._ampmItems[3]; - } - - this._prevSelectedHour = this.selectedHour; - this._prevSelectedMinute = this.selectedMinute; - this._prevSelectedAmPm = this.selectedAmPm; - - this._onTouchedCallback(); - - this._updateHourView(0, 7); - this._updateMinuteView(0, 7); - this._updateAmPmView(0, 7); - - if (this.selectedHour) { - this.scrollHourIntoView(this.selectedHour); - } - if (this.selectedMinute) { - this.scrollMinuteIntoView(this.selectedMinute); - } - if (this.selectedAmPm) { - this.scrollAmPmIntoView(this.selectedAmPm); + get template(): TemplateRef { + if (this.timePickerTemplateDirective) { + return this.timePickerTemplateDirective.template; } + return this.mode === TimePickerInteractionMode.dialog ? this.defaultTimePickerTemplate : this.dropdownInputTemplate; + } - requestAnimationFrame(() => { - this.hourList.nativeElement.focus(); - }); + /** + * Gets the context passed to the input group template. + * @memberof IgxTimePickerComponent + */ + get context() { + return { + value: this.value, + displayTime: this.displayTime, + displayValue: this.displayValue, + openDialog: () => { this.openDialog(); } + }; } constructor(@Inject(IgxOverlayService) private overlayService: IgxOverlayService) { @@ -688,26 +652,26 @@ export class IgxTimePickerComponent implements filter(event => event.id === this._overlayId), takeUntil(this._destroy$)).subscribe(() => { - this.collapsed = true; + this.collapsed = true; - if (this._input) { - this._input.nativeElement.focus(); - } + if (this._input) { + this._input.nativeElement.focus(); + } - if (this.mode === InteractionMode.dropdown) { - this._onDropDownClosed(); - } + if (this.mode === TimePickerInteractionMode.dropdown) { + this._onDropDownClosed(); + } - this.onClose.emit(this); - }); + this.onClose.emit(this); + }); this.overlayService.onOpened.pipe( filter(event => event.id === this._overlayId), takeUntil(this._destroy$)).subscribe(() => { - this.collapsed = false; - this.onOpen.emit(this); - }); + this.collapsed = false; + this.onOpen.emit(this); + }); } /** @@ -750,37 +714,13 @@ export class IgxTimePickerComponent implements /** * @hidden */ - public writeValue(value: Date) { - // use this falg to make sure that min/maxValue are checked (in _convertMinMaxValue() method) - // against the real value when initializing the component and value is bound via ngModel - this._dateFromModel = value; - - this.value = value; - - if (this.mode === InteractionMode.dropdown) { - this.displayValue = this._formatTime(this.value, this.format); - } - } - - /** - * @hidden - */ - public registerOnChange(fn: (_: Date) => void) { this._onChangeCallback = fn; } - - /** - * @hidden - */ - public registerOnTouched(fn: () => void) { this._onTouchedCallback = fn; } - - /** @hidden */ - getEditElement() { - return this._input.nativeElement; + @HostListener('keydown.spacebar', ['$event']) + @HostListener('keydown.space', ['$event']) + public onKeydownSpace(event) { + this.openDialog(); + event.preventDefault(); } - private _onTouchedCallback: () => void = () => { }; - - private _onChangeCallback: (_: Date) => void = () => { }; - private _scrollItemIntoView(item: string, items: any[], selectedItem: string, isListLoop: boolean, viewType: string): any { let itemIntoView; if (items) { @@ -1058,11 +998,7 @@ export class IgxTimePickerComponent implements const hour = parseInt(sections[0].replace(re, ''), 10); const minutes = parseInt(sections[1].replace(re, ''), 10); - if (this.validHourEntries.indexOf(hour) !== -1 && this.validMinuteEntries.indexOf(minutes) !== -1) { - return true; - } - - return false; + return this.validHourEntries.indexOf(hour) !== -1 && this.validMinuteEntries.indexOf(minutes) !== -1; } private _getCursorPosition(): number { @@ -1074,7 +1010,7 @@ export class IgxTimePickerComponent implements } private _updateEditableInput(): void { - if (this.mode === InteractionMode.dropdown) { + if (this.mode === TimePickerInteractionMode.dropdown) { this.displayValue = this._formatTime(this._getSelectedTime(), this.format); } } @@ -1140,6 +1076,113 @@ export class IgxTimePickerComponent implements } } + /** + * @hidden + */ + getEditElement() { + return this._input.nativeElement; + } + + /** + * @hidden + */ + public writeValue(value: Date) { + // use this flag to make sure that min/maxValue are checked (in _convertMinMaxValue() method) + // against the real value when initializing the component and value is bound via ngModel + this._dateFromModel = value; + + this.value = value; + + if (this.mode === TimePickerInteractionMode.dropdown) { + this.displayValue = this._formatTime(this.value, this.format); + } + } + + /** + * @hidden + */ + public registerOnChange(fn: (_: Date) => void) { this._onChangeCallback = fn; } + + /** + * @hidden + */ + public registerOnTouched(fn: () => void) { this._onTouchedCallback = fn; } + + /** + * opens the dialog. + * ```html + * + * ``` + * ```typescript + * @ViewChild('tp', { read: IgxTimePickerComponent }) tp: IgxTimePickerComponent; + * tp.openDialog(); + * ``` + */ + public openDialog(timePicker: IgxTimePickerComponent = this): void { + if (this.mode === TimePickerInteractionMode.dialog) { + this.collapsed = false; + if (this.outlet) { + this._dialogOverlaySettings.outlet = this.outlet; + } + this._overlayId = this.overlayService.show(this.container, this._dialogOverlaySettings); + } + + if (this.mode === TimePickerInteractionMode.dropdown && this.collapsed) { + this.collapsed = false; + if (this.outlet) { + this._dropDownOverlaySettings.outlet = this.outlet; + } + this._dropDownOverlaySettings.positionStrategy.settings.target = this.group.element.nativeElement; + this._overlayId = this.overlayService.show(this.container, this._dropDownOverlaySettings); + } + + if (this.value) { + const foramttedTime = this._formatTime(this.value, this.format); + const sections = foramttedTime.split(/[\s:]+/); + + this.selectedHour = sections[0]; + this.selectedMinute = sections[1]; + + if (this._ampmItems !== null) { + this.selectedAmPm = sections[2]; + } + } + + if (this.selectedHour === undefined) { + this.selectedHour = `${this._hourItems[3]}`; + } + if (this.selectedMinute === undefined) { + this.selectedMinute = '0'; + } + if (this.selectedAmPm === undefined && this._ampmItems !== null) { + this.selectedAmPm = this._ampmItems[3]; + } + + this._prevSelectedHour = this.selectedHour; + this._prevSelectedMinute = this.selectedMinute; + this._prevSelectedAmPm = this.selectedAmPm; + + this._onTouchedCallback(); + + this._updateHourView(0, 7); + this._updateMinuteView(0, 7); + this._updateAmPmView(0, 7); + + if (this.selectedHour) { + this.scrollHourIntoView(this.selectedHour); + } + if (this.selectedMinute) { + this.scrollMinuteIntoView(this.selectedMinute); + } + if (this.selectedAmPm) { + this.scrollAmPmIntoView(this.selectedAmPm); + } + + requestAnimationFrame(() => { + this.hourList.nativeElement.focus(); + }); + } + /** * Scrolls a hour item into view. * ```typescript @@ -1316,16 +1359,6 @@ export class IgxTimePickerComponent implements this.selectedAmPm = this._prevSelectedAmPm; } - /** - * @hidden - */ - @HostListener('keydown.spacebar', ['$event']) - @HostListener('keydown.space', ['$event']) - public onKeydownSpace(event) { - this.openDialog(); - event.preventDefault(); - } - /** * Returns an array of the hours currently in view. *```html @@ -1435,8 +1468,7 @@ export class IgxTimePickerComponent implements */ public onInput(event): void { const val = event.target.value; - const oldVal = this.value; - let newVal = this._convertMinMaxValue(val); + const oldVal = new Date(this.value); this.isNotEmpty = val !== this.parseMask(false); @@ -1444,6 +1476,7 @@ export class IgxTimePickerComponent implements // timepicker own value property if it is a valid Date if (val.indexOf(this.promptChar) === -1) { if (this._isEntryValid(val)) { + const newVal = this._convertMinMaxValue(val); if (oldVal.getTime() !== newVal.getTime()) { this.value = newVal; } @@ -1455,16 +1488,14 @@ export class IgxTimePickerComponent implements }; this.onValidationFailed.emit(args); } - } - // handle cases where the user deletes the display value (when pressing backspace or delete) - if (!this.value || !val || val === this.parseMask(false)) { + } else if (!this.value || !val || val === this.parseMask(false)) { this.isNotEmpty = false; + this.value.setHours(0, 0); this.displayValue = val; - newVal = new Date(this.value); - if (oldVal.getTime() !== newVal.getTime()) { + if (oldVal.getTime() !== this.value.getTime()) { const args: IgxTimePickerValueChangedEventArgs = { oldValue: oldVal, newValue: this.value @@ -1486,7 +1517,6 @@ export class IgxTimePickerComponent implements */ public onBlur(event): void { const value = event.target.value; - const oldVal = new Date(this.value); this.isNotEmpty = value !== ''; this.displayValue = value; @@ -1494,7 +1524,7 @@ export class IgxTimePickerComponent implements if (value && value !== this.parseMask()) { if (this._isEntryValid(value)) { const newVal = this._convertMinMaxValue(value); - if (oldVal.getTime() !== newVal.getTime()) { + if (this.value.getTime() !== newVal.getTime()) { this.value = newVal; } } else { @@ -1563,33 +1593,6 @@ export class IgxTimePickerComponent implements this._setCursorPosition(cursor); }); } - - /** - * Gets the input group template. - * ```typescript - * let template = this.template(); - * ``` - * @memberof IgxTimePickerComponent - */ - get template(): TemplateRef { - if (this.timePickerTemplateDirective) { - return this.timePickerTemplateDirective.template; - } - return this.mode === InteractionMode.dialog ? this.defaultTimePickerTemplate : this.dropdownInputTemplate; - } - - /** - * Gets the context passed to the input group template. - * @memberof IgxTimePickerComponent - */ - get context() { - return { - value: this.value, - displayTime: this.displayTime, - displayValue: this.displayValue, - openDialog: () => { this.openDialog(); } - }; - } } /** diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.directives.ts b/projects/igniteui-angular/src/lib/time-picker/time-picker.directives.ts index d54c50958a0..519716df313 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.directives.ts +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.directives.ts @@ -12,7 +12,7 @@ import { Input, TemplateRef } from '@angular/core'; -import { IGX_TIME_PICKER_COMPONENT, IgxTimePickerBase, InteractionMode } from './time-picker.common'; +import { IGX_TIME_PICKER_COMPONENT, IgxTimePickerBase } from './time-picker.common'; /** @hidden */ @Directive({ @@ -155,7 +155,7 @@ export class IgxItemListDirective { public onKeydownEnter(event: KeyboardEvent) { event.preventDefault(); - if (this.timePicker.mode === InteractionMode.dropdown) { + if (!this.timePicker.isModal) { this.timePicker.hideOverlay(); return; } diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.pipes.ts b/projects/igniteui-angular/src/lib/time-picker/time-picker.pipes.ts index b24a5ca3df8..bd19cda0bba 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.pipes.ts +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.pipes.ts @@ -2,11 +2,14 @@ import { Pipe, PipeTransform, Inject} from '@angular/core'; import { IGX_TIME_PICKER_COMPONENT, IgxTimePickerBase } from './time-picker.common'; -/** @hidden */ +/** + * Formats `IgxTimePickerComponent` display value according to the `format` property, + * when the input element loses focus. + **/ @Pipe({ name: 'displayFormat'}) export class TimeDisplayFormatPipe implements PipeTransform { - constructor(@Inject(IGX_TIME_PICKER_COMPONENT) public timePicker: IgxTimePickerBase) { } + constructor(@Inject(IGX_TIME_PICKER_COMPONENT) private timePicker: IgxTimePickerBase) { } transform(value: any): string { @@ -54,11 +57,14 @@ export class TimeDisplayFormatPipe implements PipeTransform { } } -/** @hidden */ +/** + * Formats `IgxTimePickerComponent` display value according to the `format` property, + * when the input element gets focus. + **/ @Pipe({ name: 'inputFormat' }) export class TimeInputFormatPipe implements PipeTransform { - constructor(@Inject(IGX_TIME_PICKER_COMPONENT) public timePicker: IgxTimePickerBase) { } + constructor(@Inject(IGX_TIME_PICKER_COMPONENT) private timePicker: IgxTimePickerBase) { } transform(value: any): string { const prompt = this.timePicker.promptChar; diff --git a/projects/igniteui-angular/src/public_api.ts b/projects/igniteui-angular/src/public_api.ts index 9bad7f0fc79..60911316908 100644 --- a/projects/igniteui-angular/src/public_api.ts +++ b/projects/igniteui-angular/src/public_api.ts @@ -87,7 +87,6 @@ export * from './lib/services/index'; export * from './lib/core/dates'; export * from './lib/core/density'; export { CancelableEventArgs } from './lib/core/utils'; -export { InteractionMode } from './lib/time-picker/time-picker.common'; export { changei18n, getCurrentResourceStrings, IResourceStrings } from './lib/core/i18n/resources'; export { IGridResourceStrings } from './lib/core/i18n/grid-resources'; export { ITimePickerResourceStrings } from './lib/core/i18n/time-picker-resources'; diff --git a/src/app/time-picker/time-picker.sample.ts b/src/app/time-picker/time-picker.sample.ts index 22b1950860b..41ff0eb2f87 100644 --- a/src/app/time-picker/time-picker.sample.ts +++ b/src/app/time-picker/time-picker.sample.ts @@ -1,5 +1,5 @@ import { Component, ViewChild } from '@angular/core'; -import { InteractionMode, IgxTimePickerComponent } from 'igniteui-angular'; +import { TimePickerInteractionMode, IgxTimePickerComponent } from 'igniteui-angular'; @Component({ selector: 'app-time-picker-sample', @@ -14,7 +14,7 @@ export class TimePickerSampleComponent { format="hh:mm tt"; isSpinLoop = true; isVertical = true; - mode = InteractionMode.dropdown; + mode = TimePickerInteractionMode.dropdown; date = new Date(2018, 10, 27, 17, 45, 0, 0); From 7975d864851130d662403007d27afa3d84a5e1a7 Mon Sep 17 00:00:00 2001 From: SAndreeva Date: Fri, 21 Dec 2018 19:36:10 +0200 Subject: [PATCH 31/34] feat(time picker): expose enum again in common #2337 --- .../src/lib/time-picker/time-picker.common.ts | 10 ++++++++- .../time-picker/time-picker.component.html | 6 +++--- .../time-picker/time-picker.component.spec.ts | 3 ++- .../lib/time-picker/time-picker.component.ts | 21 +++++-------------- .../lib/time-picker/time-picker.directives.ts | 4 ++-- projects/igniteui-angular/src/public_api.ts | 1 + src/app/time-picker/time-picker.sample.ts | 2 +- 7 files changed, 23 insertions(+), 24 deletions(-) diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.common.ts b/projects/igniteui-angular/src/lib/time-picker/time-picker.common.ts index b1951891cd1..e6ec19b6ddf 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.common.ts +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.common.ts @@ -16,7 +16,7 @@ export interface IgxTimePickerBase { promptChar: string; cleared: boolean; collapsed: boolean; - isModal: boolean; + mode: TimePickerInteractionMode; nextHour(); prevHour(); nextMinute(); @@ -31,3 +31,11 @@ export interface IgxTimePickerBase { hideOverlay(): void; parseMask(preserveAmPm?: boolean): string; } + +/** + * Defines the posible values of the igxTimePicker's time selection mode. + */ +export enum TimePickerInteractionMode { + dialog, + dropdown +} diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html index 919093e21a7..388b20af257 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.html @@ -35,8 +35,8 @@
-
+ [ngClass]="{'igx-time-picker--dropdown': mode === interactMode.dropdown, 'igx-time-picker--vertical': vertical && mode === interactMode.dialog}"> +
{{ selectedAmPm }}

{{ selectedHour }}:{{ selectedMinute }} @@ -54,7 +54,7 @@

{{ ampm }}

-
+
diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.spec.ts b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.spec.ts index 5193143ab4c..e289573e604 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.spec.ts +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.spec.ts @@ -4,10 +4,11 @@ import { FormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { IgxInputDirective } from '../directives/input/input.directive'; -import { IgxTimePickerComponent, IgxTimePickerModule, TimePickerInteractionMode } from './time-picker.component'; +import { IgxTimePickerComponent, IgxTimePickerModule } from './time-picker.component'; import { UIInteractions } from '../test-utils/ui-interactions.spec'; import { IgxInputGroupModule } from '../input-group'; import { configureTestSuite } from '../test-utils/configure-suite'; +import { TimePickerInteractionMode } from './time-picker.common'; describe('IgxTimePicker', () => { configureTestSuite(); diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts index 2aca611ffcb..15560ae6950 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts @@ -32,7 +32,7 @@ import { } from './time-picker.directives'; import { Subject } from 'rxjs'; import { EditorProvider } from '../core/edit-provider'; -import { IgxTimePickerBase, IGX_TIME_PICKER_COMPONENT } from './time-picker.common'; +import { IgxTimePickerBase, IGX_TIME_PICKER_COMPONENT, TimePickerInteractionMode } from './time-picker.common'; import { IgxOverlayService } from '../services/overlay/overlay'; import { NoOpScrollStrategy } from '../services/overlay/scroll'; import { ConnectedPositioningStrategy } from '../services/overlay/position'; @@ -52,14 +52,6 @@ const HOURS_POS = [0, 1, 2]; const MINUTES_POS = [3, 4, 5]; const AMPM_POS = [6, 7, 8]; -/** - * Defines the posible values of the igxTimePicker's time selection mode. - */ -export enum TimePickerInteractionMode { - dialog, - dropdown -} - @Injectable() export class TimePickerHammerConfig extends HammerGestureConfig { public overrides = { @@ -492,6 +484,10 @@ export class IgxTimePickerComponent implements * @hidden */ public inputFormat = new TimeInputFormatPipe(this); + /** + * @hidden + */ + public interactMode = TimePickerInteractionMode; /** * @hidden @@ -573,13 +569,6 @@ export class IgxTimePickerComponent implements return this._ampmView; } - /** - * @hidden - */ - get isModal(): boolean { - return this.mode === TimePickerInteractionMode.dialog; - } - /** * @hidden */ diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.directives.ts b/projects/igniteui-angular/src/lib/time-picker/time-picker.directives.ts index 519716df313..d3eb385534c 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.directives.ts +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.directives.ts @@ -12,7 +12,7 @@ import { Input, TemplateRef } from '@angular/core'; -import { IGX_TIME_PICKER_COMPONENT, IgxTimePickerBase } from './time-picker.common'; +import { IGX_TIME_PICKER_COMPONENT, IgxTimePickerBase, TimePickerInteractionMode } from './time-picker.common'; /** @hidden */ @Directive({ @@ -155,7 +155,7 @@ export class IgxItemListDirective { public onKeydownEnter(event: KeyboardEvent) { event.preventDefault(); - if (!this.timePicker.isModal) { + if (this.timePicker.mode === TimePickerInteractionMode.dropdown) { this.timePicker.hideOverlay(); return; } diff --git a/projects/igniteui-angular/src/public_api.ts b/projects/igniteui-angular/src/public_api.ts index 60911316908..20f6fea2257 100644 --- a/projects/igniteui-angular/src/public_api.ts +++ b/projects/igniteui-angular/src/public_api.ts @@ -89,5 +89,6 @@ export * from './lib/core/density'; export { CancelableEventArgs } from './lib/core/utils'; export { changei18n, getCurrentResourceStrings, IResourceStrings } from './lib/core/i18n/resources'; export { IGridResourceStrings } from './lib/core/i18n/grid-resources'; +export { TimePickerInteractionMode } from './lib/time-picker/time-picker.common' export { ITimePickerResourceStrings } from './lib/core/i18n/time-picker-resources'; diff --git a/src/app/time-picker/time-picker.sample.ts b/src/app/time-picker/time-picker.sample.ts index 41ff0eb2f87..e2515e9ad78 100644 --- a/src/app/time-picker/time-picker.sample.ts +++ b/src/app/time-picker/time-picker.sample.ts @@ -1,5 +1,5 @@ import { Component, ViewChild } from '@angular/core'; -import { TimePickerInteractionMode, IgxTimePickerComponent } from 'igniteui-angular'; +import { IgxTimePickerComponent, TimePickerInteractionMode } from 'igniteui-angular'; @Component({ selector: 'app-time-picker-sample', From 7beeb41d378b259ffb057a4bfbe459fb76d103da Mon Sep 17 00:00:00 2001 From: SAndreeva Date: Sat, 22 Dec 2018 21:34:53 +0200 Subject: [PATCH 32/34] feat(time picker): cover some more corner cases #2337 --- .../lib/time-picker/time-picker.component.ts | 40 ++++++++++++++----- projects/igniteui-angular/src/public_api.ts | 2 +- src/app/time-picker/time-picker.sample.html | 4 +- src/app/time-picker/time-picker.sample.ts | 2 +- 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts index 15560ae6950..d3d6e2cf4cd 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts @@ -122,6 +122,11 @@ export class IgxTimePickerComponent implements this._value = value; this._onChangeCallback(value); + const dispVal = this._formatTime(this.value, this.format); + if (this.mode === TimePickerInteractionMode.dropdown && this._displayValue !== dispVal) { + this.displayValue = dispVal; + } + const args: IgxTimePickerValueChangedEventArgs = { oldValue: oldVal, newValue: value @@ -463,10 +468,6 @@ export class IgxTimePickerComponent implements /** * @hidden */ - public displayValue = ''; - /** - * @hidden - */ public cleared = false; /** * @hidden @@ -507,6 +508,7 @@ export class IgxTimePickerComponent implements private _okButtonLabel = null; private _cancelButtonLabel = null; private _format: string; + private _displayValue: string; private _isHourListLoop = this.isSpinLoop; private _isMinuteListLoop = this.isSpinLoop; @@ -529,6 +531,20 @@ export class IgxTimePickerComponent implements private _onTouchedCallback: () => void = () => { }; private _onChangeCallback: (_: Date) => void = () => { }; + /** + * @hidden + */ + get displayValue(): string { + if (this._displayValue === undefined) { + return this._formatTime(this.value, this.format); + } + return this._displayValue; + } + + set displayValue(value: string) { + this._displayValue = value; + } + /** * Returns the current time formatted as string using the `format` option. * If there is no set time the return is an empty string. @@ -1116,13 +1132,17 @@ export class IgxTimePickerComponent implements this._overlayId = this.overlayService.show(this.container, this._dialogOverlaySettings); } - if (this.mode === TimePickerInteractionMode.dropdown && this.collapsed) { - this.collapsed = false; - if (this.outlet) { - this._dropDownOverlaySettings.outlet = this.outlet; + if (this.mode === TimePickerInteractionMode.dropdown) { + if (this.collapsed) { + this.collapsed = false; + if (this.outlet) { + this._dropDownOverlaySettings.outlet = this.outlet; + } + this._dropDownOverlaySettings.positionStrategy.settings.target = this.group.element.nativeElement; + this._overlayId = this.overlayService.show(this.container, this._dropDownOverlaySettings); + } else { + this._onDropDownClosed(); } - this._dropDownOverlaySettings.positionStrategy.settings.target = this.group.element.nativeElement; - this._overlayId = this.overlayService.show(this.container, this._dropDownOverlaySettings); } if (this.value) { diff --git a/projects/igniteui-angular/src/public_api.ts b/projects/igniteui-angular/src/public_api.ts index 20f6fea2257..25e716ae5ec 100644 --- a/projects/igniteui-angular/src/public_api.ts +++ b/projects/igniteui-angular/src/public_api.ts @@ -89,6 +89,6 @@ export * from './lib/core/density'; export { CancelableEventArgs } from './lib/core/utils'; export { changei18n, getCurrentResourceStrings, IResourceStrings } from './lib/core/i18n/resources'; export { IGridResourceStrings } from './lib/core/i18n/grid-resources'; -export { TimePickerInteractionMode } from './lib/time-picker/time-picker.common' +export { TimePickerInteractionMode } from './lib/time-picker/time-picker.common'; export { ITimePickerResourceStrings } from './lib/core/i18n/time-picker-resources'; diff --git a/src/app/time-picker/time-picker.sample.html b/src/app/time-picker/time-picker.sample.html index 046854adfa9..ad9473f92b5 100644 --- a/src/app/time-picker/time-picker.sample.html +++ b/src/app/time-picker/time-picker.sample.html @@ -9,11 +9,11 @@

Time Picker with Dropdown

+ [format]="format">
diff --git a/src/app/time-picker/time-picker.sample.ts b/src/app/time-picker/time-picker.sample.ts index e2515e9ad78..fd71b49a5fb 100644 --- a/src/app/time-picker/time-picker.sample.ts +++ b/src/app/time-picker/time-picker.sample.ts @@ -14,7 +14,7 @@ export class TimePickerSampleComponent { format="hh:mm tt"; isSpinLoop = true; isVertical = true; - mode = TimePickerInteractionMode.dropdown; + mode = TimePickerInteractionMode; date = new Date(2018, 10, 27, 17, 45, 0, 0); From 5e0f92e5fb356b68ae627393cd12dc39b94cfad2 Mon Sep 17 00:00:00 2001 From: SAndreeva Date: Wed, 26 Dec 2018 14:29:40 +0200 Subject: [PATCH 33/34] feat(time picker): some more minor bug fixes #2337 --- .../time-picker/time-picker.component.spec.ts | 20 ++++++++----------- .../lib/time-picker/time-picker.component.ts | 8 ++++++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.spec.ts b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.spec.ts index e289573e604..0b9c0c4ab43 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.spec.ts +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.spec.ts @@ -894,36 +894,32 @@ describe('IgxTimePicker', () => { const fixture = TestBed.createComponent(IgxTimePickerDropDownSingleHourComponent); fixture.detectChanges(); - const iconTime = fixture.debugElement.queryAll(By.css('.igx-icon'))[0]; const input = fixture.debugElement.query(By.directive(IgxInputDirective)); - UIInteractions.clickElement(iconTime); + input.nativeElement.dispatchEvent(new Event('focus')); fixture.detectChanges(); - expect(input.nativeElement.value).toBe('4:5'); + expect(input.nativeElement.value).toBe('04:05'); input.nativeElement.dispatchEvent(new Event('blur')); fixture.detectChanges(); - input.nativeElement.dispatchEvent(new Event('focus')); - fixture.detectChanges(); - - expect(input.nativeElement.value).toBe('04:05'); + expect(input.nativeElement.value).toBe('4:5'); fixture.componentInstance.timePicker.format = 'h:m tt'; - - UIInteractions.clickElement(iconTime); fixture.detectChanges(); expect(input.nativeElement.value).toBe('4:5 AM'); - input.nativeElement.dispatchEvent(new Event('blur')); - fixture.detectChanges(); - input.nativeElement.dispatchEvent(new Event('focus')); fixture.detectChanges(); expect(input.nativeElement.value).toBe('04:05 AM'); + + input.nativeElement.dispatchEvent(new Event('blur')); + fixture.detectChanges(); + + expect(input.nativeElement.value).toBe('4:5 AM'); })); it('should correct spin (arrow buttons) on empty value (dropdown mode)', (() => { diff --git a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts index d3d6e2cf4cd..e3af01ee839 100644 --- a/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts +++ b/projects/igniteui-angular/src/lib/time-picker/time-picker.component.ts @@ -293,6 +293,10 @@ export class IgxTimePickerComponent implements set format(formatValue: string) { this._format = formatValue; this.mask = this._format.indexOf('tt') !== -1 ? '00:00 LL' : '00:00'; + + if (this.displayValue) { + this.displayValue = this._formatTime(this.value, this._format); + } } /** @@ -1066,7 +1070,7 @@ export class IgxTimePickerComponent implements const newVal = this._convertMinMaxValue(this.displayValue); if (this._isValueValid(newVal)) { - if (oldValue.getTime() !== newVal.getTime()) { + if (!this.value || oldValue.getTime() !== newVal.getTime()) { this.value = newVal; } } else { @@ -1533,7 +1537,7 @@ export class IgxTimePickerComponent implements if (value && value !== this.parseMask()) { if (this._isEntryValid(value)) { const newVal = this._convertMinMaxValue(value); - if (this.value.getTime() !== newVal.getTime()) { + if (!this.value || this.value.getTime() !== newVal.getTime()) { this.value = newVal; } } else { From 8aa1e30a51e350610043808b44766b21e25a47e4 Mon Sep 17 00:00:00 2001 From: SAndreeva Date: Mon, 7 Jan 2019 09:57:36 +0200 Subject: [PATCH 34/34] feat(time picker): fix undesired input event firing in IE #2337 --- .../src/lib/directives/mask/mask.directive.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/projects/igniteui-angular/src/lib/directives/mask/mask.directive.ts b/projects/igniteui-angular/src/lib/directives/mask/mask.directive.ts index c7b0970716f..cf0d61b1ea3 100644 --- a/projects/igniteui-angular/src/lib/directives/mask/mask.directive.ts +++ b/projects/igniteui-angular/src/lib/directives/mask/mask.directive.ts @@ -12,6 +12,7 @@ import { } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { KEYS, MaskHelper } from './mask-helper'; +import { isIE } from '../../core/utils'; const noop = () => { }; @@ -170,6 +171,8 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { */ private _valOnPaste; + private _stopPropagation: boolean; + /** *@hidden */ @@ -249,6 +252,11 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { */ @HostListener('input', ['$event']) public onInputChanged(event): void { + if (isIE() && this._stopPropagation) { + this._stopPropagation = false; + return; + } + if (this._paste) { this._paste = false; @@ -283,6 +291,9 @@ export class IgxMaskDirective implements OnInit, ControlValueAccessor { @HostListener('focus', ['$event.target.value']) public onFocus(value) { if (this.focusedValuePipe) { + if (isIE()) { + this._stopPropagation = true; + } this.value = this.focusedValuePipe.transform(value); } else { this.value = this.maskHelper.parseValueByMaskOnInit(this.value, this._maskOptions);