Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/browser/ui/toolbar/toolbar.ts
3296 views
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 { IContextMenuProvider } from '../../contextmenu.js';
7
import { ActionBar, ActionsOrientation, IActionViewItemProvider } from '../actionbar/actionbar.js';
8
import { AnchorAlignment } from '../contextview/contextview.js';
9
import { DropdownMenuActionViewItem } from '../dropdown/dropdownActionViewItem.js';
10
import { Action, IAction, IActionRunner, SubmenuAction } from '../../../common/actions.js';
11
import { Codicon } from '../../../common/codicons.js';
12
import { ThemeIcon } from '../../../common/themables.js';
13
import { EventMultiplexer } from '../../../common/event.js';
14
import { ResolvedKeybinding } from '../../../common/keybindings.js';
15
import { Disposable, DisposableStore } from '../../../common/lifecycle.js';
16
import './toolbar.css';
17
import * as nls from '../../../../nls.js';
18
import { IHoverDelegate } from '../hover/hoverDelegate.js';
19
import { createInstantHoverDelegate } from '../hover/hoverDelegateFactory.js';
20
21
export interface IToolBarOptions {
22
orientation?: ActionsOrientation;
23
actionViewItemProvider?: IActionViewItemProvider;
24
ariaLabel?: string;
25
getKeyBinding?: (action: IAction) => ResolvedKeybinding | undefined;
26
actionRunner?: IActionRunner;
27
toggleMenuTitle?: string;
28
anchorAlignmentProvider?: () => AnchorAlignment;
29
renderDropdownAsChildElement?: boolean;
30
moreIcon?: ThemeIcon;
31
allowContextMenu?: boolean;
32
skipTelemetry?: boolean;
33
hoverDelegate?: IHoverDelegate;
34
35
/**
36
* If true, toggled primary items are highlighted with a background color.
37
*/
38
highlightToggledItems?: boolean;
39
40
/**
41
* Render action with icons (default: `true`)
42
*/
43
icon?: boolean;
44
45
/**
46
* Render action with label (default: `false`)
47
*/
48
label?: boolean;
49
}
50
51
/**
52
* A widget that combines an action bar for primary actions and a dropdown for secondary actions.
53
*/
54
export class ToolBar extends Disposable {
55
private options: IToolBarOptions;
56
protected readonly actionBar: ActionBar;
57
private toggleMenuAction: ToggleMenuAction;
58
private toggleMenuActionViewItem: DropdownMenuActionViewItem | undefined;
59
private submenuActionViewItems: DropdownMenuActionViewItem[] = [];
60
private hasSecondaryActions: boolean = false;
61
private readonly element: HTMLElement;
62
63
private _onDidChangeDropdownVisibility = this._register(new EventMultiplexer<boolean>());
64
get onDidChangeDropdownVisibility() { return this._onDidChangeDropdownVisibility.event; }
65
private readonly disposables = this._register(new DisposableStore());
66
67
constructor(container: HTMLElement, contextMenuProvider: IContextMenuProvider, options: IToolBarOptions = { orientation: ActionsOrientation.HORIZONTAL }) {
68
super();
69
70
options.hoverDelegate = options.hoverDelegate ?? this._register(createInstantHoverDelegate());
71
this.options = options;
72
73
this.toggleMenuAction = this._register(new ToggleMenuAction(() => this.toggleMenuActionViewItem?.show(), options.toggleMenuTitle));
74
75
this.element = document.createElement('div');
76
this.element.className = 'monaco-toolbar';
77
container.appendChild(this.element);
78
79
this.actionBar = this._register(new ActionBar(this.element, {
80
orientation: options.orientation,
81
ariaLabel: options.ariaLabel,
82
actionRunner: options.actionRunner,
83
allowContextMenu: options.allowContextMenu,
84
highlightToggledItems: options.highlightToggledItems,
85
hoverDelegate: options.hoverDelegate,
86
actionViewItemProvider: (action, viewItemOptions) => {
87
if (action.id === ToggleMenuAction.ID) {
88
this.toggleMenuActionViewItem = new DropdownMenuActionViewItem(
89
action,
90
(<ToggleMenuAction>action).menuActions,
91
contextMenuProvider,
92
{
93
actionViewItemProvider: this.options.actionViewItemProvider,
94
actionRunner: this.actionRunner,
95
keybindingProvider: this.options.getKeyBinding,
96
classNames: ThemeIcon.asClassNameArray(options.moreIcon ?? Codicon.toolBarMore),
97
anchorAlignmentProvider: this.options.anchorAlignmentProvider,
98
menuAsChild: !!this.options.renderDropdownAsChildElement,
99
skipTelemetry: this.options.skipTelemetry,
100
isMenu: true,
101
hoverDelegate: this.options.hoverDelegate
102
}
103
);
104
this.toggleMenuActionViewItem.setActionContext(this.actionBar.context);
105
this.disposables.add(this._onDidChangeDropdownVisibility.add(this.toggleMenuActionViewItem.onDidChangeVisibility));
106
107
return this.toggleMenuActionViewItem;
108
}
109
110
if (options.actionViewItemProvider) {
111
const result = options.actionViewItemProvider(action, viewItemOptions);
112
113
if (result) {
114
return result;
115
}
116
}
117
118
if (action instanceof SubmenuAction) {
119
const result = new DropdownMenuActionViewItem(
120
action,
121
action.actions,
122
contextMenuProvider,
123
{
124
actionViewItemProvider: this.options.actionViewItemProvider,
125
actionRunner: this.actionRunner,
126
keybindingProvider: this.options.getKeyBinding,
127
classNames: action.class,
128
anchorAlignmentProvider: this.options.anchorAlignmentProvider,
129
menuAsChild: !!this.options.renderDropdownAsChildElement,
130
skipTelemetry: this.options.skipTelemetry,
131
hoverDelegate: this.options.hoverDelegate
132
}
133
);
134
result.setActionContext(this.actionBar.context);
135
this.submenuActionViewItems.push(result);
136
this.disposables.add(this._onDidChangeDropdownVisibility.add(result.onDidChangeVisibility));
137
138
return result;
139
}
140
141
return undefined;
142
}
143
}));
144
}
145
146
set actionRunner(actionRunner: IActionRunner) {
147
this.actionBar.actionRunner = actionRunner;
148
}
149
150
get actionRunner(): IActionRunner {
151
return this.actionBar.actionRunner;
152
}
153
154
set context(context: unknown) {
155
this.actionBar.context = context;
156
this.toggleMenuActionViewItem?.setActionContext(context);
157
for (const actionViewItem of this.submenuActionViewItems) {
158
actionViewItem.setActionContext(context);
159
}
160
}
161
162
getElement(): HTMLElement {
163
return this.element;
164
}
165
166
focus(): void {
167
this.actionBar.focus();
168
}
169
170
getItemsWidth(): number {
171
let itemsWidth = 0;
172
for (let i = 0; i < this.actionBar.length(); i++) {
173
itemsWidth += this.actionBar.getWidth(i);
174
}
175
return itemsWidth;
176
}
177
178
getItemAction(indexOrElement: number | HTMLElement) {
179
return this.actionBar.getAction(indexOrElement);
180
}
181
182
getItemWidth(index: number): number {
183
return this.actionBar.getWidth(index);
184
}
185
186
getItemsLength(): number {
187
return this.actionBar.length();
188
}
189
190
setAriaLabel(label: string): void {
191
this.actionBar.setAriaLabel(label);
192
}
193
194
setActions(primaryActions: ReadonlyArray<IAction>, secondaryActions?: ReadonlyArray<IAction>): void {
195
this.clear();
196
197
const primaryActionsToSet = primaryActions ? primaryActions.slice(0) : [];
198
199
// Inject additional action to open secondary actions if present
200
this.hasSecondaryActions = !!(secondaryActions && secondaryActions.length > 0);
201
if (this.hasSecondaryActions && secondaryActions) {
202
this.toggleMenuAction.menuActions = secondaryActions.slice(0);
203
primaryActionsToSet.push(this.toggleMenuAction);
204
}
205
206
primaryActionsToSet.forEach(action => {
207
this.actionBar.push(action, { icon: this.options.icon ?? true, label: this.options.label ?? false, keybinding: this.getKeybindingLabel(action) });
208
});
209
}
210
211
isEmpty(): boolean {
212
return this.actionBar.isEmpty();
213
}
214
215
private getKeybindingLabel(action: IAction): string | undefined {
216
const key = this.options.getKeyBinding?.(action);
217
218
return key?.getLabel() ?? undefined;
219
}
220
221
private clear(): void {
222
this.submenuActionViewItems = [];
223
this.disposables.clear();
224
this.actionBar.clear();
225
}
226
227
override dispose(): void {
228
this.clear();
229
this.disposables.dispose();
230
super.dispose();
231
}
232
}
233
234
export class ToggleMenuAction extends Action {
235
236
static readonly ID = 'toolbar.toggle.more';
237
238
private _menuActions: ReadonlyArray<IAction>;
239
private toggleDropdownMenu: () => void;
240
241
constructor(toggleDropdownMenu: () => void, title?: string) {
242
title = title || nls.localize('moreActions', "More Actions...");
243
super(ToggleMenuAction.ID, title, undefined, true);
244
245
this._menuActions = [];
246
this.toggleDropdownMenu = toggleDropdownMenu;
247
}
248
249
override async run(): Promise<void> {
250
this.toggleDropdownMenu();
251
}
252
253
get menuActions(): ReadonlyArray<IAction> {
254
return this._menuActions;
255
}
256
257
set menuActions(actions: ReadonlyArray<IAction>) {
258
this._menuActions = actions;
259
}
260
}
261
262