123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124 |
- /*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
- import { $, addDisposableListener, EventType, isHTMLElement } from '../../../base/browser/dom.js';
- import { StandardMouseEvent } from '../../../base/browser/mouseEvent.js';
- import { Menu } from '../../../base/browser/ui/menu/menu.js';
- import { ActionRunner } from '../../../base/common/actions.js';
- import { isPromiseCanceledError } from '../../../base/common/errors.js';
- import { combinedDisposable, DisposableStore } from '../../../base/common/lifecycle.js';
- import './contextMenuHandler.css';
- import { attachMenuStyler } from '../../theme/common/styler.js';
- export class ContextMenuHandler {
- constructor(contextViewService, telemetryService, notificationService, keybindingService, themeService) {
- this.contextViewService = contextViewService;
- this.telemetryService = telemetryService;
- this.notificationService = notificationService;
- this.keybindingService = keybindingService;
- this.themeService = themeService;
- this.focusToReturn = null;
- this.block = null;
- this.options = { blockMouse: true };
- }
- configure(options) {
- this.options = options;
- }
- showContextMenu(delegate) {
- const actions = delegate.getActions();
- if (!actions.length) {
- return; // Don't render an empty context menu
- }
- this.focusToReturn = document.activeElement;
- let menu;
- let shadowRootElement = isHTMLElement(delegate.domForShadowRoot) ? delegate.domForShadowRoot : undefined;
- this.contextViewService.showContextView({
- getAnchor: () => delegate.getAnchor(),
- canRelayout: false,
- anchorAlignment: delegate.anchorAlignment,
- anchorAxisAlignment: delegate.anchorAxisAlignment,
- render: (container) => {
- let className = delegate.getMenuClassName ? delegate.getMenuClassName() : '';
- if (className) {
- container.className += ' ' + className;
- }
- // Render invisible div to block mouse interaction in the rest of the UI
- if (this.options.blockMouse) {
- this.block = container.appendChild($('.context-view-block'));
- this.block.style.position = 'fixed';
- this.block.style.cursor = 'initial';
- this.block.style.left = '0';
- this.block.style.top = '0';
- this.block.style.width = '100%';
- this.block.style.height = '100%';
- this.block.style.zIndex = '-1';
- // TODO@Steven: this is never getting disposed
- addDisposableListener(this.block, EventType.MOUSE_DOWN, e => e.stopPropagation());
- }
- const menuDisposables = new DisposableStore();
- const actionRunner = delegate.actionRunner || new ActionRunner();
- actionRunner.onBeforeRun(this.onActionRun, this, menuDisposables);
- actionRunner.onDidRun(this.onDidActionRun, this, menuDisposables);
- menu = new Menu(container, actions, {
- actionViewItemProvider: delegate.getActionViewItem,
- context: delegate.getActionsContext ? delegate.getActionsContext() : null,
- actionRunner,
- getKeyBinding: delegate.getKeyBinding ? delegate.getKeyBinding : action => this.keybindingService.lookupKeybinding(action.id)
- });
- menuDisposables.add(attachMenuStyler(menu, this.themeService));
- menu.onDidCancel(() => this.contextViewService.hideContextView(true), null, menuDisposables);
- menu.onDidBlur(() => this.contextViewService.hideContextView(true), null, menuDisposables);
- menuDisposables.add(addDisposableListener(window, EventType.BLUR, () => this.contextViewService.hideContextView(true)));
- menuDisposables.add(addDisposableListener(window, EventType.MOUSE_DOWN, (e) => {
- if (e.defaultPrevented) {
- return;
- }
- let event = new StandardMouseEvent(e);
- let element = event.target;
- // Don't do anything as we are likely creating a context menu
- if (event.rightButton) {
- return;
- }
- while (element) {
- if (element === container) {
- return;
- }
- element = element.parentElement;
- }
- this.contextViewService.hideContextView(true);
- }));
- return combinedDisposable(menuDisposables, menu);
- },
- focus: () => {
- if (menu) {
- menu.focus(!!delegate.autoSelectFirstItem);
- }
- },
- onHide: (didCancel) => {
- if (delegate.onHide) {
- delegate.onHide(!!didCancel);
- }
- if (this.block) {
- this.block.remove();
- this.block = null;
- }
- if (this.focusToReturn) {
- this.focusToReturn.focus();
- }
- }
- }, shadowRootElement, !!shadowRootElement);
- }
- onActionRun(e) {
- this.telemetryService.publicLog2('workbenchActionExecuted', { id: e.action.id, from: 'contextMenu' });
- this.contextViewService.hideContextView(false);
- // Restore focus here
- if (this.focusToReturn) {
- this.focusToReturn.focus();
- }
- }
- onDidActionRun(e) {
- if (e.error && !isPromiseCanceledError(e.error)) {
- this.notificationService.error(e.error);
- }
- }
- }
|