diff --git a/components/date-picker/date-picker.component.spec.ts b/components/date-picker/date-picker.component.spec.ts index 41fa3865309..b9322aa8cd7 100644 --- a/components/date-picker/date-picker.component.spec.ts +++ b/components/date-picker/date-picker.component.spec.ts @@ -71,6 +71,62 @@ describe('NzDatePickerComponent', () => { expect(getPickerContainer()).toBeNull(); })); + it('should focus on the trigger after a click outside', fakeAsync(() => { + fixture.detectChanges(); + openPickerByClickTrigger(); + + dispatchMouseEvent(queryFromOverlay('.cdk-overlay-backdrop'), 'click'); + fixture.detectChanges(); + tick(500); + fixture.detectChanges(); + expect(document.activeElement).toEqual(getPickerTrigger()); + })); + + it('should open on enter', fakeAsync(() => { + fixture.detectChanges(); + getPickerTriggerWrapper().dispatchEvent(new KeyboardEvent('keyup', { key: 'enter' })); + fixture.detectChanges(); + tick(500); + fixture.detectChanges(); + expect(getPickerContainer()).not.toBeNull(); + })); + + it('should open by click and focus on inner calendar input', fakeAsync(() => { + fixture.detectChanges(); + openPickerByClickTrigger(); + expect(document.activeElement).toEqual(queryFromOverlay('input.ant-calendar-input')); + })); + + it('should open by click, focus on inner calendar input, and submit on enter', fakeAsync(() => { + fixtureInstance.nzValue = new Date(); + fixture.detectChanges(); + // Do it 2 times to normalize the value of the element. + const action = () => { + openPickerByClickTrigger(); + expect(document.activeElement).toEqual(queryFromOverlay('input.ant-calendar-input')); + queryFromOverlay('input.ant-calendar-input').dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' })); + fixture.detectChanges(); + tick(500); + fixture.detectChanges(); + expect(getPickerContainer()).toBeNull(); + }; + action(); + action(); + })); + + it('should not submit with invalid input', fakeAsync(() => { + fixture.detectChanges(); + openPickerByClickTrigger(); + const input = queryFromOverlay('input.ant-calendar-input') as HTMLInputElement; + input.value = 'invalid input'; + fixture.detectChanges(); + input.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' })); + fixture.detectChanges(); + tick(500); + fixture.detectChanges(); + expect(getPickerContainer()).not.toBeNull(); + })); + it('should support changing language at runtime', fakeAsync(() => { fixture.detectChanges(); expect(getPickerTrigger().placeholder).toBe('请选择日期'); diff --git a/components/date-picker/lib/calendar/calendar-input.component.html b/components/date-picker/lib/calendar/calendar-input.component.html index e83b064062b..806f290f159 100644 --- a/components/date-picker/lib/calendar/calendar-input.component.html +++ b/components/date-picker/lib/calendar/calendar-input.component.html @@ -5,6 +5,7 @@ placeholder="{{ placeholder || locale.dateSelect }}" value="{{ toReadableInput(value) }}" (keyup)="onInputKeyup($event)" + #inputElement /> diff --git a/components/date-picker/lib/calendar/calendar-input.component.ts b/components/date-picker/lib/calendar/calendar-input.component.ts index 00d41b80bdc..b4ae19651ed 100644 --- a/components/date-picker/lib/calendar/calendar-input.component.ts +++ b/components/date-picker/lib/calendar/calendar-input.component.ts @@ -9,10 +9,12 @@ import { ChangeDetectionStrategy, Component, + ElementRef, EventEmitter, Input, OnInit, Output, + ViewChild, ViewEncapsulation } from '@angular/core'; @@ -34,24 +36,30 @@ export class CalendarInputComponent implements OnInit { @Input() disabledDate: (d: Date) => boolean; @Input() value: CandyDate; + @Input() autoFocus: boolean; @Output() readonly valueChange = new EventEmitter(); + @ViewChild('inputElement', { static: true }) inputRef: ElementRef; prefixCls: string = 'ant-calendar'; invalidInputClass: string = ''; constructor(private dateHelper: DateHelperService) {} - ngOnInit(): void {} + ngOnInit(): void { + if (this.autoFocus) { + this.inputRef.nativeElement.focus(); + } + } - onInputKeyup(event: Event): void { + onInputKeyup(event: KeyboardEvent): void { const date = this.checkValidInputDate(event); if (!date || (this.disabledDate && this.disabledDate(date.nativeDate))) { return; } - if (!date.isSame(this.value, 'second')) { - // Not same with original value + if (event.key === 'Enter' || !date.isSame(this.value, 'second')) { + // Explicitly submitted or different from the original value this.value = date; this.valueChange.emit(this.value); } diff --git a/components/date-picker/lib/popups/date-range-popup.component.html b/components/date-picker/lib/popups/date-range-popup.component.html index cccf1555937..b3d497bdc29 100644 --- a/components/date-picker/lib/popups/date-range-popup.component.html +++ b/components/date-picker/lib/popups/date-range-popup.component.html @@ -33,6 +33,7 @@ [locale]="locale" [disabledDate]="disabledDate" [format]="format" + [autoFocus]="partType !== 'right'" [placeholder]="getPlaceholder(partType)" > diff --git a/components/date-picker/month-picker.component.spec.ts b/components/date-picker/month-picker.component.spec.ts index 568d19b9c5a..7eac203e604 100644 --- a/components/date-picker/month-picker.component.spec.ts +++ b/components/date-picker/month-picker.component.spec.ts @@ -63,6 +63,15 @@ describe('NzMonthPickerComponent', () => { expect(getPickerContainer()).toBeNull(); })); + it('should open on enter', fakeAsync(() => { + fixture.detectChanges(); + getPickerTriggerWrapper().dispatchEvent(new KeyboardEvent('keyup', { key: 'enter' })); + fixture.detectChanges(); + tick(500); + fixture.detectChanges(); + expect(getPickerContainer()).not.toBeNull(); + })); + it('should support nzAllowClear and work properly', fakeAsync(() => { const clearBtnSelector = By.css('nz-picker i.ant-calendar-picker-clear'); const initial = (fixtureInstance.nzValue = new Date()); diff --git a/components/date-picker/picker.component.html b/components/date-picker/picker.component.html index 0c456f649ab..d4a916bbbde 100644 --- a/components/date-picker/picker.component.html +++ b/components/date-picker/picker.component.html @@ -5,6 +5,7 @@ [ngStyle]="style" tabindex="0" (click)="onClickInputBox()" + (keyup.enter)="onClickInputBox()" > diff --git a/components/date-picker/picker.component.ts b/components/date-picker/picker.component.ts index 6b0c112a062..9e7876928f8 100644 --- a/components/date-picker/picker.component.ts +++ b/components/date-picker/picker.component.ts @@ -107,14 +107,18 @@ export class NzPickerComponent implements OnInit, AfterViewInit { ngAfterViewInit(): void { if (this.autoFocus) { - if (this.isRange) { - const firstInput = (this.pickerInput.nativeElement as HTMLElement).querySelector( - 'input:first-child' - ) as HTMLInputElement; - firstInput.focus(); // Focus on the first input - } else { - this.pickerInput.nativeElement.focus(); - } + this.focus(); + } + } + + focus(): void { + if (this.isRange) { + const firstInput = (this.pickerInput.nativeElement as HTMLElement).querySelector( + 'input:first-child' + ) as HTMLInputElement; + firstInput.focus(); // Focus on the first input + } else { + this.pickerInput.nativeElement.focus(); } } @@ -135,6 +139,7 @@ export class NzPickerComponent implements OnInit, AfterViewInit { if (this.realOpenState) { this.overlayOpen = false; this.openChange.emit(this.overlayOpen); + this.focus(); } } diff --git a/components/date-picker/range-picker.component.spec.ts b/components/date-picker/range-picker.component.spec.ts index 0d1ab13d461..e8a7ad548a0 100644 --- a/components/date-picker/range-picker.component.spec.ts +++ b/components/date-picker/range-picker.component.spec.ts @@ -66,6 +66,32 @@ describe('NzRangePickerComponent', () => { expect(getPickerContainer()).toBeNull(); })); + it('should focus on the trigger after a click outside', fakeAsync(() => { + fixture.detectChanges(); + openPickerByClickTrigger(); + + dispatchMouseEvent(queryFromOverlay('.cdk-overlay-backdrop'), 'click'); + fixture.detectChanges(); + tick(500); + fixture.detectChanges(); + expect(getPickerTrigger().matches(':focus-within')).toBeTruthy(); + })); + + it('should open on enter', fakeAsync(() => { + fixture.detectChanges(); + getPickerTriggerWrapper().dispatchEvent(new KeyboardEvent('keyup', { key: 'enter' })); + fixture.detectChanges(); + tick(500); + fixture.detectChanges(); + expect(getPickerContainer()).not.toBeNull(); + })); + + it('should open by click and focus on left inner calendar input', fakeAsync(() => { + fixture.detectChanges(); + openPickerByClickTrigger(); + expect(document.activeElement).toEqual(queryFromOverlay('.ant-calendar-range-left input.ant-calendar-input')); + })); + it('should support nzAllowClear and work properly', fakeAsync(() => { const clearBtnSelector = By.css('nz-picker i.ant-calendar-picker-clear'); const initial = (fixtureInstance.modelValue = [new Date(), new Date()]); diff --git a/components/date-picker/year-picker.component.spec.ts b/components/date-picker/year-picker.component.spec.ts index eb932cca4a8..b275fface3d 100644 --- a/components/date-picker/year-picker.component.spec.ts +++ b/components/date-picker/year-picker.component.spec.ts @@ -58,6 +58,15 @@ describe('NzYearPickerComponent', () => { expect(getPickerContainer()).toBeNull(); })); + it('should open on enter', fakeAsync(() => { + fixture.detectChanges(); + getPickerTriggerWrapper().dispatchEvent(new KeyboardEvent('keyup', { key: 'enter' })); + fixture.detectChanges(); + tick(500); + fixture.detectChanges(); + expect(getPickerContainer()).not.toBeNull(); + })); + it('should support nzAllowClear and work properly', fakeAsync(() => { const clearBtnSelector = By.css('nz-picker i.ant-calendar-picker-clear'); const initial = (fixtureInstance.nzValue = new Date());