Skip to content

Commit e4e853f

Browse files
authored
Workbench hover to align and correctly apply delay setting (microsoft#204871)
* Workbench hover * 💄 * placement element for tabs and breadcrumbs * enableInstantHoverAfterRecentlyShown * 💄 * adobt custom hover in all toolbars and labels * dropdown hover * widgets * dispose hover delegate uf own instance * testing hover delegate (might be a better way of doing this?) * nullHoverDelegateFactory * registering the disposables and handing down options * Hygiene * fix quickinput
1 parent 7215958 commit e4e853f

File tree

54 files changed

+356
-318
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+356
-318
lines changed

src/vs/base/browser/ui/actionbar/actionViewItems.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { addDisposableListener, EventHelper, EventLike, EventType } from 'vs/bas
99
import { EventType as TouchEventType, Gesture } from 'vs/base/browser/touch';
1010
import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
1111
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
12+
import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate';
1213
import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';
1314
import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover';
1415
import { ISelectBoxOptions, ISelectBoxStyles, ISelectOptionItem, SelectBox } from 'vs/base/browser/ui/selectBox/selectBox';
@@ -224,16 +225,14 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem {
224225
}
225226
const title = this.getTooltip() ?? '';
226227
this.updateAriaLabel();
227-
if (!this.options.hoverDelegate) {
228-
this.element.title = title;
228+
229+
this.element.title = '';
230+
if (!this.customHover) {
231+
const hoverDelegate = this.options.hoverDelegate ?? getDefaultHoverDelegate('element');
232+
this.customHover = setupCustomHover(hoverDelegate, this.element, title);
233+
this._store.add(this.customHover);
229234
} else {
230-
this.element.title = '';
231-
if (!this.customHover) {
232-
this.customHover = setupCustomHover(this.options.hoverDelegate, this.element, title);
233-
this._store.add(this.customHover);
234-
} else {
235-
this.customHover.update(title);
236-
}
235+
this.customHover.update(title);
237236
}
238237
}
239238

src/vs/base/browser/ui/actionbar/actionbar.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import * as DOM from 'vs/base/browser/dom';
77
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
88
import { ActionViewItem, BaseActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems';
9+
import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate';
910
import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';
1011
import { ActionRunner, IAction, IActionRunner, IRunEvent, Separator } from 'vs/base/common/actions';
1112
import { Emitter } from 'vs/base/common/event';
@@ -67,6 +68,7 @@ export interface IActionOptions extends IActionViewItemOptions {
6768
export class ActionBar extends Disposable implements IActionRunner {
6869

6970
private readonly options: IActionBarOptions;
71+
private readonly _hoverDelegate: IHoverDelegate;
7072

7173
private _actionRunner: IActionRunner;
7274
private readonly _actionRunnerDisposables = this._register(new DisposableStore());
@@ -117,6 +119,8 @@ export class ActionBar extends Disposable implements IActionRunner {
117119
keys: this.options.triggerKeys?.keys ?? [KeyCode.Enter, KeyCode.Space]
118120
};
119121

122+
this._hoverDelegate = options.hoverDelegate ?? this._register(getDefaultHoverDelegate('element', true));
123+
120124
if (this.options.actionRunner) {
121125
this._actionRunner = this.options.actionRunner;
122126
} else {
@@ -358,7 +362,7 @@ export class ActionBar extends Disposable implements IActionRunner {
358362

359363
let item: IActionViewItem | undefined;
360364

361-
const viewItemOptions = { hoverDelegate: this.options.hoverDelegate, ...options };
365+
const viewItemOptions = { hoverDelegate: this._hoverDelegate, ...options };
362366
if (this.options.actionViewItemProvider) {
363367
item = this.options.actionViewItemProvider(action, viewItemOptions);
364368
}

src/vs/base/browser/ui/button/button.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { sanitize } from 'vs/base/browser/dompurify/dompurify';
99
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
1010
import { renderMarkdown, renderStringAsPlaintext } from 'vs/base/browser/markdownRenderer';
1111
import { Gesture, EventType as TouchEventType } from 'vs/base/browser/touch';
12+
import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate';
13+
import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover';
1214
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
1315
import { Action, IAction, IActionRunner } from 'vs/base/common/actions';
1416
import { Codicon } from 'vs/base/common/codicons';
@@ -74,6 +76,7 @@ export class Button extends Disposable implements IButton {
7476
protected _label: string | IMarkdownString = '';
7577
protected _labelElement: HTMLElement | undefined;
7678
protected _labelShortElement: HTMLElement | undefined;
79+
private _hover: ICustomHover | undefined;
7780

7881
private _onDidClick = this._register(new Emitter<Event>());
7982
get onDidClick(): BaseEvent<Event> { return this._onDidClick.event; }
@@ -240,10 +243,16 @@ export class Button extends Disposable implements IButton {
240243
}
241244
}
242245

246+
let title: string = '';
243247
if (typeof this.options.title === 'string') {
244-
this._element.title = this.options.title;
248+
title = this.options.title;
245249
} else if (this.options.title) {
246-
this._element.title = renderStringAsPlaintext(value);
250+
title = renderStringAsPlaintext(value);
251+
}
252+
if (!this._hover) {
253+
this._hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this._element, title));
254+
} else {
255+
this._hover.update(title);
247256
}
248257

249258
if (typeof this.options.ariaLabel === 'string') {
@@ -348,7 +357,7 @@ export class ButtonWithDropdown extends Disposable implements IButton {
348357
this.separator.style.backgroundColor = options.buttonSeparator ?? '';
349358

350359
this.dropdownButton = this._register(new Button(this.element, { ...options, title: false, supportIcons: true }));
351-
this.dropdownButton.element.title = localize("button dropdown more actions", 'More Actions...');
360+
this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.dropdownButton.element, localize("button dropdown more actions", 'More Actions...')));
352361
this.dropdownButton.element.setAttribute('aria-haspopup', 'true');
353362
this.dropdownButton.element.setAttribute('aria-expanded', 'false');
354363
this.dropdownButton.element.classList.add('monaco-dropdown-button');

src/vs/base/browser/ui/dropdown/dropdown.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { $, addDisposableListener, append, EventHelper, EventType, isMouseEvent
88
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
99
import { EventType as GestureEventType, Gesture } from 'vs/base/browser/touch';
1010
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
11+
import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate';
12+
import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover';
1113
import { IMenuOptions } from 'vs/base/browser/ui/menu/menu';
1214
import { ActionRunner, IAction } from 'vs/base/common/actions';
1315
import { Emitter } from 'vs/base/common/event';
@@ -34,6 +36,8 @@ class BaseDropdown extends ActionRunner {
3436
private _onDidChangeVisibility = this._register(new Emitter<boolean>());
3537
readonly onDidChangeVisibility = this._onDidChangeVisibility.event;
3638

39+
private hover: ICustomHover | undefined;
40+
3741
constructor(container: HTMLElement, options: IBaseDropdownOptions) {
3842
super();
3943

@@ -101,7 +105,11 @@ class BaseDropdown extends ActionRunner {
101105

102106
set tooltip(tooltip: string) {
103107
if (this._label) {
104-
this._label.title = tooltip;
108+
if (!this.hover) {
109+
this.hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this._label, tooltip));
110+
} else {
111+
this.hover.update(tooltip);
112+
}
105113
}
106114
}
107115

src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import { KeyCode } from 'vs/base/common/keyCodes';
1919
import { ResolvedKeybinding } from 'vs/base/common/keybindings';
2020
import { IDisposable } from 'vs/base/common/lifecycle';
2121
import 'vs/css!./dropdown';
22+
import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover';
23+
import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate';
2224

2325
export interface IKeybindingProvider {
2426
(action: IAction): ResolvedKeybinding | undefined;
@@ -90,7 +92,9 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
9092
this.element.setAttribute('role', 'button');
9193
this.element.setAttribute('aria-haspopup', 'true');
9294
this.element.setAttribute('aria-expanded', 'false');
93-
this.element.title = this._action.label || '';
95+
if (this._action.label) {
96+
this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.element, this._action.label));
97+
}
9498
this.element.ariaLabel = this._action.label || '';
9599

96100
return null;
@@ -203,7 +207,7 @@ export class ActionWithDropdownActionViewItem extends ActionViewItem {
203207
separator.classList.toggle('prominent', menuActionClassNames.includes('prominent'));
204208
append(this.element, separator);
205209

206-
this.dropdownMenuActionViewItem = this._register(new DropdownMenuActionViewItem(this._register(new Action('dropdownAction', nls.localize('moreActions', "More Actions..."))), menuActionsProvider, this.contextMenuProvider, { classNames: ['dropdown', ...ThemeIcon.asClassNameArray(Codicon.dropDownButton), ...menuActionClassNames] }));
210+
this.dropdownMenuActionViewItem = this._register(new DropdownMenuActionViewItem(this._register(new Action('dropdownAction', nls.localize('moreActions', "More Actions..."))), menuActionsProvider, this.contextMenuProvider, { classNames: ['dropdown', ...ThemeIcon.asClassNameArray(Codicon.dropDownButton), ...menuActionClassNames], hoverDelegate: this.options.hoverDelegate }));
207211
this.dropdownMenuActionViewItem.render(this.element);
208212

209213
this._register(addDisposableListener(this.element, EventType.KEY_DOWN, e => {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { IHoverDelegate, IScopedHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';
7+
import { Lazy } from 'vs/base/common/lazy';
8+
9+
const nullHoverDelegateFactory = () => ({
10+
get delay(): number { return -1; },
11+
dispose: () => { },
12+
showHover: () => { return undefined; },
13+
});
14+
15+
let hoverDelegateFactory: (placement: 'mouse' | 'element', enableInstantHover: boolean) => IScopedHoverDelegate = nullHoverDelegateFactory;
16+
const defaultHoverDelegateMouse = new Lazy<IHoverDelegate>(() => hoverDelegateFactory('mouse', false));
17+
const defaultHoverDelegateElement = new Lazy<IHoverDelegate>(() => hoverDelegateFactory('element', false));
18+
19+
export function setHoverDelegateFactory(hoverDelegateProvider: ((placement: 'mouse' | 'element', enableInstantHover: boolean) => IScopedHoverDelegate)): void {
20+
hoverDelegateFactory = hoverDelegateProvider;
21+
}
22+
23+
export function getDefaultHoverDelegate(placement: 'mouse' | 'element'): IHoverDelegate;
24+
export function getDefaultHoverDelegate(placement: 'mouse' | 'element', enableInstantHover: true): IScopedHoverDelegate;
25+
export function getDefaultHoverDelegate(placement: 'mouse' | 'element', enableInstantHover?: boolean): IHoverDelegate | IScopedHoverDelegate {
26+
if (enableInstantHover) {
27+
// If instant hover is enabled, the consumer is responsible for disposing the hover delegate
28+
return hoverDelegateFactory(placement, true);
29+
}
30+
31+
if (placement === 'element') {
32+
return defaultHoverDelegateElement.value;
33+
}
34+
return defaultHoverDelegateMouse.value;
35+
}

src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ export interface IHoverDelegate {
6464
placement?: 'mouse' | 'element';
6565
}
6666

67+
export interface IScopedHoverDelegate extends IHoverDelegate, IDisposable { }
68+
6769
export interface IHoverWidget extends IDisposable {
6870
readonly isDisposed: boolean;
6971
}

src/vs/base/browser/ui/iconLabel/iconLabel.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ import 'vs/css!./iconlabel';
77
import * as dom from 'vs/base/browser/dom';
88
import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
99
import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';
10-
import { ITooltipMarkdownString, setupCustomHover, setupNativeHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover';
10+
import { ITooltipMarkdownString, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover';
1111
import { IMatch } from 'vs/base/common/filters';
1212
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
1313
import { equals } from 'vs/base/common/objects';
1414
import { Range } from 'vs/base/common/range';
15+
import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate';
1516

1617
export interface IIconLabelCreationOptions {
1718
readonly supportHighlights?: boolean;
@@ -94,7 +95,7 @@ export class IconLabel extends Disposable {
9495

9596
private readonly labelContainer: HTMLElement;
9697

97-
private readonly hoverDelegate: IHoverDelegate | undefined;
98+
private readonly hoverDelegate: IHoverDelegate;
9899
private readonly customHovers: Map<HTMLElement, IDisposable> = new Map();
99100

100101
constructor(container: HTMLElement, options?: IIconLabelCreationOptions) {
@@ -113,7 +114,7 @@ export class IconLabel extends Disposable {
113114
this.nameNode = new Label(this.nameContainer);
114115
}
115116

116-
this.hoverDelegate = options?.hoverDelegate;
117+
this.hoverDelegate = options?.hoverDelegate ?? getDefaultHoverDelegate('mouse');
117118
}
118119

119120
get element(): HTMLElement {
@@ -186,13 +187,9 @@ export class IconLabel extends Disposable {
186187
return;
187188
}
188189

189-
if (!this.hoverDelegate) {
190-
setupNativeHover(htmlElement, tooltip);
191-
} else {
192-
const hoverDisposable = setupCustomHover(this.hoverDelegate, htmlElement, tooltip);
193-
if (hoverDisposable) {
194-
this.customHovers.set(htmlElement, hoverDisposable);
195-
}
190+
const hoverDisposable = setupCustomHover(this.hoverDelegate, htmlElement, tooltip);
191+
if (hoverDisposable) {
192+
this.customHovers.set(htmlElement, hoverDisposable);
196193
}
197194
}
198195

src/vs/base/browser/ui/inputbox/inputBox.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer';
1111
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
1212
import * as aria from 'vs/base/browser/ui/aria/aria';
1313
import { AnchorAlignment, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
14+
import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate';
15+
import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover';
1416
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
1517
import { Widget } from 'vs/base/browser/ui/widget';
1618
import { IAction } from 'vs/base/common/actions';
@@ -111,6 +113,7 @@ export class InputBox extends Widget {
111113
private cachedContentHeight: number | undefined;
112114
private maxHeight: number = Number.POSITIVE_INFINITY;
113115
private scrollableElement: ScrollableElement | undefined;
116+
private hover: ICustomHover | undefined;
114117

115118
private _onDidChange = this._register(new Emitter<string>());
116119
public readonly onDidChange: Event<string> = this._onDidChange.event;
@@ -230,7 +233,11 @@ export class InputBox extends Widget {
230233

231234
public setTooltip(tooltip: string): void {
232235
this.tooltip = tooltip;
233-
this.input.title = tooltip;
236+
if (!this.hover) {
237+
this.hover = this._register(setupCustomHover(getDefaultHoverDelegate('element'), this.input, tooltip));
238+
} else {
239+
this.hover.update(tooltip);
240+
}
234241
}
235242

236243
public setAriaLabel(label: string): void {

src/vs/base/browser/ui/selectBox/selectBoxCustom.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { IContentActionHandler } from 'vs/base/browser/formattedTextRenderer';
99
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
1010
import { renderMarkdown } from 'vs/base/browser/markdownRenderer';
1111
import { AnchorPosition, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
12+
import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate';
13+
import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover';
1214
import { IListEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
1315
import { List } from 'vs/base/browser/ui/list/listWidget';
1416
import { ISelectBoxDelegate, ISelectBoxOptions, ISelectBoxStyles, ISelectData, ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox';
@@ -101,6 +103,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
101103
private selectionDetailsPane!: HTMLElement;
102104
private _skipLayout: boolean = false;
103105
private _cachedMaxDetailsHeight?: number;
106+
private _hover: ICustomHover;
104107

105108
private _sticky: boolean = false; // for dev purposes only
106109

@@ -131,6 +134,8 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
131134
this.selectElement.setAttribute('aria-description', this.selectBoxOptions.ariaDescription);
132135
}
133136

137+
this._hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.selectElement, ''));
138+
134139
this._onDidSelect = new Emitter<ISelectData>();
135140
this._register(this._onDidSelect);
136141

@@ -199,7 +204,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
199204
selected: e.target.value
200205
});
201206
if (!!this.options[this.selected] && !!this.options[this.selected].text) {
202-
this.selectElement.title = this.options[this.selected].text;
207+
this._hover.update(this.options[this.selected].text);
203208
}
204209
}));
205210

@@ -309,7 +314,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
309314

310315
this.selectElement.selectedIndex = this.selected;
311316
if (!!this.options[this.selected] && !!this.options[this.selected].text) {
312-
this.selectElement.title = this.options[this.selected].text;
317+
this._hover.update(this.options[this.selected].text);
313318
}
314319
}
315320

@@ -837,7 +842,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
837842

838843
});
839844
if (!!this.options[this.selected] && !!this.options[this.selected].text) {
840-
this.selectElement.title = this.options[this.selected].text;
845+
this._hover.update(this.options[this.selected].text);
841846
}
842847
}
843848

@@ -936,7 +941,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
936941
selected: this.options[this.selected].text
937942
});
938943
if (!!this.options[this.selected] && !!this.options[this.selected].text) {
939-
this.selectElement.title = this.options[this.selected].text;
944+
this._hover.update(this.options[this.selected].text);
940945
}
941946
}
942947

0 commit comments

Comments
 (0)