Skip to content

feat(material/datepicker): allow for focus restoration to be disabled #21444

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion src/material/datepicker/datepicker-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,18 @@ export abstract class MatDatepickerBase<C extends MatDatepickerControl<D>, S,
@Input()
yPosition: DatepickerDropdownPositionY = 'below';

/**
* Whether to restore focus to the previously-focused element when the calendar is closed.
* Note that automatic focus restoration is an accessibility feature and it is recommended that
* you provide your own equivalent, if you decide to turn it off.
*/
@Input()
get restoreFocus(): boolean { return this._restoreFocus; }
set restoreFocus(value: boolean) {
this._restoreFocus = coerceBooleanProperty(value);
}
private _restoreFocus = true;

/**
* Emits selected year in multiyear view.
* This doesn't imply a change on the selected date.
Expand Down Expand Up @@ -529,7 +541,7 @@ export abstract class MatDatepickerBase<C extends MatDatepickerControl<D>, S,
}
};

if (this._focusedElementBeforeOpen &&
if (this._restoreFocus && this._focusedElementBeforeOpen &&
typeof this._focusedElementBeforeOpen.focus === 'function') {
// Because IE moves focus asynchronously, we can't count on it being restored before we've
// marked the datepicker as closed. If the event fires out of sequence and the element that
Expand Down Expand Up @@ -691,4 +703,5 @@ export abstract class MatDatepickerBase<C extends MatDatepickerControl<D>, S,
static ngAcceptInputType_disabled: BooleanInput;
static ngAcceptInputType_opened: BooleanInput;
static ngAcceptInputType_touchUi: BooleanInput;
static ngAcceptInputType_restoreFocus: BooleanInput;
}
29 changes: 28 additions & 1 deletion src/material/datepicker/datepicker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1119,6 +1119,32 @@ describe('MatDatepicker', () => {
expect(document.activeElement).toBe(toggle, 'Expected focus to be restored to toggle.');
});

it('should allow for focus restoration to be disabled', () => {
let toggle = fixture.debugElement.query(By.css('button'))!.nativeElement;

fixture.componentInstance.touchUI = false;
fixture.componentInstance.restoreFocus = false;
fixture.detectChanges();

toggle.focus();
expect(document.activeElement).toBe(toggle, 'Expected toggle to be focused.');

fixture.componentInstance.datepicker.open();
fixture.detectChanges();

let pane = document.querySelector('.cdk-overlay-pane')!;

expect(pane).toBeTruthy('Expected calendar to be open.');
expect(pane.contains(document.activeElement))
.toBe(true, 'Expected focus to be inside the calendar.');

fixture.componentInstance.datepicker.close();
fixture.detectChanges();

expect(document.activeElement)
.not.toBe(toggle, 'Expected focus not to be restored to toggle.');
});

it('should not override focus if it was moved inside the closed event in touchUI mode',
fakeAsync(() => {
const focusTarget = document.createElement('button');
Expand Down Expand Up @@ -2318,13 +2344,14 @@ class DatepickerWithFormControl {
template: `
<input [matDatepicker]="d">
<mat-datepicker-toggle [for]="d" [aria-label]="ariaLabel"></mat-datepicker-toggle>
<mat-datepicker #d [touchUi]="touchUI"></mat-datepicker>
<mat-datepicker #d [touchUi]="touchUI" [restoreFocus]="restoreFocus"></mat-datepicker>
`,
})
class DatepickerWithToggle {
@ViewChild('d') datepicker: MatDatepicker<Date>;
@ViewChild(MatDatepickerInput) input: MatDatepickerInput<Date>;
touchUI = true;
restoreFocus = true;
ariaLabel: string;
}

Expand Down