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
5236 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
const actions = this.mnemonics.get(key);
144
if (actions !== undefined) {
145
EventHelper.stop(e, true);
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
const actionViewItems = this.mnemonics.get(mnemonic);
402
if (actionViewItems !== undefined) {
403
actionViewItems.push(menuActionViewItem);
404
} else {
405
this.mnemonics.set(mnemonic, [menuActionViewItem]);
406
}
407
}
408
}
409
410
return menuActionViewItem;
411
} else {
412
const keybindingLabel = options.getKeyBinding?.(action)?.getLabel();
413
const menuItemOptions: IMenuItemOptions = {
414
enableMnemonics: options.enableMnemonics,
415
useEventAsContext: options.useEventAsContext,
416
keybinding: keybindingLabel,
417
};
418
419
const menuActionViewItem = new BaseMenuActionViewItem(options.context, action, menuItemOptions, this.menuStyles);
420
421
if (options.enableMnemonics) {
422
const mnemonic = menuActionViewItem.getMnemonic();
423
if (mnemonic && menuActionViewItem.isEnabled()) {
424
const actionViewItems = this.mnemonics.get(mnemonic);
425
if (actionViewItems !== undefined) {
426
actionViewItems.push(menuActionViewItem);
427
} else {
428
this.mnemonics.set(mnemonic, [menuActionViewItem]);
429
}
430
}
431
}
432
433
return menuActionViewItem;
434
}
435
}
436
}
437
438
interface IMenuItemOptions extends IActionViewItemOptions {
439
readonly enableMnemonics?: boolean;
440
}
441
442
class BaseMenuActionViewItem extends BaseActionViewItem {
443
444
public container: HTMLElement | undefined;
445
446
protected override options: IMenuItemOptions;
447
protected item: HTMLElement | undefined;
448
449
private runOnceToEnableMouseUp: RunOnceScheduler;
450
private label: HTMLElement | undefined;
451
private check: HTMLElement | undefined;
452
private mnemonic: string | undefined;
453
private cssClass: string;
454
455
constructor(ctx: unknown, action: IAction, options: IMenuItemOptions, protected readonly menuStyle: IMenuStyles) {
456
options = {
457
...options,
458
isMenu: true,
459
icon: options.icon !== undefined ? options.icon : false,
460
label: options.label !== undefined ? options.label : true,
461
};
462
super(action, action, options);
463
464
this.options = options;
465
this.cssClass = '';
466
467
// Set mnemonic
468
if (this.options.label && options.enableMnemonics) {
469
const label = this.action.label;
470
if (label) {
471
const matches = MENU_MNEMONIC_REGEX.exec(label);
472
if (matches) {
473
this.mnemonic = (!!matches[1] ? matches[1] : matches[3]).toLocaleLowerCase();
474
}
475
}
476
}
477
478
// Add mouse up listener later to avoid accidental clicks
479
this.runOnceToEnableMouseUp = new RunOnceScheduler(() => {
480
if (!this.element) {
481
return;
482
}
483
484
this._register(addDisposableListener(this.element, EventType.MOUSE_UP, e => {
485
// removed default prevention as it conflicts
486
// with BaseActionViewItem #101537
487
// add back if issues arise and link new issue
488
EventHelper.stop(e, true);
489
490
// See https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Interact_with_the_clipboard
491
// > Writing to the clipboard
492
// > You can use the "cut" and "copy" commands without any special
493
// permission if you are using them in a short-lived event handler
494
// for a user action (for example, a click handler).
495
496
// => to get the Copy and Paste context menu actions working on Firefox,
497
// there should be no timeout here
498
if (isFirefox) {
499
const mouseEvent = new StandardMouseEvent(getWindow(this.element), e);
500
501
// Allowing right click to trigger the event causes the issue described below,
502
// but since the solution below does not work in FF, we must disable right click
503
if (mouseEvent.rightButton) {
504
return;
505
}
506
507
this.onClick(e);
508
}
509
510
// In all other cases, set timeout to allow context menu cancellation to trigger
511
// otherwise the action will destroy the menu and a second context menu
512
// will still trigger for right click.
513
else {
514
setTimeout(() => {
515
this.onClick(e);
516
}, 0);
517
}
518
}));
519
520
this._register(addDisposableListener(this.element, EventType.CONTEXT_MENU, e => {
521
EventHelper.stop(e, true);
522
}));
523
}, 100);
524
525
this._register(this.runOnceToEnableMouseUp);
526
}
527
528
override render(container: HTMLElement): void {
529
super.render(container);
530
531
if (!this.element) {
532
return;
533
}
534
535
this.container = container;
536
537
this.item = append(this.element, $('a.action-menu-item'));
538
if (this._action.id === Separator.ID) {
539
// A separator is a presentation item
540
this.item.setAttribute('role', 'presentation');
541
} else {
542
this.item.setAttribute('role', 'menuitem');
543
if (this.mnemonic) {
544
this.item.setAttribute('aria-keyshortcuts', `${this.mnemonic}`);
545
}
546
}
547
548
this.check = append(this.item, $('span.menu-item-check' + ThemeIcon.asCSSSelector(Codicon.menuSelection)));
549
this.check.setAttribute('role', 'none');
550
551
this.label = append(this.item, $('span.action-label'));
552
553
if (this.options.label && this.options.keybinding) {
554
append(this.item, $('span.keybinding')).textContent = this.options.keybinding;
555
}
556
557
// Adds mouse up listener to actually run the action
558
this.runOnceToEnableMouseUp.schedule();
559
560
this.updateClass();
561
this.updateLabel();
562
this.updateTooltip();
563
this.updateEnabled();
564
this.updateChecked();
565
566
this.applyStyle();
567
}
568
569
override blur(): void {
570
super.blur();
571
this.applyStyle();
572
}
573
574
override focus(): void {
575
super.focus();
576
577
this.item?.focus();
578
579
this.applyStyle();
580
}
581
582
updatePositionInSet(pos: number, setSize: number): void {
583
if (this.item) {
584
this.item.setAttribute('aria-posinset', `${pos}`);
585
this.item.setAttribute('aria-setsize', `${setSize}`);
586
}
587
}
588
589
protected override updateLabel(): void {
590
if (!this.label) {
591
return;
592
}
593
594
if (this.options.label) {
595
clearNode(this.label);
596
597
let label = stripIcons(this.action.label);
598
if (label) {
599
const cleanLabel = cleanMnemonic(label);
600
if (!this.options.enableMnemonics) {
601
label = cleanLabel;
602
}
603
604
this.label.setAttribute('aria-label', cleanLabel.replace(/&&/g, '&'));
605
606
const matches = MENU_MNEMONIC_REGEX.exec(label);
607
608
if (matches) {
609
label = strings.escape(label);
610
611
// This is global, reset it
612
MENU_ESCAPED_MNEMONIC_REGEX.lastIndex = 0;
613
let escMatch = MENU_ESCAPED_MNEMONIC_REGEX.exec(label);
614
615
// We can't use negative lookbehind so if we match our negative and skip
616
while (escMatch && escMatch[1]) {
617
escMatch = MENU_ESCAPED_MNEMONIC_REGEX.exec(label);
618
}
619
620
const replaceDoubleEscapes = (str: string) => str.replace(/&amp;&amp;/g, '&amp;');
621
622
if (escMatch) {
623
this.label.append(
624
strings.ltrim(replaceDoubleEscapes(label.substr(0, escMatch.index)), ' '),
625
$('u', { 'aria-hidden': 'true' },
626
escMatch[3]),
627
strings.rtrim(replaceDoubleEscapes(label.substr(escMatch.index + escMatch[0].length)), ' '));
628
} else {
629
this.label.textContent = replaceDoubleEscapes(label).trim();
630
}
631
632
this.item?.setAttribute('aria-keyshortcuts', (!!matches[1] ? matches[1] : matches[3]).toLocaleLowerCase());
633
} else {
634
this.label.textContent = label.replace(/&&/g, '&').trim();
635
}
636
}
637
}
638
}
639
640
protected override updateTooltip(): void {
641
// menus should function like native menus and they do not have tooltips
642
}
643
644
protected override updateClass(): void {
645
if (this.cssClass && this.item) {
646
this.item.classList.remove(...this.cssClass.split(' '));
647
}
648
if (this.options.icon && this.label) {
649
this.cssClass = this.action.class || '';
650
this.label.classList.add('icon');
651
if (this.cssClass) {
652
this.label.classList.add(...this.cssClass.split(' '));
653
}
654
this.updateEnabled();
655
} else if (this.label) {
656
this.label.classList.remove('icon');
657
}
658
}
659
660
protected override updateEnabled(): void {
661
if (this.action.enabled) {
662
if (this.element) {
663
this.element.classList.remove('disabled');
664
this.element.removeAttribute('aria-disabled');
665
}
666
667
if (this.item) {
668
this.item.classList.remove('disabled');
669
this.item.removeAttribute('aria-disabled');
670
this.item.tabIndex = 0;
671
}
672
} else {
673
if (this.element) {
674
this.element.classList.add('disabled');
675
this.element.setAttribute('aria-disabled', 'true');
676
}
677
678
if (this.item) {
679
this.item.classList.add('disabled');
680
this.item.setAttribute('aria-disabled', 'true');
681
}
682
}
683
}
684
685
protected override updateChecked(): void {
686
if (!this.item) {
687
return;
688
}
689
690
const checked = this.action.checked;
691
this.item.classList.toggle('checked', !!checked);
692
if (checked !== undefined) {
693
this.item.setAttribute('role', 'menuitemcheckbox');
694
this.item.setAttribute('aria-checked', checked ? 'true' : 'false');
695
} else {
696
this.item.setAttribute('role', 'menuitem');
697
this.item.setAttribute('aria-checked', '');
698
}
699
}
700
701
getMnemonic(): string | undefined {
702
return this.mnemonic;
703
}
704
705
protected applyStyle(): void {
706
const isSelected = this.element && this.element.classList.contains('focused');
707
const fgColor = isSelected && this.menuStyle.selectionForegroundColor ? this.menuStyle.selectionForegroundColor : this.menuStyle.foregroundColor;
708
const bgColor = isSelected && this.menuStyle.selectionBackgroundColor ? this.menuStyle.selectionBackgroundColor : undefined;
709
const outline = isSelected && this.menuStyle.selectionBorderColor ? `1px solid ${this.menuStyle.selectionBorderColor}` : '';
710
const outlineOffset = isSelected && this.menuStyle.selectionBorderColor ? `-1px` : '';
711
712
if (this.item) {
713
this.item.style.color = fgColor ?? '';
714
this.item.style.backgroundColor = bgColor ?? '';
715
this.item.style.outline = outline;
716
this.item.style.outlineOffset = outlineOffset;
717
}
718
719
if (this.check) {
720
this.check.style.color = fgColor ?? '';
721
}
722
}
723
}
724
725
class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
726
private mysubmenu: Menu | null = null;
727
private submenuContainer: HTMLElement | undefined;
728
private submenuIndicator: HTMLElement | undefined;
729
private readonly submenuDisposables = this._register(new DisposableStore());
730
private mouseOver: boolean = false;
731
private showScheduler: RunOnceScheduler;
732
private hideScheduler: RunOnceScheduler;
733
private expandDirection: IMenuDirection;
734
735
constructor(
736
action: IAction,
737
private submenuActions: ReadonlyArray<IAction>,
738
private parentData: ISubMenuData,
739
private submenuOptions: IMenuOptions,
740
menuStyles: IMenuStyles
741
) {
742
super(action, action, submenuOptions, menuStyles);
743
744
this.expandDirection = submenuOptions && submenuOptions.expandDirection !== undefined ? submenuOptions.expandDirection : { horizontal: HorizontalDirection.Right, vertical: VerticalDirection.Below };
745
746
this.showScheduler = new RunOnceScheduler(() => {
747
if (this.mouseOver) {
748
this.cleanupExistingSubmenu(false);
749
this.createSubmenu(false);
750
}
751
}, 250);
752
753
this.hideScheduler = new RunOnceScheduler(() => {
754
if (this.element && (!isAncestor(getActiveElement(), this.element) && this.parentData.submenu === this.mysubmenu)) {
755
this.parentData.parent.focus(false);
756
this.cleanupExistingSubmenu(true);
757
}
758
}, 750);
759
}
760
761
override render(container: HTMLElement): void {
762
super.render(container);
763
764
if (!this.element) {
765
return;
766
}
767
768
if (this.item) {
769
this.item.classList.add('monaco-submenu-item');
770
this.item.tabIndex = 0;
771
this.item.setAttribute('aria-haspopup', 'true');
772
this.updateAriaExpanded('false');
773
this.submenuIndicator = append(this.item, $('span.submenu-indicator' + ThemeIcon.asCSSSelector(Codicon.menuSubmenu)));
774
this.submenuIndicator.setAttribute('aria-hidden', 'true');
775
}
776
777
this._register(addDisposableListener(this.element, EventType.KEY_UP, e => {
778
const event = new StandardKeyboardEvent(e);
779
if (event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Enter)) {
780
EventHelper.stop(e, true);
781
782
this.createSubmenu(true);
783
}
784
}));
785
786
this._register(addDisposableListener(this.element, EventType.KEY_DOWN, e => {
787
const event = new StandardKeyboardEvent(e);
788
789
if (getActiveElement() === this.item) {
790
if (event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Enter)) {
791
EventHelper.stop(e, true);
792
}
793
}
794
}));
795
796
this._register(addDisposableListener(this.element, EventType.MOUSE_OVER, e => {
797
if (!this.mouseOver) {
798
this.mouseOver = true;
799
800
this.showScheduler.schedule();
801
}
802
}));
803
804
this._register(addDisposableListener(this.element, EventType.MOUSE_LEAVE, e => {
805
this.mouseOver = false;
806
}));
807
808
this._register(addDisposableListener(this.element, EventType.FOCUS_OUT, e => {
809
if (this.element && !isAncestor(getActiveElement(), this.element)) {
810
this.hideScheduler.schedule();
811
}
812
}));
813
814
this._register(this.parentData.parent.onScroll(() => {
815
if (this.parentData.submenu === this.mysubmenu) {
816
this.parentData.parent.focus(false);
817
this.cleanupExistingSubmenu(true);
818
}
819
}));
820
}
821
822
protected override updateEnabled(): void {
823
// override on submenu entry
824
// native menus do not observe enablement on sumbenus
825
// we mimic that behavior
826
}
827
828
open(selectFirst?: boolean): void {
829
this.cleanupExistingSubmenu(false);
830
this.createSubmenu(selectFirst);
831
}
832
833
override onClick(e: EventLike): void {
834
// stop clicking from trying to run an action
835
EventHelper.stop(e, true);
836
837
this.cleanupExistingSubmenu(false);
838
this.createSubmenu(true);
839
}
840
841
private cleanupExistingSubmenu(force: boolean): void {
842
if (this.parentData.submenu && (force || (this.parentData.submenu !== this.mysubmenu))) {
843
844
// disposal may throw if the submenu has already been removed
845
try {
846
this.parentData.submenu.dispose();
847
} catch { }
848
849
this.parentData.submenu = undefined;
850
this.updateAriaExpanded('false');
851
if (this.submenuContainer) {
852
this.submenuDisposables.clear();
853
this.submenuContainer = undefined;
854
}
855
}
856
}
857
858
private calculateSubmenuMenuLayout(windowDimensions: Dimension, submenu: Dimension, entry: IDomNodePagePosition, expandDirection: IMenuDirection): { top: number; left: number } {
859
const ret = { top: 0, left: 0 };
860
861
// Start with horizontal
862
ret.left = layout(windowDimensions.width, submenu.width, { position: expandDirection.horizontal === HorizontalDirection.Right ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After, offset: entry.left, size: entry.width });
863
864
// We don't have enough room to layout the menu fully, so we are overlapping the menu
865
if (ret.left >= entry.left && ret.left < entry.left + entry.width) {
866
if (entry.left + 10 + submenu.width <= windowDimensions.width) {
867
ret.left = entry.left + 10;
868
}
869
870
entry.top += 10;
871
entry.height = 0;
872
}
873
874
// Now that we have a horizontal position, try layout vertically
875
ret.top = layout(windowDimensions.height, submenu.height, { position: LayoutAnchorPosition.Before, offset: entry.top, size: 0 });
876
877
// We didn't have enough room below, but we did above, so we shift down to align the menu
878
if (ret.top + submenu.height === entry.top && ret.top + entry.height + submenu.height <= windowDimensions.height) {
879
ret.top += entry.height;
880
}
881
882
return ret;
883
}
884
885
private createSubmenu(selectFirstItem = true): void {
886
if (!this.element) {
887
return;
888
}
889
890
if (!this.parentData.submenu) {
891
this.updateAriaExpanded('true');
892
this.submenuContainer = append(this.element, $('div.monaco-submenu'));
893
this.submenuContainer.classList.add('menubar-menu-items-holder', 'context-view');
894
895
// Set the top value of the menu container before construction
896
// This allows the menu constructor to calculate the proper max height
897
const computedStyles = getWindow(this.parentData.parent.domNode).getComputedStyle(this.parentData.parent.domNode);
898
const paddingTop = parseFloat(computedStyles.paddingTop || '0') || 0;
899
this.submenuContainer.style.position = 'fixed';
900
this.submenuContainer.style.top = '0';
901
this.submenuContainer.style.left = '0';
902
// Fix to #263546, for submenu of treeView view/item/context z-index issue - ensure submenu appears above other elements
903
this.submenuContainer.style.zIndex = '1';
904
905
this.parentData.submenu = new Menu(this.submenuContainer, this.submenuActions.length ? this.submenuActions : [new EmptySubmenuAction()], this.submenuOptions, this.menuStyle);
906
907
// layout submenu
908
const entryBox = this.element.getBoundingClientRect();
909
const entryBoxUpdated = {
910
top: entryBox.top - paddingTop,
911
left: entryBox.left,
912
height: entryBox.height + 2 * paddingTop,
913
width: entryBox.width
914
};
915
916
const viewBox = this.submenuContainer.getBoundingClientRect();
917
918
const window = getWindow(this.element);
919
const { top, left } = this.calculateSubmenuMenuLayout(new Dimension(window.innerWidth, window.innerHeight), Dimension.lift(viewBox), entryBoxUpdated, this.expandDirection);
920
// subtract offsets caused by transform parent
921
this.submenuContainer.style.left = `${left - viewBox.left}px`;
922
this.submenuContainer.style.top = `${top - viewBox.top}px`;
923
924
this.submenuDisposables.add(addDisposableListener(this.submenuContainer, EventType.KEY_UP, e => {
925
const event = new StandardKeyboardEvent(e);
926
if (event.equals(KeyCode.LeftArrow)) {
927
EventHelper.stop(e, true);
928
929
this.parentData.parent.focus();
930
931
this.cleanupExistingSubmenu(true);
932
}
933
}));
934
935
this.submenuDisposables.add(addDisposableListener(this.submenuContainer, EventType.KEY_DOWN, e => {
936
const event = new StandardKeyboardEvent(e);
937
if (event.equals(KeyCode.LeftArrow)) {
938
EventHelper.stop(e, true);
939
}
940
}));
941
942
943
this.submenuDisposables.add(this.parentData.submenu.onDidCancel(() => {
944
this.parentData.parent.focus();
945
946
this.cleanupExistingSubmenu(true);
947
}));
948
949
this.parentData.submenu.focus(selectFirstItem);
950
951
this.mysubmenu = this.parentData.submenu;
952
} else {
953
this.parentData.submenu.focus(false);
954
}
955
}
956
957
private updateAriaExpanded(value: string): void {
958
if (this.item) {
959
this.item?.setAttribute('aria-expanded', value);
960
}
961
}
962
963
protected override applyStyle(): void {
964
super.applyStyle();
965
966
const isSelected = this.element && this.element.classList.contains('focused');
967
const fgColor = isSelected && this.menuStyle.selectionForegroundColor ? this.menuStyle.selectionForegroundColor : this.menuStyle.foregroundColor;
968
969
if (this.submenuIndicator) {
970
this.submenuIndicator.style.color = fgColor ?? '';
971
}
972
}
973
974
override dispose(): void {
975
super.dispose();
976
977
this.hideScheduler.dispose();
978
979
if (this.mysubmenu) {
980
this.mysubmenu.dispose();
981
this.mysubmenu = null;
982
}
983
984
if (this.submenuContainer) {
985
this.submenuContainer = undefined;
986
}
987
}
988
}
989
990
class MenuSeparatorActionViewItem extends ActionViewItem {
991
constructor(context: unknown, action: IAction, options: IActionViewItemOptions, private readonly menuStyles: IMenuStyles) {
992
super(context, action, options);
993
}
994
995
override render(container: HTMLElement): void {
996
super.render(container);
997
if (this.label) {
998
this.label.style.borderBottomColor = this.menuStyles.separatorColor ? `${this.menuStyles.separatorColor}` : '';
999
}
1000
}
1001
}
1002
1003
export function cleanMnemonic(label: string): string {
1004
const regex = MENU_MNEMONIC_REGEX;
1005
1006
const matches = regex.exec(label);
1007
if (!matches) {
1008
return label;
1009
}
1010
1011
const mnemonicInText = !matches[1];
1012
1013
return label.replace(regex, mnemonicInText ? '$2$3' : '').trim();
1014
}
1015
1016
export function formatRule(c: ThemeIcon) {
1017
const fontCharacter = getCodiconFontCharacters()[c.id];
1018
return `.codicon-${c.id}:before { content: '\\${fontCharacter.toString(16)}'; }`;
1019
}
1020
1021
export function getMenuWidgetCSS(style: IMenuStyles, isForShadowDom: boolean): string {
1022
let result = /* css */`
1023
.monaco-menu {
1024
font-size: 13px;
1025
border-radius: 5px;
1026
min-width: 160px;
1027
}
1028
1029
${formatRule(Codicon.menuSelection)}
1030
${formatRule(Codicon.menuSubmenu)}
1031
1032
.monaco-menu .monaco-action-bar {
1033
text-align: right;
1034
overflow: hidden;
1035
white-space: nowrap;
1036
}
1037
1038
.monaco-menu .monaco-action-bar .actions-container {
1039
display: flex;
1040
margin: 0 auto;
1041
padding: 0;
1042
width: 100%;
1043
justify-content: flex-end;
1044
}
1045
1046
.monaco-menu .monaco-action-bar.vertical .actions-container {
1047
display: inline-block;
1048
}
1049
1050
.monaco-menu .monaco-action-bar.reverse .actions-container {
1051
flex-direction: row-reverse;
1052
}
1053
1054
.monaco-menu .monaco-action-bar .action-item {
1055
cursor: pointer;
1056
display: inline-block;
1057
transition: transform 50ms ease;
1058
position: relative; /* DO NOT REMOVE - this is the key to preventing the ghosting icon bug in Chrome 42 */
1059
}
1060
1061
.monaco-menu .monaco-action-bar .action-item.disabled {
1062
cursor: default;
1063
}
1064
1065
.monaco-menu .monaco-action-bar .action-item .icon,
1066
.monaco-menu .monaco-action-bar .action-item .codicon {
1067
display: inline-block;
1068
}
1069
1070
.monaco-menu .monaco-action-bar .action-item .codicon {
1071
display: flex;
1072
align-items: center;
1073
}
1074
1075
.monaco-menu .monaco-action-bar .action-label {
1076
font-size: 11px;
1077
margin-right: 4px;
1078
}
1079
1080
.monaco-menu .monaco-action-bar .action-item.disabled .action-label,
1081
.monaco-menu .monaco-action-bar .action-item.disabled .action-label:hover {
1082
color: var(--vscode-disabledForeground);
1083
}
1084
1085
/* Vertical actions */
1086
1087
.monaco-menu .monaco-action-bar.vertical {
1088
text-align: left;
1089
}
1090
1091
.monaco-menu .monaco-action-bar.vertical .action-item {
1092
display: block;
1093
}
1094
1095
.monaco-menu .monaco-action-bar.vertical .action-label.separator {
1096
display: block;
1097
border-bottom: 1px solid var(--vscode-menu-separatorBackground);
1098
padding-top: 1px;
1099
padding: 30px;
1100
}
1101
1102
.monaco-menu .secondary-actions .monaco-action-bar .action-label {
1103
margin-left: 6px;
1104
}
1105
1106
/* Action Items */
1107
.monaco-menu .monaco-action-bar .action-item.select-container {
1108
overflow: hidden; /* somehow the dropdown overflows its container, we prevent it here to not push */
1109
flex: 1;
1110
max-width: 170px;
1111
min-width: 60px;
1112
display: flex;
1113
align-items: center;
1114
justify-content: center;
1115
margin-right: 10px;
1116
}
1117
1118
.monaco-menu .monaco-action-bar.vertical {
1119
margin-left: 0;
1120
overflow: visible;
1121
}
1122
1123
.monaco-menu .monaco-action-bar.vertical .actions-container {
1124
display: block;
1125
}
1126
1127
.monaco-menu .monaco-action-bar.vertical .action-item {
1128
padding: 0;
1129
transform: none;
1130
display: flex;
1131
}
1132
1133
.monaco-menu .monaco-action-bar.vertical .action-item.active {
1134
transform: none;
1135
}
1136
1137
.monaco-menu .monaco-action-bar.vertical .action-menu-item {
1138
flex: 1 1 auto;
1139
display: flex;
1140
height: 2em;
1141
align-items: center;
1142
position: relative;
1143
margin: 0 4px;
1144
border-radius: 4px;
1145
}
1146
1147
.monaco-menu .monaco-action-bar.vertical .action-menu-item:hover .keybinding,
1148
.monaco-menu .monaco-action-bar.vertical .action-menu-item:focus .keybinding {
1149
opacity: unset;
1150
}
1151
1152
.monaco-menu .monaco-action-bar.vertical .action-label {
1153
flex: 1 1 auto;
1154
text-decoration: none;
1155
padding: 0 1em;
1156
background: none;
1157
font-size: 12px;
1158
line-height: 1;
1159
}
1160
1161
.monaco-menu .monaco-action-bar.vertical .keybinding,
1162
.monaco-menu .monaco-action-bar.vertical .submenu-indicator {
1163
display: inline-block;
1164
flex: 2 1 auto;
1165
padding: 0 1em;
1166
text-align: right;
1167
font-size: 12px;
1168
line-height: 1;
1169
opacity: 0.7;
1170
}
1171
1172
.monaco-menu .monaco-action-bar.vertical .submenu-indicator {
1173
height: 100%;
1174
}
1175
1176
.monaco-menu .monaco-action-bar.vertical .submenu-indicator.codicon {
1177
font-size: 16px !important;
1178
display: flex;
1179
align-items: center;
1180
}
1181
1182
.monaco-menu .monaco-action-bar.vertical .submenu-indicator.codicon::before {
1183
margin-left: auto;
1184
margin-right: -20px;
1185
}
1186
1187
.monaco-menu .monaco-action-bar.vertical .action-item.disabled .keybinding,
1188
.monaco-menu .monaco-action-bar.vertical .action-item.disabled .submenu-indicator {
1189
opacity: 0.4;
1190
}
1191
1192
.monaco-menu .monaco-action-bar.vertical .action-label:not(.separator) {
1193
display: inline-block;
1194
box-sizing: border-box;
1195
margin: 0;
1196
}
1197
1198
.monaco-menu .monaco-action-bar.vertical .action-item {
1199
position: static;
1200
overflow: visible;
1201
}
1202
1203
.monaco-menu .monaco-action-bar.vertical .action-item .monaco-submenu {
1204
position: absolute;
1205
}
1206
1207
.monaco-menu .monaco-action-bar.vertical .action-label.separator {
1208
width: 100%;
1209
height: 0px !important;
1210
opacity: 1;
1211
}
1212
1213
.monaco-menu .monaco-action-bar.vertical .action-label.separator.text {
1214
padding: 0.7em 1em 0.1em 1em;
1215
font-weight: bold;
1216
opacity: 1;
1217
}
1218
1219
.monaco-menu .monaco-action-bar.vertical .action-label:hover {
1220
color: inherit;
1221
}
1222
1223
.monaco-menu .monaco-action-bar.vertical .menu-item-check {
1224
position: absolute;
1225
visibility: hidden;
1226
width: 1em;
1227
height: 100%;
1228
}
1229
1230
.monaco-menu .monaco-action-bar.vertical .action-menu-item.checked .menu-item-check {
1231
visibility: visible;
1232
display: flex;
1233
align-items: center;
1234
justify-content: center;
1235
}
1236
1237
/* Context Menu */
1238
1239
.context-view.monaco-menu-container {
1240
outline: 0;
1241
border: none;
1242
animation: fadeIn 0.083s linear;
1243
-webkit-app-region: no-drag;
1244
}
1245
1246
.context-view.monaco-menu-container :focus,
1247
.context-view.monaco-menu-container .monaco-action-bar.vertical:focus,
1248
.context-view.monaco-menu-container .monaco-action-bar.vertical :focus {
1249
outline: 0;
1250
}
1251
1252
.hc-black .context-view.monaco-menu-container,
1253
.hc-light .context-view.monaco-menu-container,
1254
:host-context(.hc-black) .context-view.monaco-menu-container,
1255
:host-context(.hc-light) .context-view.monaco-menu-container {
1256
box-shadow: none;
1257
}
1258
1259
.hc-black .monaco-menu .monaco-action-bar.vertical .action-item.focused,
1260
.hc-light .monaco-menu .monaco-action-bar.vertical .action-item.focused,
1261
:host-context(.hc-black) .monaco-menu .monaco-action-bar.vertical .action-item.focused,
1262
:host-context(.hc-light) .monaco-menu .monaco-action-bar.vertical .action-item.focused {
1263
background: none;
1264
}
1265
1266
/* Vertical Action Bar Styles */
1267
1268
.monaco-menu .monaco-action-bar.vertical {
1269
padding: 4px 0;
1270
}
1271
1272
.monaco-menu .monaco-action-bar.vertical .action-menu-item {
1273
height: 2em;
1274
}
1275
1276
.monaco-menu .monaco-action-bar.vertical .action-label:not(.separator),
1277
.monaco-menu .monaco-action-bar.vertical .keybinding {
1278
font-size: inherit;
1279
padding: 0 2em;
1280
max-height: 100%;
1281
}
1282
1283
.monaco-menu .monaco-action-bar.vertical .menu-item-check {
1284
font-size: inherit;
1285
width: 2em;
1286
}
1287
1288
.monaco-menu .monaco-action-bar.vertical .action-label.separator {
1289
font-size: inherit;
1290
margin: 5px 0 !important;
1291
padding: 0;
1292
border-radius: 0;
1293
}
1294
1295
.linux .monaco-menu .monaco-action-bar.vertical .action-label.separator,
1296
:host-context(.linux) .monaco-menu .monaco-action-bar.vertical .action-label.separator {
1297
margin-left: 0;
1298
margin-right: 0;
1299
}
1300
1301
.monaco-menu .monaco-action-bar.vertical .submenu-indicator {
1302
font-size: 60%;
1303
padding: 0 1.8em;
1304
}
1305
1306
.linux .monaco-menu .monaco-action-bar.vertical .submenu-indicator,
1307
:host-context(.linux) .monaco-menu .monaco-action-bar.vertical .submenu-indicator {
1308
height: 100%;
1309
mask-size: 10px 10px;
1310
-webkit-mask-size: 10px 10px;
1311
}
1312
1313
.monaco-menu .action-item {
1314
cursor: default;
1315
}`;
1316
1317
if (isForShadowDom) {
1318
// Only define scrollbar styles when used inside shadow dom,
1319
// otherwise leave their styling to the global workbench styling.
1320
result += `
1321
/* Arrows */
1322
.monaco-scrollable-element > .scrollbar > .scra {
1323
cursor: pointer;
1324
font-size: 11px !important;
1325
}
1326
1327
.monaco-scrollable-element > .visible {
1328
opacity: 1;
1329
1330
/* Background rule added for IE9 - to allow clicks on dom node */
1331
background:rgba(0,0,0,0);
1332
1333
transition: opacity 100ms linear;
1334
}
1335
.monaco-scrollable-element > .invisible {
1336
opacity: 0;
1337
pointer-events: none;
1338
}
1339
.monaco-scrollable-element > .invisible.fade {
1340
transition: opacity 800ms linear;
1341
}
1342
1343
/* Scrollable Content Inset Shadow */
1344
.monaco-scrollable-element > .shadow {
1345
position: absolute;
1346
display: none;
1347
}
1348
.monaco-scrollable-element > .shadow.top {
1349
display: block;
1350
top: 0;
1351
left: 3px;
1352
height: 3px;
1353
width: 100%;
1354
}
1355
.monaco-scrollable-element > .shadow.left {
1356
display: block;
1357
top: 3px;
1358
left: 0;
1359
height: 100%;
1360
width: 3px;
1361
}
1362
.monaco-scrollable-element > .shadow.top-left-corner {
1363
display: block;
1364
top: 0;
1365
left: 0;
1366
height: 3px;
1367
width: 3px;
1368
}
1369
/* Fix for https://github.com/microsoft/vscode/issues/103170 */
1370
.monaco-menu .action-item .monaco-submenu {
1371
z-index: 1;
1372
}
1373
`;
1374
1375
// Scrollbars
1376
const scrollbarShadowColor = style.scrollbarShadow;
1377
if (scrollbarShadowColor) {
1378
result += `
1379
.monaco-scrollable-element > .shadow.top {
1380
box-shadow: ${scrollbarShadowColor} 0 6px 6px -6px inset;
1381
}
1382
1383
.monaco-scrollable-element > .shadow.left {
1384
box-shadow: ${scrollbarShadowColor} 6px 0 6px -6px inset;
1385
}
1386
1387
.monaco-scrollable-element > .shadow.top.left {
1388
box-shadow: ${scrollbarShadowColor} 6px 6px 6px -6px inset;
1389
}
1390
`;
1391
}
1392
1393
const scrollbarSliderBackgroundColor = style.scrollbarSliderBackground;
1394
if (scrollbarSliderBackgroundColor) {
1395
result += `
1396
.monaco-scrollable-element > .scrollbar > .slider {
1397
background: ${scrollbarSliderBackgroundColor};
1398
}
1399
`;
1400
}
1401
1402
const scrollbarSliderHoverBackgroundColor = style.scrollbarSliderHoverBackground;
1403
if (scrollbarSliderHoverBackgroundColor) {
1404
result += `
1405
.monaco-scrollable-element > .scrollbar > .slider:hover {
1406
background: ${scrollbarSliderHoverBackgroundColor};
1407
}
1408
`;
1409
}
1410
1411
const scrollbarSliderActiveBackgroundColor = style.scrollbarSliderActiveBackground;
1412
if (scrollbarSliderActiveBackgroundColor) {
1413
result += `
1414
.monaco-scrollable-element > .scrollbar > .slider.active {
1415
background: ${scrollbarSliderActiveBackgroundColor};
1416
}
1417
`;
1418
}
1419
}
1420
1421
return result;
1422
}
1423
1424