diff --git a/src/sql/base/browser/ui/panel/media/panel.css b/src/sql/base/browser/ui/panel/media/panel.css index 1e68420360..7c9a6551f1 100644 --- a/src/sql/base/browser/ui/panel/media/panel.css +++ b/src/sql/base/browser/ui/panel/media/panel.css @@ -70,15 +70,15 @@ panel { font-weight: 600; } -.tabbedPanel .tabList .tab-header:hover:not(.active) { +.tabbedPanel .tabList .tab-header:hover:not(.selected) { background-color: #dcdcdc; outline: none; } -.vs-dark .tabbedPanel .tabList .tab-header:hover:not(.active) { +.vs-dark .tabbedPanel .tabList .tab-header:hover:not(.selected) { background-color: #2a2d2e; outline: none; } -.hc-black .tabbedPanel .tabList .tab-header:hover:not(.active) { +.hc-black .tabbedPanel .tabList .tab-header:hover:not(.selected) { background-color: initial; outline: 1px dashed #f38518; outline-offset: -3px; @@ -135,7 +135,7 @@ panel { flex: 0 0 auto; } -.tab > .tabLabel.active { +.tab > .tabLabel.selected { border-bottom: 1px solid; } diff --git a/src/sql/base/browser/ui/panel/media/tabHeader.css b/src/sql/base/browser/ui/panel/media/tabHeader.css index 35fa24986f..4ca112e6e5 100644 --- a/src/sql/base/browser/ui/panel/media/tabHeader.css +++ b/src/sql/base/browser/ui/panel/media/tabHeader.css @@ -13,7 +13,7 @@ tab-header .action-item { line-height: 1.4em; } -tab-header .tab-header.active .action-label, /* always show it for active tab */ +tab-header .tab-header.selected .action-label, /* always show it for selected tab */ tab-header .tab-header:hover .action-label, /* always show it on hover */ tab-header .tab-header:focus .action-label { /* always show it on focus */ opacity: 1; diff --git a/src/sql/base/browser/ui/panel/panel.component.ts b/src/sql/base/browser/ui/panel/panel.component.ts index 4aba758dfc..9534820890 100644 --- a/src/sql/base/browser/ui/panel/panel.component.ts +++ b/src/sql/base/browser/ui/panel/panel.component.ts @@ -57,10 +57,10 @@ let idPool = 0;
-
+
- +
@@ -102,7 +102,7 @@ export class PanelComponent extends Disposable implements IThemable { @Output() public onTabChange = new EventEmitter(); @Output() public onTabClose = new EventEmitter(); - private _activeTab?: TabComponent; + private _selectedTab?: TabComponent; private _actionbar?: ActionBar; private _mru: TabComponent[] = []; private _tabExpanded: boolean = true; @@ -218,39 +218,43 @@ export class PanelComponent extends Disposable implements IThemable { } }); - if (this._activeTab && tab === this._activeTab) { + if (this._selectedTab && tab === this._selectedTab) { this.onTabChange.emit(tab); return; } this._zone.run(() => { - if (this._activeTab) { - this._activeTab.active = false; + if (this._selectedTab) { + this._selectedTab.selected = false; } - this._activeTab = tab; + this._selectedTab = tab; this.setMostRecentlyUsed(tab); - this._activeTab.active = true; + this._selectedTab.selected = true; this.onTabChange.emit(tab); }); + + this._tabHeaders?.forEach(tabHeader => { + tabHeader.tabIndex = tabHeader.tab.identifier === foundTab.identifier ? 0 : -1; + }); } } } /** - * Get the id of the active tab + * Get the id of the selected tab */ - public get getActiveTab(): string | undefined { - return this._activeTab?.identifier; + public get getSelectedTab(): string | undefined { + return this._selectedTab?.identifier; } /** * Select on the next tab */ public selectOnNextTab(): void { - let activeIndex = this._tabs.toArray().findIndex(i => i === this._activeTab); - let nextTabIndex = activeIndex + 1; + let selectedIndex = this._tabs.toArray().findIndex(i => i === this._selectedTab); + let nextTabIndex = selectedIndex + 1; if (nextTabIndex === this._tabs.length) { nextTabIndex = 0; } @@ -315,7 +319,7 @@ export class PanelComponent extends Disposable implements IThemable { } public layout() { - this._activeTab?.layout(); + this._selectedTab?.layout(); } onKey(e: KeyboardEvent): void { @@ -328,15 +332,34 @@ export class PanelComponent extends Disposable implements IThemable { this.focusPreviousTab(); eventHandled = true; } - if (eventHandled) { event.preventDefault(); event.stopPropagation(); } } + onTabHeaderFocusOut(e: Event): void { + /** + * Making the selected tab header focusable when the focus leaves the tab header div. + * This fixes an issue when users press up/left arrow in vertical tab header and move up to + * the previous tab header. The next focus was being set to the selected tab and then the tab + * contents. Now, the focus will directly move to tab contents. And, when users press + * shift-tab on the first focusable element of tab content, the focus will move back to + * selected tab header. + */ + + if (!(e.currentTarget).contains((e).relatedTarget)) { + this._tabHeaders.forEach(th => { + if (th.tab === this._selectedTab) { + th.tabIndex = 0; + } + }); + } + } + private focusPreviousTab(): void { const currentIndex = this.focusedTabHeaderIndex; + this._tabHeaders.toArray()[currentIndex].tabIndex = -1; if (currentIndex !== -1) { // Move to the previous tab, if we are at the first tab then move to the last tab. this.focusOnTabHeader(currentIndex === 0 ? this._tabHeaders.length - 1 : currentIndex - 1); @@ -345,6 +368,7 @@ export class PanelComponent extends Disposable implements IThemable { private focusNextTab(): void { const currentIndex = this.focusedTabHeaderIndex; + this._tabHeaders.toArray()[currentIndex].tabIndex = -1; if (currentIndex !== -1) { // Move to the next tab, if we are at the last tab then move to the first tab. this.focusOnTabHeader(currentIndex === this._tabHeaders.length - 1 ? 0 : currentIndex + 1); @@ -366,32 +390,36 @@ export class PanelComponent extends Disposable implements IThemable { style(styles: ITabbedPanelStyles) { if (this._styleElement) { const content: string[] = []; - if (styles.titleInactiveForeground) { + if (styles.titleUnSelectedForeground) { content.push(`.tabbedPanel.horizontal > .title .tabList .tab-header { - color: ${styles.titleInactiveForeground} + color: ${styles.titleUnSelectedForeground} }`); } - if (styles.titleActiveBorder && styles.titleActiveForeground) { - content.push(`.tabbedPanel.horizontal > .title .tabList .tab-header:focus, - .tabbedPanel.horizontal > .title .tabList .tab-header.active { - border-color: ${styles.titleActiveBorder}; + if (styles.titleSelectedBorder && styles.titleSelectedForeground) { + content.push(`.tabbedPanel.horizontal > .title .tabList .tab-header.selected { + border-color: ${styles.titleSelectedBorder}; border-style: solid; - color: ${styles.titleActiveForeground} + color: ${styles.titleSelectedForeground} }`); - content.push(`.tabbedPanel.horizontal > .title .tabList .tab-header:focus, - .tabbedPanel.horizontal > .title .tabList .tab-header.active {; - border-width: 0 0 ${styles.activeTabContrastBorder ? '0' : '2'}px 0; + content.push(`.tabbedPanel.horizontal > .title .tabList .tab-header.selected {; + border-width: 0 0 ${styles.selectedTabContrastBorder ? '0' : '2'}px 0; }`); content.push(`.tabbedPanel.horizontal > .title .tabList .tab-header:hover { - color: ${styles.titleActiveForeground} + color: ${styles.titleSelectedForeground} + }`); + + content.push(`.tabbedPanel.horizontal > .title .tabList .tab-header:focus { + outline: 1px solid; + outline-offset: 2px; + outline-color: ${styles.titleSelectedBorder}; }`); } - if (styles.activeBackgroundForVerticalLayout) { - content.push(`.tabbedPanel.vertical > .title .tabList .tab-header.active { - background-color:${styles.activeBackgroundForVerticalLayout} + if (styles.selectedBackgroundForVerticalLayout) { + content.push(`.tabbedPanel.vertical > .title .tabList .tab-header.selected { + background-color:${styles.selectedBackgroundForVerticalLayout} }`); } @@ -407,18 +435,14 @@ export class PanelComponent extends Disposable implements IThemable { }`); } - if (styles.activeTabContrastBorder) { + if (styles.selectedTabContrastBorder) { content.push(` - .tabbedPanel > .title .tabList .tab-header.active { + .tabbedPanel > .title .tabList .tab-header.selected { outline: 1px solid; outline-offset: -3px; - outline-color: ${styles.activeTabContrastBorder}; + outline-color: ${styles.selectedTabContrastBorder}; } `); - } else { - content.push(`.tabbedPanel.horizontal > .title .tabList .tab-header:focus { - outline-width: 0px; - }`); } const newStyles = content.join('\n'); diff --git a/src/sql/base/browser/ui/panel/panel.ts b/src/sql/base/browser/ui/panel/panel.ts index b55c65ef14..7be5f58286 100644 --- a/src/sql/base/browser/ui/panel/panel.ts +++ b/src/sql/base/browser/ui/panel/panel.ts @@ -16,14 +16,14 @@ import { Color } from 'vs/base/common/color'; import { isUndefinedOrNull } from 'vs/base/common/types'; export interface ITabbedPanelStyles { - titleActiveForeground?: Color; - titleActiveBorder?: Color; - titleInactiveForeground?: Color; + titleSelectedForeground?: Color; + titleSelectedBorder?: Color; + titleUnSelectedForeground?: Color; focusBorder?: Color; outline?: Color; - activeBackgroundForVerticalLayout?: Color; + selectedBackgroundForVerticalLayout?: Color; border?: Color; - activeTabContrastBorder?: Color; + selectedTabContrastBorder?: Color; } export interface IPanelOptions { @@ -108,7 +108,7 @@ export class TabbedPanel extends Disposable { return this.parent; } - public get activeTabId(): string | undefined { + public get selectedTabId(): string | undefined { return this._shownTabId; } @@ -209,8 +209,8 @@ export class TabbedPanel extends Disposable { if (this._shownTabId) { const shownTab = this._tabMap.get(this._shownTabId); if (shownTab) { - shownTab.label.classList.remove('active'); - shownTab.header.classList.remove('active'); + shownTab.label.classList.remove('selected'); + shownTab.header.classList.remove('selected'); shownTab.header.setAttribute('aria-selected', 'false'); shownTab.header.tabIndex = -1; if (shownTab.body) { @@ -239,8 +239,8 @@ export class TabbedPanel extends Disposable { } this.body.appendChild(tab.body); this.body.setAttribute('aria-labelledby', tab.tab.identifier); - tab.label.classList.add('active'); - tab.header.classList.add('active'); + tab.label.classList.add('selected'); + tab.header.classList.add('selected'); tab.header.setAttribute('aria-selected', 'true'); this._onTabChange.fire(id); if (tab.tab.view.onShow) { @@ -329,27 +329,27 @@ export class TabbedPanel extends Disposable { }`); } - if (styles.titleActiveForeground && styles.titleActiveBorder) { + if (styles.titleSelectedForeground && styles.titleSelectedBorder) { content.push(` .tabbedPanel > .title .tabList .tab:hover .tabLabel, - .tabbedPanel > .title .tabList .tab .tabLabel.active { - color: ${styles.titleActiveForeground}; - border-bottom-color: ${styles.titleActiveBorder}; + .tabbedPanel > .title .tabList .tab .tabLabel.selected { + color: ${styles.titleSelectedForeground}; + border-bottom-color: ${styles.titleSelectedBorder}; border-bottom-width: 2px; }`); } - if (styles.titleInactiveForeground) { + if (styles.titleUnSelectedForeground) { content.push(` .tabbedPanel > .title .tabList .tab .tabLabel { - color: ${styles.titleInactiveForeground}; + color: ${styles.titleUnSelectedForeground}; }`); } - if (styles.focusBorder && styles.titleActiveForeground) { + if (styles.focusBorder && styles.titleSelectedForeground) { content.push(` .tabbedPanel > .title .tabList .tab .tabLabel:focus { - color: ${styles.titleActiveForeground}; + color: ${styles.titleSelectedForeground}; border-bottom-color: ${styles.focusBorder} !important; border-bottom: 1px solid; outline: none; @@ -358,7 +358,7 @@ export class TabbedPanel extends Disposable { if (styles.outline) { content.push(` - .tabbedPanel > .title .tabList .tab-header.active, + .tabbedPanel > .title .tabList .tab-header.selected, .tabbedPanel > .title .tabList .tab-header:hover { outline-color: ${styles.outline}; outline-width: 1px; @@ -367,7 +367,7 @@ export class TabbedPanel extends Disposable { outline-offset: -5px; } - .tabbedPanel > .title .tabList .tab-header:hover:not(.active) { + .tabbedPanel > .title .tabList .tab-header:hover:not(.selected) { outline-style: dashed; }`); } diff --git a/src/sql/base/browser/ui/panel/tab.component.ts b/src/sql/base/browser/ui/panel/tab.component.ts index 3761f2d765..338517ca58 100644 --- a/src/sql/base/browser/ui/panel/tab.component.ts +++ b/src/sql/base/browser/ui/panel/tab.component.ts @@ -29,7 +29,7 @@ export class TabComponent implements OnDestroy { @Input() public canClose!: boolean; @Input() public actions?: Array; @Input() public iconClass?: string; - public _active = false; + private _selected = false; @Input() public identifier!: string; @Input() public type: TabType = 'tab'; @Input() private visibilityType: 'if' | 'visibility' = 'if'; @@ -38,7 +38,7 @@ export class TabComponent implements OnDestroy { @ContentChild(TabChild) public set child(tab: TabChild) { this._child = tab; - if (this.active && this._child) { + if (this.selected && this._child) { this._child.layout(); } } @@ -47,21 +47,21 @@ export class TabComponent implements OnDestroy { @Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef ) { } - public set active(val: boolean) { + public set selected(val: boolean) { if (!this.destroyed) { - this._active = val; - if (this.active) { + this._selected = val; + if (this.selected) { this.rendered = true; } this._cd.detectChanges(); - if (this.active && this._child) { + if (this.selected && this._child) { this._child.layout(); } } } - public get active(): boolean { - return this._active; + public get selected(): boolean { + return this._selected; } ngOnDestroy() { @@ -72,7 +72,7 @@ export class TabComponent implements OnDestroy { } shouldBeIfed(): boolean { - if (this.active) { + if (this.selected) { return true; } else if (this.visibilityType === 'visibility' && this.rendered) { return true; @@ -82,7 +82,7 @@ export class TabComponent implements OnDestroy { } shouldBeHidden(): boolean { - if (this.visibilityType === 'visibility' && !this.active) { + if (this.visibilityType === 'visibility' && !this.selected) { return true; } else { return false; diff --git a/src/sql/base/browser/ui/panel/tabHeader.component.ts b/src/sql/base/browser/ui/panel/tabHeader.component.ts index bafd7feb6b..a799a71f60 100644 --- a/src/sql/base/browser/ui/panel/tabHeader.component.ts +++ b/src/sql/base/browser/ui/panel/tabHeader.component.ts @@ -19,10 +19,10 @@ import { CloseTabAction } from 'sql/base/browser/ui/panel/tabActions'; @Component({ selector: 'tab-header', template: ` -