mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
only return visible elements as focusable (#14864)
This commit is contained in:
@@ -44,3 +44,23 @@ export function convertSizeToNumber(size: number | string | undefined): number {
|
|||||||
}
|
}
|
||||||
return +size;
|
return +size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tabbableElementsQuerySelector = 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex="0"]';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the focusable elements inside a HTML element
|
||||||
|
* @param container The container element inside which we should look for the focusable elements
|
||||||
|
* @returns The focusable elements
|
||||||
|
*/
|
||||||
|
export function getFocusableElements(container: HTMLElement): HTMLElement[] {
|
||||||
|
const elements = [];
|
||||||
|
container.querySelectorAll(tabbableElementsQuerySelector).forEach((element: HTMLElement) => {
|
||||||
|
const style = window.getComputedStyle(element);
|
||||||
|
// We should only return the elements that are visible. There are many ways to hide an element, for example setting the
|
||||||
|
// visibility attribute to hidden/collapse, setting the display property to none, or if one of its ancestors is invisible.
|
||||||
|
if (element.offsetWidth > 0 && element.offsetHeight > 0 && style.visibility === 'visible') {
|
||||||
|
elements.push(element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,30 +2,32 @@
|
|||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
import 'vs/css!./media/modal';
|
|
||||||
import 'vs/css!./media/calloutDialog';
|
import 'vs/css!./media/calloutDialog';
|
||||||
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
|
import 'vs/css!./media/modal';
|
||||||
import { Color } from 'vs/base/common/color';
|
import { getFocusableElements } from 'sql/base/browser/dom';
|
||||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
import { Button } from 'sql/base/browser/ui/button/button';
|
||||||
import { mixin } from 'vs/base/common/objects';
|
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
|
||||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
|
||||||
import * as DOM from 'vs/base/browser/dom';
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||||
import { generateUuid } from 'vs/base/common/uuid';
|
|
||||||
import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
|
||||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
|
||||||
import { Button } from 'sql/base/browser/ui/button/button';
|
|
||||||
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
|
|
||||||
import { localize } from 'vs/nls';
|
|
||||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
|
||||||
import { ILogService } from 'vs/platform/log/common/log';
|
|
||||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
|
|
||||||
import { URI } from 'vs/base/common/uri';
|
|
||||||
import { Schemas } from 'vs/base/common/network';
|
|
||||||
import { IThemable } from 'vs/base/common/styler';
|
|
||||||
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
|
|
||||||
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
|
|
||||||
import { alert } from 'vs/base/browser/ui/aria/aria';
|
import { alert } from 'vs/base/browser/ui/aria/aria';
|
||||||
|
import { Color } from 'vs/base/common/color';
|
||||||
|
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||||
|
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||||
|
import { Schemas } from 'vs/base/common/network';
|
||||||
|
import { mixin } from 'vs/base/common/objects';
|
||||||
|
import { IThemable } from 'vs/base/common/styler';
|
||||||
|
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||||
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
import { generateUuid } from 'vs/base/common/uuid';
|
||||||
|
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
|
||||||
|
import { localize } from 'vs/nls';
|
||||||
|
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||||
|
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||||
|
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
|
||||||
|
import { ILogService } from 'vs/platform/log/common/log';
|
||||||
|
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
|
||||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
|
|
||||||
export enum MessageLevel {
|
export enum MessageLevel {
|
||||||
@@ -97,8 +99,6 @@ const defaultOptions: IModalOptions = {
|
|||||||
dialogProperties: undefined
|
dialogProperties: undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
const tabbableElementsQuerySelector = 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex="0"]';
|
|
||||||
|
|
||||||
export type HideReason = 'close' | 'cancel' | 'ok';
|
export type HideReason = 'close' | 'cancel' | 'ok';
|
||||||
|
|
||||||
export abstract class Modal extends Disposable implements IThemable {
|
export abstract class Modal extends Disposable implements IThemable {
|
||||||
@@ -400,7 +400,7 @@ export abstract class Modal extends Disposable implements IThemable {
|
|||||||
* Figures out the first and last elements which the user can tab to in the dialog
|
* Figures out the first and last elements which the user can tab to in the dialog
|
||||||
*/
|
*/
|
||||||
public setFirstLastTabbableElement() {
|
public setFirstLastTabbableElement() {
|
||||||
const tabbableElements = this._bodyContainer!.querySelectorAll(tabbableElementsQuerySelector);
|
const tabbableElements = getFocusableElements(this._bodyContainer!);
|
||||||
if (tabbableElements && tabbableElements.length > 0) {
|
if (tabbableElements && tabbableElements.length > 0) {
|
||||||
this._firstTabbableElement = <HTMLElement>tabbableElements[0];
|
this._firstTabbableElement = <HTMLElement>tabbableElements[0];
|
||||||
this._lastTabbableElement = <HTMLElement>tabbableElements[tabbableElements.length - 1];
|
this._lastTabbableElement = <HTMLElement>tabbableElements[tabbableElements.length - 1];
|
||||||
@@ -413,16 +413,13 @@ export abstract class Modal extends Disposable implements IThemable {
|
|||||||
public setInitialFocusedElement() {
|
public setInitialFocusedElement() {
|
||||||
// Try to find focusable element in dialog pane rather than overall container. _modalBodySection contains items in the pane for a wizard.
|
// 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.
|
// This ensures that we are setting the focus on a useful element in the form when possible.
|
||||||
const focusableElements = this._modalBodySection ?
|
const focusableElements = getFocusableElements(this._modalBodySection ?? this._bodyContainer!);
|
||||||
this._modalBodySection.querySelectorAll(tabbableElementsQuerySelector) :
|
|
||||||
this._bodyContainer!.querySelectorAll(tabbableElementsQuerySelector);
|
|
||||||
|
|
||||||
if (focusableElements && focusableElements.length > 0) {
|
if (focusableElements && focusableElements.length > 0) {
|
||||||
(<HTMLElement>focusableElements[0]).focus();
|
(<HTMLElement>focusableElements[0]).focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tasks to perform before callout dialog is shown
|
* Tasks to perform before callout dialog is shown
|
||||||
* Includes: positioning of dialog
|
* Includes: positioning of dialog
|
||||||
|
|||||||
Reference in New Issue
Block a user