UNPKG

monaco-editor

Version:
245 lines (242 loc) • 12.3 kB
import { ActionBar } from '../actionbar/actionbar.js'; import { DropdownMenuActionViewItem } from '../dropdown/dropdownActionViewItem.js'; import { Action, SubmenuAction, Separator } from '../../../common/actions.js'; import { Codicon } from '../../../common/codicons.js'; import { ThemeIcon } from '../../../common/themables.js'; import { EventMultiplexer } from '../../../common/event.js'; import { Disposable, DisposableStore, toDisposable } from '../../../common/lifecycle.js'; import './toolbar.css'; import { localize } from '../../../../nls.js'; import { createInstantHoverDelegate } from '../hover/hoverDelegateFactory.js'; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ const ACTION_MIN_WIDTH = 24; /* 20px codicon + 4px left padding*/ /** * A widget that combines an action bar for primary actions and a dropdown for secondary actions. */ class ToolBar extends Disposable { get onDidChangeDropdownVisibility() { return this._onDidChangeDropdownVisibility.event; } constructor(container, contextMenuProvider, options = { orientation: 0 /* ActionsOrientation.HORIZONTAL */ }) { super(); this.submenuActionViewItems = []; this.hasSecondaryActions = false; this._onDidChangeDropdownVisibility = this._register(new EventMultiplexer()); this.originalPrimaryActions = []; this.originalSecondaryActions = []; this.hiddenActions = []; this.disposables = this._register(new DisposableStore()); options.hoverDelegate = options.hoverDelegate ?? this._register(createInstantHoverDelegate()); this.options = options; this.toggleMenuAction = this._register(new ToggleMenuAction(() => this.toggleMenuActionViewItem?.show(), options.toggleMenuTitle)); this.element = document.createElement('div'); this.element.className = 'monaco-toolbar'; container.appendChild(this.element); this.actionBar = this._register(new ActionBar(this.element, { orientation: options.orientation, ariaLabel: options.ariaLabel, actionRunner: options.actionRunner, allowContextMenu: options.allowContextMenu, highlightToggledItems: options.highlightToggledItems, hoverDelegate: options.hoverDelegate, actionViewItemProvider: (action, viewItemOptions) => { if (action.id === ToggleMenuAction.ID) { this.toggleMenuActionViewItem = new DropdownMenuActionViewItem(action, { getActions: () => this.toggleMenuAction.menuActions }, contextMenuProvider, { actionViewItemProvider: this.options.actionViewItemProvider, actionRunner: this.actionRunner, keybindingProvider: this.options.getKeyBinding, classNames: ThemeIcon.asClassNameArray(options.moreIcon ?? Codicon.toolBarMore), anchorAlignmentProvider: this.options.anchorAlignmentProvider, menuAsChild: !!this.options.renderDropdownAsChildElement, skipTelemetry: this.options.skipTelemetry, isMenu: true, hoverDelegate: this.options.hoverDelegate }); this.toggleMenuActionViewItem.setActionContext(this.actionBar.context); this.disposables.add(this._onDidChangeDropdownVisibility.add(this.toggleMenuActionViewItem.onDidChangeVisibility)); return this.toggleMenuActionViewItem; } if (options.actionViewItemProvider) { const result = options.actionViewItemProvider(action, viewItemOptions); if (result) { return result; } } if (action instanceof SubmenuAction) { const result = new DropdownMenuActionViewItem(action, action.actions, contextMenuProvider, { actionViewItemProvider: this.options.actionViewItemProvider, actionRunner: this.actionRunner, keybindingProvider: this.options.getKeyBinding, classNames: action.class, anchorAlignmentProvider: this.options.anchorAlignmentProvider, menuAsChild: !!this.options.renderDropdownAsChildElement, skipTelemetry: this.options.skipTelemetry, hoverDelegate: this.options.hoverDelegate }); result.setActionContext(this.actionBar.context); this.submenuActionViewItems.push(result); this.disposables.add(this._onDidChangeDropdownVisibility.add(result.onDidChangeVisibility)); return result; } return undefined; } })); // Responsive support if (this.options.responsive) { this.element.classList.add('responsive'); const observer = new ResizeObserver(() => { this.setToolbarMaxWidth(this.element.getBoundingClientRect().width); }); observer.observe(this.element); this._store.add(toDisposable(() => observer.disconnect())); } } set actionRunner(actionRunner) { this.actionBar.actionRunner = actionRunner; } get actionRunner() { return this.actionBar.actionRunner; } set context(context) { this.actionBar.context = context; this.toggleMenuActionViewItem?.setActionContext(context); for (const actionViewItem of this.submenuActionViewItems) { actionViewItem.setActionContext(context); } } getElement() { return this.element; } getItemAction(indexOrElement) { return this.actionBar.getAction(indexOrElement); } getItemWidth(index) { return this.actionBar.getWidth(index); } setActions(primaryActions, secondaryActions) { this.clear(); // Store primary and secondary actions as rendered initially this.originalPrimaryActions = primaryActions ? primaryActions.slice(0) : []; this.originalSecondaryActions = secondaryActions ? secondaryActions.slice(0) : []; const primaryActionsToSet = primaryActions ? primaryActions.slice(0) : []; // Inject additional action to open secondary actions if present this.hasSecondaryActions = !!(secondaryActions && secondaryActions.length > 0); if (this.hasSecondaryActions && secondaryActions) { this.toggleMenuAction.menuActions = secondaryActions.slice(0); primaryActionsToSet.push(this.toggleMenuAction); } if (primaryActionsToSet.length > 0 && this.options.trailingSeparator) { primaryActionsToSet.push(new Separator()); } primaryActionsToSet.forEach(action => { this.actionBar.push(action, { icon: this.options.icon ?? true, label: this.options.label ?? false, keybinding: this.getKeybindingLabel(action) }); }); if (this.options.responsive) { // Reset hidden actions this.hiddenActions.length = 0; // Update toolbar to fit with container width this.setToolbarMaxWidth(this.element.getBoundingClientRect().width); } } getKeybindingLabel(action) { const key = this.options.getKeyBinding?.(action); return key?.getLabel() ?? undefined; } getItemsWidthResponsive() { // Each action is assumed to have a minimum width so that actions with a label // can shrink to the action's minimum width. We do this so that action visibility // takes precedence over the action label. return this.actionBar.length() * ACTION_MIN_WIDTH; } setToolbarMaxWidth(maxWidth) { if (this.actionBar.isEmpty() || (this.getItemsWidthResponsive() <= maxWidth && this.hiddenActions.length === 0)) { return; } if (this.getItemsWidthResponsive() > maxWidth) { // Hide actions from the right while (this.getItemsWidthResponsive() > maxWidth && this.actionBar.length() > 0) { const index = this.originalPrimaryActions.length - this.hiddenActions.length - 1; if (index < 0) { break; } // Store the action and its size const size = Math.min(ACTION_MIN_WIDTH, this.getItemWidth(index)); const action = this.originalPrimaryActions[index]; this.hiddenActions.unshift({ action, size }); // Remove the action this.actionBar.pull(index); // There are no secondary actions, but we have actions that we need to hide so we // create the overflow menu. This will ensure that another primary action will be // removed making space for the overflow menu. if (this.originalSecondaryActions.length === 0 && this.hiddenActions.length === 1) { this.actionBar.push(this.toggleMenuAction, { icon: this.options.icon ?? true, label: this.options.label ?? false, keybinding: this.getKeybindingLabel(this.toggleMenuAction), }); } } } else { // Show actions from the top of the toggle menu while (this.hiddenActions.length > 0) { const entry = this.hiddenActions.shift(); if (this.getItemsWidthResponsive() + entry.size > maxWidth) { // Not enough space to show the action this.hiddenActions.unshift(entry); break; } // Add the action this.actionBar.push(entry.action, { icon: this.options.icon ?? true, label: this.options.label ?? false, keybinding: this.getKeybindingLabel(entry.action), index: this.originalPrimaryActions.length - this.hiddenActions.length - 1 }); // There are no secondary actions, and there is only one hidden item left so we // remove the overflow menu making space for the last hidden action to be shown. if (this.originalSecondaryActions.length === 0 && this.hiddenActions.length === 1) { this.toggleMenuAction.menuActions = []; this.actionBar.pull(this.actionBar.length() - 1); } } } // Update overflow menu const hiddenActions = this.hiddenActions.map(entry => entry.action); if (this.originalSecondaryActions.length > 0 || hiddenActions.length > 0) { const secondaryActions = this.originalSecondaryActions.slice(0); this.toggleMenuAction.menuActions = Separator.join(hiddenActions, secondaryActions); } } clear() { this.submenuActionViewItems = []; this.disposables.clear(); this.actionBar.clear(); } dispose() { this.clear(); this.disposables.dispose(); super.dispose(); } } class ToggleMenuAction extends Action { static { this.ID = 'toolbar.toggle.more'; } constructor(toggleDropdownMenu, title) { title = title || localize(17, "More Actions..."); super(ToggleMenuAction.ID, title, undefined, true); this._menuActions = []; this.toggleDropdownMenu = toggleDropdownMenu; } async run() { this.toggleDropdownMenu(); } get menuActions() { return this._menuActions; } set menuActions(actions) { this._menuActions = actions; } } export { ToggleMenuAction, ToolBar };