diff --git a/extensions/big-data-cluster/src/bigDataCluster/dialog/addControllerDialog.ts b/extensions/big-data-cluster/src/bigDataCluster/dialog/addControllerDialog.ts index 4c07a04700..20d6a023b0 100644 --- a/extensions/big-data-cluster/src/bigDataCluster/dialog/addControllerDialog.ts +++ b/extensions/big-data-cluster/src/bigDataCluster/dialog/addControllerDialog.ts @@ -180,6 +180,7 @@ export class AddControllerDialog { }]).withLayout({ width: '100%' }).component(); this.onAuthChanged(); await view.initializeModel(formModel); + this.urlInputBox.focus(); }); this.dialog.registerCloseValidator(async () => await this.validate()); diff --git a/extensions/dacpac/src/wizard/pages/selectOperationpage.ts b/extensions/dacpac/src/wizard/pages/selectOperationpage.ts index 675067effd..8989612a4b 100644 --- a/extensions/dacpac/src/wizard/pages/selectOperationpage.ts +++ b/extensions/dacpac/src/wizard/pages/selectOperationpage.ts @@ -38,10 +38,6 @@ export class SelectOperationPage extends BasePage { let importComponent = await this.createImportRadioButton(); let exportComponent = await this.createExportRadioButton(); - // default have the first radio button checked - this.deployRadioButton.checked = true; - this.deployRadioButton.focused = true; - this.form = this.view.modelBuilder.formContainer() .withFormItems( [ @@ -54,6 +50,7 @@ export class SelectOperationPage extends BasePage { }).component(); await this.view.initializeModel(this.form); + this.deployRadioButton.focus(); this.instance.setDoneButton(Operation.deploy); return true; } @@ -67,6 +64,7 @@ export class SelectOperationPage extends BasePage { .withProperties({ name: 'selectedOperation', label: localize('dacFx.deployRadioButtonLabel', "Deploy a data-tier application .dacpac file to an instance of SQL Server [Deploy Dacpac]"), + checked: true // Default to first radio button being selected }).component(); this.deployRadioButton.onDidClick(() => { diff --git a/extensions/schema-compare/src/dialogs/schemaCompareDialog.ts b/extensions/schema-compare/src/dialogs/schemaCompareDialog.ts index 50ee8426ff..ef009a2d78 100644 --- a/extensions/schema-compare/src/dialogs/schemaCompareDialog.ts +++ b/extensions/schema-compare/src/dialogs/schemaCompareDialog.ts @@ -2,12 +2,10 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; import * as nls from 'vscode-nls'; import * as azdata from 'azdata'; import * as vscode from 'vscode'; -import * as os from 'os'; import { SchemaCompareMainWindow } from '../schemaCompareMainWindow'; import { promises as fs } from 'fs'; import { Telemetry } from '../telemetry'; @@ -46,6 +44,8 @@ async function exists(path: string): Promise { export class SchemaCompareDialog { public dialog: azdata.window.Dialog; public dialogName: string; + private sourceDacpacRadioButton: azdata.RadioButtonComponent; + private sourceDatabaseRadioButton: azdata.RadioButtonComponent; private schemaCompareTab: azdata.window.DialogTab; private sourceDacpacComponent: azdata.FormComponent; private sourceTextBox: azdata.InputBoxComponent; @@ -291,6 +291,11 @@ export class SchemaCompareDialog { let formModel = this.formBuilder.component(); await view.initializeModel(formModel); + if (this.sourceIsDacpac) { + this.sourceDacpacRadioButton.focus(); + } else { + this.sourceDatabaseRadioButton.focus(); + } }); } @@ -346,20 +351,20 @@ export class SchemaCompareDialog { } private async createSourceRadiobuttons(view: azdata.ModelView): Promise { - let dacpacRadioButton = view.modelBuilder.radioButton() + this.sourceDacpacRadioButton = view.modelBuilder.radioButton() .withProperties({ name: 'source', label: DacpacRadioButtonLabel }).component(); - let databaseRadioButton = view.modelBuilder.radioButton() + this.sourceDatabaseRadioButton = view.modelBuilder.radioButton() .withProperties({ name: 'source', label: DatabaseRadioButtonLabel }).component(); // show dacpac file browser - dacpacRadioButton.onDidClick(async () => { + this.sourceDacpacRadioButton.onDidClick(async () => { this.sourceIsDacpac = true; this.formBuilder.removeFormItem(this.sourceNoActiveConnectionsText); this.formBuilder.removeFormItem(this.sourceServerComponent); @@ -369,7 +374,7 @@ export class SchemaCompareDialog { }); // show server and db dropdowns or 'No active connections' text - databaseRadioButton.onDidClick(async () => { + this.sourceDatabaseRadioButton.onDidClick(async () => { this.sourceIsDacpac = false; if ((this.sourceServerDropdown.value as ConnectionDropdownValue)) { this.formBuilder.insertFormItem(this.sourceServerComponent, 2, { horizontal: true, titleFontSize: titleFontSize }); @@ -383,17 +388,15 @@ export class SchemaCompareDialog { // if source is currently a db, show it in the server and db dropdowns if (this.schemaCompareResult.sourceEndpointInfo && this.schemaCompareResult.sourceEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Database) { - databaseRadioButton.checked = true; - databaseRadioButton.focused = true; + this.sourceDatabaseRadioButton.checked = true; this.sourceIsDacpac = false; } else { - dacpacRadioButton.checked = true; - dacpacRadioButton.focused = true; + this.sourceDacpacRadioButton.checked = true; this.sourceIsDacpac = true; } let flexRadioButtonsModel = view.modelBuilder.flexContainer() .withLayout({ flexFlow: 'column' }) - .withItems([dacpacRadioButton, databaseRadioButton] + .withItems([this.sourceDacpacRadioButton, this.sourceDatabaseRadioButton] ).component(); return { diff --git a/src/sql/azdata.d.ts b/src/sql/azdata.d.ts index 25cb9df521..29a31a783d 100644 --- a/src/sql/azdata.d.ts +++ b/src/sql/azdata.d.ts @@ -2646,6 +2646,11 @@ declare module 'azdata' { * Run the component's validations */ validate(): Thenable; + + /** + * Focuses the component. + */ + focus(): Thenable; } export interface FormComponent { @@ -3088,7 +3093,6 @@ declare module 'azdata' { ariaRowCount?: number; ariaColumnCount?: number; ariaRole?: string; - focused?: boolean; updateCells?: TableCell[]; moveFocusOutWithTab?: boolean; //accessibility requirement for tables with no actionable cells } @@ -3123,7 +3127,6 @@ declare module 'azdata' { label?: string; value?: string; checked?: boolean; - focused?: boolean; } export interface TextComponentProperties extends ComponentProperties, TitledComponentProperties { diff --git a/src/sql/platform/model/browser/modelViewService.ts b/src/sql/platform/model/browser/modelViewService.ts index 4884ca2fc2..d6456dfe7f 100644 --- a/src/sql/platform/model/browser/modelViewService.ts +++ b/src/sql/platform/model/browser/modelViewService.ts @@ -31,4 +31,5 @@ export interface IModelView extends IView { onEvent: Event; validate(componentId: string): Thenable; readonly onDestroy: Event; + focus(componentId: string): void; } diff --git a/src/sql/sqlops.proposed.d.ts b/src/sql/sqlops.proposed.d.ts index 87081a9fa0..cc71b7b5a7 100644 --- a/src/sql/sqlops.proposed.d.ts +++ b/src/sql/sqlops.proposed.d.ts @@ -183,6 +183,11 @@ declare module 'sqlops' { * Run the component's validations */ validate(): Thenable; + + /** + * Focuses the component. + */ + focus(): Thenable; } export interface FormComponent { diff --git a/src/sql/workbench/api/browser/mainThreadModelView.ts b/src/sql/workbench/api/browser/mainThreadModelView.ts index d3247532c8..d6275f4f60 100644 --- a/src/sql/workbench/api/browser/mainThreadModelView.ts +++ b/src/sql/workbench/api/browser/mainThreadModelView.ts @@ -97,6 +97,10 @@ export class MainThreadModelView extends Disposable implements MainThreadModelVi return new Promise(resolve => this.execModelViewAction(handle, (modelView) => resolve(modelView.validate(componentId)))); } + $focus(handle: number, componentId: string): Thenable { + return new Promise(resolve => this.execModelViewAction(handle, (modelView) => resolve(modelView.focus(componentId)))); + } + private runCustomValidations(handle: number, componentId: string): Thenable { return this._proxy.$runCustomValidations(handle, componentId); } diff --git a/src/sql/workbench/api/common/extHostModelView.ts b/src/sql/workbench/api/common/extHostModelView.ts index 5b283e9d6f..f9ac68987a 100644 --- a/src/sql/workbench/api/common/extHostModelView.ts +++ b/src/sql/workbench/api/common/extHostModelView.ts @@ -711,6 +711,10 @@ class ComponentWrapper implements azdata.Component { public get valid(): boolean { return this._valid; } + + public focus() { + return this._proxy.$focus(this._handle, this._id); + } } class ComponentWithIconWrapper extends ComponentWrapper { @@ -1144,12 +1148,6 @@ class RadioButtonWrapper extends ComponentWrapper implements azdata.RadioButtonC public set checked(v: boolean) { this.setProperty('checked', v); } - public get focused(): boolean { - return this.properties['focused']; - } - public set focused(v: boolean) { - this.setProperty('focused', v); - } public get onDidClick(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onDidClick); @@ -1266,13 +1264,6 @@ class TableComponentWrapper extends ComponentWrapper implements azdata.TableComp this.setProperty('moveFocusOutWithTab', v); } - public get focused(): boolean { - return this.properties['focused']; - } - public set focused(v: boolean) { - this.setProperty('focused', v); - } - public get updateCells(): azdata.TableCell[] { return this.properties['updateCells']; } diff --git a/src/sql/workbench/api/common/sqlExtHost.protocol.ts b/src/sql/workbench/api/common/sqlExtHost.protocol.ts index dd8767125e..39e7b7dc11 100644 --- a/src/sql/workbench/api/common/sqlExtHost.protocol.ts +++ b/src/sql/workbench/api/common/sqlExtHost.protocol.ts @@ -736,6 +736,7 @@ export interface MainThreadModelViewShape extends IDisposable { $validate(handle: number, componentId: string): Thenable; $setDataProvider(handle: number, componentId: string): Thenable; $refreshDataProvider(handle: number, componentId: string, item?: any): Thenable; + $focus(handle: number, componentId: string): Thenable; } export interface ExtHostObjectExplorerShape { diff --git a/src/sql/workbench/browser/modelComponents/button.component.ts b/src/sql/workbench/browser/modelComponents/button.component.ts index a8465bc7fd..2f25d92e40 100644 --- a/src/sql/workbench/browser/modelComponents/button.component.ts +++ b/src/sql/workbench/browser/modelComponents/button.component.ts @@ -125,6 +125,10 @@ export default class ButtonComponent extends ComponentWithIconBase implements IC this._changeRef.detectChanges(); } + public focus(): void { + this._button.focus(); + } + protected updateIcon() { if (this.iconPath) { if (!this._iconClass) { diff --git a/src/sql/workbench/browser/modelComponents/checkbox.component.ts b/src/sql/workbench/browser/modelComponents/checkbox.component.ts index a0ad9d53c4..51c7337f98 100644 --- a/src/sql/workbench/browser/modelComponents/checkbox.component.ts +++ b/src/sql/workbench/browser/modelComponents/checkbox.component.ts @@ -105,4 +105,8 @@ export default class CheckBoxComponent extends ComponentBase implements ICompone private set label(newValue: string) { this.setPropertyFromUI((properties, label) => { properties.label = label; }, newValue); } + + public focus(): void { + this._input.focus(); + } } diff --git a/src/sql/workbench/browser/modelComponents/componentBase.ts b/src/sql/workbench/browser/modelComponents/componentBase.ts index 3939a9c19e..ede1ef5809 100644 --- a/src/sql/workbench/browser/modelComponents/componentBase.ts +++ b/src/sql/workbench/browser/modelComponents/componentBase.ts @@ -262,6 +262,12 @@ export abstract class ComponentBase extends Disposable implements IComponent, On }); } + public focus(): void { + // Default is to just focus on the native element, components should override this if they + // want their own behavior (such as focusing a particular child element) + (this._el.nativeElement).focus(); + } + protected onkeydown(domNode: HTMLElement, listener: (e: IKeyboardEvent) => void): void { this._register(addDisposableListener(domNode, EventType.KEY_DOWN, (e: KeyboardEvent) => listener(new StandardKeyboardEvent(e)))); } diff --git a/src/sql/workbench/browser/modelComponents/inputbox.component.ts b/src/sql/workbench/browser/modelComponents/inputbox.component.ts index 1099cb743a..0c6a5b0fe3 100644 --- a/src/sql/workbench/browser/modelComponents/inputbox.component.ts +++ b/src/sql/workbench/browser/modelComponents/inputbox.component.ts @@ -325,4 +325,8 @@ export default class InputBoxComponent extends ComponentBase implements ICompone public set stopEnterPropagation(newValue: boolean) { this.setPropertyFromUI((props, value) => props.stopEnterPropagation = value, newValue); } + + public focus(): void { + this.inputElement.focus(); + } } diff --git a/src/sql/workbench/browser/modelComponents/interfaces.ts b/src/sql/workbench/browser/modelComponents/interfaces.ts index 85792ef236..2e428d49e0 100644 --- a/src/sql/workbench/browser/modelComponents/interfaces.ts +++ b/src/sql/workbench/browser/modelComponents/interfaces.ts @@ -27,6 +27,7 @@ export interface IComponent extends IDisposable { validate(): Thenable; setDataProvider(handle: number, componentId: string, context: any): void; refreshDataProvider(item: any): void; + focus(): void; } export const COMPONENT_CONFIG = new InjectionToken('component_config'); diff --git a/src/sql/workbench/browser/modelComponents/radioButton.component.ts b/src/sql/workbench/browser/modelComponents/radioButton.component.ts index a70b42fa93..d394ebc29a 100644 --- a/src/sql/workbench/browser/modelComponents/radioButton.component.ts +++ b/src/sql/workbench/browser/modelComponents/radioButton.component.ts @@ -75,7 +75,6 @@ export default class RadioButtonComponent extends ComponentBase implements IComp this._input.label = this.label; this._input.enabled = this.enabled; this._input.checked = this.checked; - this.focused ? this._input.focus() : this._input.blur(); } // CSS-bound properties @@ -116,11 +115,8 @@ export default class RadioButtonComponent extends ComponentBase implements IComp this.setPropertyFromUI((properties, label) => { properties.name = label; }, newValue); } - public get focused(): boolean { - return this.getPropertyOrDefault((props) => props.focused, false); + public focus(): void { + this._input.focus(); } - public set focused(newValue: boolean) { - this.setPropertyFromUI((properties, value) => { properties.focused = value; }, newValue); - } } diff --git a/src/sql/workbench/browser/modelComponents/table.component.ts b/src/sql/workbench/browser/modelComponents/table.component.ts index 3e95138e9b..87586c9afd 100644 --- a/src/sql/workbench/browser/modelComponents/table.component.ts +++ b/src/sql/workbench/browser/modelComponents/table.component.ts @@ -252,10 +252,6 @@ export default class TableComponent extends ComponentBase implements IComponent, this._table.ariaRole = this.ariaRole; } - if (this.focused) { - this._table.focus(); - } - if (this.updateCells !== undefined) { this.updateTableCells(this.updateCells); } @@ -368,14 +364,6 @@ export default class TableComponent extends ComponentBase implements IComponent, return this.getPropertyOrDefault((props) => props.moveFocusOutWithTab, false); } - public get focused(): boolean { - return this.getPropertyOrDefault((props) => props.focused, false); - } - - public set focused(newValue: boolean) { - this.setPropertyFromUI((properties, value) => { properties.focused = value; }, newValue); - } - public get updateCells(): azdata.TableCell[] { return this.getPropertyOrDefault((props) => props.updateCells, undefined); } diff --git a/src/sql/workbench/browser/modelComponents/viewBase.ts b/src/sql/workbench/browser/modelComponents/viewBase.ts index 42e846a859..2010c33abc 100644 --- a/src/sql/workbench/browser/modelComponents/viewBase.ts +++ b/src/sql/workbench/browser/modelComponents/viewBase.ts @@ -146,4 +146,8 @@ export abstract class ViewBase extends AngularDisposable implements IModelView { public setDataProvider(handle: number, componentId: string, context: any): any { return this.queueAction(componentId, (component) => component.setDataProvider(handle, componentId, context)); } + + public focus(componentId: string): void { + return this.queueAction(componentId, (component) => component.focus()); + } }