monaco-editor-core
Version:
A browser based code editor
124 lines • 6.56 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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, getActiveElement, getWindow, isAncestor, 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 { isCancellationError } from '../../../base/common/errors.js';
import { combinedDisposable, DisposableStore } from '../../../base/common/lifecycle.js';
import { defaultMenuStyles } from '../../theme/browser/defaultStyles.js';
export class ContextMenuHandler {
constructor(contextViewService, telemetryService, notificationService, keybindingService) {
this.contextViewService = contextViewService;
this.telemetryService = telemetryService;
this.notificationService = notificationService;
this.keybindingService = keybindingService;
this.focusToReturn = null;
this.lastContainer = null;
this.block = null;
this.blockDisposable = 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 = getActiveElement();
let menu;
const shadowRootElement = isHTMLElement(delegate.domForShadowRoot) ? delegate.domForShadowRoot : undefined;
this.contextViewService.showContextView({
getAnchor: () => delegate.getAnchor(),
canRelayout: false,
anchorAlignment: delegate.anchorAlignment,
anchorAxisAlignment: delegate.anchorAxisAlignment,
layer: delegate.layer,
render: (container) => {
this.lastContainer = container;
const 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';
this.blockDisposable?.dispose();
this.blockDisposable = addDisposableListener(this.block, EventType.MOUSE_DOWN, e => e.stopPropagation());
}
const menuDisposables = new DisposableStore();
const actionRunner = delegate.actionRunner || menuDisposables.add(new ActionRunner());
actionRunner.onWillRun(evt => this.onActionRun(evt, !delegate.skipTelemetry), 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)
}, defaultMenuStyles);
menu.onDidCancel(() => this.contextViewService.hideContextView(true), null, menuDisposables);
menu.onDidBlur(() => this.contextViewService.hideContextView(true), null, menuDisposables);
const targetWindow = getWindow(container);
menuDisposables.add(addDisposableListener(targetWindow, EventType.BLUR, () => this.contextViewService.hideContextView(true)));
menuDisposables.add(addDisposableListener(targetWindow, EventType.MOUSE_DOWN, (e) => {
if (e.defaultPrevented) {
return;
}
const event = new StandardMouseEvent(targetWindow, 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: () => {
menu?.focus(!!delegate.autoSelectFirstItem);
},
onHide: (didCancel) => {
delegate.onHide?.(!!didCancel);
if (this.block) {
this.block.remove();
this.block = null;
}
this.blockDisposable?.dispose();
this.blockDisposable = null;
if (!!this.lastContainer && (getActiveElement() === this.lastContainer || isAncestor(getActiveElement(), this.lastContainer))) {
this.focusToReturn?.focus();
}
this.lastContainer = null;
}
}, shadowRootElement, !!shadowRootElement);
}
onActionRun(e, logTelemetry) {
if (logTelemetry) {
this.telemetryService.publicLog2('workbenchActionExecuted', { id: e.action.id, from: 'contextMenu' });
}
this.contextViewService.hideContextView(false);
}
onDidActionRun(e) {
if (e.error && !isCancellationError(e.error)) {
this.notificationService.error(e.error);
}
}
}
//# sourceMappingURL=contextMenuHandler.js.map