Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/actions/browser/menuEntryActionViewItem.ts
3294 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 { asCSSUrl } from '../../../base/browser/cssValue.js';
7
import { $, addDisposableListener, append, EventType, ModifierKeyEmitter, prepend } from '../../../base/browser/dom.js';
8
import { StandardKeyboardEvent } from '../../../base/browser/keyboardEvent.js';
9
import { ActionViewItem, BaseActionViewItem, SelectActionViewItem } from '../../../base/browser/ui/actionbar/actionViewItems.js';
10
import { DropdownMenuActionViewItem, IDropdownMenuActionViewItemOptions } from '../../../base/browser/ui/dropdown/dropdownActionViewItem.js';
11
import { IHoverDelegate } from '../../../base/browser/ui/hover/hoverDelegate.js';
12
import { ActionRunner, IAction, IRunEvent, Separator, SubmenuAction } from '../../../base/common/actions.js';
13
import { Event } from '../../../base/common/event.js';
14
import { UILabelProvider } from '../../../base/common/keybindingLabels.js';
15
import { ResolvedKeybinding } from '../../../base/common/keybindings.js';
16
import { KeyCode } from '../../../base/common/keyCodes.js';
17
import { combinedDisposable, DisposableStore, MutableDisposable, toDisposable } from '../../../base/common/lifecycle.js';
18
import { isLinux, isWindows, OS } from '../../../base/common/platform.js';
19
import { ThemeIcon } from '../../../base/common/themables.js';
20
import { assertType } from '../../../base/common/types.js';
21
import { localize } from '../../../nls.js';
22
import { IAccessibilityService } from '../../accessibility/common/accessibility.js';
23
import { ICommandAction, isICommandActionToggleInfo } from '../../action/common/action.js';
24
import { IConfigurationService } from '../../configuration/common/configuration.js';
25
import { IContextKeyService } from '../../contextkey/common/contextkey.js';
26
import { IContextMenuService, IContextViewService } from '../../contextview/browser/contextView.js';
27
import { IInstantiationService } from '../../instantiation/common/instantiation.js';
28
import { IKeybindingService } from '../../keybinding/common/keybinding.js';
29
import { INotificationService } from '../../notification/common/notification.js';
30
import { IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js';
31
import { defaultSelectBoxStyles } from '../../theme/browser/defaultStyles.js';
32
import { asCssVariable, selectBorder } from '../../theme/common/colorRegistry.js';
33
import { isDark } from '../../theme/common/theme.js';
34
import { IThemeService } from '../../theme/common/themeService.js';
35
import { hasNativeContextMenu } from '../../window/common/window.js';
36
import { IMenuService, MenuItemAction, SubmenuItemAction } from '../common/actions.js';
37
import './menuEntryActionViewItem.css';
38
39
export interface PrimaryAndSecondaryActions {
40
primary: IAction[];
41
secondary: IAction[];
42
}
43
44
export function getContextMenuActions(
45
groups: ReadonlyArray<[string, ReadonlyArray<MenuItemAction | SubmenuItemAction>]>,
46
primaryGroup?: string
47
): PrimaryAndSecondaryActions {
48
const target: PrimaryAndSecondaryActions = { primary: [], secondary: [] };
49
getContextMenuActionsImpl(groups, target, primaryGroup);
50
return target;
51
}
52
53
export function getFlatContextMenuActions(
54
groups: ReadonlyArray<[string, ReadonlyArray<MenuItemAction | SubmenuItemAction>]>,
55
primaryGroup?: string
56
): IAction[] {
57
const target: IAction[] = [];
58
getContextMenuActionsImpl(groups, target, primaryGroup);
59
return target;
60
}
61
62
function getContextMenuActionsImpl(
63
groups: ReadonlyArray<[string, ReadonlyArray<MenuItemAction | SubmenuItemAction>]>,
64
target: IAction[] | PrimaryAndSecondaryActions,
65
primaryGroup?: string
66
) {
67
const modifierKeyEmitter = ModifierKeyEmitter.getInstance();
68
const useAlternativeActions = modifierKeyEmitter.keyStatus.altKey || ((isWindows || isLinux) && modifierKeyEmitter.keyStatus.shiftKey);
69
fillInActions(groups, target, useAlternativeActions, primaryGroup ? actionGroup => actionGroup === primaryGroup : actionGroup => actionGroup === 'navigation');
70
}
71
72
73
export function getActionBarActions(
74
groups: [string, Array<MenuItemAction | SubmenuItemAction>][],
75
primaryGroup?: string | ((actionGroup: string) => boolean),
76
shouldInlineSubmenu?: (action: SubmenuAction, group: string, groupSize: number) => boolean,
77
useSeparatorsInPrimaryActions?: boolean
78
): PrimaryAndSecondaryActions {
79
const target: PrimaryAndSecondaryActions = { primary: [], secondary: [] };
80
fillInActionBarActions(groups, target, primaryGroup, shouldInlineSubmenu, useSeparatorsInPrimaryActions);
81
return target;
82
}
83
84
export function getFlatActionBarActions(
85
groups: [string, Array<MenuItemAction | SubmenuItemAction>][],
86
primaryGroup?: string | ((actionGroup: string) => boolean),
87
shouldInlineSubmenu?: (action: SubmenuAction, group: string, groupSize: number) => boolean,
88
useSeparatorsInPrimaryActions?: boolean
89
): IAction[] {
90
const target: IAction[] = [];
91
fillInActionBarActions(groups, target, primaryGroup, shouldInlineSubmenu, useSeparatorsInPrimaryActions);
92
return target;
93
}
94
95
export function fillInActionBarActions(
96
groups: [string, Array<MenuItemAction | SubmenuItemAction>][],
97
target: IAction[] | PrimaryAndSecondaryActions,
98
primaryGroup?: string | ((actionGroup: string) => boolean),
99
shouldInlineSubmenu?: (action: SubmenuAction, group: string, groupSize: number) => boolean,
100
useSeparatorsInPrimaryActions?: boolean
101
): void {
102
const isPrimaryAction = typeof primaryGroup === 'string' ? (actionGroup: string) => actionGroup === primaryGroup : primaryGroup;
103
104
// Action bars handle alternative actions on their own so the alternative actions should be ignored
105
fillInActions(groups, target, false, isPrimaryAction, shouldInlineSubmenu, useSeparatorsInPrimaryActions);
106
}
107
108
function fillInActions(
109
groups: ReadonlyArray<[string, ReadonlyArray<MenuItemAction | SubmenuItemAction>]>,
110
target: IAction[] | PrimaryAndSecondaryActions,
111
useAlternativeActions: boolean,
112
isPrimaryAction: (actionGroup: string) => boolean = actionGroup => actionGroup === 'navigation',
113
shouldInlineSubmenu: (action: SubmenuAction, group: string, groupSize: number) => boolean = () => false,
114
useSeparatorsInPrimaryActions: boolean = false
115
): void {
116
117
let primaryBucket: IAction[];
118
let secondaryBucket: IAction[];
119
if (Array.isArray(target)) {
120
primaryBucket = target;
121
secondaryBucket = target;
122
} else {
123
primaryBucket = target.primary;
124
secondaryBucket = target.secondary;
125
}
126
127
const submenuInfo = new Set<{ group: string; action: SubmenuAction; index: number }>();
128
129
for (const [group, actions] of groups) {
130
131
let target: IAction[];
132
if (isPrimaryAction(group)) {
133
target = primaryBucket;
134
if (target.length > 0 && useSeparatorsInPrimaryActions) {
135
target.push(new Separator());
136
}
137
} else {
138
target = secondaryBucket;
139
if (target.length > 0) {
140
target.push(new Separator());
141
}
142
}
143
144
for (let action of actions) {
145
if (useAlternativeActions) {
146
action = action instanceof MenuItemAction && action.alt ? action.alt : action;
147
}
148
const newLen = target.push(action);
149
// keep submenu info for later inlining
150
if (action instanceof SubmenuAction) {
151
submenuInfo.add({ group, action, index: newLen - 1 });
152
}
153
}
154
}
155
156
// ask the outside if submenu should be inlined or not. only ask when
157
// there would be enough space
158
for (const { group, action, index } of submenuInfo) {
159
const target = isPrimaryAction(group) ? primaryBucket : secondaryBucket;
160
161
// inlining submenus with length 0 or 1 is easy,
162
// larger submenus need to be checked with the overall limit
163
const submenuActions = action.actions;
164
if (shouldInlineSubmenu(action, group, target.length)) {
165
target.splice(index, 1, ...submenuActions);
166
}
167
}
168
}
169
170
export interface IMenuEntryActionViewItemOptions {
171
readonly draggable?: boolean;
172
readonly keybinding?: string | null;
173
readonly hoverDelegate?: IHoverDelegate;
174
readonly keybindingNotRenderedWithLabel?: boolean;
175
}
176
177
export class MenuEntryActionViewItem<T extends IMenuEntryActionViewItemOptions = IMenuEntryActionViewItemOptions> extends ActionViewItem {
178
179
private _wantsAltCommand: boolean = false;
180
private readonly _itemClassDispose = this._register(new MutableDisposable());
181
private readonly _altKey: ModifierKeyEmitter;
182
183
constructor(
184
action: MenuItemAction,
185
protected readonly _options: T | undefined,
186
@IKeybindingService protected readonly _keybindingService: IKeybindingService,
187
@INotificationService protected readonly _notificationService: INotificationService,
188
@IContextKeyService protected readonly _contextKeyService: IContextKeyService,
189
@IThemeService protected readonly _themeService: IThemeService,
190
@IContextMenuService protected readonly _contextMenuService: IContextMenuService,
191
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService
192
) {
193
super(undefined, action, { icon: !!(action.class || action.item.icon), label: !action.class && !action.item.icon, draggable: _options?.draggable, keybinding: _options?.keybinding, hoverDelegate: _options?.hoverDelegate, keybindingNotRenderedWithLabel: _options?.keybindingNotRenderedWithLabel });
194
this._altKey = ModifierKeyEmitter.getInstance();
195
}
196
197
protected get _menuItemAction(): MenuItemAction {
198
return <MenuItemAction>this._action;
199
}
200
201
protected get _commandAction(): MenuItemAction {
202
return this._wantsAltCommand && this._menuItemAction.alt || this._menuItemAction;
203
}
204
205
override async onClick(event: MouseEvent): Promise<void> {
206
event.preventDefault();
207
event.stopPropagation();
208
209
try {
210
await this.actionRunner.run(this._commandAction, this._context);
211
} catch (err) {
212
this._notificationService.error(err);
213
}
214
}
215
216
override render(container: HTMLElement): void {
217
super.render(container);
218
container.classList.add('menu-entry');
219
220
if (this.options.icon) {
221
this._updateItemClass(this._menuItemAction.item);
222
}
223
224
if (this._menuItemAction.alt) {
225
let isMouseOver = false;
226
227
const updateAltState = () => {
228
const wantsAltCommand = !!this._menuItemAction.alt?.enabled &&
229
(!this._accessibilityService.isMotionReduced() || isMouseOver) && (
230
this._altKey.keyStatus.altKey ||
231
(this._altKey.keyStatus.shiftKey && isMouseOver)
232
);
233
234
if (wantsAltCommand !== this._wantsAltCommand) {
235
this._wantsAltCommand = wantsAltCommand;
236
this.updateLabel();
237
this.updateTooltip();
238
this.updateClass();
239
}
240
};
241
242
this._register(this._altKey.event(updateAltState));
243
244
this._register(addDisposableListener(container, 'mouseleave', _ => {
245
isMouseOver = false;
246
updateAltState();
247
}));
248
249
this._register(addDisposableListener(container, 'mouseenter', _ => {
250
isMouseOver = true;
251
updateAltState();
252
}));
253
254
updateAltState();
255
}
256
}
257
258
protected override updateLabel(): void {
259
if (this.options.label && this.label) {
260
this.label.textContent = this._commandAction.label;
261
}
262
}
263
264
protected override getTooltip() {
265
const keybinding = this._keybindingService.lookupKeybinding(this._commandAction.id, this._contextKeyService);
266
const keybindingLabel = keybinding && keybinding.getLabel();
267
268
const tooltip = this._commandAction.tooltip || this._commandAction.label;
269
let title = keybindingLabel
270
? localize('titleAndKb', "{0} ({1})", tooltip, keybindingLabel)
271
: tooltip;
272
if (!this._wantsAltCommand && this._menuItemAction.alt?.enabled) {
273
const altTooltip = this._menuItemAction.alt.tooltip || this._menuItemAction.alt.label;
274
const altKeybinding = this._keybindingService.lookupKeybinding(this._menuItemAction.alt.id, this._contextKeyService);
275
const altKeybindingLabel = altKeybinding && altKeybinding.getLabel();
276
const altTitleSection = altKeybindingLabel
277
? localize('titleAndKb', "{0} ({1})", altTooltip, altKeybindingLabel)
278
: altTooltip;
279
280
title = localize('titleAndKbAndAlt', "{0}\n[{1}] {2}", title, UILabelProvider.modifierLabels[OS].altKey, altTitleSection);
281
}
282
return title;
283
}
284
285
protected override updateClass(): void {
286
if (this.options.icon) {
287
if (this._commandAction !== this._menuItemAction) {
288
if (this._menuItemAction.alt) {
289
this._updateItemClass(this._menuItemAction.alt.item);
290
}
291
} else {
292
this._updateItemClass(this._menuItemAction.item);
293
}
294
}
295
}
296
297
private _updateItemClass(item: ICommandAction): void {
298
this._itemClassDispose.value = undefined;
299
300
const { element, label } = this;
301
if (!element || !label) {
302
return;
303
}
304
305
const icon = this._commandAction.checked && isICommandActionToggleInfo(item.toggled) && item.toggled.icon ? item.toggled.icon : item.icon;
306
307
if (!icon) {
308
return;
309
}
310
311
if (ThemeIcon.isThemeIcon(icon)) {
312
// theme icons
313
const iconClasses = ThemeIcon.asClassNameArray(icon);
314
label.classList.add(...iconClasses);
315
this._itemClassDispose.value = toDisposable(() => {
316
label.classList.remove(...iconClasses);
317
});
318
319
} else {
320
// icon path/url
321
label.style.backgroundImage = (
322
isDark(this._themeService.getColorTheme().type)
323
? asCSSUrl(icon.dark)
324
: asCSSUrl(icon.light)
325
);
326
label.classList.add('icon');
327
this._itemClassDispose.value = combinedDisposable(
328
toDisposable(() => {
329
label.style.backgroundImage = '';
330
label.classList.remove('icon');
331
}),
332
this._themeService.onDidColorThemeChange(() => {
333
// refresh when the theme changes in case we go between dark <-> light
334
this.updateClass();
335
})
336
);
337
}
338
}
339
}
340
341
export interface ITextOnlyMenuEntryActionViewItemOptions extends IMenuEntryActionViewItemOptions {
342
readonly conversational?: boolean;
343
readonly useComma?: boolean;
344
}
345
346
export class TextOnlyMenuEntryActionViewItem extends MenuEntryActionViewItem<ITextOnlyMenuEntryActionViewItemOptions> {
347
348
override render(container: HTMLElement): void {
349
this.options.label = true;
350
this.options.icon = false;
351
super.render(container);
352
container.classList.add('text-only');
353
container.classList.toggle('use-comma', this._options?.useComma ?? false);
354
}
355
356
protected override updateLabel() {
357
const kb = this._keybindingService.lookupKeybinding(this._action.id, this._contextKeyService);
358
if (!kb) {
359
return super.updateLabel();
360
}
361
if (this.label) {
362
const kb2 = TextOnlyMenuEntryActionViewItem._symbolPrintEnter(kb);
363
364
if (this._options?.conversational) {
365
this.label.textContent = localize({ key: 'content2', comment: ['A label with keybindg like "ESC to dismiss"'] }, '{1} to {0}', this._action.label, kb2);
366
367
} else {
368
this.label.textContent = localize({ key: 'content', comment: ['A label', 'A keybinding'] }, '{0} ({1})', this._action.label, kb2);
369
}
370
}
371
}
372
373
private static _symbolPrintEnter(kb: ResolvedKeybinding) {
374
return kb.getLabel()
375
?.replace(/\benter\b/gi, '\u23CE')
376
.replace(/\bEscape\b/gi, 'Esc');
377
}
378
}
379
380
export class SubmenuEntryActionViewItem extends DropdownMenuActionViewItem {
381
382
constructor(
383
action: SubmenuItemAction,
384
options: IDropdownMenuActionViewItemOptions | undefined,
385
@IKeybindingService protected _keybindingService: IKeybindingService,
386
@IContextMenuService protected _contextMenuService: IContextMenuService,
387
@IThemeService protected _themeService: IThemeService
388
) {
389
const dropdownOptions: IDropdownMenuActionViewItemOptions = {
390
...options,
391
menuAsChild: options?.menuAsChild ?? false,
392
classNames: options?.classNames ?? (ThemeIcon.isThemeIcon(action.item.icon) ? ThemeIcon.asClassName(action.item.icon) : undefined),
393
keybindingProvider: options?.keybindingProvider ?? (action => _keybindingService.lookupKeybinding(action.id))
394
};
395
396
super(action, { getActions: () => action.actions }, _contextMenuService, dropdownOptions);
397
}
398
399
override render(container: HTMLElement): void {
400
super.render(container);
401
assertType(this.element);
402
403
container.classList.add('menu-entry');
404
const action = <SubmenuItemAction>this._action;
405
const { icon } = action.item;
406
if (icon && !ThemeIcon.isThemeIcon(icon)) {
407
this.element.classList.add('icon');
408
const setBackgroundImage = () => {
409
if (this.element) {
410
this.element.style.backgroundImage = (
411
isDark(this._themeService.getColorTheme().type)
412
? asCSSUrl(icon.dark)
413
: asCSSUrl(icon.light)
414
);
415
}
416
};
417
setBackgroundImage();
418
this._register(this._themeService.onDidColorThemeChange(() => {
419
// refresh when the theme changes in case we go between dark <-> light
420
setBackgroundImage();
421
}));
422
}
423
}
424
}
425
426
export interface IDropdownWithDefaultActionViewItemOptions extends IDropdownMenuActionViewItemOptions {
427
renderKeybindingWithDefaultActionLabel?: boolean;
428
persistLastActionId?: boolean;
429
}
430
431
export class DropdownWithDefaultActionViewItem extends BaseActionViewItem {
432
private readonly _options: IDropdownWithDefaultActionViewItemOptions | undefined;
433
private _defaultAction: ActionViewItem;
434
private readonly _defaultActionDisposables = this._register(new DisposableStore());
435
private readonly _dropdown: DropdownMenuActionViewItem;
436
private _container: HTMLElement | null = null;
437
private readonly _storageKey: string;
438
439
get onDidChangeDropdownVisibility(): Event<boolean> {
440
return this._dropdown.onDidChangeVisibility;
441
}
442
443
constructor(
444
submenuAction: SubmenuItemAction,
445
options: IDropdownWithDefaultActionViewItemOptions | undefined,
446
@IKeybindingService protected readonly _keybindingService: IKeybindingService,
447
@INotificationService protected _notificationService: INotificationService,
448
@IContextMenuService protected _contextMenuService: IContextMenuService,
449
@IMenuService protected _menuService: IMenuService,
450
@IInstantiationService protected _instaService: IInstantiationService,
451
@IStorageService protected _storageService: IStorageService
452
) {
453
super(null, submenuAction);
454
this._options = options;
455
this._storageKey = `${submenuAction.item.submenu.id}_lastActionId`;
456
457
// determine default action
458
let defaultAction: IAction | undefined;
459
const defaultActionId = options?.persistLastActionId ? _storageService.get(this._storageKey, StorageScope.WORKSPACE) : undefined;
460
if (defaultActionId) {
461
defaultAction = submenuAction.actions.find(a => defaultActionId === a.id);
462
}
463
if (!defaultAction) {
464
defaultAction = submenuAction.actions[0];
465
}
466
467
this._defaultAction = this._defaultActionDisposables.add(this._instaService.createInstance(MenuEntryActionViewItem, <MenuItemAction>defaultAction, { keybinding: this._getDefaultActionKeybindingLabel(defaultAction) }));
468
469
const dropdownOptions: IDropdownMenuActionViewItemOptions = {
470
keybindingProvider: action => this._keybindingService.lookupKeybinding(action.id),
471
...options,
472
menuAsChild: options?.menuAsChild ?? true,
473
classNames: options?.classNames ?? ['codicon', 'codicon-chevron-down'],
474
actionRunner: options?.actionRunner ?? this._register(new ActionRunner()),
475
};
476
477
this._dropdown = this._register(new DropdownMenuActionViewItem(submenuAction, submenuAction.actions, this._contextMenuService, dropdownOptions));
478
this._register(this._dropdown.actionRunner.onDidRun((e: IRunEvent) => {
479
if (e.action instanceof MenuItemAction) {
480
this.update(e.action);
481
}
482
}));
483
}
484
485
private update(lastAction: MenuItemAction): void {
486
if (this._options?.persistLastActionId) {
487
this._storageService.store(this._storageKey, lastAction.id, StorageScope.WORKSPACE, StorageTarget.MACHINE);
488
}
489
490
this._defaultActionDisposables.clear();
491
this._defaultAction = this._defaultActionDisposables.add(this._instaService.createInstance(MenuEntryActionViewItem, lastAction, { keybinding: this._getDefaultActionKeybindingLabel(lastAction) }));
492
this._defaultAction.actionRunner = this._defaultActionDisposables.add(new class extends ActionRunner {
493
protected override async runAction(action: IAction, context?: unknown): Promise<void> {
494
await action.run(undefined);
495
}
496
}());
497
498
if (this._container) {
499
this._defaultAction.render(prepend(this._container, $('.action-container')));
500
}
501
}
502
503
private _getDefaultActionKeybindingLabel(defaultAction: IAction) {
504
let defaultActionKeybinding: string | undefined;
505
if (this._options?.renderKeybindingWithDefaultActionLabel) {
506
const kb = this._keybindingService.lookupKeybinding(defaultAction.id);
507
if (kb) {
508
defaultActionKeybinding = `(${kb.getLabel()})`;
509
}
510
}
511
return defaultActionKeybinding;
512
}
513
514
override setActionContext(newContext: unknown): void {
515
super.setActionContext(newContext);
516
this._defaultAction.setActionContext(newContext);
517
this._dropdown.setActionContext(newContext);
518
}
519
520
override render(container: HTMLElement): void {
521
this._container = container;
522
super.render(this._container);
523
524
this._container.classList.add('monaco-dropdown-with-default');
525
526
const primaryContainer = $('.action-container');
527
this._defaultAction.render(append(this._container, primaryContainer));
528
this._register(addDisposableListener(primaryContainer, EventType.KEY_DOWN, (e: KeyboardEvent) => {
529
const event = new StandardKeyboardEvent(e);
530
if (event.equals(KeyCode.RightArrow)) {
531
this._defaultAction.element!.tabIndex = -1;
532
this._dropdown.focus();
533
event.stopPropagation();
534
}
535
}));
536
537
const dropdownContainer = $('.dropdown-action-container');
538
this._dropdown.render(append(this._container, dropdownContainer));
539
this._register(addDisposableListener(dropdownContainer, EventType.KEY_DOWN, (e: KeyboardEvent) => {
540
const event = new StandardKeyboardEvent(e);
541
if (event.equals(KeyCode.LeftArrow)) {
542
this._defaultAction.element!.tabIndex = 0;
543
this._dropdown.setFocusable(false);
544
this._defaultAction.element?.focus();
545
event.stopPropagation();
546
}
547
}));
548
}
549
550
override focus(fromRight?: boolean): void {
551
if (fromRight) {
552
this._dropdown.focus();
553
} else {
554
this._defaultAction.element!.tabIndex = 0;
555
this._defaultAction.element!.focus();
556
}
557
}
558
559
override blur(): void {
560
this._defaultAction.element!.tabIndex = -1;
561
this._dropdown.blur();
562
this._container!.blur();
563
}
564
565
override setFocusable(focusable: boolean): void {
566
if (focusable) {
567
this._defaultAction.element!.tabIndex = 0;
568
} else {
569
this._defaultAction.element!.tabIndex = -1;
570
this._dropdown.setFocusable(false);
571
}
572
}
573
}
574
575
class SubmenuEntrySelectActionViewItem extends SelectActionViewItem {
576
577
constructor(
578
action: SubmenuItemAction,
579
@IContextViewService contextViewService: IContextViewService,
580
@IConfigurationService configurationService: IConfigurationService,
581
) {
582
super(null, action, action.actions.map(a => ({
583
text: a.id === Separator.ID ? '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500' : a.label,
584
isDisabled: !a.enabled,
585
})), 0, contextViewService, defaultSelectBoxStyles, { ariaLabel: action.tooltip, optionsAsChildren: true, useCustomDrawn: !hasNativeContextMenu(configurationService) });
586
this.select(Math.max(0, action.actions.findIndex(a => a.checked)));
587
}
588
589
override render(container: HTMLElement): void {
590
super.render(container);
591
container.style.borderColor = asCssVariable(selectBorder);
592
}
593
594
protected override runAction(option: string, index: number): void {
595
const action = (this.action as SubmenuItemAction).actions[index];
596
if (action) {
597
this.actionRunner.run(action);
598
}
599
}
600
601
}
602
603
/**
604
* Creates action view items for menu actions or submenu actions.
605
*/
606
export function createActionViewItem(instaService: IInstantiationService, action: IAction, options: IDropdownMenuActionViewItemOptions | IMenuEntryActionViewItemOptions | undefined): undefined | MenuEntryActionViewItem | SubmenuEntryActionViewItem | BaseActionViewItem {
607
if (action instanceof MenuItemAction) {
608
return instaService.createInstance(MenuEntryActionViewItem, action, options);
609
} else if (action instanceof SubmenuItemAction) {
610
if (action.item.isSelection) {
611
return instaService.createInstance(SubmenuEntrySelectActionViewItem, action);
612
} else {
613
if (action.item.rememberDefaultAction) {
614
return instaService.createInstance(DropdownWithDefaultActionViewItem, action, { ...options, persistLastActionId: true });
615
} else {
616
return instaService.createInstance(SubmenuEntryActionViewItem, action, options);
617
}
618
}
619
} else {
620
return undefined;
621
}
622
}
623
624