Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/browser/ui/menu/menu.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 { isFirefox } from '../../browser.js';
7
import { EventType as TouchEventType, Gesture } from '../../touch.js';
8
import { $, addDisposableListener, append, clearNode, Dimension, EventHelper, EventLike, EventType, getActiveElement, getWindow, IDomNodePagePosition, isAncestor, isInShadowDOM } from '../../dom.js';
9
import { createStyleSheet } from '../../domStylesheets.js';
10
import { StandardKeyboardEvent } from '../../keyboardEvent.js';
11
import { StandardMouseEvent } from '../../mouseEvent.js';
12
import { ActionBar, ActionsOrientation, IActionViewItemProvider } from '../actionbar/actionbar.js';
13
import { ActionViewItem, BaseActionViewItem, IActionViewItemOptions } from '../actionbar/actionViewItems.js';
14
import { AnchorAlignment, layout, LayoutAnchorPosition } from '../contextview/contextview.js';
15
import { DomScrollableElement } from '../scrollbar/scrollableElement.js';
16
import { EmptySubmenuAction, IAction, IActionRunner, Separator, SubmenuAction } from '../../../common/actions.js';
17
import { RunOnceScheduler } from '../../../common/async.js';
18
import { Codicon } from '../../../common/codicons.js';
19
import { getCodiconFontCharacters } from '../../../common/codiconsUtil.js';
20
import { ThemeIcon } from '../../../common/themables.js';
21
import { Event } from '../../../common/event.js';
22
import { stripIcons } from '../../../common/iconLabels.js';
23
import { KeyCode } from '../../../common/keyCodes.js';
24
import { ResolvedKeybinding } from '../../../common/keybindings.js';
25
import { DisposableStore } from '../../../common/lifecycle.js';
26
import { isLinux, isMacintosh } from '../../../common/platform.js';
27
import { ScrollbarVisibility, ScrollEvent } from '../../../common/scrollable.js';
28
import * as strings from '../../../common/strings.js';
29
30
export const MENU_MNEMONIC_REGEX = /\(&([^\s&])\)|(^|[^&])&([^\s&])/;
31
export const MENU_ESCAPED_MNEMONIC_REGEX = /(&)?(&)([^\s&])/g;
32
33
34
35
export enum HorizontalDirection {
36
Right,
37
Left
38
}
39
40
export enum VerticalDirection {
41
Above,
42
Below
43
}
44
45
export interface IMenuDirection {
46
horizontal: HorizontalDirection;
47
vertical: VerticalDirection;
48
}
49
50
export interface IMenuOptions {
51
context?: unknown;
52
actionViewItemProvider?: IActionViewItemProvider;
53
actionRunner?: IActionRunner;
54
getKeyBinding?: (action: IAction) => ResolvedKeybinding | undefined;
55
ariaLabel?: string;
56
enableMnemonics?: boolean;
57
anchorAlignment?: AnchorAlignment;
58
expandDirection?: IMenuDirection;
59
useEventAsContext?: boolean;
60
submenuIds?: Set<string>;
61
}
62
63
export interface IMenuStyles {
64
shadowColor: string | undefined;
65
borderColor: string | undefined;
66
foregroundColor: string | undefined;
67
backgroundColor: string | undefined;
68
selectionForegroundColor: string | undefined;
69
selectionBackgroundColor: string | undefined;
70
selectionBorderColor: string | undefined;
71
separatorColor: string | undefined;
72
scrollbarShadow: string | undefined;
73
scrollbarSliderBackground: string | undefined;
74
scrollbarSliderHoverBackground: string | undefined;
75
scrollbarSliderActiveBackground: string | undefined;
76
}
77
78
export const unthemedMenuStyles: IMenuStyles = {
79
shadowColor: undefined,
80
borderColor: undefined,
81
foregroundColor: undefined,
82
backgroundColor: undefined,
83
selectionForegroundColor: undefined,
84
selectionBackgroundColor: undefined,
85
selectionBorderColor: undefined,
86
separatorColor: undefined,
87
scrollbarShadow: undefined,
88
scrollbarSliderBackground: undefined,
89
scrollbarSliderHoverBackground: undefined,
90
scrollbarSliderActiveBackground: undefined
91
};
92
93
interface ISubMenuData {
94
parent: Menu;
95
submenu?: Menu;
96
}
97
98
export class Menu extends ActionBar {
99
private mnemonics: Map<string, Array<BaseMenuActionViewItem>>;
100
private scrollableElement: DomScrollableElement;
101
private menuElement: HTMLElement;
102
static globalStyleSheet: HTMLStyleElement;
103
protected styleSheet: HTMLStyleElement | undefined;
104
105
constructor(container: HTMLElement, actions: ReadonlyArray<IAction>, options: IMenuOptions, private readonly menuStyles: IMenuStyles) {
106
container.classList.add('monaco-menu-container');
107
container.setAttribute('role', 'presentation');
108
const menuElement = document.createElement('div');
109
menuElement.classList.add('monaco-menu');
110
menuElement.setAttribute('role', 'presentation');
111
112
super(menuElement, {
113
orientation: ActionsOrientation.VERTICAL,
114
actionViewItemProvider: action => this.doGetActionViewItem(action, options, parentData),
115
context: options.context,
116
actionRunner: options.actionRunner,
117
ariaLabel: options.ariaLabel,
118
ariaRole: 'menu',
119
focusOnlyEnabledItems: true,
120
triggerKeys: { keys: [KeyCode.Enter, ...(isMacintosh || isLinux ? [KeyCode.Space] : [])], keyDown: true }
121
});
122
123
this.menuElement = menuElement;
124
125
this.actionsList.tabIndex = 0;
126
127
this.initializeOrUpdateStyleSheet(container, menuStyles);
128
129
this._register(Gesture.addTarget(menuElement));
130
131
this._register(addDisposableListener(menuElement, EventType.KEY_DOWN, (e) => {
132
const event = new StandardKeyboardEvent(e);
133
134
// Stop tab navigation of menus
135
if (event.equals(KeyCode.Tab)) {
136
e.preventDefault();
137
}
138
}));
139
140
if (options.enableMnemonics) {
141
this._register(addDisposableListener(menuElement, EventType.KEY_DOWN, (e) => {
142
const key = e.key.toLocaleLowerCase();
143
if (this.mnemonics.has(key)) {
144
EventHelper.stop(e, true);
145
const actions = this.mnemonics.get(key)!;
146
147
if (actions.length === 1) {
148
if (actions[0] instanceof SubmenuMenuActionViewItem && actions[0].container) {
149
this.focusItemByElement(actions[0].container);
150
}
151
152
actions[0].onClick(e);
153
}
154
155
if (actions.length > 1) {
156
const action = actions.shift();
157
if (action && action.container) {
158
this.focusItemByElement(action.container);
159
actions.push(action);
160
}
161
162
this.mnemonics.set(key, actions);
163
}
164
}
165
}));
166
}
167
168
if (isLinux) {
169
this._register(addDisposableListener(menuElement, EventType.KEY_DOWN, e => {
170
const event = new StandardKeyboardEvent(e);
171
172
if (event.equals(KeyCode.Home) || event.equals(KeyCode.PageUp)) {
173
this.focusedItem = this.viewItems.length - 1;
174
this.focusNext();
175
EventHelper.stop(e, true);
176
} else if (event.equals(KeyCode.End) || event.equals(KeyCode.PageDown)) {
177
this.focusedItem = 0;
178
this.focusPrevious();
179
EventHelper.stop(e, true);
180
}
181
}));
182
}
183
184
this._register(addDisposableListener(this.domNode, EventType.MOUSE_OUT, e => {
185
const relatedTarget = e.relatedTarget as HTMLElement;
186
if (!isAncestor(relatedTarget, this.domNode)) {
187
this.focusedItem = undefined;
188
this.updateFocus();
189
e.stopPropagation();
190
}
191
}));
192
193
this._register(addDisposableListener(this.actionsList, EventType.MOUSE_OVER, e => {
194
let target = e.target as HTMLElement;
195
if (!target || !isAncestor(target, this.actionsList) || target === this.actionsList) {
196
return;
197
}
198
199
while (target.parentElement !== this.actionsList && target.parentElement !== null) {
200
target = target.parentElement;
201
}
202
203
if (target.classList.contains('action-item')) {
204
const lastFocusedItem = this.focusedItem;
205
this.setFocusedItem(target);
206
207
if (lastFocusedItem !== this.focusedItem) {
208
this.updateFocus();
209
}
210
}
211
}));
212
213
// Support touch on actions list to focus items (needed for submenus)
214
this._register(Gesture.addTarget(this.actionsList));
215
this._register(addDisposableListener(this.actionsList, TouchEventType.Tap, e => {
216
let target = e.initialTarget as HTMLElement;
217
if (!target || !isAncestor(target, this.actionsList) || target === this.actionsList) {
218
return;
219
}
220
221
while (target.parentElement !== this.actionsList && target.parentElement !== null) {
222
target = target.parentElement;
223
}
224
225
if (target.classList.contains('action-item')) {
226
const lastFocusedItem = this.focusedItem;
227
this.setFocusedItem(target);
228
229
if (lastFocusedItem !== this.focusedItem) {
230
this.updateFocus();
231
}
232
}
233
}));
234
235
236
const parentData: ISubMenuData = {
237
parent: this
238
};
239
240
this.mnemonics = new Map<string, Array<BaseMenuActionViewItem>>();
241
242
// Scroll Logic
243
this.scrollableElement = this._register(new DomScrollableElement(menuElement, {
244
alwaysConsumeMouseWheel: true,
245
horizontal: ScrollbarVisibility.Hidden,
246
vertical: ScrollbarVisibility.Visible,
247
verticalScrollbarSize: 7,
248
handleMouseWheel: true,
249
useShadows: true
250
}));
251
252
const scrollElement = this.scrollableElement.getDomNode();
253
scrollElement.style.position = '';
254
255
this.styleScrollElement(scrollElement, menuStyles);
256
257
// Support scroll on menu drag
258
this._register(addDisposableListener(menuElement, TouchEventType.Change, e => {
259
EventHelper.stop(e, true);
260
261
const scrollTop = this.scrollableElement.getScrollPosition().scrollTop;
262
this.scrollableElement.setScrollPosition({ scrollTop: scrollTop - e.translationY });
263
}));
264
265
this._register(addDisposableListener(scrollElement, EventType.MOUSE_UP, e => {
266
// Absorb clicks in menu dead space https://github.com/microsoft/vscode/issues/63575
267
// We do this on the scroll element so the scroll bar doesn't dismiss the menu either
268
e.preventDefault();
269
}));
270
271
const window = getWindow(container);
272
menuElement.style.maxHeight = `${Math.max(10, window.innerHeight - container.getBoundingClientRect().top - 35)}px`;
273
274
actions = actions.filter((a, idx) => {
275
if (options.submenuIds?.has(a.id)) {
276
console.warn(`Found submenu cycle: ${a.id}`);
277
return false;
278
}
279
280
// Filter out consecutive or useless separators
281
if (a instanceof Separator) {
282
if (idx === actions.length - 1 || idx === 0) {
283
return false;
284
}
285
286
const prevAction = actions[idx - 1];
287
if (prevAction instanceof Separator) {
288
return false;
289
}
290
}
291
292
return true;
293
});
294
295
this.push(actions, { icon: true, label: true, isMenu: true });
296
297
container.appendChild(this.scrollableElement.getDomNode());
298
this.scrollableElement.scanDomNode();
299
300
this.viewItems.filter(item => !(item instanceof MenuSeparatorActionViewItem)).forEach((item, index, array) => {
301
(item as BaseMenuActionViewItem).updatePositionInSet(index + 1, array.length);
302
});
303
}
304
305
private initializeOrUpdateStyleSheet(container: HTMLElement, style: IMenuStyles): void {
306
if (!this.styleSheet) {
307
if (isInShadowDOM(container)) {
308
this.styleSheet = createStyleSheet(container);
309
} else {
310
if (!Menu.globalStyleSheet) {
311
Menu.globalStyleSheet = createStyleSheet();
312
}
313
this.styleSheet = Menu.globalStyleSheet;
314
}
315
}
316
this.styleSheet.textContent = getMenuWidgetCSS(style, isInShadowDOM(container));
317
}
318
319
private styleScrollElement(scrollElement: HTMLElement, style: IMenuStyles): void {
320
321
const fgColor = style.foregroundColor ?? '';
322
const bgColor = style.backgroundColor ?? '';
323
const border = style.borderColor ? `1px solid ${style.borderColor}` : '';
324
const borderRadius = '5px';
325
const shadow = style.shadowColor ? `0 2px 8px ${style.shadowColor}` : '';
326
327
scrollElement.style.outline = border;
328
scrollElement.style.borderRadius = borderRadius;
329
scrollElement.style.color = fgColor;
330
scrollElement.style.backgroundColor = bgColor;
331
scrollElement.style.boxShadow = shadow;
332
}
333
334
override getContainer(): HTMLElement {
335
return this.scrollableElement.getDomNode();
336
}
337
338
get onScroll(): Event<ScrollEvent> {
339
return this.scrollableElement.onScroll;
340
}
341
342
get scrollOffset(): number {
343
return this.menuElement.scrollTop;
344
}
345
346
trigger(index: number): void {
347
if (index <= this.viewItems.length && index >= 0) {
348
const item = this.viewItems[index];
349
if (item instanceof SubmenuMenuActionViewItem) {
350
super.focus(index);
351
item.open(true);
352
} else if (item instanceof BaseMenuActionViewItem) {
353
super.run(item._action, item._context);
354
} else {
355
return;
356
}
357
}
358
}
359
360
private focusItemByElement(element: HTMLElement) {
361
const lastFocusedItem = this.focusedItem;
362
this.setFocusedItem(element);
363
364
if (lastFocusedItem !== this.focusedItem) {
365
this.updateFocus();
366
}
367
}
368
369
private setFocusedItem(element: HTMLElement): void {
370
for (let i = 0; i < this.actionsList.children.length; i++) {
371
const elem = this.actionsList.children[i];
372
if (element === elem) {
373
this.focusedItem = i;
374
break;
375
}
376
}
377
}
378
379
protected override updateFocus(fromRight?: boolean): void {
380
super.updateFocus(fromRight, true, true);
381
382
if (typeof this.focusedItem !== 'undefined') {
383
// Workaround for #80047 caused by an issue in chromium
384
// https://bugs.chromium.org/p/chromium/issues/detail?id=414283
385
// When that's fixed, just call this.scrollableElement.scanDomNode()
386
this.scrollableElement.setScrollPosition({
387
scrollTop: Math.round(this.menuElement.scrollTop)
388
});
389
}
390
}
391
392
private doGetActionViewItem(action: IAction, options: IMenuOptions, parentData: ISubMenuData): BaseActionViewItem {
393
if (action instanceof Separator) {
394
return new MenuSeparatorActionViewItem(options.context, action, { icon: true }, this.menuStyles);
395
} else if (action instanceof SubmenuAction) {
396
const menuActionViewItem = new SubmenuMenuActionViewItem(action, action.actions, parentData, { ...options, submenuIds: new Set([...(options.submenuIds || []), action.id]) }, this.menuStyles);
397
398
if (options.enableMnemonics) {
399
const mnemonic = menuActionViewItem.getMnemonic();
400
if (mnemonic && menuActionViewItem.isEnabled()) {
401
let actionViewItems: BaseMenuActionViewItem[] = [];
402
if (this.mnemonics.has(mnemonic)) {
403
actionViewItems = this.mnemonics.get(mnemonic)!;
404
}
405
406
actionViewItems.push(menuActionViewItem);
407
408
this.mnemonics.set(mnemonic, actionViewItems);
409
}
410
}
411
412
return menuActionViewItem;
413
} else {
414
const keybindingLabel = options.getKeyBinding?.(action)?.getLabel();
415
const menuItemOptions: IMenuItemOptions = {
416
enableMnemonics: options.enableMnemonics,
417
useEventAsContext: options.useEventAsContext,
418
keybinding: keybindingLabel,
419
};
420
421
const menuActionViewItem = new BaseMenuActionViewItem(options.context, action, menuItemOptions, this.menuStyles);
422
423
if (options.enableMnemonics) {
424
const mnemonic = menuActionViewItem.getMnemonic();
425
if (mnemonic && menuActionViewItem.isEnabled()) {
426
let actionViewItems: BaseMenuActionViewItem[] = [];
427
if (this.mnemonics.has(mnemonic)) {
428
actionViewItems = this.mnemonics.get(mnemonic)!;
429
}
430
431
actionViewItems.push(menuActionViewItem);
432
433
this.mnemonics.set(mnemonic, actionViewItems);
434
}
435
}
436
437
return menuActionViewItem;
438
}
439
}
440
}
441
442
interface IMenuItemOptions extends IActionViewItemOptions {
443
readonly enableMnemonics?: boolean;
444
}
445
446
class BaseMenuActionViewItem extends BaseActionViewItem {
447
448
public container: HTMLElement | undefined;
449
450
protected override options: IMenuItemOptions;
451
protected item: HTMLElement | undefined;
452
453
private runOnceToEnableMouseUp: RunOnceScheduler;
454
private label: HTMLElement | undefined;
455
private check: HTMLElement | undefined;
456
private mnemonic: string | undefined;
457
private cssClass: string;
458
459
constructor(ctx: unknown, action: IAction, options: IMenuItemOptions, protected readonly menuStyle: IMenuStyles) {
460
options = {
461
...options,
462
isMenu: true,
463
icon: options.icon !== undefined ? options.icon : false,
464
label: options.label !== undefined ? options.label : true,
465
};
466
super(action, action, options);
467
468
this.options = options;
469
this.cssClass = '';
470
471
// Set mnemonic
472
if (this.options.label && options.enableMnemonics) {
473
const label = this.action.label;
474
if (label) {
475
const matches = MENU_MNEMONIC_REGEX.exec(label);
476
if (matches) {
477
this.mnemonic = (!!matches[1] ? matches[1] : matches[3]).toLocaleLowerCase();
478
}
479
}
480
}
481
482
// Add mouse up listener later to avoid accidental clicks
483
this.runOnceToEnableMouseUp = new RunOnceScheduler(() => {
484
if (!this.element) {
485
return;
486
}
487
488
this._register(addDisposableListener(this.element, EventType.MOUSE_UP, e => {
489
// removed default prevention as it conflicts
490
// with BaseActionViewItem #101537
491
// add back if issues arise and link new issue
492
EventHelper.stop(e, true);
493
494
// See https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Interact_with_the_clipboard
495
// > Writing to the clipboard
496
// > You can use the "cut" and "copy" commands without any special
497
// permission if you are using them in a short-lived event handler
498
// for a user action (for example, a click handler).
499
500
// => to get the Copy and Paste context menu actions working on Firefox,
501
// there should be no timeout here
502
if (isFirefox) {
503
const mouseEvent = new StandardMouseEvent(getWindow(this.element), e);
504
505
// Allowing right click to trigger the event causes the issue described below,
506
// but since the solution below does not work in FF, we must disable right click
507
if (mouseEvent.rightButton) {
508
return;
509
}
510
511
this.onClick(e);
512
}
513
514
// In all other cases, set timeout to allow context menu cancellation to trigger
515
// otherwise the action will destroy the menu and a second context menu
516
// will still trigger for right click.
517
else {
518
setTimeout(() => {
519
this.onClick(e);
520
}, 0);
521
}
522
}));
523
524
this._register(addDisposableListener(this.element, EventType.CONTEXT_MENU, e => {
525
EventHelper.stop(e, true);
526
}));
527
}, 100);
528
529
this._register(this.runOnceToEnableMouseUp);
530
}
531
532
override render(container: HTMLElement): void {
533
super.render(container);
534
535
if (!this.element) {
536
return;
537
}
538
539
this.container = container;
540
541
this.item = append(this.element, $('a.action-menu-item'));
542
if (this._action.id === Separator.ID) {
543
// A separator is a presentation item
544
this.item.setAttribute('role', 'presentation');
545
} else {
546
this.item.setAttribute('role', 'menuitem');
547
if (this.mnemonic) {
548
this.item.setAttribute('aria-keyshortcuts', `${this.mnemonic}`);
549
}
550
}
551
552
this.check = append(this.item, $('span.menu-item-check' + ThemeIcon.asCSSSelector(Codicon.menuSelection)));
553
this.check.setAttribute('role', 'none');
554
555
this.label = append(this.item, $('span.action-label'));
556
557
if (this.options.label && this.options.keybinding) {
558
append(this.item, $('span.keybinding')).textContent = this.options.keybinding;
559
}
560
561
// Adds mouse up listener to actually run the action
562
this.runOnceToEnableMouseUp.schedule();
563
564
this.updateClass();
565
this.updateLabel();
566
this.updateTooltip();
567
this.updateEnabled();
568
this.updateChecked();
569
570
this.applyStyle();
571
}
572
573
override blur(): void {
574
super.blur();
575
this.applyStyle();
576
}
577
578
override focus(): void {
579
super.focus();
580
581
this.item?.focus();
582
583
this.applyStyle();
584
}
585
586
updatePositionInSet(pos: number, setSize: number): void {
587
if (this.item) {
588
this.item.setAttribute('aria-posinset', `${pos}`);
589
this.item.setAttribute('aria-setsize', `${setSize}`);
590
}
591
}
592
593
protected override updateLabel(): void {
594
if (!this.label) {
595
return;
596
}
597
598
if (this.options.label) {
599
clearNode(this.label);
600
601
let label = stripIcons(this.action.label);
602
if (label) {
603
const cleanLabel = cleanMnemonic(label);
604
if (!this.options.enableMnemonics) {
605
label = cleanLabel;
606
}
607
608
this.label.setAttribute('aria-label', cleanLabel.replace(/&&/g, '&'));
609
610
const matches = MENU_MNEMONIC_REGEX.exec(label);
611
612
if (matches) {
613
label = strings.escape(label);
614
615
// This is global, reset it
616
MENU_ESCAPED_MNEMONIC_REGEX.lastIndex = 0;
617
let escMatch = MENU_ESCAPED_MNEMONIC_REGEX.exec(label);
618
619
// We can't use negative lookbehind so if we match our negative and skip
620
while (escMatch && escMatch[1]) {
621
escMatch = MENU_ESCAPED_MNEMONIC_REGEX.exec(label);
622
}
623
624
const replaceDoubleEscapes = (str: string) => str.replace(/&amp;&amp;/g, '&amp;');
625
626
if (escMatch) {
627
this.label.append(
628
strings.ltrim(replaceDoubleEscapes(label.substr(0, escMatch.index)), ' '),
629
$('u', { 'aria-hidden': 'true' },
630
escMatch[3]),
631
strings.rtrim(replaceDoubleEscapes(label.substr(escMatch.index + escMatch[0].length)), ' '));
632
} else {
633
this.label.textContent = replaceDoubleEscapes(label).trim();
634
}
635
636
this.item?.setAttribute('aria-keyshortcuts', (!!matches[1] ? matches[1] : matches[3]).toLocaleLowerCase());
637
} else {
638
this.label.textContent = label.replace(/&&/g, '&').trim();
639
}
640
}
641
}
642
}
643
644
protected override updateTooltip(): void {
645
// menus should function like native menus and they do not have tooltips
646
}
647
648
protected override updateClass(): void {
649
if (this.cssClass && this.item) {
650
this.item.classList.remove(...this.cssClass.split(' '));
651
}
652
if (this.options.icon && this.label) {
653
this.cssClass = this.action.class || '';
654
this.label.classList.add('icon');
655
if (this.cssClass) {
656
this.label.classList.add(...this.cssClass.split(' '));
657
}
658
this.updateEnabled();
659
} else if (this.label) {
660
this.label.classList.remove('icon');
661
}
662
}
663
664
protected override updateEnabled(): void {
665
if (this.action.enabled) {
666
if (this.element) {
667
this.element.classList.remove('disabled');
668
this.element.removeAttribute('aria-disabled');
669
}
670
671
if (this.item) {
672
this.item.classList.remove('disabled');
673
this.item.removeAttribute('aria-disabled');
674
this.item.tabIndex = 0;
675
}
676
} else {
677
if (this.element) {
678
this.element.classList.add('disabled');
679
this.element.setAttribute('aria-disabled', 'true');
680
}
681
682
if (this.item) {
683
this.item.classList.add('disabled');
684
this.item.setAttribute('aria-disabled', 'true');
685
}
686
}
687
}
688
689
protected override updateChecked(): void {
690
if (!this.item) {
691
return;
692
}
693
694
const checked = this.action.checked;
695
this.item.classList.toggle('checked', !!checked);
696
if (checked !== undefined) {
697
this.item.setAttribute('role', 'menuitemcheckbox');
698
this.item.setAttribute('aria-checked', checked ? 'true' : 'false');
699
} else {
700
this.item.setAttribute('role', 'menuitem');
701
this.item.setAttribute('aria-checked', '');
702
}
703
}
704
705
getMnemonic(): string | undefined {
706
return this.mnemonic;
707
}
708
709
protected applyStyle(): void {
710
const isSelected = this.element && this.element.classList.contains('focused');
711
const fgColor = isSelected && this.menuStyle.selectionForegroundColor ? this.menuStyle.selectionForegroundColor : this.menuStyle.foregroundColor;
712
const bgColor = isSelected && this.menuStyle.selectionBackgroundColor ? this.menuStyle.selectionBackgroundColor : undefined;
713
const outline = isSelected && this.menuStyle.selectionBorderColor ? `1px solid ${this.menuStyle.selectionBorderColor}` : '';
714
const outlineOffset = isSelected && this.menuStyle.selectionBorderColor ? `-1px` : '';
715
716
if (this.item) {
717
this.item.style.color = fgColor ?? '';
718
this.item.style.backgroundColor = bgColor ?? '';
719
this.item.style.outline = outline;
720
this.item.style.outlineOffset = outlineOffset;
721
}
722
723
if (this.check) {
724
this.check.style.color = fgColor ?? '';
725
}
726
}
727
}
728
729
class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
730
private mysubmenu: Menu | null = null;
731
private submenuContainer: HTMLElement | undefined;
732
private submenuIndicator: HTMLElement | undefined;
733
private readonly submenuDisposables = this._register(new DisposableStore());
734
private mouseOver: boolean = false;
735
private showScheduler: RunOnceScheduler;
736
private hideScheduler: RunOnceScheduler;
737
private expandDirection: IMenuDirection;
738
739
constructor(
740
action: IAction,
741
private submenuActions: ReadonlyArray<IAction>,
742
private parentData: ISubMenuData,
743
private submenuOptions: IMenuOptions,
744
menuStyles: IMenuStyles
745
) {
746
super(action, action, submenuOptions, menuStyles);
747
748
this.expandDirection = submenuOptions && submenuOptions.expandDirection !== undefined ? submenuOptions.expandDirection : { horizontal: HorizontalDirection.Right, vertical: VerticalDirection.Below };
749
750
this.showScheduler = new RunOnceScheduler(() => {
751
if (this.mouseOver) {
752
this.cleanupExistingSubmenu(false);
753
this.createSubmenu(false);
754
}
755
}, 250);
756
757
this.hideScheduler = new RunOnceScheduler(() => {
758
if (this.element && (!isAncestor(getActiveElement(), this.element) && this.parentData.submenu === this.mysubmenu)) {
759
this.parentData.parent.focus(false);
760
this.cleanupExistingSubmenu(true);
761
}
762
}, 750);
763
}
764
765
override render(container: HTMLElement): void {
766
super.render(container);
767
768
if (!this.element) {
769
return;
770
}
771
772
if (this.item) {
773
this.item.classList.add('monaco-submenu-item');
774
this.item.tabIndex = 0;
775
this.item.setAttribute('aria-haspopup', 'true');
776
this.updateAriaExpanded('false');
777
this.submenuIndicator = append(this.item, $('span.submenu-indicator' + ThemeIcon.asCSSSelector(Codicon.menuSubmenu)));
778
this.submenuIndicator.setAttribute('aria-hidden', 'true');
779
}
780
781
this._register(addDisposableListener(this.element, EventType.KEY_UP, e => {
782
const event = new StandardKeyboardEvent(e);
783
if (event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Enter)) {
784
EventHelper.stop(e, true);
785
786
this.createSubmenu(true);
787
}
788
}));
789
790
this._register(addDisposableListener(this.element, EventType.KEY_DOWN, e => {
791
const event = new StandardKeyboardEvent(e);
792
793
if (getActiveElement() === this.item) {
794
if (event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Enter)) {
795
EventHelper.stop(e, true);
796
}
797
}
798
}));
799
800
this._register(addDisposableListener(this.element, EventType.MOUSE_OVER, e => {
801
if (!this.mouseOver) {
802
this.mouseOver = true;
803
804
this.showScheduler.schedule();
805
}
806
}));
807
808
this._register(addDisposableListener(this.element, EventType.MOUSE_LEAVE, e => {
809
this.mouseOver = false;
810
}));
811
812
this._register(addDisposableListener(this.element, EventType.FOCUS_OUT, e => {
813
if (this.element && !isAncestor(getActiveElement(), this.element)) {
814
this.hideScheduler.schedule();
815
}
816
}));
817
818
this._register(this.parentData.parent.onScroll(() => {
819
if (this.parentData.submenu === this.mysubmenu) {
820
this.parentData.parent.focus(false);
821
this.cleanupExistingSubmenu(true);
822
}
823
}));
824
}
825
826
protected override updateEnabled(): void {
827
// override on submenu entry
828
// native menus do not observe enablement on sumbenus
829
// we mimic that behavior
830
}
831
832
open(selectFirst?: boolean): void {
833
this.cleanupExistingSubmenu(false);
834
this.createSubmenu(selectFirst);
835
}
836
837
override onClick(e: EventLike): void {
838
// stop clicking from trying to run an action
839
EventHelper.stop(e, true);
840
841
this.cleanupExistingSubmenu(false);
842
this.createSubmenu(true);
843
}
844
845
private cleanupExistingSubmenu(force: boolean): void {
846
if (this.parentData.submenu && (force || (this.parentData.submenu !== this.mysubmenu))) {
847
848
// disposal may throw if the submenu has already been removed
849
try {
850
this.parentData.submenu.dispose();
851
} catch { }
852
853
this.parentData.submenu = undefined;
854
this.updateAriaExpanded('false');
855
if (this.submenuContainer) {
856
this.submenuDisposables.clear();
857
this.submenuContainer = undefined;
858
}
859
}
860
}
861
862
private calculateSubmenuMenuLayout(windowDimensions: Dimension, submenu: Dimension, entry: IDomNodePagePosition, expandDirection: IMenuDirection): { top: number; left: number } {
863
const ret = { top: 0, left: 0 };
864
865
// Start with horizontal
866
ret.left = layout(windowDimensions.width, submenu.width, { position: expandDirection.horizontal === HorizontalDirection.Right ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After, offset: entry.left, size: entry.width });
867
868
// We don't have enough room to layout the menu fully, so we are overlapping the menu
869
if (ret.left >= entry.left && ret.left < entry.left + entry.width) {
870
if (entry.left + 10 + submenu.width <= windowDimensions.width) {
871
ret.left = entry.left + 10;
872
}
873
874
entry.top += 10;
875
entry.height = 0;
876
}
877
878
// Now that we have a horizontal position, try layout vertically
879
ret.top = layout(windowDimensions.height, submenu.height, { position: LayoutAnchorPosition.Before, offset: entry.top, size: 0 });
880
881
// We didn't have enough room below, but we did above, so we shift down to align the menu
882
if (ret.top + submenu.height === entry.top && ret.top + entry.height + submenu.height <= windowDimensions.height) {
883
ret.top += entry.height;
884
}
885
886
return ret;
887
}
888
889
private createSubmenu(selectFirstItem = true): void {
890
if (!this.element) {
891
return;
892
}
893
894
if (!this.parentData.submenu) {
895
this.updateAriaExpanded('true');
896
this.submenuContainer = append(this.element, $('div.monaco-submenu'));
897
this.submenuContainer.classList.add('menubar-menu-items-holder', 'context-view');
898
899
// Set the top value of the menu container before construction
900
// This allows the menu constructor to calculate the proper max height
901
const computedStyles = getWindow(this.parentData.parent.domNode).getComputedStyle(this.parentData.parent.domNode);
902
const paddingTop = parseFloat(computedStyles.paddingTop || '0') || 0;
903
this.submenuContainer.style.position = 'fixed';
904
this.submenuContainer.style.top = '0';
905
this.submenuContainer.style.left = '0';
906
907
this.parentData.submenu = new Menu(this.submenuContainer, this.submenuActions.length ? this.submenuActions : [new EmptySubmenuAction()], this.submenuOptions, this.menuStyle);
908
909
// layout submenu
910
const entryBox = this.element.getBoundingClientRect();
911
const entryBoxUpdated = {
912
top: entryBox.top - paddingTop,
913
left: entryBox.left,
914
height: entryBox.height + 2 * paddingTop,
915
width: entryBox.width
916
};
917
918
const viewBox = this.submenuContainer.getBoundingClientRect();
919
920
const window = getWindow(this.element);
921
const { top, left } = this.calculateSubmenuMenuLayout(new Dimension(window.innerWidth, window.innerHeight), Dimension.lift(viewBox), entryBoxUpdated, this.expandDirection);
922
// subtract offsets caused by transform parent
923
this.submenuContainer.style.left = `${left - viewBox.left}px`;
924
this.submenuContainer.style.top = `${top - viewBox.top}px`;
925
926
this.submenuDisposables.add(addDisposableListener(this.submenuContainer, EventType.KEY_UP, e => {
927
const event = new StandardKeyboardEvent(e);
928
if (event.equals(KeyCode.LeftArrow)) {
929
EventHelper.stop(e, true);
930
931
this.parentData.parent.focus();
932
933
this.cleanupExistingSubmenu(true);
934
}
935
}));
936
937
this.submenuDisposables.add(addDisposableListener(this.submenuContainer, EventType.KEY_DOWN, e => {
938
const event = new StandardKeyboardEvent(e);
939
if (event.equals(KeyCode.LeftArrow)) {
940
EventHelper.stop(e, true);
941
}
942
}));
943
944
945
this.submenuDisposables.add(this.parentData.submenu.onDidCancel(() => {
946
this.parentData.parent.focus();
947
948
this.cleanupExistingSubmenu(true);
949
}));
950
951
this.parentData.submenu.focus(selectFirstItem);
952
953
this.mysubmenu = this.parentData.submenu;
954
} else {
955
this.parentData.submenu.focus(false);
956
}
957
}
958
959
private updateAriaExpanded(value: string): void {
960
if (this.item) {
961
this.item?.setAttribute('aria-expanded', value);
962
}
963
}
964
965
protected override applyStyle(): void {
966
super.applyStyle();
967
968
const isSelected = this.element && this.element.classList.contains('focused');
969
const fgColor = isSelected && this.menuStyle.selectionForegroundColor ? this.menuStyle.selectionForegroundColor : this.menuStyle.foregroundColor;
970
971
if (this.submenuIndicator) {
972
this.submenuIndicator.style.color = fgColor ?? '';
973
}
974
}
975
976
override dispose(): void {
977
super.dispose();
978
979
this.hideScheduler.dispose();
980
981
if (this.mysubmenu) {
982
this.mysubmenu.dispose();
983
this.mysubmenu = null;
984
}
985
986
if (this.submenuContainer) {
987
this.submenuContainer = undefined;
988
}
989
}
990
}
991
992
class MenuSeparatorActionViewItem extends ActionViewItem {
993
constructor(context: unknown, action: IAction, options: IActionViewItemOptions, private readonly menuStyles: IMenuStyles) {
994
super(context, action, options);
995
}
996
997
override render(container: HTMLElement): void {
998
super.render(container);
999
if (this.label) {
1000
this.label.style.borderBottomColor = this.menuStyles.separatorColor ? `${this.menuStyles.separatorColor}` : '';
1001
}
1002
}
1003
}
1004
1005
export function cleanMnemonic(label: string): string {
1006
const regex = MENU_MNEMONIC_REGEX;
1007
1008
const matches = regex.exec(label);
1009
if (!matches) {
1010
return label;
1011
}
1012
1013
const mnemonicInText = !matches[1];
1014
1015
return label.replace(regex, mnemonicInText ? '$2$3' : '').trim();
1016
}
1017
1018
export function formatRule(c: ThemeIcon) {
1019
const fontCharacter = getCodiconFontCharacters()[c.id];
1020
return `.codicon-${c.id}:before { content: '\\${fontCharacter.toString(16)}'; }`;
1021
}
1022
1023
export function getMenuWidgetCSS(style: IMenuStyles, isForShadowDom: boolean): string {
1024
let result = /* css */`
1025
.monaco-menu {
1026
font-size: 13px;
1027
border-radius: 5px;
1028
min-width: 160px;
1029
}
1030
1031
${formatRule(Codicon.menuSelection)}
1032
${formatRule(Codicon.menuSubmenu)}
1033
1034
.monaco-menu .monaco-action-bar {
1035
text-align: right;
1036
overflow: hidden;
1037
white-space: nowrap;
1038
}
1039
1040
.monaco-menu .monaco-action-bar .actions-container {
1041
display: flex;
1042
margin: 0 auto;
1043
padding: 0;
1044
width: 100%;
1045
justify-content: flex-end;
1046
}
1047
1048
.monaco-menu .monaco-action-bar.vertical .actions-container {
1049
display: inline-block;
1050
}
1051
1052
.monaco-menu .monaco-action-bar.reverse .actions-container {
1053
flex-direction: row-reverse;
1054
}
1055
1056
.monaco-menu .monaco-action-bar .action-item {
1057
cursor: pointer;
1058
display: inline-block;
1059
transition: transform 50ms ease;
1060
position: relative; /* DO NOT REMOVE - this is the key to preventing the ghosting icon bug in Chrome 42 */
1061
}
1062
1063
.monaco-menu .monaco-action-bar .action-item.disabled {
1064
cursor: default;
1065
}
1066
1067
.monaco-menu .monaco-action-bar .action-item .icon,
1068
.monaco-menu .monaco-action-bar .action-item .codicon {
1069
display: inline-block;
1070
}
1071
1072
.monaco-menu .monaco-action-bar .action-item .codicon {
1073
display: flex;
1074
align-items: center;
1075
}
1076
1077
.monaco-menu .monaco-action-bar .action-label {
1078
font-size: 11px;
1079
margin-right: 4px;
1080
}
1081
1082
.monaco-menu .monaco-action-bar .action-item.disabled .action-label,
1083
.monaco-menu .monaco-action-bar .action-item.disabled .action-label:hover {
1084
color: var(--vscode-disabledForeground);
1085
}
1086
1087
/* Vertical actions */
1088
1089
.monaco-menu .monaco-action-bar.vertical {
1090
text-align: left;
1091
}
1092
1093
.monaco-menu .monaco-action-bar.vertical .action-item {
1094
display: block;
1095
}
1096
1097
.monaco-menu .monaco-action-bar.vertical .action-label.separator {
1098
display: block;
1099
border-bottom: 1px solid var(--vscode-menu-separatorBackground);
1100
padding-top: 1px;
1101
padding: 30px;
1102
}
1103
1104
.monaco-menu .secondary-actions .monaco-action-bar .action-label {
1105
margin-left: 6px;
1106
}
1107
1108
/* Action Items */
1109
.monaco-menu .monaco-action-bar .action-item.select-container {
1110
overflow: hidden; /* somehow the dropdown overflows its container, we prevent it here to not push */
1111
flex: 1;
1112
max-width: 170px;
1113
min-width: 60px;
1114
display: flex;
1115
align-items: center;
1116
justify-content: center;
1117
margin-right: 10px;
1118
}
1119
1120
.monaco-menu .monaco-action-bar.vertical {
1121
margin-left: 0;
1122
overflow: visible;
1123
}
1124
1125
.monaco-menu .monaco-action-bar.vertical .actions-container {
1126
display: block;
1127
}
1128
1129
.monaco-menu .monaco-action-bar.vertical .action-item {
1130
padding: 0;
1131
transform: none;
1132
display: flex;
1133
}
1134
1135
.monaco-menu .monaco-action-bar.vertical .action-item.active {
1136
transform: none;
1137
}
1138
1139
.monaco-menu .monaco-action-bar.vertical .action-menu-item {
1140
flex: 1 1 auto;
1141
display: flex;
1142
height: 2em;
1143
align-items: center;
1144
position: relative;
1145
margin: 0 4px;
1146
border-radius: 4px;
1147
}
1148
1149
.monaco-menu .monaco-action-bar.vertical .action-menu-item:hover .keybinding,
1150
.monaco-menu .monaco-action-bar.vertical .action-menu-item:focus .keybinding {
1151
opacity: unset;
1152
}
1153
1154
.monaco-menu .monaco-action-bar.vertical .action-label {
1155
flex: 1 1 auto;
1156
text-decoration: none;
1157
padding: 0 1em;
1158
background: none;
1159
font-size: 12px;
1160
line-height: 1;
1161
}
1162
1163
.monaco-menu .monaco-action-bar.vertical .keybinding,
1164
.monaco-menu .monaco-action-bar.vertical .submenu-indicator {
1165
display: inline-block;
1166
flex: 2 1 auto;
1167
padding: 0 1em;
1168
text-align: right;
1169
font-size: 12px;
1170
line-height: 1;
1171
opacity: 0.7;
1172
}
1173
1174
.monaco-menu .monaco-action-bar.vertical .submenu-indicator {
1175
height: 100%;
1176
}
1177
1178
.monaco-menu .monaco-action-bar.vertical .submenu-indicator.codicon {
1179
font-size: 16px !important;
1180
display: flex;
1181
align-items: center;
1182
}
1183
1184
.monaco-menu .monaco-action-bar.vertical .submenu-indicator.codicon::before {
1185
margin-left: auto;
1186
margin-right: -20px;
1187
}
1188
1189
.monaco-menu .monaco-action-bar.vertical .action-item.disabled .keybinding,
1190
.monaco-menu .monaco-action-bar.vertical .action-item.disabled .submenu-indicator {
1191
opacity: 0.4;
1192
}
1193
1194
.monaco-menu .monaco-action-bar.vertical .action-label:not(.separator) {
1195
display: inline-block;
1196
box-sizing: border-box;
1197
margin: 0;
1198
}
1199
1200
.monaco-menu .monaco-action-bar.vertical .action-item {
1201
position: static;
1202
overflow: visible;
1203
}
1204
1205
.monaco-menu .monaco-action-bar.vertical .action-item .monaco-submenu {
1206
position: absolute;
1207
}
1208
1209
.monaco-menu .monaco-action-bar.vertical .action-label.separator {
1210
width: 100%;
1211
height: 0px !important;
1212
opacity: 1;
1213
}
1214
1215
.monaco-menu .monaco-action-bar.vertical .action-label.separator.text {
1216
padding: 0.7em 1em 0.1em 1em;
1217
font-weight: bold;
1218
opacity: 1;
1219
}
1220
1221
.monaco-menu .monaco-action-bar.vertical .action-label:hover {
1222
color: inherit;
1223
}
1224
1225
.monaco-menu .monaco-action-bar.vertical .menu-item-check {
1226
position: absolute;
1227
visibility: hidden;
1228
width: 1em;
1229
height: 100%;
1230
}
1231
1232
.monaco-menu .monaco-action-bar.vertical .action-menu-item.checked .menu-item-check {
1233
visibility: visible;
1234
display: flex;
1235
align-items: center;
1236
justify-content: center;
1237
}
1238
1239
/* Context Menu */
1240
1241
.context-view.monaco-menu-container {
1242
outline: 0;
1243
border: none;
1244
animation: fadeIn 0.083s linear;
1245
-webkit-app-region: no-drag;
1246
}
1247
1248
.context-view.monaco-menu-container :focus,
1249
.context-view.monaco-menu-container .monaco-action-bar.vertical:focus,
1250
.context-view.monaco-menu-container .monaco-action-bar.vertical :focus {
1251
outline: 0;
1252
}
1253
1254
.hc-black .context-view.monaco-menu-container,
1255
.hc-light .context-view.monaco-menu-container,
1256
:host-context(.hc-black) .context-view.monaco-menu-container,
1257
:host-context(.hc-light) .context-view.monaco-menu-container {
1258
box-shadow: none;
1259
}
1260
1261
.hc-black .monaco-menu .monaco-action-bar.vertical .action-item.focused,
1262
.hc-light .monaco-menu .monaco-action-bar.vertical .action-item.focused,
1263
:host-context(.hc-black) .monaco-menu .monaco-action-bar.vertical .action-item.focused,
1264
:host-context(.hc-light) .monaco-menu .monaco-action-bar.vertical .action-item.focused {
1265
background: none;
1266
}
1267
1268
/* Vertical Action Bar Styles */
1269
1270
.monaco-menu .monaco-action-bar.vertical {
1271
padding: 4px 0;
1272
}
1273
1274
.monaco-menu .monaco-action-bar.vertical .action-menu-item {
1275
height: 2em;
1276
}
1277
1278
.monaco-menu .monaco-action-bar.vertical .action-label:not(.separator),
1279
.monaco-menu .monaco-action-bar.vertical .keybinding {
1280
font-size: inherit;
1281
padding: 0 2em;
1282
max-height: 100%;
1283
}
1284
1285
.monaco-menu .monaco-action-bar.vertical .menu-item-check {
1286
font-size: inherit;
1287
width: 2em;
1288
}
1289
1290
.monaco-menu .monaco-action-bar.vertical .action-label.separator {
1291
font-size: inherit;
1292
margin: 5px 0 !important;
1293
padding: 0;
1294
border-radius: 0;
1295
}
1296
1297
.linux .monaco-menu .monaco-action-bar.vertical .action-label.separator,
1298
:host-context(.linux) .monaco-menu .monaco-action-bar.vertical .action-label.separator {
1299
margin-left: 0;
1300
margin-right: 0;
1301
}
1302
1303
.monaco-menu .monaco-action-bar.vertical .submenu-indicator {
1304
font-size: 60%;
1305
padding: 0 1.8em;
1306
}
1307
1308
.linux .monaco-menu .monaco-action-bar.vertical .submenu-indicator,
1309
:host-context(.linux) .monaco-menu .monaco-action-bar.vertical .submenu-indicator {
1310
height: 100%;
1311
mask-size: 10px 10px;
1312
-webkit-mask-size: 10px 10px;
1313
}
1314
1315
.monaco-menu .action-item {
1316
cursor: default;
1317
}`;
1318
1319
if (isForShadowDom) {
1320
// Only define scrollbar styles when used inside shadow dom,
1321
// otherwise leave their styling to the global workbench styling.
1322
result += `
1323
/* Arrows */
1324
.monaco-scrollable-element > .scrollbar > .scra {
1325
cursor: pointer;
1326
font-size: 11px !important;
1327
}
1328
1329
.monaco-scrollable-element > .visible {
1330
opacity: 1;
1331
1332
/* Background rule added for IE9 - to allow clicks on dom node */
1333
background:rgba(0,0,0,0);
1334
1335
transition: opacity 100ms linear;
1336
}
1337
.monaco-scrollable-element > .invisible {
1338
opacity: 0;
1339
pointer-events: none;
1340
}
1341
.monaco-scrollable-element > .invisible.fade {
1342
transition: opacity 800ms linear;
1343
}
1344
1345
/* Scrollable Content Inset Shadow */
1346
.monaco-scrollable-element > .shadow {
1347
position: absolute;
1348
display: none;
1349
}
1350
.monaco-scrollable-element > .shadow.top {
1351
display: block;
1352
top: 0;
1353
left: 3px;
1354
height: 3px;
1355
width: 100%;
1356
}
1357
.monaco-scrollable-element > .shadow.left {
1358
display: block;
1359
top: 3px;
1360
left: 0;
1361
height: 100%;
1362
width: 3px;
1363
}
1364
.monaco-scrollable-element > .shadow.top-left-corner {
1365
display: block;
1366
top: 0;
1367
left: 0;
1368
height: 3px;
1369
width: 3px;
1370
}
1371
/* Fix for https://github.com/microsoft/vscode/issues/103170 */
1372
.monaco-menu .action-item .monaco-submenu {
1373
z-index: 1;
1374
}
1375
`;
1376
1377
// Scrollbars
1378
const scrollbarShadowColor = style.scrollbarShadow;
1379
if (scrollbarShadowColor) {
1380
result += `
1381
.monaco-scrollable-element > .shadow.top {
1382
box-shadow: ${scrollbarShadowColor} 0 6px 6px -6px inset;
1383
}
1384
1385
.monaco-scrollable-element > .shadow.left {
1386
box-shadow: ${scrollbarShadowColor} 6px 0 6px -6px inset;
1387
}
1388
1389
.monaco-scrollable-element > .shadow.top.left {
1390
box-shadow: ${scrollbarShadowColor} 6px 6px 6px -6px inset;
1391
}
1392
`;
1393
}
1394
1395
const scrollbarSliderBackgroundColor = style.scrollbarSliderBackground;
1396
if (scrollbarSliderBackgroundColor) {
1397
result += `
1398
.monaco-scrollable-element > .scrollbar > .slider {
1399
background: ${scrollbarSliderBackgroundColor};
1400
}
1401
`;
1402
}
1403
1404
const scrollbarSliderHoverBackgroundColor = style.scrollbarSliderHoverBackground;
1405
if (scrollbarSliderHoverBackgroundColor) {
1406
result += `
1407
.monaco-scrollable-element > .scrollbar > .slider:hover {
1408
background: ${scrollbarSliderHoverBackgroundColor};
1409
}
1410
`;
1411
}
1412
1413
const scrollbarSliderActiveBackgroundColor = style.scrollbarSliderActiveBackground;
1414
if (scrollbarSliderActiveBackgroundColor) {
1415
result += `
1416
.monaco-scrollable-element > .scrollbar > .slider.active {
1417
background: ${scrollbarSliderActiveBackgroundColor};
1418
}
1419
`;
1420
}
1421
}
1422
1423
return result;
1424
}
1425
1426