Tab trap for modal (closes #5930) (#8043)

* Tab trap for modal (closes #5930)

* Addressing PR comments

* Fixed formatting.
This commit is contained in:
Shafiq Ur Rahman
2019-10-30 11:38:11 -07:00
committed by GitHub
parent 82e5221024
commit f8858a3511
2 changed files with 28 additions and 26 deletions

View File

@@ -84,9 +84,8 @@ export abstract class Modal extends Disposable implements IThemable {
private _messageDetailText: string;
private _spinnerElement: HTMLElement;
private _focusableElements: NodeListOf<Element>;
private _firstFocusableElement: HTMLElement;
private _lastFocusableElement: HTMLElement;
private _firstTabbableElement: HTMLElement; // The first element in the dialog the user could tab to
private _lastTabbableElement: HTMLElement; // The last element in the dialog the user could tab to
private _focusedElementBeforeOpen: HTMLElement;
private _dialogForeground?: Color;
@@ -269,16 +268,18 @@ export abstract class Modal extends Disposable implements IThemable {
}
private handleBackwardTab(e: KeyboardEvent) {
if (this._firstFocusableElement && this._lastFocusableElement && document.activeElement === this._firstFocusableElement) {
this.setFirstLastTabbableElement(); // called every time to get the current elements
if (this._firstTabbableElement && this._lastTabbableElement && document.activeElement === this._firstTabbableElement) {
e.preventDefault();
this._lastFocusableElement.focus();
this._lastTabbableElement.focus();
}
}
private handleForwardTab(e: KeyboardEvent) {
if (this._firstFocusableElement && this._lastFocusableElement && document.activeElement === this._lastFocusableElement) {
this.setFirstLastTabbableElement(); // called everytime to get the current elements
if (this._firstTabbableElement && this._lastTabbableElement && document.activeElement === this._lastTabbableElement) {
e.preventDefault();
this._firstFocusableElement.focus();
this._firstTabbableElement.focus();
}
}
@@ -316,29 +317,30 @@ export abstract class Modal extends Disposable implements IThemable {
}
/**
* Set focusable elements in the modal dialog
* Figures out the first and last elements which the user can tab to in the dialog
*/
public setFocusableElements() {
// try to find focusable element in dialog pane rather than overall container
this._focusableElements = this._modalBodySection ?
this._modalBodySection.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex="0"]') :
this._bodyContainer.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex="0"]');
if (this._focusableElements && this._focusableElements.length > 0) {
this._firstFocusableElement = <HTMLElement>this._focusableElements[0];
this._lastFocusableElement = <HTMLElement>this._focusableElements[this._focusableElements.length - 1];
public setFirstLastTabbableElement() {
let tabbableElements = this._bodyContainer.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex="0"]');
if (tabbableElements && tabbableElements.length > 0) {
this._firstTabbableElement = <HTMLElement>tabbableElements[0];
this._lastTabbableElement = <HTMLElement>tabbableElements[tabbableElements.length - 1];
}
this._focusedElementBeforeOpen = <HTMLElement>document.activeElement;
this.focus();
}
/**
* Focuses the modal
* Default behavior: focus the first focusable element
* Set focusable elements in the modal dialog
*/
protected focus() {
if (this._firstFocusableElement) {
this._firstFocusableElement.focus();
public setInitialFocusedElement() {
// Try to find focusable element in dialog pane rather than overall container. _modalBodySection contains items in the pane for a wizard.
// This ensures that we are setting the focus on a useful element in the form when possible.
let focusableElements = this._modalBodySection ?
this._modalBodySection.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex="0"]') :
this._bodyContainer.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex="0"]');
this._focusedElementBeforeOpen = <HTMLElement>document.activeElement;
if (focusableElements && focusableElements.length > 0) {
(<HTMLElement>focusableElements[0]).focus();
}
}
@@ -348,7 +350,7 @@ export abstract class Modal extends Disposable implements IThemable {
protected show() {
this._modalShowingContext.get()!.push(this._staticKey);
DOM.append(this.layoutService.container, this._bodyContainer);
this.setFocusableElements();
this.setInitialFocusedElement();
this._keydownListener = DOM.addDisposableListener(document, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
let context = this._modalShowingContext.get()!;