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