diff --git a/components/date-picker/lib/calendar/calendar-input.component.html b/components/date-picker/lib/calendar/calendar-input.component.html index e83b064062..806f290f15 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 8fc8993274..b59a65149b 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,29 @@ 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') { 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 cccf155593..b3d497bdc2 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/nz-date-picker.component.spec.ts b/components/date-picker/nz-date-picker.component.spec.ts index 7ab9f3b997..8de67365b3 100644 --- a/components/date-picker/nz-date-picker.component.spec.ts +++ b/components/date-picker/nz-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('请选择日期'); @@ -197,6 +253,21 @@ describe('NzDatePickerComponent', () => { fixture.detectChanges(); const disabledCell = queryFromOverlay('tbody.ant-calendar-tbody td.ant-calendar-disabled-cell'); expect(disabledCell.textContent!.trim()).toBe('15'); + const input = queryFromOverlay('input.ant-calendar-input') as HTMLInputElement; + const submit = (date: string) => { + input.value = date; + fixture.detectChanges(); + input.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' })); + fixture.detectChanges(); + tick(500); + fixture.detectChanges(); + }; + // Should fail to submit a disabled date + submit('2018-11-15'); + expect(getPickerContainer()).not.toBeNull(); + // But it should be fine to submit an enabled date + submit('2018-11-11'); + expect(getPickerContainer()).toBeNull(); })); it('should support nzLocale', () => { @@ -719,7 +790,7 @@ describe('NzDatePickerComponent', () => { // Correct inputing input.value = '2018-11-22'; - input.dispatchEvent(new KeyboardEvent('keyup')); + input.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' })); // dispatchKeyboardEvent(input, 'keyup', ENTER); // Not working? fixture.detectChanges(); flush(); diff --git a/components/date-picker/nz-month-picker.component.spec.ts b/components/date-picker/nz-month-picker.component.spec.ts index a567b453fd..3dca50d6e2 100644 --- a/components/date-picker/nz-month-picker.component.spec.ts +++ b/components/date-picker/nz-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/nz-range-picker.component.spec.ts b/components/date-picker/nz-range-picker.component.spec.ts index 2322e8cced..f2305facc3 100644 --- a/components/date-picker/nz-range-picker.component.spec.ts +++ b/components/date-picker/nz-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()]); @@ -643,10 +669,10 @@ describe('NzRangePickerComponent', () => { const rightInput = queryFromOverlay('.ant-calendar-range-right input.ant-calendar-input') as HTMLInputElement; leftInput.value = '2018-11-11'; - leftInput.dispatchEvent(new KeyboardEvent('keyup')); + leftInput.dispatchEvent(new KeyboardEvent('keyup', {key: 'Enter'})); fixture.detectChanges(); rightInput.value = '2018-12-12'; - rightInput.dispatchEvent(new KeyboardEvent('keyup')); + rightInput.dispatchEvent(new KeyboardEvent('keyup', {key: 'Enter'})); fixture.detectChanges(); tick(500); expect(nzOnChange).toHaveBeenCalled(); diff --git a/components/date-picker/nz-year-picker.component.spec.ts b/components/date-picker/nz-year-picker.component.spec.ts index 285bf0db9f..52a64e48d4 100644 --- a/components/date-picker/nz-year-picker.component.spec.ts +++ b/components/date-picker/nz-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()); diff --git a/components/date-picker/picker.component.html b/components/date-picker/picker.component.html index 0c456f649a..d4a916bbbd 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 6b7c069600..f407167c8c 100644 --- a/components/date-picker/picker.component.ts +++ b/components/date-picker/picker.component.ts @@ -105,14 +105,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(); } } @@ -133,6 +137,7 @@ export class NzPickerComponent implements OnInit, AfterViewInit { if (this.realOpenState) { this.overlayOpen = false; this.openChange.emit(this.overlayOpen); + this.focus(); } }