mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-29 01:25:37 -05:00
* Tab trap for modal (closes #5930) * Addressing PR comments * Fixed formatting.
This commit is contained in:
@@ -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()!;
|
||||
|
||||
Reference in New Issue
Block a user