Skip to content
Permalink
Browse files

fix(module:date-picker): open on enter and focus on inner input (#3804)

Extracted the good parts suitable for a11y from #3146. See discussion there.
  • Loading branch information...
jimmytheneutrino authored and wenqi73 committed Aug 6, 2019
1 parent 31dbe1a commit 3f03ee1fbb7f614c3ff0c7a1ad86132aa274572d
@@ -5,6 +5,7 @@
placeholder="{{ placeholder || locale.dateSelect }}"
value="{{ toReadableInput(value) }}"
(keyup)="onInputKeyup($event)"
#inputElement
/>
</div>
<a class="{{ prefixCls }}-clear-btn" role="button" title="{{ locale.clear }}">
@@ -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<CandyDate>();
@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);
}
@@ -33,6 +33,7 @@
[locale]="locale"
[disabledDate]="disabledDate"
[format]="format"
[autoFocus]="partType !== 'right'"
[placeholder]="getPlaceholder(partType)"
></calendar-input>
</ng-template>
@@ -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();
@@ -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());
@@ -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();
@@ -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());
@@ -5,6 +5,7 @@
[ngStyle]="style"
tabindex="0"
(click)="onClickInputBox()"
(keyup.enter)="onClickInputBox()"
>
<!-- Content of single picker -->
<ng-container *ngIf="!isRange">
@@ -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();
}
}

0 comments on commit 3f03ee1

Please sign in to comment.
You can’t perform that action at this time.