Merge from vscode 731f9c25632dbbf01ee3a7892ad9d2791fe0260c

This commit is contained in:
ADS Merger
2020-07-24 05:27:34 +00:00
parent eccf3cf5fe
commit d965d4aef3
145 changed files with 3072 additions and 1550 deletions

View File

@@ -3,10 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IAction, IActionRunner } from 'vs/base/common/actions';
import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { IAction, IActionRunner, IActionViewItem } from 'vs/base/common/actions';
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { SubmenuAction } from 'vs/base/browser/ui/menu/menu';
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
export interface IContextMenuEvent {
@@ -16,15 +14,9 @@ export interface IContextMenuEvent {
readonly metaKey?: boolean;
}
export class ContextSubMenu extends SubmenuAction {
constructor(label: string, public entries: Array<ContextSubMenu | IAction>) {
super(label, entries, 'contextsubmenu');
}
}
export interface IContextMenuDelegate {
getAnchor(): HTMLElement | { x: number; y: number; width?: number; height?: number; };
getActions(): ReadonlyArray<IAction | ContextSubMenu>;
getActions(): IAction[];
getCheckedActionsRepresentation?(action: IAction): 'radio' | 'checkbox';
getActionViewItem?(action: IAction): IActionViewItem | undefined;
getActionsContext?(event?: IContextMenuEvent): any;
@@ -36,3 +28,7 @@ export interface IContextMenuDelegate {
anchorAlignment?: AnchorAlignment;
domForShadowRoot?: HTMLElement;
}
export interface IContextMenuProvider {
showContextMenu(delegate: IContextMenuDelegate): void;
}

View File

@@ -0,0 +1,406 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./actionbar';
import * as platform from 'vs/base/common/platform';
import * as nls from 'vs/nls';
import { Disposable } from 'vs/base/common/lifecycle';
import { SelectBox, ISelectOptionItem, ISelectBoxOptions } from 'vs/base/browser/ui/selectBox/selectBox';
import { IAction, IActionRunner, Action, IActionChangeEvent, ActionRunner, Separator, IActionViewItem } from 'vs/base/common/actions';
import * as DOM from 'vs/base/browser/dom';
import * as types from 'vs/base/common/types';
import { EventType, Gesture } from 'vs/base/browser/touch';
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
import { DataTransfers } from 'vs/base/browser/dnd';
import { isFirefox } from 'vs/base/browser/browser';
export interface IBaseActionViewItemOptions {
draggable?: boolean;
isMenu?: boolean;
useEventAsContext?: boolean;
}
export class BaseActionViewItem extends Disposable implements IActionViewItem {
element: HTMLElement | undefined;
_context: any;
_action: IAction;
private _actionRunner: IActionRunner | undefined;
constructor(context: any, action: IAction, protected options: IBaseActionViewItemOptions = {}) {
super();
this._context = context || this;
this._action = action;
if (action instanceof Action) {
this._register(action.onDidChange(event => {
if (!this.element) {
// we have not been rendered yet, so there
// is no point in updating the UI
return;
}
this.handleActionChangeEvent(event);
}));
}
}
private handleActionChangeEvent(event: IActionChangeEvent): void {
if (event.enabled !== undefined) {
this.updateEnabled();
}
if (event.checked !== undefined) {
this.updateChecked();
}
if (event.class !== undefined) {
this.updateClass();
}
if (event.label !== undefined) {
this.updateLabel();
this.updateTooltip();
}
if (event.tooltip !== undefined) {
this.updateTooltip();
}
}
get actionRunner(): IActionRunner {
if (!this._actionRunner) {
this._actionRunner = this._register(new ActionRunner());
}
return this._actionRunner;
}
set actionRunner(actionRunner: IActionRunner) {
this._actionRunner = actionRunner;
}
getAction(): IAction {
return this._action;
}
isEnabled(): boolean {
return this._action.enabled;
}
setActionContext(newContext: unknown): void {
this._context = newContext;
}
render(container: HTMLElement): void {
const element = this.element = container;
this._register(Gesture.addTarget(container));
const enableDragging = this.options && this.options.draggable;
if (enableDragging) {
container.draggable = true;
if (isFirefox) {
// Firefox: requires to set a text data transfer to get going
this._register(DOM.addDisposableListener(container, DOM.EventType.DRAG_START, e => e.dataTransfer?.setData(DataTransfers.TEXT, this._action.label)));
}
}
this._register(DOM.addDisposableListener(element, EventType.Tap, e => this.onClick(e)));
this._register(DOM.addDisposableListener(element, DOM.EventType.MOUSE_DOWN, e => {
if (!enableDragging) {
DOM.EventHelper.stop(e, true); // do not run when dragging is on because that would disable it
}
if (this._action.enabled && e.button === 0) {
DOM.addClass(element, 'active');
}
}));
if (platform.isMacintosh) {
// macOS: allow to trigger the button when holding Ctrl+key and pressing the
// main mouse button. This is for scenarios where e.g. some interaction forces
// the Ctrl+key to be pressed and hold but the user still wants to interact
// with the actions (for example quick access in quick navigation mode).
this._register(DOM.addDisposableListener(element, DOM.EventType.CONTEXT_MENU, e => {
if (e.button === 0 && e.ctrlKey === true) {
this.onClick(e);
}
}));
}
this._register(DOM.addDisposableListener(element, DOM.EventType.CLICK, e => {
DOM.EventHelper.stop(e, true);
// See https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Interact_with_the_clipboard
// > Writing to the clipboard
// > You can use the "cut" and "copy" commands without any special
// permission if you are using them in a short-lived event handler
// for a user action (for example, a click handler).
// => to get the Copy and Paste context menu actions working on Firefox,
// there should be no timeout here
if (this.options && this.options.isMenu) {
this.onClick(e);
} else {
platform.setImmediate(() => this.onClick(e));
}
}));
this._register(DOM.addDisposableListener(element, DOM.EventType.DBLCLICK, e => {
DOM.EventHelper.stop(e, true);
}));
[DOM.EventType.MOUSE_UP, DOM.EventType.MOUSE_OUT].forEach(event => {
this._register(DOM.addDisposableListener(element, event, e => {
DOM.EventHelper.stop(e);
DOM.removeClass(element, 'active');
}));
});
}
onClick(event: DOM.EventLike): void {
DOM.EventHelper.stop(event, true);
const context = types.isUndefinedOrNull(this._context) ? this.options?.useEventAsContext ? event : undefined : this._context;
this.actionRunner.run(this._action, context);
}
focus(): void {
if (this.element) {
this.element.focus();
DOM.addClass(this.element, 'focused');
}
}
blur(): void {
if (this.element) {
this.element.blur();
DOM.removeClass(this.element, 'focused');
}
}
protected updateEnabled(): void {
// implement in subclass
}
protected updateLabel(): void {
// implement in subclass
}
protected updateTooltip(): void {
// implement in subclass
}
protected updateClass(): void {
// implement in subclass
}
protected updateChecked(): void {
// implement in subclass
}
dispose(): void {
if (this.element) {
DOM.removeNode(this.element);
this.element = undefined;
}
super.dispose();
}
}
export interface IActionViewItemOptions extends IBaseActionViewItemOptions {
icon?: boolean;
label?: boolean;
keybinding?: string | null;
}
export class ActionViewItem extends BaseActionViewItem {
protected label: HTMLElement | undefined;
protected options: IActionViewItemOptions;
private cssClass?: string;
constructor(context: unknown, action: IAction, options: IActionViewItemOptions = {}) {
super(context, action, options);
this.options = options;
this.options.icon = options.icon !== undefined ? options.icon : false;
this.options.label = options.label !== undefined ? options.label : true;
this.cssClass = '';
}
render(container: HTMLElement): void {
super.render(container);
if (this.element) {
this.label = DOM.append(this.element, DOM.$('a.action-label'));
}
if (this.label) {
if (this._action.id === Separator.ID) {
this.label.setAttribute('role', 'presentation'); // A separator is a presentation item
} else {
if (this.options.isMenu) {
this.label.setAttribute('role', 'menuitem');
} else {
this.label.setAttribute('role', 'button');
}
}
}
if (this.options.label && this.options.keybinding && this.element) {
DOM.append(this.element, DOM.$('span.keybinding')).textContent = this.options.keybinding;
}
this.updateClass();
this.updateLabel();
this.updateTooltip();
this.updateEnabled();
this.updateChecked();
}
focus(): void {
super.focus();
if (this.label) {
this.label.focus();
}
}
updateLabel(): void {
if (this.options.label && this.label) {
this.label.textContent = this.getAction().label;
}
}
updateTooltip(): void {
let title: string | null = null;
if (this.getAction().tooltip) {
title = this.getAction().tooltip;
} else if (!this.options.label && this.getAction().label && this.options.icon) {
title = this.getAction().label;
if (this.options.keybinding) {
title = nls.localize({ key: 'titleLabel', comment: ['action title', 'action keybinding'] }, "{0} ({1})", title, this.options.keybinding);
}
}
if (title && this.label) {
this.label.title = title;
}
}
updateClass(): void {
if (this.cssClass && this.label) {
DOM.removeClasses(this.label, this.cssClass);
}
if (this.options.icon) {
this.cssClass = this.getAction().class;
if (this.label) {
DOM.addClass(this.label, 'codicon');
if (this.cssClass) {
DOM.addClasses(this.label, this.cssClass);
}
}
this.updateEnabled();
} else {
if (this.label) {
DOM.removeClass(this.label, 'codicon');
}
}
}
updateEnabled(): void {
if (this.getAction().enabled) {
if (this.label) {
this.label.removeAttribute('aria-disabled');
DOM.removeClass(this.label, 'disabled');
this.label.tabIndex = 0;
}
if (this.element) {
DOM.removeClass(this.element, 'disabled');
}
} else {
if (this.label) {
this.label.setAttribute('aria-disabled', 'true');
DOM.addClass(this.label, 'disabled');
DOM.removeTabIndexAndUpdateFocus(this.label);
}
if (this.element) {
DOM.addClass(this.element, 'disabled');
}
}
}
updateChecked(): void {
if (this.label) {
if (this.getAction().checked) {
DOM.addClass(this.label, 'checked');
} else {
DOM.removeClass(this.label, 'checked');
}
}
}
}
export class SelectActionViewItem extends BaseActionViewItem {
protected selectBox: SelectBox;
constructor(ctx: unknown, action: IAction, options: ISelectOptionItem[], selected: number, contextViewProvider: IContextViewProvider, selectBoxOptions?: ISelectBoxOptions) {
super(ctx, action);
this.selectBox = new SelectBox(options, selected, contextViewProvider, undefined, selectBoxOptions);
this._register(this.selectBox);
this.registerListeners();
}
setOptions(options: ISelectOptionItem[], selected?: number): void {
this.selectBox.setOptions(options, selected);
}
select(index: number): void {
this.selectBox.select(index);
}
private registerListeners(): void {
this._register(this.selectBox.onDidSelect(e => {
this.actionRunner.run(this._action, this.getActionContext(e.selected, e.index));
}));
}
protected getActionContext(option: string, index: number) {
return option;
}
focus(): void {
if (this.selectBox) {
this.selectBox.focus();
}
}
blur(): void {
if (this.selectBox) {
this.selectBox.blur();
}
}
render(container: HTMLElement): void {
this.selectBox.render(container);
}
}

View File

@@ -4,382 +4,14 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./actionbar';
import * as platform from 'vs/base/common/platform';
import * as nls from 'vs/nls';
import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { SelectBox, ISelectOptionItem, ISelectBoxOptions } from 'vs/base/browser/ui/selectBox/selectBox';
import { IAction, IActionRunner, Action, IActionChangeEvent, ActionRunner, IRunEvent } from 'vs/base/common/actions';
import { Disposable, dispose } from 'vs/base/common/lifecycle';
import { IAction, IActionRunner, ActionRunner, IRunEvent, Separator, IActionViewItem, IActionViewItemProvider } from 'vs/base/common/actions';
import * as DOM from 'vs/base/browser/dom';
import * as types from 'vs/base/common/types';
import { EventType, Gesture } from 'vs/base/browser/touch';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
import { Event, Emitter } from 'vs/base/common/event';
import { DataTransfers } from 'vs/base/browser/dnd';
import { isFirefox } from 'vs/base/browser/browser';
export interface IActionViewItem extends IDisposable {
actionRunner: IActionRunner;
setActionContext(context: any): void;
render(element: HTMLElement): void;
isEnabled(): boolean;
focus(fromRight?: boolean): void;
blur(): void;
}
export interface IBaseActionViewItemOptions {
draggable?: boolean;
isMenu?: boolean;
useEventAsContext?: boolean;
}
export class BaseActionViewItem extends Disposable implements IActionViewItem {
element: HTMLElement | undefined;
_context: any;
_action: IAction;
private _actionRunner: IActionRunner | undefined;
constructor(context: any, action: IAction, protected options?: IBaseActionViewItemOptions) {
super();
this._context = context || this;
this._action = action;
if (action instanceof Action) {
this._register(action.onDidChange(event => {
if (!this.element) {
// we have not been rendered yet, so there
// is no point in updating the UI
return;
}
this.handleActionChangeEvent(event);
}));
}
}
private handleActionChangeEvent(event: IActionChangeEvent): void {
if (event.enabled !== undefined) {
this.updateEnabled();
}
if (event.checked !== undefined) {
this.updateChecked();
}
if (event.class !== undefined) {
this.updateClass();
}
if (event.label !== undefined) {
this.updateLabel();
this.updateTooltip();
}
if (event.tooltip !== undefined) {
this.updateTooltip();
}
}
get actionRunner(): IActionRunner {
if (!this._actionRunner) {
this._actionRunner = this._register(new ActionRunner());
}
return this._actionRunner;
}
set actionRunner(actionRunner: IActionRunner) {
this._actionRunner = actionRunner;
}
getAction(): IAction {
return this._action;
}
isEnabled(): boolean {
return this._action.enabled;
}
setActionContext(newContext: unknown): void {
this._context = newContext;
}
render(container: HTMLElement): void {
const element = this.element = container;
this._register(Gesture.addTarget(container));
const enableDragging = this.options && this.options.draggable;
if (enableDragging) {
container.draggable = true;
if (isFirefox) {
// Firefox: requires to set a text data transfer to get going
this._register(DOM.addDisposableListener(container, DOM.EventType.DRAG_START, e => e.dataTransfer?.setData(DataTransfers.TEXT, this._action.label)));
}
}
this._register(DOM.addDisposableListener(element, EventType.Tap, e => this.onClick(e)));
this._register(DOM.addDisposableListener(element, DOM.EventType.MOUSE_DOWN, e => {
if (!enableDragging) {
DOM.EventHelper.stop(e, true); // do not run when dragging is on because that would disable it
}
if (this._action.enabled && e.button === 0) {
DOM.addClass(element, 'active');
}
}));
if (platform.isMacintosh) {
// macOS: allow to trigger the button when holding Ctrl+key and pressing the
// main mouse button. This is for scenarios where e.g. some interaction forces
// the Ctrl+key to be pressed and hold but the user still wants to interact
// with the actions (for example quick access in quick navigation mode).
this._register(DOM.addDisposableListener(element, DOM.EventType.CONTEXT_MENU, e => {
if (e.button === 0 && e.ctrlKey === true) {
this.onClick(e);
}
}));
}
this._register(DOM.addDisposableListener(element, DOM.EventType.CLICK, e => {
DOM.EventHelper.stop(e, true);
// See https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Interact_with_the_clipboard
// > Writing to the clipboard
// > You can use the "cut" and "copy" commands without any special
// permission if you are using them in a short-lived event handler
// for a user action (for example, a click handler).
// => to get the Copy and Paste context menu actions working on Firefox,
// there should be no timeout here
if (this.options && this.options.isMenu) {
this.onClick(e);
} else {
platform.setImmediate(() => this.onClick(e));
}
}));
this._register(DOM.addDisposableListener(element, DOM.EventType.DBLCLICK, e => {
DOM.EventHelper.stop(e, true);
}));
[DOM.EventType.MOUSE_UP, DOM.EventType.MOUSE_OUT].forEach(event => {
this._register(DOM.addDisposableListener(element, event, e => {
DOM.EventHelper.stop(e);
DOM.removeClass(element, 'active');
}));
});
}
onClick(event: DOM.EventLike): void {
DOM.EventHelper.stop(event, true);
const context = types.isUndefinedOrNull(this._context) ? this.options?.useEventAsContext ? event : undefined : this._context;
this.actionRunner.run(this._action, context);
}
focus(): void {
if (this.element) {
this.element.focus();
DOM.addClass(this.element, 'focused');
}
}
blur(): void {
if (this.element) {
this.element.blur();
DOM.removeClass(this.element, 'focused');
}
}
protected updateEnabled(): void {
// implement in subclass
}
protected updateLabel(): void {
// implement in subclass
}
protected updateTooltip(): void {
// implement in subclass
}
protected updateClass(): void {
// implement in subclass
}
protected updateChecked(): void {
// implement in subclass
}
dispose(): void {
if (this.element) {
DOM.removeNode(this.element);
this.element = undefined;
}
super.dispose();
}
}
export class Separator extends Action {
static readonly ID = 'vs.actions.separator';
constructor(label?: string) {
super(Separator.ID, label, label ? 'separator text' : 'separator');
this.checked = false;
this.enabled = false;
}
}
export interface IActionViewItemOptions extends IBaseActionViewItemOptions {
icon?: boolean;
label?: boolean;
keybinding?: string | null;
}
export class ActionViewItem extends BaseActionViewItem {
protected label: HTMLElement | undefined;
protected options: IActionViewItemOptions;
private cssClass?: string;
constructor(context: unknown, action: IAction, options: IActionViewItemOptions = {}) {
super(context, action, options);
this.options = options;
this.options.icon = options.icon !== undefined ? options.icon : false;
this.options.label = options.label !== undefined ? options.label : true;
this.cssClass = '';
}
render(container: HTMLElement): void {
super.render(container);
if (this.element) {
this.label = DOM.append(this.element, DOM.$('a.action-label'));
}
if (this.label) {
if (this._action.id === Separator.ID) {
this.label.setAttribute('role', 'presentation'); // A separator is a presentation item
} else {
if (this.options.isMenu) {
this.label.setAttribute('role', 'menuitem');
} else {
this.label.setAttribute('role', 'button');
}
}
}
if (this.options.label && this.options.keybinding && this.element) {
DOM.append(this.element, DOM.$('span.keybinding')).textContent = this.options.keybinding;
}
this.updateClass();
this.updateLabel();
this.updateTooltip();
this.updateEnabled();
this.updateChecked();
}
focus(): void {
super.focus();
if (this.label) {
this.label.focus();
}
}
updateLabel(): void {
if (this.options.label && this.label) {
this.label.textContent = this.getAction().label;
}
}
updateTooltip(): void {
let title: string | null = null;
if (this.getAction().tooltip) {
title = this.getAction().tooltip;
} else if (!this.options.label && this.getAction().label && this.options.icon) {
title = this.getAction().label;
if (this.options.keybinding) {
title = nls.localize({ key: 'titleLabel', comment: ['action title', 'action keybinding'] }, "{0} ({1})", title, this.options.keybinding);
}
}
if (title && this.label) {
this.label.title = title;
}
}
updateClass(): void {
if (this.cssClass && this.label) {
DOM.removeClasses(this.label, this.cssClass);
}
if (this.options.icon) {
this.cssClass = this.getAction().class;
if (this.label) {
DOM.addClass(this.label, 'codicon');
if (this.cssClass) {
DOM.addClasses(this.label, this.cssClass);
}
}
this.updateEnabled();
} else {
if (this.label) {
DOM.removeClass(this.label, 'codicon');
}
}
}
updateEnabled(): void {
if (this.getAction().enabled) {
if (this.label) {
this.label.removeAttribute('aria-disabled');
DOM.removeClass(this.label, 'disabled');
this.label.tabIndex = 0;
}
if (this.element) {
DOM.removeClass(this.element, 'disabled');
}
} else {
if (this.label) {
this.label.setAttribute('aria-disabled', 'true');
DOM.addClass(this.label, 'disabled');
DOM.removeTabIndexAndUpdateFocus(this.label);
}
if (this.element) {
DOM.addClass(this.element, 'disabled');
}
}
}
updateChecked(): void {
if (this.label) {
if (this.getAction().checked) {
DOM.addClass(this.label, 'checked');
} else {
DOM.removeClass(this.label, 'checked');
}
}
}
}
import { IActionViewItemOptions, ActionViewItem, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
export const enum ActionsOrientation {
HORIZONTAL,
@@ -393,41 +25,30 @@ export interface ActionTrigger {
keyDown: boolean;
}
export interface IActionViewItemProvider {
(action: IAction): IActionViewItem | undefined;
}
export interface IActionBarOptions {
orientation?: ActionsOrientation;
context?: any;
actionViewItemProvider?: IActionViewItemProvider;
actionRunner?: IActionRunner;
ariaLabel?: string;
animated?: boolean;
triggerKeys?: ActionTrigger;
allowContextMenu?: boolean;
preventLoopNavigation?: boolean;
readonly orientation?: ActionsOrientation;
readonly context?: any;
readonly actionViewItemProvider?: IActionViewItemProvider;
readonly actionRunner?: IActionRunner;
readonly ariaLabel?: string;
readonly animated?: boolean;
readonly triggerKeys?: ActionTrigger;
readonly allowContextMenu?: boolean;
readonly preventLoopNavigation?: boolean;
}
const defaultOptions: IActionBarOptions = {
orientation: ActionsOrientation.HORIZONTAL,
context: null,
triggerKeys: {
keys: [KeyCode.Enter, KeyCode.Space],
keyDown: false
}
};
export interface IActionOptions extends IActionViewItemOptions {
index?: number;
}
export class ActionBar extends Disposable implements IActionRunner {
options: IActionBarOptions;
private readonly options: IActionBarOptions;
private _actionRunner: IActionRunner;
private _context: unknown;
private _orientation: ActionsOrientation;
private _triggerKeys: ActionTrigger;
// View Items
viewItems: IActionViewItem[];
@@ -450,15 +71,16 @@ export class ActionBar extends Disposable implements IActionRunner {
private _onDidBeforeRun = this._register(new Emitter<IRunEvent>());
readonly onDidBeforeRun: Event<IRunEvent> = this._onDidBeforeRun.event;
constructor(container: HTMLElement, options: IActionBarOptions = defaultOptions) {
constructor(container: HTMLElement, options: IActionBarOptions = {}) {
super();
this.options = options;
this._context = options.context;
if (!this.options.triggerKeys) {
this.options.triggerKeys = defaultOptions.triggerKeys;
}
this._context = options.context ?? null;
this._orientation = this.options.orientation ?? ActionsOrientation.HORIZONTAL;
this._triggerKeys = this.options.triggerKeys ?? {
keys: [KeyCode.Enter, KeyCode.Space],
keyDown: false
};
if (this.options.actionRunner) {
this._actionRunner = this.options.actionRunner;
@@ -483,7 +105,7 @@ export class ActionBar extends Disposable implements IActionRunner {
let previousKey: KeyCode;
let nextKey: KeyCode;
switch (this.options.orientation) {
switch (this._orientation) {
case ActionsOrientation.HORIZONTAL:
previousKey = KeyCode.LeftArrow;
nextKey = KeyCode.RightArrow;
@@ -517,7 +139,7 @@ export class ActionBar extends Disposable implements IActionRunner {
this._onDidCancel.fire();
} else if (this.isTriggerKeyEvent(event)) {
// Staying out of the else branch even if not triggered
if (this.options.triggerKeys && this.options.triggerKeys.keyDown) {
if (this._triggerKeys.keyDown) {
this.doTrigger(event);
}
} else {
@@ -535,7 +157,7 @@ export class ActionBar extends Disposable implements IActionRunner {
// Run action on Enter/Space
if (this.isTriggerKeyEvent(event)) {
if (this.options.triggerKeys && !this.options.triggerKeys.keyDown) {
if (!this._triggerKeys.keyDown) {
this.doTrigger(event);
}
@@ -582,11 +204,9 @@ export class ActionBar extends Disposable implements IActionRunner {
private isTriggerKeyEvent(event: StandardKeyboardEvent): boolean {
let ret = false;
if (this.options.triggerKeys) {
this.options.triggerKeys.keys.forEach(keyCode => {
ret = ret || event.equals(keyCode);
});
}
this._triggerKeys.keys.forEach(keyCode => {
ret = ret || event.equals(keyCode);
});
return ret;
}
@@ -845,53 +465,6 @@ export class ActionBar extends Disposable implements IActionRunner {
}
}
export class SelectActionViewItem extends BaseActionViewItem {
protected selectBox: SelectBox;
constructor(ctx: unknown, action: IAction, options: ISelectOptionItem[], selected: number, contextViewProvider: IContextViewProvider, selectBoxOptions?: ISelectBoxOptions) {
super(ctx, action);
this.selectBox = new SelectBox(options, selected, contextViewProvider, undefined, selectBoxOptions);
this._register(this.selectBox);
this.registerListeners();
}
setOptions(options: ISelectOptionItem[], selected?: number): void {
this.selectBox.setOptions(options, selected);
}
select(index: number): void {
this.selectBox.select(index);
}
private registerListeners(): void {
this._register(this.selectBox.onDidSelect(e => {
this.actionRunner.run(this._action, this.getActionContext(e.selected, e.index));
}));
}
protected getActionContext(option: string, index: number) {
return option;
}
focus(): void {
if (this.selectBox) {
this.selectBox.focus();
}
}
blur(): void {
if (this.selectBox) {
this.selectBox.blur();
}
}
render(container: HTMLElement): void {
this.selectBox.render(container);
}
}
export function prepareActions(actions: IAction[]): IAction[] {
if (!actions.length) {
return actions;

View File

@@ -10,9 +10,9 @@ import { Widget } from 'vs/base/browser/ui/widget';
import { Color } from 'vs/base/common/color';
import { Emitter, Event } from 'vs/base/common/event';
import { KeyCode } from 'vs/base/common/keyCodes';
import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { Codicon } from 'vs/base/common/codicons';
import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
export interface ICheckboxOpts extends ICheckboxStyles {
readonly actionClassName?: string;

View File

@@ -5,14 +5,13 @@
import 'vs/css!./dropdown';
import { Gesture, EventType as GestureEventType } from 'vs/base/browser/touch';
import { ActionRunner, IAction, IActionRunner } from 'vs/base/common/actions';
import { BaseActionViewItem, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar';
import { ActionRunner, IAction } from 'vs/base/common/actions';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IContextViewProvider, IAnchor, AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
import { IMenuOptions } from 'vs/base/browser/ui/menu/menu';
import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes';
import { EventHelper, EventType, removeClass, addClass, append, $, addDisposableListener, addClasses, DOMEvent } from 'vs/base/browser/dom';
import { IContextMenuDelegate } from 'vs/base/browser/contextmenu';
import { KeyCode } from 'vs/base/common/keyCodes';
import { EventHelper, EventType, removeClass, addClass, append, $, addDisposableListener, DOMEvent } from 'vs/base/browser/dom';
import { IContextMenuProvider } from 'vs/base/browser/contextmenu';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { Emitter } from 'vs/base/common/event';
@@ -201,17 +200,13 @@ export class Dropdown extends BaseDropdown {
}
}
export interface IContextMenuProvider {
showContextMenu(delegate: IContextMenuDelegate): void;
}
export interface IActionProvider {
getActions(): ReadonlyArray<IAction>;
getActions(): IAction[];
}
export interface IDropdownMenuOptions extends IBaseDropdownOptions {
contextMenuProvider: IContextMenuProvider;
actions?: ReadonlyArray<IAction>;
actions?: IAction[];
actionProvider?: IActionProvider;
menuClassName?: string;
menuAsChild?: boolean; // scope down for #99448
@@ -220,7 +215,7 @@ export interface IDropdownMenuOptions extends IBaseDropdownOptions {
export class DropdownMenu extends BaseDropdown {
private _contextMenuProvider: IContextMenuProvider;
private _menuOptions: IMenuOptions | undefined;
private _actions: ReadonlyArray<IAction> = [];
private _actions: IAction[] = [];
private actionProvider?: IActionProvider;
private menuClassName: string;
private menuAsChild?: boolean;
@@ -243,7 +238,7 @@ export class DropdownMenu extends BaseDropdown {
return this._menuOptions;
}
private get actions(): ReadonlyArray<IAction> {
private get actions(): IAction[] {
if (this.actionProvider) {
return this.actionProvider.getActions();
}
@@ -251,7 +246,7 @@ export class DropdownMenu extends BaseDropdown {
return this._actions;
}
private set actions(actions: ReadonlyArray<IAction>) {
private set actions(actions: IAction[]) {
this._actions = actions;
}
@@ -283,106 +278,3 @@ export class DropdownMenu extends BaseDropdown {
removeClass(this.element, 'active');
}
}
export class DropdownMenuActionViewItem extends BaseActionViewItem {
private menuActionsOrProvider: ReadonlyArray<IAction> | IActionProvider;
private dropdownMenu: DropdownMenu | undefined;
private contextMenuProvider: IContextMenuProvider;
private actionViewItemProvider?: IActionViewItemProvider;
private keybindings?: (action: IAction) => ResolvedKeybinding | undefined;
private clazz: string | undefined;
private anchorAlignmentProvider: (() => AnchorAlignment) | undefined;
private menuAsChild?: boolean;
private _onDidChangeVisibility = this._register(new Emitter<boolean>());
readonly onDidChangeVisibility = this._onDidChangeVisibility.event;
constructor(action: IAction, menuActions: ReadonlyArray<IAction>, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner | undefined, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment, menuAsChild?: boolean);
constructor(action: IAction, actionProvider: IActionProvider, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner | undefined, keybindings: ((action: IAction) => ResolvedKeybinding) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment, menuAsChild?: boolean);
constructor(action: IAction, menuActionsOrProvider: ReadonlyArray<IAction> | IActionProvider, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner | undefined, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment, menuAsChild?: boolean) {
super(null, action);
this.menuActionsOrProvider = menuActionsOrProvider;
this.contextMenuProvider = contextMenuProvider;
this.actionViewItemProvider = actionViewItemProvider;
if (actionRunner) {
this.actionRunner = actionRunner;
}
this.keybindings = keybindings;
this.clazz = clazz;
this.anchorAlignmentProvider = anchorAlignmentProvider;
this.menuAsChild = menuAsChild;
}
render(container: HTMLElement): void {
const labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable | null => {
this.element = append(el, $('a.action-label.codicon')); // todo@aeschli: remove codicon, should come through `this.clazz`
if (this.clazz) {
addClasses(this.element, this.clazz);
}
this.element.tabIndex = 0;
this.element.setAttribute('role', 'button');
this.element.setAttribute('aria-haspopup', 'true');
this.element.setAttribute('aria-expanded', 'false');
this.element.title = this._action.label || '';
return null;
};
const options: IDropdownMenuOptions = {
contextMenuProvider: this.contextMenuProvider,
labelRenderer: labelRenderer,
menuAsChild: this.menuAsChild
};
// Render the DropdownMenu around a simple action to toggle it
if (Array.isArray(this.menuActionsOrProvider)) {
options.actions = this.menuActionsOrProvider;
} else {
options.actionProvider = this.menuActionsOrProvider as IActionProvider;
}
this.dropdownMenu = this._register(new DropdownMenu(container, options));
this._register(this.dropdownMenu.onDidChangeVisibility(visible => {
this.element?.setAttribute('aria-expanded', `${visible}`);
this._onDidChangeVisibility.fire(visible);
}));
this.dropdownMenu.menuOptions = {
actionViewItemProvider: this.actionViewItemProvider,
actionRunner: this.actionRunner,
getKeyBinding: this.keybindings,
context: this._context
};
if (this.anchorAlignmentProvider) {
const that = this;
this.dropdownMenu.menuOptions = {
...this.dropdownMenu.menuOptions,
get anchorAlignment(): AnchorAlignment {
return that.anchorAlignmentProvider!();
}
};
}
}
setActionContext(newContext: unknown): void {
super.setActionContext(newContext);
if (this.dropdownMenu) {
if (this.dropdownMenu.menuOptions) {
this.dropdownMenu.menuOptions.context = newContext;
} else {
this.dropdownMenu.menuOptions = { context: newContext };
}
}
}
show(): void {
if (this.dropdownMenu) {
this.dropdownMenu.show();
}
}
}

View File

@@ -0,0 +1,136 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./dropdown';
import { IAction, IActionRunner, IActionViewItemProvider } from 'vs/base/common/actions';
import { IDisposable } from 'vs/base/common/lifecycle';
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { append, $, addClasses } from 'vs/base/browser/dom';
import { Emitter } from 'vs/base/common/event';
import { BaseActionViewItem, IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { IActionProvider, DropdownMenu, IDropdownMenuOptions, ILabelRenderer } from 'vs/base/browser/ui/dropdown/dropdown';
import { IContextMenuProvider } from 'vs/base/browser/contextmenu';
import { asArray } from 'vs/base/common/arrays';
export interface IKeybindingProvider {
(action: IAction): ResolvedKeybinding | undefined;
}
export interface IAnchorAlignmentProvider {
(): AnchorAlignment;
}
export interface IDropdownMenuActionViewItemOptions extends IBaseActionViewItemOptions {
readonly actionViewItemProvider?: IActionViewItemProvider;
readonly keybindingProvider?: IKeybindingProvider;
readonly actionRunner?: IActionRunner;
readonly classNames?: string[] | string;
readonly anchorAlignmentProvider?: IAnchorAlignmentProvider;
readonly menuAsChild?: boolean;
}
export class DropdownMenuActionViewItem extends BaseActionViewItem {
private menuActionsOrProvider: readonly IAction[] | IActionProvider;
private dropdownMenu: DropdownMenu | undefined;
private contextMenuProvider: IContextMenuProvider;
private _onDidChangeVisibility = this._register(new Emitter<boolean>());
readonly onDidChangeVisibility = this._onDidChangeVisibility.event;
constructor(
action: IAction,
menuActionsOrProvider: readonly IAction[] | IActionProvider,
contextMenuProvider: IContextMenuProvider,
protected options: IDropdownMenuActionViewItemOptions = {}
) {
super(null, action, options);
this.menuActionsOrProvider = menuActionsOrProvider;
this.contextMenuProvider = contextMenuProvider;
if (this.options.actionRunner) {
this.actionRunner = this.options.actionRunner;
}
}
render(container: HTMLElement): void {
const labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable | null => {
this.element = append(el, $('a.action-label'));
const classNames = this.options.classNames ? asArray(this.options.classNames) : [];
// todo@aeschli: remove codicon, should come through `this.options.classNames`
if (!classNames.find(c => c === 'icon')) {
classNames.push('codicon');
}
addClasses(this.element, ...classNames);
this.element.tabIndex = 0;
this.element.setAttribute('role', 'button');
this.element.setAttribute('aria-haspopup', 'true');
this.element.setAttribute('aria-expanded', 'false');
this.element.title = this._action.label || '';
return null;
};
const options: IDropdownMenuOptions = {
contextMenuProvider: this.contextMenuProvider,
labelRenderer: labelRenderer,
menuAsChild: this.options.menuAsChild
};
// Render the DropdownMenu around a simple action to toggle it
if (Array.isArray(this.menuActionsOrProvider)) {
options.actions = this.menuActionsOrProvider;
} else {
options.actionProvider = this.menuActionsOrProvider as IActionProvider;
}
this.dropdownMenu = this._register(new DropdownMenu(container, options));
this._register(this.dropdownMenu.onDidChangeVisibility(visible => {
this.element?.setAttribute('aria-expanded', `${visible}`);
this._onDidChangeVisibility.fire(visible);
}));
this.dropdownMenu.menuOptions = {
actionViewItemProvider: this.options.actionViewItemProvider,
actionRunner: this.actionRunner,
getKeyBinding: this.options.keybindingProvider,
context: this._context
};
if (this.options.anchorAlignmentProvider) {
const that = this;
this.dropdownMenu.menuOptions = {
...this.dropdownMenu.menuOptions,
get anchorAlignment(): AnchorAlignment {
return that.options.anchorAlignmentProvider!();
}
};
}
}
setActionContext(newContext: unknown): void {
super.setActionContext(newContext);
if (this.dropdownMenu) {
if (this.dropdownMenu.menuOptions) {
this.dropdownMenu.menuOptions.context = newContext;
} else {
this.dropdownMenu.menuOptions = { context: newContext };
}
}
}
show(): void {
if (this.dropdownMenu) {
this.dropdownMenu.show();
}
}
}

View File

@@ -5,8 +5,8 @@
import * as nls from 'vs/nls';
import * as strings from 'vs/base/common/strings';
import { IActionRunner, IAction, Action } from 'vs/base/common/actions';
import { ActionBar, IActionViewItemProvider, ActionsOrientation, Separator, ActionViewItem, IActionViewItemOptions, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { IActionRunner, IAction, SubmenuAction, Separator, IActionViewItemProvider } from 'vs/base/common/actions';
import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes';
import { addClass, EventType, EventHelper, EventLike, removeTabIndexAndUpdateFocus, isAncestor, hasClass, addDisposableListener, removeClass, append, $, addClasses, removeClasses, clearNode, createStyleSheet, isInShadowDOM } from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
@@ -19,6 +19,7 @@ import { Event } from 'vs/base/common/event';
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
import { isLinux, isMacintosh } from 'vs/base/common/platform';
import { Codicon, registerIcon, stripCodicons } from 'vs/base/common/codicons';
import { BaseActionViewItem, ActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { formatRule } from 'vs/base/browser/ui/codicons/codiconStyles';
export const MENU_MNEMONIC_REGEX = /\(&([^\s&])\)|(^|[^&])&([^\s&])/;
@@ -42,6 +43,7 @@ export interface IMenuOptions {
anchorAlignment?: AnchorAlignment;
expandDirection?: Direction;
useEventAsContext?: boolean;
submenuIds?: Set<string>;
}
export interface IMenuStyles {
@@ -55,12 +57,6 @@ export interface IMenuStyles {
separatorColor?: Color;
}
export class SubmenuAction extends Action {
constructor(label: string, public entries: ReadonlyArray<SubmenuAction | IAction>, cssClass?: string) {
super(!!cssClass ? cssClass : 'submenu', label, '', true);
}
}
interface ISubMenuData {
parent: Menu;
submenu?: Menu;
@@ -209,6 +205,15 @@ export class Menu extends ActionBar {
menuElement.style.maxHeight = `${Math.max(10, window.innerHeight - container.getBoundingClientRect().top - 30)}px`;
actions = actions.filter(a => {
if (options.submenuIds?.has(a.id)) {
console.warn(`Found submenu cycle: ${a.id}`);
return false;
}
return true;
});
this.push(actions, { icon: true, label: true, isMenu: true });
container.appendChild(this.scrollableElement.getDomNode());
@@ -317,7 +322,8 @@ export class Menu extends ActionBar {
if (action instanceof Separator) {
return new MenuSeparatorActionViewItem(options.context, action, { icon: true });
} else if (action instanceof SubmenuAction) {
const menuActionViewItem = new SubmenuMenuActionViewItem(action, action.entries, parentData, options);
const actions = Array.isArray(action.actions) ? action.actions : action.actions();
const menuActionViewItem = new SubmenuMenuActionViewItem(action, actions, parentData, { ...options, submenuIds: new Set([...(options.submenuIds || []), action.id]) });
if (options.enableMnemonics) {
const mnemonic = menuActionViewItem.getMnemonic();

View File

@@ -11,8 +11,8 @@ import * as nls from 'vs/nls';
import { domEvent } from 'vs/base/browser/event';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { EventType, Gesture, GestureEvent } from 'vs/base/browser/touch';
import { cleanMnemonic, IMenuOptions, Menu, MENU_ESCAPED_MNEMONIC_REGEX, MENU_MNEMONIC_REGEX, SubmenuAction, IMenuStyles, Direction } from 'vs/base/browser/ui/menu/menu';
import { ActionRunner, IAction, IActionRunner } from 'vs/base/common/actions';
import { cleanMnemonic, IMenuOptions, Menu, MENU_ESCAPED_MNEMONIC_REGEX, MENU_MNEMONIC_REGEX, IMenuStyles, Direction } from 'vs/base/browser/ui/menu/menu';
import { ActionRunner, IAction, IActionRunner, SubmenuAction, Separator } from 'vs/base/common/actions';
import { RunOnceScheduler } from 'vs/base/common/async';
import { Event, Emitter } from 'vs/base/common/event';
import { KeyCode, ResolvedKeybinding, KeyMod } from 'vs/base/common/keyCodes';
@@ -22,7 +22,6 @@ import { asArray } from 'vs/base/common/arrays';
import { ScanCodeUtils, ScanCode } from 'vs/base/common/scanCode';
import { isMacintosh } from 'vs/base/common/platform';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { Codicon, registerIcon } from 'vs/base/common/codicons';
const $ = DOM.$;
@@ -40,7 +39,7 @@ export interface IMenuBarOptions {
}
export interface MenuBarMenu {
actions: ReadonlyArray<IAction>;
actions: IAction[];
label: string;
}
@@ -59,7 +58,7 @@ export class MenuBar extends Disposable {
buttonElement: HTMLElement;
titleElement: HTMLElement;
label: string;
actions?: ReadonlyArray<IAction>;
actions?: IAction[];
}[];
private overflowMenu!: {
@@ -506,7 +505,7 @@ export class MenuBar extends Disposable {
this.overflowMenu.actions = [];
for (let idx = this.numMenusShown; idx < this.menuCache.length; idx++) {
this.overflowMenu.actions.push(new SubmenuAction(this.menuCache[idx].label, this.menuCache[idx].actions || []));
this.overflowMenu.actions.push(new SubmenuAction(`menubar.submenu.${this.menuCache[idx].label}`, this.menuCache[idx].label, this.menuCache[idx].actions || []));
}
if (this.overflowMenu.buttonElement.nextElementSibling !== this.menuCache[this.numMenusShown].buttonElement) {

View File

@@ -5,17 +5,16 @@
import 'vs/css!./toolbar';
import * as nls from 'vs/nls';
import { Action, IActionRunner, IAction } from 'vs/base/common/actions';
import { ActionBar, ActionsOrientation, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar';
import { IContextMenuProvider, DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown';
import { Action, IActionRunner, IAction, IActionViewItemProvider, SubmenuAction } from 'vs/base/common/actions';
import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { Disposable, IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
import { withNullAsUndefined } from 'vs/base/common/types';
import { Codicon, registerIcon } from 'vs/base/common/codicons';
import { Emitter } from 'vs/base/common/event';
export const CONTEXT = 'context.toolbar';
import { EventMultiplexer } from 'vs/base/common/event';
import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem';
import { IContextMenuProvider } from 'vs/base/browser/contextmenu';
const toolBarMoreIcon = registerIcon('toolbar-more', Codicon.more);
@@ -37,12 +36,13 @@ export class ToolBar extends Disposable {
private actionBar: ActionBar;
private toggleMenuAction: ToggleMenuAction;
private toggleMenuActionViewItem: DropdownMenuActionViewItem | undefined;
private toggleMenuActionViewItemDisposable: IDisposable = Disposable.None;
private submenuActionViewItems: DropdownMenuActionViewItem[] = [];
private hasSecondaryActions: boolean = false;
private lookupKeybindings: boolean;
private _onDidChangeDropdownVisibility = this._register(new Emitter<boolean>());
private _onDidChangeDropdownVisibility = this._register(new EventMultiplexer<boolean>());
readonly onDidChangeDropdownVisibility = this._onDidChangeDropdownVisibility.event;
private disposables = new DisposableStore();
constructor(container: HTMLElement, contextMenuProvider: IContextMenuProvider, options: IToolBarOptions = { orientation: ActionsOrientation.HORIZONTAL }) {
super();
@@ -61,35 +61,57 @@ export class ToolBar extends Disposable {
ariaLabel: options.ariaLabel,
actionRunner: options.actionRunner,
actionViewItemProvider: (action: IAction) => {
// Return special action item for the toggle menu action
if (action.id === ToggleMenuAction.ID) {
this.toggleMenuActionViewItemDisposable.dispose();
// Create new
this.toggleMenuActionViewItem = new DropdownMenuActionViewItem(
action,
(<ToggleMenuAction>action).menuActions,
contextMenuProvider,
this.options.actionViewItemProvider,
this.actionRunner,
this.options.getKeyBinding,
toolBarMoreIcon.classNames,
this.options.anchorAlignmentProvider,
true
{
actionViewItemProvider: this.options.actionViewItemProvider,
actionRunner: this.actionRunner,
keybindingProvider: this.options.getKeyBinding,
classNames: toolBarMoreIcon.classNames,
anchorAlignmentProvider: this.options.anchorAlignmentProvider,
menuAsChild: true
}
);
this.toggleMenuActionViewItem.setActionContext(this.actionBar.context);
this.toggleMenuActionViewItemDisposable = combinedDisposable(
this.toggleMenuActionViewItem,
this.toggleMenuActionViewItem.onDidChangeVisibility(e => this._onDidChangeDropdownVisibility.fire(e))
);
this.disposables.add(this._onDidChangeDropdownVisibility.add(this.toggleMenuActionViewItem.onDidChangeVisibility));
return this.toggleMenuActionViewItem;
}
return options.actionViewItemProvider ? options.actionViewItemProvider(action) : undefined;
if (options.actionViewItemProvider) {
const result = options.actionViewItemProvider(action);
if (result) {
return result;
}
}
if (action instanceof SubmenuAction) {
const actions = Array.isArray(action.actions) ? action.actions : action.actions();
const result = new DropdownMenuActionViewItem(
action,
actions,
contextMenuProvider,
{
actionViewItemProvider: this.options.actionViewItemProvider,
actionRunner: this.actionRunner,
keybindingProvider: this.options.getKeyBinding,
classNames: action.class,
anchorAlignmentProvider: this.options.anchorAlignmentProvider,
menuAsChild: true
}
);
result.setActionContext(this.actionBar.context);
this.submenuActionViewItems.push(result);
this.disposables.add(this._onDidChangeDropdownVisibility.add(result.onDidChangeVisibility));
return result;
}
return undefined;
}
}));
}
@@ -107,6 +129,9 @@ export class ToolBar extends Disposable {
if (this.toggleMenuActionViewItem) {
this.toggleMenuActionViewItem.setActionContext(context);
}
for (const actionViewItem of this.submenuActionViewItems) {
actionViewItem.setActionContext(context);
}
}
getContainer(): HTMLElement {
@@ -126,6 +151,8 @@ export class ToolBar extends Disposable {
}
setActions(primaryActions: ReadonlyArray<IAction>, secondaryActions?: ReadonlyArray<IAction>): void {
this.clear();
let primaryActionsToSet = primaryActions ? primaryActions.slice(0) : [];
// Inject additional action to open secondary actions if present
@@ -135,8 +162,6 @@ export class ToolBar extends Disposable {
primaryActionsToSet.push(this.toggleMenuAction);
}
this.actionBar.clear();
primaryActionsToSet.forEach(action => {
this.actionBar.push(action, { icon: true, label: false, keybinding: this.getKeybindingLabel(action) });
});
@@ -148,25 +173,15 @@ export class ToolBar extends Disposable {
return withNullAsUndefined(key?.getLabel());
}
addPrimaryAction(primaryAction: IAction): () => void {
return () => {
// Add after the "..." action if we have secondary actions
if (this.hasSecondaryActions) {
let itemCount = this.actionBar.length();
this.actionBar.push(primaryAction, { icon: true, label: false, index: itemCount, keybinding: this.getKeybindingLabel(primaryAction) });
}
// Otherwise just add to the end
else {
this.actionBar.push(primaryAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(primaryAction) });
}
};
private clear(): void {
this.submenuActionViewItems = [];
this.disposables.clear();
this.actionBar.clear();
}
dispose(): void {
this.clear();
super.dispose();
this.toggleMenuActionViewItemDisposable.dispose();
}
}