diff --git a/src/sql/base/browser/ui/fieldset/fieldset.ts b/src/sql/base/browser/ui/fieldset/fieldset.ts new file mode 100644 index 0000000000..5ffc3f73ce --- /dev/null +++ b/src/sql/base/browser/ui/fieldset/fieldset.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Widget } from 'vs/base/browser/ui/widget'; +import * as DOM from 'vs/base/browser/dom'; +import 'vs/css!./media/fieldset'; + +export interface FieldSetOptions { + ariaLabel: string; +} + +/** + * A wrapper for the HTML FieldSet element, used to group logically related elements in a form. + * Note: The element must be put under the context of an HTML form element in order to make the screen reader announce + * the group name. + */ +export class FieldSet extends Widget { + public readonly element: HTMLFieldSetElement; + + constructor(container: HTMLElement, opts: FieldSetOptions) { + super(); + this.element = DOM.$('fieldset.default-fieldset'); + this.element.setAttribute('aria-label', opts.ariaLabel); + container.appendChild(this.element); + } +} diff --git a/src/sql/base/browser/ui/fieldset/media/fieldset.css b/src/sql/base/browser/ui/fieldset/media/fieldset.css new file mode 100644 index 0000000000..fdaa441d03 --- /dev/null +++ b/src/sql/base/browser/ui/fieldset/media/fieldset.css @@ -0,0 +1,11 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.default-fieldset { + border: none; + margin-inline: 0px; + padding-inline: 0px; + padding-block: 0px; +} diff --git a/src/sql/workbench/browser/modal/media/modal.css b/src/sql/workbench/browser/modal/media/modal.css index 231802653d..1385380a03 100644 --- a/src/sql/workbench/browser/modal/media/modal.css +++ b/src/sql/workbench/browser/modal/media/modal.css @@ -333,3 +333,7 @@ -webkit-mask-size: 100%; mask-size: 100%; } + +.modal-dialog form { + height: 100%; +} diff --git a/src/sql/workbench/browser/modal/modal.ts b/src/sql/workbench/browser/modal/modal.ts index dcca096f1a..975efca575 100644 --- a/src/sql/workbench/browser/modal/modal.ts +++ b/src/sql/workbench/browser/modal/modal.ts @@ -223,12 +223,13 @@ export abstract class Modal extends Disposable implements IThemable { } this._bodyContainer.style.top = `${top}px`; this._modalDialog = DOM.append(this._bodyContainer, DOM.$('.modal-dialog')); + const formElement = DOM.append(this._modalDialog, DOM.$('form')); if (this._modalOptions.dialogStyle === 'callout') { let arrowClass = `.callout-arrow.from-${this._modalOptions.dialogPosition}`; - this._modalContent = DOM.append(this._modalDialog, DOM.$(`.modal-content${arrowClass}`)); + this._modalContent = DOM.append(formElement, DOM.$(`.modal-content${arrowClass}`)); } else { - this._modalContent = DOM.append(this._modalDialog, DOM.$('.modal-content')); + this._modalContent = DOM.append(formElement, DOM.$('.modal-content')); } if (typeof this._modalOptions.width === 'number') { diff --git a/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts b/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts index ae91cb7bf1..11c96939ab 100644 --- a/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts +++ b/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts @@ -45,6 +45,7 @@ import { ConnectionBrowseTab } from 'sql/workbench/services/connection/browser/c import { ElementSizeObserver } from 'vs/editor/browser/config/elementSizeObserver'; import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { FieldSet } from 'sql/base/browser/ui/fieldset/fieldset'; export interface OnShowUIResponse { selectedProviderDisplayName: string; @@ -224,18 +225,21 @@ export class ConnectionDialogWidget extends Modal { })); this._panel.pushTab(this.browsePanel); + const connectionDetailsGroupLabel = localize('connectionDetailsTitle', "Connection Details"); + const connectionDetailsFieldSet = new FieldSet(this._body, { ariaLabel: connectionDetailsGroupLabel }); + this._register(connectionDetailsFieldSet); + this._connectionDetailTitle = DOM.append(connectionDetailsFieldSet.element, DOM.$('.connection-details-title')); - this._connectionDetailTitle = DOM.append(this._body, DOM.$('.connection-details-title')); + this._connectionDetailTitle.innerText = connectionDetailsGroupLabel; - this._connectionDetailTitle.innerText = localize('connectionDetailsTitle', "Connection Details"); - - this._connectionTypeContainer = DOM.append(this._body, DOM.$('.connection-type')); + this._connectionTypeContainer = DOM.append(connectionDetailsFieldSet.element, DOM.$('.connection-type')); const table = DOM.append(this._connectionTypeContainer, DOM.$('table.connection-table-content')); + table.setAttribute('role', 'presentation'); DialogHelper.appendInputSelectBox( DialogHelper.appendRow(table, connectTypeLabel, 'connection-label', 'connection-input'), this._providerTypeSelectBox); this._connectionUIContainer = DOM.$('.connection-provider-info', { id: 'connectionProviderInfo' }); - this._body.append(this._connectionUIContainer); + connectionDetailsFieldSet.element.append(this._connectionUIContainer); this._register(this._themeService.onDidColorThemeChange(e => this.updateTheme(e))); this.updateTheme(this._themeService.getColorTheme()); diff --git a/src/sql/workbench/services/connection/browser/connectionWidget.ts b/src/sql/workbench/services/connection/browser/connectionWidget.ts index 1a1a46a20b..8068f2f141 100644 --- a/src/sql/workbench/services/connection/browser/connectionWidget.ts +++ b/src/sql/workbench/services/connection/browser/connectionWidget.ts @@ -45,6 +45,7 @@ import { AuthLibrary, getAuthLibrary } from 'sql/workbench/services/accountManag import { adjustForMssqlAppName } from 'sql/platform/connection/common/utils'; import { isMssqlAuthProviderEnabled } from 'sql/workbench/services/connection/browser/utils'; import { RequiredIndicatorClassName } from 'sql/base/browser/ui/label/label'; +import { FieldSet } from 'sql/base/browser/ui/fieldset/fieldset'; const ConnectionStringText = localize('connectionWidget.connectionString', "Connection string"); @@ -214,9 +215,11 @@ export class ConnectionWidget extends lifecycle.Disposable { private addInputOptionRadioButtons(): void { if (this._connectionStringOptions.isEnabled) { const groupName = 'input-option-type'; - const inputOptionsContainer = DialogHelper.appendRow(this._tableContainer, '', 'connection-label', 'connection-input', 'connection-input-options'); - this._defaultInputOptionRadioButton = new RadioButton(inputOptionsContainer, { label: 'Parameters', checked: !this._connectionStringOptions.isDefault }); - this._connectionStringRadioButton = new RadioButton(inputOptionsContainer, { label: 'Connection String', checked: this._connectionStringOptions.isDefault }); + const inputTypeLabel = localize('connectionWidget.inputTypeLabel', "Input type"); + const inputOptionsContainer = DialogHelper.appendRow(this._tableContainer, inputTypeLabel, 'connection-label', 'connection-input', 'connection-input-options'); + const inputTypeGroup = new FieldSet(inputOptionsContainer, { ariaLabel: inputTypeLabel }); + this._defaultInputOptionRadioButton = new RadioButton(inputTypeGroup.element, { label: localize('connectionWidget.inputType.parameters', "Parameters"), checked: !this._connectionStringOptions.isDefault }); + this._connectionStringRadioButton = new RadioButton(inputTypeGroup.element, { label: localize('connectionWidget.inputType.connectionString', "Connection String"), checked: this._connectionStringOptions.isDefault }); this._defaultInputOptionRadioButton.name = groupName; this._connectionStringRadioButton.name = groupName; this._register(this._defaultInputOptionRadioButton);