dropdownActionViewItem.ts 4.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. 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';
J
João Moreno 已提交
11
import { append, $ } from 'vs/base/browser/dom';
12
import { Emitter } from 'vs/base/common/event';
13
import { BaseActionViewItem, IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems';
J
João Moreno 已提交
14 15
import { IActionProvider, DropdownMenu, IDropdownMenuOptions, ILabelRenderer } from 'vs/base/browser/ui/dropdown/dropdown';
import { IContextMenuProvider } from 'vs/base/browser/contextmenu';
16

17 18 19 20 21 22 23 24 25 26 27 28
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;
J
João Moreno 已提交
29
	readonly classNames?: string[] | string;
30 31 32 33
	readonly anchorAlignmentProvider?: IAnchorAlignmentProvider;
	readonly menuAsChild?: boolean;
}

34
export class DropdownMenuActionViewItem extends BaseActionViewItem {
35
	private menuActionsOrProvider: readonly IAction[] | IActionProvider;
36 37 38 39 40 41
	private dropdownMenu: DropdownMenu | undefined;
	private contextMenuProvider: IContextMenuProvider;

	private _onDidChangeVisibility = this._register(new Emitter<boolean>());
	readonly onDidChangeVisibility = this._onDidChangeVisibility.event;

42 43 44 45 46 47 48
	constructor(
		action: IAction,
		menuActionsOrProvider: readonly IAction[] | IActionProvider,
		contextMenuProvider: IContextMenuProvider,
		protected options: IDropdownMenuActionViewItemOptions = {}
	) {
		super(null, action, options);
49 50 51

		this.menuActionsOrProvider = menuActionsOrProvider;
		this.contextMenuProvider = contextMenuProvider;
52 53 54 55

		if (this.options.actionRunner) {
			this.actionRunner = this.options.actionRunner;
		}
56 57 58 59
	}

	render(container: HTMLElement): void {
		const labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable | null => {
J
João Moreno 已提交
60 61
			this.element = append(el, $('a.action-label'));

J
João Moreno 已提交
62 63 64
			let classNames: string[] = [];

			if (typeof this.options.classNames === 'string') {
E
Eric Amodio 已提交
65
				classNames = this.options.classNames.split(/\s+/g).filter(s => !!s);
J
João Moreno 已提交
66 67 68
			} else if (this.options.classNames) {
				classNames = this.options.classNames;
			}
J
João Moreno 已提交
69 70 71 72

			// todo@aeschli: remove codicon, should come through `this.options.classNames`
			if (!classNames.find(c => c === 'icon')) {
				classNames.push('codicon');
73 74
			}

J
João Moreno 已提交
75
			this.element.classList.add(...classNames);
J
João Moreno 已提交
76

77 78 79 80 81 82 83 84 85
			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;
		};

S
SteVen Batten 已提交
86
		const isActionsArray = Array.isArray(this.menuActionsOrProvider);
87 88 89
		const options: IDropdownMenuOptions = {
			contextMenuProvider: this.contextMenuProvider,
			labelRenderer: labelRenderer,
S
SteVen Batten 已提交
90 91 92
			menuAsChild: this.options.menuAsChild,
			actions: isActionsArray ? this.menuActionsOrProvider as IAction[] : undefined,
			actionProvider: isActionsArray ? undefined : this.menuActionsOrProvider as IActionProvider
93 94 95 96 97 98 99 100 101
		};

		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 = {
102
			actionViewItemProvider: this.options.actionViewItemProvider,
103
			actionRunner: this.actionRunner,
104
			getKeyBinding: this.options.keybindingProvider,
105 106 107
			context: this._context
		};

108
		if (this.options.anchorAlignmentProvider) {
109 110 111 112 113
			const that = this;

			this.dropdownMenu.menuOptions = {
				...this.dropdownMenu.menuOptions,
				get anchorAlignment(): AnchorAlignment {
114
					return that.options.anchorAlignmentProvider!();
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
				}
			};
		}
	}

	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();
		}
	}
}