diff --git a/src/sql/base/browser/ui/panel/panel.component.ts b/src/sql/base/browser/ui/panel/panel.component.ts index 25ee178e83..a1ab75534e 100644 --- a/src/sql/base/browser/ui/panel/panel.component.ts +++ b/src/sql/base/browser/ui/panel/panel.component.ts @@ -8,8 +8,6 @@ import { Input, EventEmitter, Output, ViewChild, ElementRef } from '@angular/core'; -import './panelStyles'; - import { TabComponent } from './tab.component'; import { ScrollableDirective } from 'sql/base/browser/ui/scrollable/scrollable.directive'; import { subscriptionToDisposable } from 'sql/base/node/lifecycle'; diff --git a/src/sql/base/browser/ui/panel/panel.ts b/src/sql/base/browser/ui/panel/panel.ts index 2070cd4a78..1e515dcb44 100644 --- a/src/sql/base/browser/ui/panel/panel.ts +++ b/src/sql/base/browser/ui/panel/panel.ts @@ -3,17 +3,23 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IThemable } from 'vs/platform/theme/common/styler'; +import 'vs/css!./media/panel'; + import { Event, Emitter } from 'vs/base/common/event'; import * as DOM from 'vs/base/browser/dom'; import { IAction } from 'vs/base/common/actions'; import { IActionOptions, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; -import './panelStyles'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Color } from 'vs/base/common/color'; -export interface IPanelStyles { +export interface ITabbedPanelStyles { + titleActiveForeground?: Color; + titleActiveBorder?: Color; + titleInactiveForeground?: Color; + focusBorder?: Color; + outline?: Color; } export interface IPanelOptions { @@ -45,7 +51,7 @@ const defaultOptions: IPanelOptions = { export type PanelTabIdentifier = string; -export class TabbedPanel extends Disposable implements IThemable { +export class TabbedPanel extends Disposable { private _tabMap = new Map(); private _shownTabId?: PanelTabIdentifier; public readonly headersize = 35; @@ -57,6 +63,7 @@ export class TabbedPanel extends Disposable implements IThemable { private _currentDimensions: DOM.Dimension; private _collapsed = false; private _headerVisible: boolean; + private _styleElement: HTMLStyleElement; private _onTabChange = new Emitter(); public onTabChange: Event = this._onTabChange.event; @@ -66,6 +73,7 @@ export class TabbedPanel extends Disposable implements IThemable { constructor(container: HTMLElement, private options: IPanelOptions = defaultOptions) { super(); this.parent = DOM.$('.tabbedPanel'); + this._styleElement = DOM.createStyleSheet(this.parent); container.appendChild(this.parent); this.header = DOM.$('.composite.title'); this.tabList = DOM.$('.tabList'); @@ -92,6 +100,7 @@ export class TabbedPanel extends Disposable implements IThemable { this.tabList.remove(); this.body.remove(); this.parent.remove(); + this._styleElement.remove(); } public contains(tab: IPanelTab): boolean { @@ -216,8 +225,60 @@ export class TabbedPanel extends Disposable implements IThemable { } } - public style(styles: IPanelStyles): void { + public style(styles: ITabbedPanelStyles): void { + const content: string[] = []; + if (styles.titleActiveForeground && styles.titleActiveBorder) { + content.push(` + .tabbedPanel > .title .tabList .tab:hover .tabLabel, + .tabbedPanel > .title .tabList .tab .tabLabel.active { + color: ${styles.titleActiveForeground}; + border-bottom-color: ${styles.titleActiveBorder}; + border-bottom-width: 2px; + } + + .tabbedPanel > .title .tabList .tab-header.active { + outline: none; + }`); + } + + if (styles.titleInactiveForeground) { + content.push(` + .tabbedPanel > .title .tabList .tab .tabLabel { + color: ${styles.titleInactiveForeground}; + }`); + } + + if (styles.focusBorder && styles.titleActiveForeground) { + content.push(` + .tabbedPanel > .title .tabList .tab .tabLabel:focus { + color: ${styles.titleActiveForeground}; + border-bottom-color: ${styles.focusBorder} !important; + border-bottom: 1px solid; + outline: none; + }`); + } + + if (styles.outline) { + content.push(` + .tabbedPanel > .title .tabList .tab-header.active, + .tabbedPanel > .title .tabList .tab-header:hover { + outline-color: ${styles.outline}; + outline-width: 1px; + outline-style: solid; + padding-bottom: 0; + outline-offset: -5px; + } + + .tabbedPanel > .title .tabList .tab-header:hover:not(.active) { + outline-style: dashed; + }`); + } + + const newStyles = content.join('\n'); + if (newStyles !== this._styleElement.innerHTML) { + this._styleElement.innerHTML = newStyles; + } } public layout(dimension: DOM.Dimension): void { diff --git a/src/sql/base/browser/ui/panel/panelStyles.ts b/src/sql/base/browser/ui/panel/panelStyles.ts deleted file mode 100644 index 09c5bfb0b2..0000000000 --- a/src/sql/base/browser/ui/panel/panelStyles.ts +++ /dev/null @@ -1,73 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import 'vs/css!./media/panel'; - -import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; -import { PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER } from 'vs/workbench/common/theme'; -import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; - -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { - - // Title Active - const titleActive = theme.getColor(PANEL_ACTIVE_TITLE_FOREGROUND); - const titleActiveBorder = theme.getColor(PANEL_ACTIVE_TITLE_BORDER); - if (titleActive || titleActiveBorder) { - collector.addRule(` - .tabbedPanel > .title .tabList .tab:hover .tabLabel, - .tabbedPanel > .title .tabList .tab .tabLabel.active { - color: ${titleActive}; - border-bottom-color: ${titleActiveBorder}; - border-bottom-width: 2px; - } - - .tabbedPanel > .title .tabList .tab-header.active { - outline: none; - } - `); - } - - // Title Inactive - const titleInactive = theme.getColor(PANEL_INACTIVE_TITLE_FOREGROUND); - if (titleInactive) { - collector.addRule(` - .tabbedPanel > .title .tabList .tab .tabLabel { - color: ${titleInactive}; - } - `); - } - - // Title focus - const focusBorderColor = theme.getColor(focusBorder); - if (focusBorderColor) { - collector.addRule(` - .tabbedPanel > .title .tabList .tab .tabLabel:focus { - color: ${titleActive}; - border-bottom-color: ${focusBorderColor} !important; - border-bottom: 1px solid; - outline: none; - } - `); - } - - // Styling with Outline color (e.g. high contrast theme) - const outline = theme.getColor(activeContrastBorder); - if (outline) { - collector.addRule(` - .tabbedPanel > .title .tabList .tab-header.active, - .tabbedPanel > .title .tabList .tab-header:hover { - outline-color: ${outline}; - outline-width: 1px; - outline-style: solid; - padding-bottom: 0; - outline-offset: -5px; - } - - .tabbedPanel > .title .tabList .tab-header:hover:not(.active) { - outline-style: dashed; - } - `); - } -}); diff --git a/src/sql/platform/dialog/dialogModal.ts b/src/sql/platform/dialog/dialogModal.ts index f0fe3a2390..e139def20b 100644 --- a/src/sql/platform/dialog/dialogModal.ts +++ b/src/sql/platform/dialog/dialogModal.ts @@ -112,7 +112,7 @@ export class DialogModal extends Modal { const body = append(container, $('div.dialogModal-body')); this._dialogPane = new DialogPane(this._dialog.title, this._dialog.content, - valid => this._dialog.notifyValidityChanged(valid), this._instantiationService, false); + valid => this._dialog.notifyValidityChanged(valid), this._instantiationService, this._themeService, false); this._dialogPane.createBody(body); } diff --git a/src/sql/platform/dialog/dialogPane.ts b/src/sql/platform/dialog/dialogPane.ts index 2888448e9b..38fb442574 100644 --- a/src/sql/platform/dialog/dialogPane.ts +++ b/src/sql/platform/dialog/dialogPane.ts @@ -3,8 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import 'vs/css!./media/dialogModal'; import { NgModuleRef } from '@angular/core'; @@ -21,6 +19,8 @@ import { IThemable } from 'vs/platform/theme/common/styler'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Emitter } from 'vs/base/common/event'; +import { attachTabbedPanelStyler } from 'sql/platform/theme/common/styler'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; export class DialogPane extends Disposable implements IThemable { private _tabbedPanel: TabbedPanel; @@ -40,6 +40,7 @@ export class DialogPane extends Disposable implements IThemable { private _content: string | DialogTab[], private _validityChangedCallback: (valid: boolean) => void, private _instantiationService: IInstantiationService, + private themeService: IThemeService, public displayPageTitle: boolean, public description?: string, ) { @@ -53,6 +54,7 @@ export class DialogPane extends Disposable implements IThemable { this.initializeModelViewContainer(this._body, modelViewId); } else { this._tabbedPanel = new TabbedPanel(this._body); + attachTabbedPanelStyler(this._tabbedPanel, this.themeService); this._content.forEach((tab, tabIndex) => { if (this._selectedTabIndex === tabIndex) { this._selectedTabContent = tab.content; diff --git a/src/sql/platform/dialog/wizardModal.ts b/src/sql/platform/dialog/wizardModal.ts index 1ee9c0441b..3d58d5ac56 100644 --- a/src/sql/platform/dialog/wizardModal.ts +++ b/src/sql/platform/dialog/wizardModal.ts @@ -156,7 +156,7 @@ export class WizardModal extends Modal { } private registerPage(page: WizardPage): void { - let dialogPane = new DialogPane(page.title, page.content, valid => page.notifyValidityChanged(valid), this._instantiationService, this._wizard.displayPageTitles, page.description); + let dialogPane = new DialogPane(page.title, page.content, valid => page.notifyValidityChanged(valid), this._instantiationService, this._themeService, this._wizard.displayPageTitles, page.description); dialogPane.createBody(this._pageContainer); this._dialogPanes.set(page, dialogPane); page.onUpdate(() => this.setButtonsForPage(this._wizard.currentPage)); diff --git a/src/sql/platform/theme/common/styler.ts b/src/sql/platform/theme/common/styler.ts index d995d825e1..7f8ddf823e 100644 --- a/src/sql/platform/theme/common/styler.ts +++ b/src/sql/platform/theme/common/styler.ts @@ -3,15 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import * as sqlcolors from './colors'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import * as cr from 'vs/platform/theme/common/colorRegistry'; import { IThemable, attachStyler } from 'vs/platform/theme/common/styler'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { SIDE_BAR_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme'; +import { SIDE_BAR_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_DRAG_AND_DROP_BACKGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND } from 'vs/workbench/common/theme'; import { IPanelColors } from 'vs/workbench/browser/parts/views/panelViewlet'; export function attachModalDialogStyler(widget: IThemable, themeService: IThemeService, style?: @@ -281,3 +279,13 @@ export function attachPanelStyler(widget: IThemable, themeService: IThemeService dropBackground: SIDE_BAR_DRAG_AND_DROP_BACKGROUND }, widget); } + +export function attachTabbedPanelStyler(widget: IThemable, themeService: IThemeService) { + return attachStyler(themeService, { + titleActiveForeground: PANEL_ACTIVE_TITLE_FOREGROUND, + titleActiveBorder: PANEL_ACTIVE_TITLE_BORDER, + titleInactiveForeground: PANEL_INACTIVE_TITLE_FOREGROUND, + focusBorder: cr.focusBorder, + outline: cr.activeContrastBorder + }, widget); +} diff --git a/src/sql/workbench/electron-browser/modelComponents/modelViewInput.ts b/src/sql/workbench/electron-browser/modelComponents/modelViewInput.ts index 3491adfbeb..254d12033c 100644 --- a/src/sql/workbench/electron-browser/modelComponents/modelViewInput.ts +++ b/src/sql/workbench/electron-browser/modelComponents/modelViewInput.ts @@ -13,6 +13,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { DialogPane } from 'sql/platform/dialog/dialogPane'; import { Emitter, Event } from 'vs/base/common/event'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; export type ModeViewSaveHandler = (handle: number) => Thenable; @@ -56,7 +57,8 @@ export class ModelViewInput extends EditorInput { constructor(private _title: string, private _model: ModelViewInputModel, private _options: azdata.ModelViewEditorOptions, @IInstantiationService private _instantiationService: IInstantiationService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @IThemeService private readonly themeService: IThemeService ) { super(); this._model.onDidChangeDirty(() => this._onDidChangeDirty.fire()); @@ -107,7 +109,7 @@ export class ModelViewInput extends EditorInput { private createDialogPane(): void { this._dialogPaneContainer = DOM.$('div.model-view-container'); - this._dialogPane = new DialogPane(this.title, this.modelViewId, () => undefined, this._instantiationService, false); + this._dialogPane = new DialogPane(this.title, this.modelViewId, () => undefined, this._instantiationService, this.themeService, false); this._dialogPane.createBody(this._dialogPaneContainer); } diff --git a/src/sql/workbench/parts/profiler/browser/profilerEditor.ts b/src/sql/workbench/parts/profiler/browser/profilerEditor.ts index 0b4d34df12..e6ccd1b625 100644 --- a/src/sql/workbench/parts/profiler/browser/profilerEditor.ts +++ b/src/sql/workbench/parts/profiler/browser/profilerEditor.ts @@ -9,7 +9,7 @@ import { Table } from 'sql/base/browser/ui/table/table'; import { TableDataView } from 'sql/base/browser/ui/table/tableDataView'; import { IProfilerService, IProfilerViewTemplate } from 'sql/workbench/services/profiler/common/interfaces'; import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar'; -import { attachTableStyler } from 'sql/platform/theme/common/styler'; +import { attachTableStyler, attachTabbedPanelStyler } from 'sql/platform/theme/common/styler'; import { IProfilerStateChangedEvent } from 'sql/workbench/parts/profiler/common/profilerState'; import { ProfilerTableEditor, ProfilerTableViewState } from 'sql/workbench/parts/profiler/browser/profilerTableEditor'; import * as Actions from 'sql/workbench/parts/profiler/browser/profilerActions'; @@ -335,6 +335,7 @@ export class ProfilerEditor extends BaseEditor { let tabbedPanelContainer = document.createElement('div'); tabbedPanelContainer.className = 'profiler-tabbedPane'; this._tabbedPanel = new TabbedPanel(tabbedPanelContainer); + attachTabbedPanelStyler(this._tabbedPanel, this.themeService); this._tabbedPanel.pushTab({ identifier: 'editor', title: nls.localize('text', "Text"), diff --git a/src/sql/workbench/parts/query/browser/queryResultsEditor.ts b/src/sql/workbench/parts/query/browser/queryResultsEditor.ts index ae95b83fc2..272d4f992a 100644 --- a/src/sql/workbench/parts/query/browser/queryResultsEditor.ts +++ b/src/sql/workbench/parts/query/browser/queryResultsEditor.ts @@ -131,7 +131,7 @@ export class QueryResultsEditor extends BaseEditor { this.styleSheet.remove(); parent.appendChild(this.styleSheet); if (!this.resultsView) { - this.resultsView = this._register(new QueryResultsView(parent, this._instantiationService, this._queryModelService)); + this.resultsView = this._register(this._instantiationService.createInstance(QueryResultsView, parent)); } } diff --git a/src/sql/workbench/parts/query/browser/queryResultsView.ts b/src/sql/workbench/parts/query/browser/queryResultsView.ts index bb54a1cebd..35bf0ed810 100644 --- a/src/sql/workbench/parts/query/browser/queryResultsView.ts +++ b/src/sql/workbench/parts/query/browser/queryResultsView.ts @@ -20,6 +20,8 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import * as DOM from 'vs/base/browser/dom'; import { Event } from 'vs/base/common/event'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; +import { attachTabbedPanelStyler } from 'sql/platform/theme/common/styler'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; class ResultsView extends Disposable implements IPanelView { private panelViewlet: PanelViewlet; @@ -182,7 +184,8 @@ export class QueryResultsView extends Disposable { constructor( container: HTMLElement, @IInstantiationService private instantiationService: IInstantiationService, - @IQueryModelService private queryModelService: IQueryModelService + @IQueryModelService private queryModelService: IQueryModelService, + @IThemeService themeService: IThemeService ) { super(); this.resultsTab = this._register(new ResultsTab(instantiationService)); @@ -191,6 +194,8 @@ export class QueryResultsView extends Disposable { this.qpTab = this._register(new QueryPlanTab()); this.topOperationsTab = this._register(new TopOperationsTab(instantiationService)); + attachTabbedPanelStyler(this._panelView, themeService); + this._panelView.pushTab(this.resultsTab); this._register(this._panelView.onTabChange(e => { if (this.input) { diff --git a/src/sql/workbench/parts/restore/browser/restoreDialog.ts b/src/sql/workbench/parts/restore/browser/restoreDialog.ts index 473e1d481c..ed64b9ed3d 100644 --- a/src/sql/workbench/parts/restore/browser/restoreDialog.ts +++ b/src/sql/workbench/parts/restore/browser/restoreDialog.ts @@ -32,7 +32,7 @@ import { Table } from 'sql/base/browser/ui/table/table'; import { TableDataView } from 'sql/base/browser/ui/table/tableDataView'; import * as DialogHelper from 'sql/workbench/browser/modal/dialogHelper'; import { Modal } from 'sql/workbench/browser/modal/modal'; -import { attachButtonStyler, attachModalDialogStyler, attachTableStyler, attachInputBoxStyler, attachSelectBoxStyler, attachEditableDropdownStyler, attachCheckboxStyler } from 'sql/platform/theme/common/styler'; +import { attachButtonStyler, attachModalDialogStyler, attachTableStyler, attachInputBoxStyler, attachSelectBoxStyler, attachEditableDropdownStyler, attachCheckboxStyler, attachTabbedPanelStyler } from 'sql/platform/theme/common/styler'; import * as TelemetryKeys from 'sql/platform/telemetry/telemetryKeys'; import * as BackupConstants from 'sql/workbench/parts/backup/common/constants'; import { RestoreViewModel, RestoreOptionParam, SouceDatabaseNamesParam } from 'sql/workbench/parts/restore/browser/restoreViewModel'; @@ -326,6 +326,7 @@ export class RestoreDialog extends Modal { const restorePanel = DOM.$('.restore-panel'); container.appendChild(restorePanel); this._panel = new TabbedPanel(restorePanel); + attachTabbedPanelStyler(this._panel, this._themeService); this._generalTabId = this._panel.pushTab({ identifier: 'general', title: localize('generalTitle', 'General'), diff --git a/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts b/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts index 98bfe87279..afecdc53e5 100644 --- a/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts +++ b/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/connectionDialog'; import { Button } from 'sql/base/browser/ui/button/button'; -import { attachModalDialogStyler, attachButtonStyler } from 'sql/platform/theme/common/styler'; +import { attachModalDialogStyler, attachButtonStyler, attachTabbedPanelStyler } from 'sql/platform/theme/common/styler'; import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { Modal } from 'sql/workbench/browser/modal/modal'; @@ -153,6 +153,7 @@ export class ConnectionDialogWidget extends Modal { DOM.hide(this._savedConnection); this._panel = new TabbedPanel(this._body); + attachTabbedPanelStyler(this._panel, this._themeService); this._recentConnectionTabId = this._panel.pushTab({ identifier: 'recent_connection', title: localize('recentConnectionTitle', "Recent Connections"), diff --git a/src/sqltest/platform/dialog/dialogPane.test.ts b/src/sqltest/platform/dialog/dialogPane.test.ts index ae9def96d8..9b7cab7930 100644 --- a/src/sqltest/platform/dialog/dialogPane.test.ts +++ b/src/sqltest/platform/dialog/dialogPane.test.ts @@ -10,6 +10,7 @@ import { Dialog, DialogTab } from 'sql/platform/dialog/dialogTypes'; import { DialogPane } from 'sql/platform/dialog/dialogPane'; import { DialogComponentParams } from 'sql/platform/dialog/dialogContainer.component'; import { bootstrapAngular } from 'sql/platform/bootstrap/node/bootstrapService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; interface BootstrapAngular { @@ -41,7 +42,8 @@ suite('Dialog Pane Tests', () => { bootstrapCalls++; }); dialog.content = modelViewId; - let dialogPane = new DialogPane(dialog.title, dialog.content, () => undefined, undefined, false); + const themeService = new TestThemeService(); + let dialogPane = new DialogPane(dialog.title, dialog.content, () => undefined, undefined, themeService, false); dialogPane.createBody(container); assert.equal(bootstrapCalls, 1); }); @@ -55,7 +57,8 @@ suite('Dialog Pane Tests', () => { bootstrapCalls++; }); dialog.content = [new DialogTab('', modelViewId)]; - let dialogPane = new DialogPane(dialog.title, dialog.content, () => undefined, undefined, false); + const themeService = new TestThemeService(); + let dialogPane = new DialogPane(dialog.title, dialog.content, () => undefined, undefined, themeService, false); dialogPane.createBody(container); assert.equal(bootstrapCalls, 1); }); @@ -71,7 +74,8 @@ suite('Dialog Pane Tests', () => { let modelViewId1 = 'test_content_1'; let modelViewId2 = 'test_content_2'; dialog.content = [new DialogTab('tab1', modelViewId1), new DialogTab('tab2', modelViewId2)]; - let dialogPane = new DialogPane(dialog.title, dialog.content, valid => dialog.notifyValidityChanged(valid), undefined, false); + const themeService = new TestThemeService(); + let dialogPane = new DialogPane(dialog.title, dialog.content, valid => dialog.notifyValidityChanged(valid), undefined, themeService, false); dialogPane.createBody(container); let validityChanges: boolean[] = [];