fix(list): 🐛 list/tree should show focus outline while context menu is open

Closes: #123771
This commit is contained in:
João Moreno 2021-11-08 15:51:31 +01:00
parent 0961dbb56d
commit 144d7e3f68
No known key found for this signature in database
GPG key ID: 896B853774D1A575
8 changed files with 81 additions and 12 deletions

View file

@ -79,5 +79,12 @@
},
"typescript.tsc.autoDetect": "off",
"testing.autoRun.mode": "rerun",
"conventionalCommits.scopes": ["tree", "scm", "grid", "splitview", "table"]
"conventionalCommits.scopes": [
"tree",
"scm",
"grid",
"splitview",
"table",
"list"
]
}

View file

@ -854,6 +854,7 @@ export class DefaultStyleController implements IStyleController {
content.push(`
.monaco-drag-image,
.monaco-list${suffix}:focus .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; }
.monaco-workbench.context-menu-visible .monaco-list${suffix}.last-focused .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; }
`);
}

View file

@ -19,7 +19,11 @@ export class ContextMenuService extends Disposable implements IContextMenuServic
private contextMenuHandler: ContextMenuHandler;
readonly onDidShowContextMenu = new Emitter<void>().event;
private readonly _onDidShowContextMenu = new Emitter<void>();
readonly onDidShowContextMenu = this._onDidShowContextMenu.event;
private readonly _onDidHideContextMenu = new Emitter<void>();
readonly onDidHideContextMenu = this._onDidHideContextMenu.event;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@ -40,7 +44,17 @@ export class ContextMenuService extends Disposable implements IContextMenuServic
// ContextMenu
showContextMenu(delegate: IContextMenuDelegate): void {
this.contextMenuHandler.showContextMenu(delegate);
this.contextMenuHandler.showContextMenu({
...delegate,
onHide: (didCancel) => {
if (delegate.onHide) {
delegate.onHide(didCancel);
}
this._onDidHideContextMenu.fire();
}
});
ModifierKeyEmitter.getInstance().resetKeyStatus();
this._onDidShowContextMenu.fire();
}
}

View file

@ -42,6 +42,7 @@ export interface IContextMenuService {
readonly _serviceBrand: undefined;
readonly onDidShowContextMenu: Event<void>;
readonly onDidHideContextMenu: Event<void>;
showContextMenu(delegate: IContextMenuDelegate): void;
}

View file

@ -62,7 +62,16 @@ export class ListService implements IListService {
return this._lastFocusedWidget;
}
constructor(@IThemeService private readonly _themeService: IThemeService) {
constructor(@IThemeService private readonly _themeService: IThemeService) { }
private setLastFocusedList(widget: WorkbenchListWidget | undefined): void {
if (widget === this._lastFocusedWidget) {
return;
}
this._lastFocusedWidget?.getHTMLElement().classList.remove('last-focused');
this._lastFocusedWidget = widget;
this._lastFocusedWidget?.getHTMLElement().classList.add('last-focused');
}
register(widget: WorkbenchListWidget, extraContextKeys?: (IContextKey<boolean>)[]): IDisposable {
@ -83,16 +92,16 @@ export class ListService implements IListService {
// Check for currently being focused
if (widget.getHTMLElement() === document.activeElement) {
this._lastFocusedWidget = widget;
this.setLastFocusedList(widget);
}
return combinedDisposable(
widget.onDidFocus(() => this._lastFocusedWidget = widget),
widget.onDidFocus(() => this.setLastFocusedList(widget)),
toDisposable(() => this.lists.splice(this.lists.indexOf(registeredList), 1)),
widget.onDidDispose(() => {
this.lists = this.lists.filter(l => l !== registeredList);
if (this._lastFocusedWidget === widget) {
this._lastFocusedWidget = undefined;
this.setLastFocusedList(undefined);
}
})
);

View file

@ -0,0 +1,28 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DisposableStore } from 'vs/base/common/lifecycle';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
class ContextMenuContribution implements IWorkbenchContribution {
private readonly disposables = new DisposableStore();
constructor(
@ILayoutService layoutService: ILayoutService,
@IContextMenuService contextMenuService: IContextMenuService
) {
const update = (visible: boolean) => layoutService.container.classList.toggle('context-menu-visible', visible);
contextMenuService.onDidShowContextMenu(() => update(true), null, this.disposables);
contextMenuService.onDidHideContextMenu(() => update(false), null, this.disposables);
}
}
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
.registerWorkbenchContribution(ContextMenuContribution, LifecyclePhase.Eventually);

View file

@ -24,7 +24,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { stripIcons } from 'vs/base/common/iconLabels';
import { coalesce } from 'vs/base/common/arrays';
import { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
export class ContextMenuService extends Disposable implements IContextMenuService {
@ -32,8 +32,8 @@ export class ContextMenuService extends Disposable implements IContextMenuServic
private impl: IContextMenuService;
private readonly _onDidShowContextMenu = this._register(new Emitter<void>());
readonly onDidShowContextMenu = this._onDidShowContextMenu.event;
get onDidShowContextMenu(): Event<void> { return this.impl.onDidShowContextMenu; }
get onDidHideContextMenu(): Event<void> { return this.impl.onDidHideContextMenu; }
constructor(
@INotificationService notificationService: INotificationService,
@ -58,7 +58,6 @@ export class ContextMenuService extends Disposable implements IContextMenuServic
showContextMenu(delegate: IContextMenuDelegate): void {
this.impl.showContextMenu(delegate);
this._onDidShowContextMenu.fire();
}
}
@ -66,7 +65,11 @@ class NativeContextMenuService extends Disposable implements IContextMenuService
declare readonly _serviceBrand: undefined;
readonly onDidShowContextMenu = new Emitter<void>().event;
private readonly _onDidShowContextMenu = new Emitter<void>();
readonly onDidShowContextMenu = this._onDidShowContextMenu.event;
private readonly _onDidHideContextMenu = new Emitter<void>();
readonly onDidHideContextMenu = this._onDidHideContextMenu.event;
constructor(
@INotificationService private readonly notificationService: INotificationService,
@ -85,6 +88,7 @@ class NativeContextMenuService extends Disposable implements IContextMenuService
}
dom.ModifierKeyEmitter.getInstance().resetKeyStatus();
this._onDidHideContextMenu.fire();
});
const menu = this.createMenu(delegate, actions, onHide);
@ -120,6 +124,8 @@ class NativeContextMenuService extends Disposable implements IContextMenuService
y: Math.floor(y),
positioningItem: delegate.autoSelectFirstItem ? 0 : undefined,
}, () => onHide());
this._onDidShowContextMenu.fire();
}
}

View file

@ -157,6 +157,9 @@ import 'vs/workbench/contrib/preferences/browser/preferencesSearch';
// Performance
import 'vs/workbench/contrib/performance/browser/performance.contribution';
// Context Menus
import 'vs/workbench/contrib/contextmenu/browser/contextmenu.contribution';
// Notebook
import 'vs/workbench/contrib/notebook/browser/notebook.contribution';