mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-14 12:08:36 -05:00
Merge VS Code 1.21 source code (#1067)
* Initial VS Code 1.21 file copy with patches * A few more merges * Post npm install * Fix batch of build breaks * Fix more build breaks * Fix more build errors * Fix more build breaks * Runtime fixes 1 * Get connection dialog working with some todos * Fix a few packaging issues * Copy several node_modules to package build to fix loader issues * Fix breaks from master * A few more fixes * Make tests pass * First pass of license header updates * Second pass of license header updates * Fix restore dialog issues * Remove add additional themes menu items * fix select box issues where the list doesn't show up * formatting * Fix editor dispose issue * Copy over node modules to correct location on all platforms
This commit is contained in:
@@ -17,6 +17,7 @@ import types = require('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';
|
||||
|
||||
export interface IActionItem {
|
||||
@@ -138,7 +139,7 @@ export class BaseActionItem implements IActionItem {
|
||||
if (this.options && this.options.isMenu) {
|
||||
this.onClick(e);
|
||||
} else {
|
||||
setTimeout(() => this.onClick(e), 50);
|
||||
setImmediate(() => this.onClick(e));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -755,9 +756,10 @@ export class SelectActionItem extends BaseActionItem {
|
||||
protected selectBox: SelectBox;
|
||||
protected toDispose: lifecycle.IDisposable[];
|
||||
|
||||
constructor(ctx: any, action: IAction, options: string[], selected: number) {
|
||||
constructor(ctx: any, action: IAction, options: string[], selected: number, contextViewProvider: IContextViewProvider
|
||||
) {
|
||||
super(ctx, action);
|
||||
this.selectBox = new SelectBox(options, selected);
|
||||
this.selectBox = new SelectBox(options, selected, contextViewProvider);
|
||||
|
||||
this.toDispose = [];
|
||||
this.toDispose.push(this.selectBox);
|
||||
|
||||
@@ -20,11 +20,4 @@
|
||||
.monaco-button.disabled {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Theming support */
|
||||
|
||||
.vs .monaco-text-button:focus,
|
||||
.vs-dark .monaco-text-button:focus {
|
||||
outline-color: rgba(255, 255, 255, .5); /* buttons have a blue color, so focus indication needs to be different */
|
||||
}
|
||||
@@ -13,8 +13,10 @@ import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export interface IButtonOptions extends IButtonStyles {
|
||||
title?: boolean;
|
||||
}
|
||||
|
||||
export interface IButtonStyles {
|
||||
@@ -44,6 +46,8 @@ export class Button {
|
||||
private _onDidClick = new Emitter<any>();
|
||||
readonly onDidClick: Event<any> = this._onDidClick.event;
|
||||
|
||||
private focusTracker: DOM.IFocusTracker;
|
||||
|
||||
constructor(container: Builder, options?: IButtonOptions);
|
||||
constructor(container: HTMLElement, options?: IButtonOptions);
|
||||
constructor(container: any, options?: IButtonOptions) {
|
||||
@@ -60,7 +64,7 @@ export class Button {
|
||||
'role': 'button'
|
||||
}).appendTo(container);
|
||||
|
||||
this.$el.on(DOM.EventType.CLICK, (e) => {
|
||||
this.$el.on(DOM.EventType.CLICK, e => {
|
||||
if (!this.enabled) {
|
||||
DOM.EventHelper.stop(e);
|
||||
return;
|
||||
@@ -69,7 +73,7 @@ export class Button {
|
||||
this._onDidClick.fire(e);
|
||||
});
|
||||
|
||||
this.$el.on(DOM.EventType.KEY_DOWN, (e) => {
|
||||
this.$el.on(DOM.EventType.KEY_DOWN, e => {
|
||||
let event = new StandardKeyboardEvent(e as KeyboardEvent);
|
||||
let eventHandled = false;
|
||||
if (this.enabled && event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
|
||||
@@ -85,22 +89,31 @@ export class Button {
|
||||
}
|
||||
});
|
||||
|
||||
this.$el.on(DOM.EventType.MOUSE_OVER, (e) => {
|
||||
this.$el.on(DOM.EventType.MOUSE_OVER, e => {
|
||||
if (!this.$el.hasClass('disabled')) {
|
||||
const hoverBackground = this.buttonHoverBackground ? this.buttonHoverBackground.toString() : null;
|
||||
if (hoverBackground) {
|
||||
this.$el.style('background-color', hoverBackground);
|
||||
}
|
||||
this.setHoverBackground();
|
||||
}
|
||||
});
|
||||
|
||||
this.$el.on(DOM.EventType.MOUSE_OUT, (e) => {
|
||||
this.$el.on(DOM.EventType.MOUSE_OUT, e => {
|
||||
this.applyStyles(); // restore standard styles
|
||||
});
|
||||
|
||||
// Also set hover background when button is focused for feedback
|
||||
this.focusTracker = DOM.trackFocus(this.$el.getHTMLElement());
|
||||
this.focusTracker.onDidFocus(() => this.setHoverBackground());
|
||||
this.focusTracker.onDidBlur(() => this.applyStyles()); // restore standard styles
|
||||
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
private setHoverBackground(): void {
|
||||
const hoverBackground = this.buttonHoverBackground ? this.buttonHoverBackground.toString() : null;
|
||||
if (hoverBackground) {
|
||||
this.$el.style('background-color', hoverBackground);
|
||||
}
|
||||
}
|
||||
|
||||
style(styles: IButtonStyles): void {
|
||||
this.buttonForeground = styles.buttonForeground;
|
||||
this.buttonBackground = styles.buttonBackground;
|
||||
@@ -126,7 +139,7 @@ export class Button {
|
||||
}
|
||||
}
|
||||
|
||||
getElement(): HTMLElement {
|
||||
get element(): HTMLElement {
|
||||
return this.$el.getHTMLElement();
|
||||
}
|
||||
|
||||
@@ -135,6 +148,9 @@ export class Button {
|
||||
this.$el.addClass('monaco-text-button');
|
||||
}
|
||||
this.$el.text(value);
|
||||
if (this.options.title) {
|
||||
this.$el.title(value);
|
||||
}
|
||||
}
|
||||
|
||||
set icon(iconClassName: string) {
|
||||
@@ -167,8 +183,66 @@ export class Button {
|
||||
if (this.$el) {
|
||||
this.$el.dispose();
|
||||
this.$el = null;
|
||||
|
||||
this.focusTracker.dispose();
|
||||
this.focusTracker = null;
|
||||
}
|
||||
|
||||
this._onDidClick.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class ButtonGroup {
|
||||
private _buttons: Button[];
|
||||
private toDispose: IDisposable[];
|
||||
|
||||
constructor(container: Builder, count: number, options?: IButtonOptions);
|
||||
constructor(container: HTMLElement, count: number, options?: IButtonOptions);
|
||||
constructor(container: any, count: number, options?: IButtonOptions) {
|
||||
this._buttons = [];
|
||||
this.toDispose = [];
|
||||
|
||||
this.create(container, count, options);
|
||||
}
|
||||
|
||||
get buttons(): Button[] {
|
||||
return this._buttons;
|
||||
}
|
||||
|
||||
private create(container: Builder, count: number, options?: IButtonOptions): void;
|
||||
private create(container: HTMLElement, count: number, options?: IButtonOptions): void;
|
||||
private create(container: any, count: number, options?: IButtonOptions): void {
|
||||
for (let index = 0; index < count; index++) {
|
||||
const button = new Button(container, options);
|
||||
this._buttons.push(button);
|
||||
this.toDispose.push(button);
|
||||
|
||||
// Implement keyboard access in buttons if there are multiple
|
||||
if (count > 1) {
|
||||
$(button.element).on(DOM.EventType.KEY_DOWN, e => {
|
||||
const event = new StandardKeyboardEvent(e as KeyboardEvent);
|
||||
let eventHandled = true;
|
||||
|
||||
// Next / Previous Button
|
||||
let buttonIndexToFocus: number;
|
||||
if (event.equals(KeyCode.LeftArrow)) {
|
||||
buttonIndexToFocus = index > 0 ? index - 1 : this._buttons.length - 1;
|
||||
} else if (event.equals(KeyCode.RightArrow)) {
|
||||
buttonIndexToFocus = index === this._buttons.length - 1 ? 0 : index + 1;
|
||||
} else {
|
||||
eventHandled = false;
|
||||
}
|
||||
|
||||
if (eventHandled) {
|
||||
this._buttons[buttonIndexToFocus].focus();
|
||||
DOM.EventHelper.stop(e, true);
|
||||
}
|
||||
}, this.toDispose);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.custom-checkbox {
|
||||
.monaco-custom-checkbox {
|
||||
margin-left: 2px;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
@@ -28,15 +28,15 @@
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.custom-checkbox:hover,
|
||||
.custom-checkbox.checked {
|
||||
.monaco-custom-checkbox:hover,
|
||||
.monaco-custom-checkbox.checked {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.hc-black .custom-checkbox {
|
||||
.hc-black .monaco-custom-checkbox {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.hc-black .custom-checkbox:hover {
|
||||
.hc-black .monaco-custom-checkbox:hover {
|
||||
background: none;
|
||||
}
|
||||
@@ -45,7 +45,7 @@ export class Checkbox extends Widget {
|
||||
|
||||
this.domNode = document.createElement('div');
|
||||
this.domNode.title = this._opts.title;
|
||||
this.domNode.className = 'custom-checkbox ' + this._opts.actionClassName + ' ' + (this._checked ? 'checked' : 'unchecked');
|
||||
this.domNode.className = 'monaco-custom-checkbox ' + this._opts.actionClassName + ' ' + (this._checked ? 'checked' : 'unchecked');
|
||||
this.domNode.tabIndex = 0;
|
||||
this.domNode.setAttribute('role', 'checkbox');
|
||||
this.domNode.setAttribute('aria-checked', String(this._checked));
|
||||
|
||||
@@ -128,6 +128,7 @@ export class ContextView {
|
||||
|
||||
public setContainer(container: HTMLElement): void {
|
||||
if (this.$container) {
|
||||
this.$container.getHTMLElement().removeChild(this.$view.getHTMLElement());
|
||||
this.$container.off(ContextView.BUBBLE_UP_EVENTS);
|
||||
this.$container.off(ContextView.BUBBLE_DOWN_EVENTS, true);
|
||||
this.$container = null;
|
||||
@@ -230,7 +231,7 @@ export class ContextView {
|
||||
this.$view.removeClass('top', 'bottom', 'left', 'right');
|
||||
this.$view.addClass(anchorPosition === AnchorPosition.BELOW ? 'bottom' : 'top');
|
||||
this.$view.addClass(anchorAlignment === AnchorAlignment.LEFT ? 'left' : 'right');
|
||||
this.$view.style({ top: result.top + 'px', left: result.left + 1 + 'px', width: 'initial' });
|
||||
this.$view.style({ top: result.top + 'px', left: result.left + 'px', width: 'initial' });
|
||||
}
|
||||
|
||||
public hide(data?: any): void {
|
||||
|
||||
@@ -29,8 +29,4 @@
|
||||
|
||||
.dropdown > .dropdown-action, .dropdown > .dropdown-action > .action-label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.dropdown > .dropdown-label:not(:empty) {
|
||||
padding: 0 .5em;
|
||||
}
|
||||
@@ -9,13 +9,14 @@ import 'vs/css!./dropdown';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Gesture, EventType as GestureEventType } from 'vs/base/browser/touch';
|
||||
import { ActionRunner, IAction } from 'vs/base/common/actions';
|
||||
import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ActionRunner, IAction, IActionRunner } from 'vs/base/common/actions';
|
||||
import { BaseActionItem, IActionItemProvider } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { IMenuOptions } from 'vs/base/browser/ui/menu/menu';
|
||||
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { EventHelper, EventType } from 'vs/base/browser/dom';
|
||||
import { IContextMenuDelegate } from 'vs/base/browser/contextmenu';
|
||||
|
||||
export interface ILabelRenderer {
|
||||
(container: HTMLElement): IDisposable;
|
||||
@@ -53,12 +54,11 @@ export class BaseDropdown extends ActionRunner {
|
||||
this.$label.on([EventType.CLICK, EventType.MOUSE_DOWN, GestureEventType.Tap], (e: Event) => {
|
||||
EventHelper.stop(e, true); // prevent default click behaviour to trigger
|
||||
}).on([EventType.MOUSE_DOWN, GestureEventType.Tap], (e: Event) => {
|
||||
// We want to show the context menu on dropdown so that as a user you can press and hold the
|
||||
// mouse button, make a choice of action in the menu and release the mouse to trigger that
|
||||
// action.
|
||||
// Due to some weird bugs though, we delay showing the menu to unwind event stack
|
||||
// (see https://github.com/Microsoft/vscode/issues/27648)
|
||||
setTimeout(() => this.show(), 100);
|
||||
if (e instanceof MouseEvent && e.detail > 1) {
|
||||
return; // prevent multiple clicks to open multiple context menus (https://github.com/Microsoft/vscode/issues/41363)
|
||||
}
|
||||
|
||||
this.show();
|
||||
}).appendTo(this.$el);
|
||||
|
||||
let cleanupFn = labelRenderer(this.$label.getHTMLElement());
|
||||
@@ -165,16 +165,6 @@ export class Dropdown extends BaseDropdown {
|
||||
}
|
||||
}
|
||||
|
||||
export interface IContextMenuDelegate {
|
||||
getAnchor(): HTMLElement | { x: number; y: number; };
|
||||
getActions(): TPromise<IAction[]>;
|
||||
getActionItem?(action: IAction): IActionItem;
|
||||
getActionsContext?(): any;
|
||||
getKeyBinding?(action: IAction): ResolvedKeybinding;
|
||||
getMenuClassName?(): string;
|
||||
onHide?(didCancel: boolean): void;
|
||||
}
|
||||
|
||||
export interface IContextMenuProvider {
|
||||
showContextMenu(delegate: IContextMenuDelegate): void;
|
||||
}
|
||||
@@ -236,11 +226,91 @@ export class DropdownMenu extends BaseDropdown {
|
||||
getActionItem: (action) => this.menuOptions && this.menuOptions.actionItemProvider ? this.menuOptions.actionItemProvider(action) : null,
|
||||
getKeyBinding: (action: IAction) => this.menuOptions && this.menuOptions.getKeyBinding ? this.menuOptions.getKeyBinding(action) : null,
|
||||
getMenuClassName: () => this.menuClassName,
|
||||
onHide: () => this.element.removeClass('active')
|
||||
onHide: () => this.element.removeClass('active'),
|
||||
actionRunner: this.menuOptions ? this.menuOptions.actionRunner : null
|
||||
});
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
export class DropdownMenuActionItem extends BaseActionItem {
|
||||
private menuActionsOrProvider: any;
|
||||
private dropdownMenu: DropdownMenu;
|
||||
private contextMenuProvider: IContextMenuProvider;
|
||||
private actionItemProvider: IActionItemProvider;
|
||||
private keybindings: (action: IAction) => ResolvedKeybinding;
|
||||
private clazz: string;
|
||||
|
||||
constructor(action: IAction, menuActions: IAction[], contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, actionRunner: IActionRunner, keybindings: (action: IAction) => ResolvedKeybinding, clazz: string);
|
||||
constructor(action: IAction, actionProvider: IActionProvider, contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, actionRunner: IActionRunner, keybindings: (action: IAction) => ResolvedKeybinding, clazz: string);
|
||||
constructor(action: IAction, menuActionsOrProvider: any, contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, actionRunner: IActionRunner, keybindings: (action: IAction) => ResolvedKeybinding, clazz: string) {
|
||||
super(null, action);
|
||||
|
||||
this.menuActionsOrProvider = menuActionsOrProvider;
|
||||
this.contextMenuProvider = contextMenuProvider;
|
||||
this.actionItemProvider = actionItemProvider;
|
||||
this.actionRunner = actionRunner;
|
||||
this.keybindings = keybindings;
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
let labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable => {
|
||||
this.builder = $('a.action-label').attr({
|
||||
tabIndex: '0',
|
||||
role: 'button',
|
||||
'aria-haspopup': 'true',
|
||||
title: this._action.label || '',
|
||||
class: this.clazz
|
||||
});
|
||||
|
||||
this.builder.appendTo(el);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
let options: IDropdownMenuOptions = {
|
||||
contextMenuProvider: this.contextMenuProvider,
|
||||
labelRenderer: labelRenderer
|
||||
};
|
||||
|
||||
// Render the DropdownMenu around a simple action to toggle it
|
||||
if (Array.isArray(this.menuActionsOrProvider)) {
|
||||
options.actions = this.menuActionsOrProvider;
|
||||
} else {
|
||||
options.actionProvider = this.menuActionsOrProvider;
|
||||
}
|
||||
|
||||
this.dropdownMenu = new DropdownMenu(container, options);
|
||||
|
||||
this.dropdownMenu.menuOptions = {
|
||||
actionItemProvider: this.actionItemProvider,
|
||||
actionRunner: this.actionRunner,
|
||||
getKeyBinding: this.keybindings,
|
||||
context: this._context
|
||||
};
|
||||
}
|
||||
|
||||
public setActionContext(newContext: any): void {
|
||||
super.setActionContext(newContext);
|
||||
|
||||
if (this.dropdownMenu) {
|
||||
this.dropdownMenu.menuOptions.context = newContext;
|
||||
}
|
||||
}
|
||||
|
||||
public show(): void {
|
||||
if (this.dropdownMenu) {
|
||||
this.dropdownMenu.show();
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.dropdownMenu.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -3,29 +3,29 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.vs .custom-checkbox.monaco-case-sensitive {
|
||||
.vs .monaco-custom-checkbox.monaco-case-sensitive {
|
||||
background: url('case-sensitive.svg') center center no-repeat;
|
||||
}
|
||||
.hc-black .custom-checkbox.monaco-case-sensitive,
|
||||
.hc-black .custom-checkbox.monaco-case-sensitive:hover,
|
||||
.vs-dark .custom-checkbox.monaco-case-sensitive {
|
||||
.hc-black .monaco-custom-checkbox.monaco-case-sensitive,
|
||||
.hc-black .monaco-custom-checkbox.monaco-case-sensitive:hover,
|
||||
.vs-dark .monaco-custom-checkbox.monaco-case-sensitive {
|
||||
background: url('case-sensitive-dark.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs .custom-checkbox.monaco-whole-word {
|
||||
.vs .monaco-custom-checkbox.monaco-whole-word {
|
||||
background: url('whole-word.svg') center center no-repeat;
|
||||
}
|
||||
.hc-black .custom-checkbox.monaco-whole-word,
|
||||
.hc-black .custom-checkbox.monaco-whole-word:hover,
|
||||
.vs-dark .custom-checkbox.monaco-whole-word {
|
||||
.hc-black .monaco-custom-checkbox.monaco-whole-word,
|
||||
.hc-black .monaco-custom-checkbox.monaco-whole-word:hover,
|
||||
.vs-dark .monaco-custom-checkbox.monaco-whole-word {
|
||||
background: url('whole-word-dark.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs .custom-checkbox.monaco-regex {
|
||||
.vs .monaco-custom-checkbox.monaco-regex {
|
||||
background: url('regex.svg') center center no-repeat;
|
||||
}
|
||||
.hc-black .custom-checkbox.monaco-regex,
|
||||
.hc-black .custom-checkbox.monaco-regex:hover,
|
||||
.vs-dark .custom-checkbox.monaco-regex {
|
||||
.hc-black .monaco-custom-checkbox.monaco-regex,
|
||||
.hc-black .monaco-custom-checkbox.monaco-regex:hover,
|
||||
.vs-dark .monaco-custom-checkbox.monaco-regex {
|
||||
background: url('regex-dark.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
@@ -16,13 +16,16 @@ import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export interface IIconLabelCreationOptions {
|
||||
supportHighlights?: boolean;
|
||||
supportDescriptionHighlights?: boolean;
|
||||
}
|
||||
|
||||
export interface IIconLabelOptions {
|
||||
export interface IIconLabelValueOptions {
|
||||
title?: string;
|
||||
descriptionTitle?: string;
|
||||
extraClasses?: string[];
|
||||
italic?: boolean;
|
||||
matches?: IMatch[];
|
||||
descriptionMatches?: IMatch[];
|
||||
}
|
||||
|
||||
class FastLabelNode {
|
||||
@@ -63,7 +66,11 @@ class FastLabelNode {
|
||||
}
|
||||
|
||||
this._title = title;
|
||||
this._element.title = title;
|
||||
if (this._title) {
|
||||
this._element.title = title;
|
||||
} else {
|
||||
this._element.removeAttribute('title');
|
||||
}
|
||||
}
|
||||
|
||||
public set empty(empty: boolean) {
|
||||
@@ -82,21 +89,27 @@ class FastLabelNode {
|
||||
|
||||
export class IconLabel {
|
||||
private domNode: FastLabelNode;
|
||||
private labelDescriptionContainer: FastLabelNode;
|
||||
private labelNode: FastLabelNode | HighlightedLabel;
|
||||
private descriptionNode: FastLabelNode;
|
||||
private descriptionNode: FastLabelNode | HighlightedLabel;
|
||||
private descriptionNodeFactory: () => FastLabelNode | HighlightedLabel;
|
||||
|
||||
constructor(container: HTMLElement, options?: IIconLabelCreationOptions) {
|
||||
this.domNode = new FastLabelNode(dom.append(container, dom.$('.monaco-icon-label')));
|
||||
|
||||
const labelDescriptionContainer = new FastLabelNode(dom.append(this.domNode.element, dom.$('.monaco-icon-label-description-container')));
|
||||
this.labelDescriptionContainer = new FastLabelNode(dom.append(this.domNode.element, dom.$('.monaco-icon-label-description-container')));
|
||||
|
||||
if (options && options.supportHighlights) {
|
||||
this.labelNode = new HighlightedLabel(dom.append(labelDescriptionContainer.element, dom.$('a.label-name')));
|
||||
this.labelNode = new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name')));
|
||||
} else {
|
||||
this.labelNode = new FastLabelNode(dom.append(labelDescriptionContainer.element, dom.$('a.label-name')));
|
||||
this.labelNode = new FastLabelNode(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name')));
|
||||
}
|
||||
|
||||
this.descriptionNode = new FastLabelNode(dom.append(labelDescriptionContainer.element, dom.$('span.label-description')));
|
||||
if (options && options.supportDescriptionHighlights) {
|
||||
this.descriptionNodeFactory = () => new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description')));
|
||||
} else {
|
||||
this.descriptionNodeFactory = () => new FastLabelNode(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description')));
|
||||
}
|
||||
}
|
||||
|
||||
public get element(): HTMLElement {
|
||||
@@ -105,21 +118,11 @@ export class IconLabel {
|
||||
|
||||
public onClick(callback: (event: MouseEvent) => void): IDisposable {
|
||||
return combinedDisposable([
|
||||
dom.addDisposableListener(this.labelElement, dom.EventType.CLICK, (e: MouseEvent) => callback(e)),
|
||||
dom.addDisposableListener(this.descriptionNode.element, dom.EventType.CLICK, (e: MouseEvent) => callback(e))
|
||||
dom.addDisposableListener(this.labelDescriptionContainer.element, dom.EventType.CLICK, (e: MouseEvent) => callback(e)),
|
||||
]);
|
||||
}
|
||||
|
||||
private get labelElement(): HTMLElement {
|
||||
const labelNode = this.labelNode;
|
||||
if (labelNode instanceof HighlightedLabel) {
|
||||
return labelNode.element;
|
||||
}
|
||||
|
||||
return labelNode.element;
|
||||
}
|
||||
|
||||
public setValue(label?: string, description?: string, options?: IIconLabelOptions): void {
|
||||
public setValue(label?: string, description?: string, options?: IIconLabelValueOptions): void {
|
||||
const classes = ['monaco-icon-label'];
|
||||
if (options) {
|
||||
if (options.extraClasses) {
|
||||
@@ -134,21 +137,39 @@ export class IconLabel {
|
||||
this.domNode.className = classes.join(' ');
|
||||
this.domNode.title = options && options.title ? options.title : '';
|
||||
|
||||
const labelNode = this.labelNode;
|
||||
if (labelNode instanceof HighlightedLabel) {
|
||||
labelNode.set(label || '', options ? options.matches : void 0);
|
||||
if (this.labelNode instanceof HighlightedLabel) {
|
||||
this.labelNode.set(label || '', options ? options.matches : void 0);
|
||||
} else {
|
||||
labelNode.textContent = label || '';
|
||||
this.labelNode.textContent = label || '';
|
||||
}
|
||||
|
||||
this.descriptionNode.textContent = description || '';
|
||||
this.descriptionNode.empty = !description;
|
||||
if (description || this.descriptionNode) {
|
||||
if (!this.descriptionNode) {
|
||||
this.descriptionNode = this.descriptionNodeFactory(); // description node is created lazily on demand
|
||||
}
|
||||
|
||||
if (this.descriptionNode instanceof HighlightedLabel) {
|
||||
this.descriptionNode.set(description || '', options ? options.descriptionMatches : void 0);
|
||||
if (options && options.descriptionTitle) {
|
||||
this.descriptionNode.element.title = options.descriptionTitle;
|
||||
} else {
|
||||
this.descriptionNode.element.removeAttribute('title');
|
||||
}
|
||||
} else {
|
||||
this.descriptionNode.textContent = description || '';
|
||||
this.descriptionNode.title = options && options.descriptionTitle ? options.descriptionTitle : '';
|
||||
this.descriptionNode.empty = !description;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.domNode.dispose();
|
||||
this.labelNode.dispose();
|
||||
this.descriptionNode.dispose();
|
||||
|
||||
if (this.descriptionNode) {
|
||||
this.descriptionNode.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,8 +63,8 @@
|
||||
/* make sure selection color wins when a label is being selected */
|
||||
.monaco-tree.focused .selected .monaco-icon-label, /* tree */
|
||||
.monaco-tree.focused .selected .monaco-icon-label::after,
|
||||
.monaco-list:focus .focused.selected .monaco-icon-label, /* list */
|
||||
.monaco-list:focus .focused.selected .monaco-icon-label::after
|
||||
.monaco-list:focus .selected .monaco-icon-label, /* list */
|
||||
.monaco-list:focus .selected .monaco-icon-label::after
|
||||
{
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
line-height: 17px;
|
||||
min-height: 34px;
|
||||
margin-top: -1px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Action bar support */
|
||||
|
||||
@@ -29,7 +29,7 @@ export interface IInputOptions extends IInputBoxStyles {
|
||||
flexibleHeight?: boolean;
|
||||
actions?: IAction[];
|
||||
|
||||
// {{SQL CARBON EDIT}} Canidate for addition to vscode
|
||||
// {{SQL CARBON EDIT}} Candidate for addition to vscode
|
||||
min?: string;
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ export class InputBox extends Widget {
|
||||
|
||||
if (this.options.validationOptions) {
|
||||
this.validation = this.options.validationOptions.validation;
|
||||
// {{SQL CARBON EDIT}} Canidate for addition to vscode
|
||||
// {{SQL CARBON EDIT}} Candidate for addition to vscode
|
||||
this.showValidationMessage = true;
|
||||
}
|
||||
|
||||
@@ -173,8 +173,7 @@ export class InputBox extends Widget {
|
||||
}
|
||||
|
||||
if (this.placeholder) {
|
||||
this.input.setAttribute('placeholder', this.placeholder);
|
||||
this.input.title = this.placeholder;
|
||||
this.setPlaceHolder(this.placeholder);
|
||||
}
|
||||
|
||||
this.oninput(this.input, () => this.onValueChange());
|
||||
@@ -201,7 +200,7 @@ export class InputBox extends Widget {
|
||||
if (this.options.actions) {
|
||||
this.actionbar = this._register(new ActionBar(this.element));
|
||||
this.actionbar.push(this.options.actions, { icon: true, label: false });
|
||||
// {{SQL CARBON EDIT}} Canidate for addition to vscode
|
||||
// {{SQL CARBON EDIT}} Candidate for addition to vscode
|
||||
this.input.style.paddingRight = (this.options.actions.length * 22) + 'px';
|
||||
}
|
||||
|
||||
@@ -219,6 +218,7 @@ export class InputBox extends Widget {
|
||||
public setPlaceHolder(placeHolder: string): void {
|
||||
if (this.input) {
|
||||
this.input.setAttribute('placeholder', placeHolder);
|
||||
this.input.title = placeHolder;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,7 +378,7 @@ export class InputBox extends Widget {
|
||||
}
|
||||
|
||||
private _showMessage(): void {
|
||||
// {{SQL CARBON EDIT}} Canidate for addition to vscode
|
||||
// {{SQL CARBON EDIT}} Candidate for addition to vscode
|
||||
if (!this.contextViewProvider || !this.message || !this.showValidationMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-list {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -17,6 +17,12 @@ export interface IRenderer<TElement, TTemplateData> {
|
||||
disposeTemplate(templateData: TTemplateData): void;
|
||||
}
|
||||
|
||||
export interface IListOpenEvent<T> {
|
||||
elements: T[];
|
||||
indexes: number[];
|
||||
browserEvent?: UIEvent;
|
||||
}
|
||||
|
||||
export interface IListEvent<T> {
|
||||
elements: T[];
|
||||
indexes: number[];
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
import 'vs/css!./list';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { range } from 'vs/base/common/arrays';
|
||||
import { IDelegate, IRenderer, IListEvent } from './list';
|
||||
import { List, IListOptions, IListStyles } from './listWidget';
|
||||
import { IDelegate, IRenderer, IListEvent, IListOpenEvent } from './list';
|
||||
import { List, IListStyles, IListOptions } from './listWidget';
|
||||
import { IPagedModel } from 'vs/base/common/paging';
|
||||
import Event, { mapEvent } from 'vs/base/common/event';
|
||||
|
||||
@@ -67,7 +67,7 @@ export class PagedList<T> {
|
||||
container: HTMLElement,
|
||||
delegate: IDelegate<number>,
|
||||
renderers: IPagedRenderer<T, any>[],
|
||||
options: IListOptions<any> = {} // TODO@Joao: should be IListOptions<T>
|
||||
options: IListOptions<any> = {}
|
||||
) {
|
||||
const pagedRenderers = renderers.map(r => new PagedRenderer<T, ITemplateData<T>>(r, () => this.model));
|
||||
this.list = new List(container, delegate, pagedRenderers, options);
|
||||
@@ -97,6 +97,10 @@ export class PagedList<T> {
|
||||
return mapEvent(this.list.onFocusChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
|
||||
}
|
||||
|
||||
get onOpen(): Event<IListOpenEvent<T>> {
|
||||
return mapEvent(this.list.onOpen, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));
|
||||
}
|
||||
|
||||
get onSelectionChange(): Event<IListEvent<T>> {
|
||||
return mapEvent(this.list.onSelectionChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
|
||||
}
|
||||
@@ -126,8 +130,8 @@ export class PagedList<T> {
|
||||
this.list.scrollTop = scrollTop;
|
||||
}
|
||||
|
||||
open(indexes: number[]): void {
|
||||
this.list.open(indexes);
|
||||
open(indexes: number[], browserEvent?: UIEvent): void {
|
||||
this.list.open(indexes, browserEvent);
|
||||
}
|
||||
|
||||
setFocus(indexes: number[]): void {
|
||||
@@ -166,6 +170,10 @@ export class PagedList<T> {
|
||||
this.list.setSelection(indexes);
|
||||
}
|
||||
|
||||
getSelection(): number[] {
|
||||
return this.list.getSelection();
|
||||
}
|
||||
|
||||
layout(height?: number): void {
|
||||
this.list.layout(height);
|
||||
}
|
||||
|
||||
@@ -52,10 +52,12 @@ interface IItem<T> {
|
||||
|
||||
export interface IListViewOptions {
|
||||
useShadows?: boolean;
|
||||
verticalScrollMode?: ScrollbarVisibility;
|
||||
}
|
||||
|
||||
const DefaultOptions: IListViewOptions = {
|
||||
useShadows: true
|
||||
useShadows: true,
|
||||
verticalScrollMode: ScrollbarVisibility.Auto
|
||||
};
|
||||
|
||||
export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
@@ -106,18 +108,23 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
this.scrollableElement = new ScrollableElement(this.rowsContainer, {
|
||||
alwaysConsumeMouseWheel: true,
|
||||
horizontal: ScrollbarVisibility.Hidden,
|
||||
vertical: ScrollbarVisibility.Auto,
|
||||
vertical: getOrDefault(options, o => o.verticalScrollMode, DefaultOptions.verticalScrollMode),
|
||||
useShadows: getOrDefault(options, o => o.useShadows, DefaultOptions.useShadows)
|
||||
});
|
||||
|
||||
this._domNode.appendChild(this.scrollableElement.getDomNode());
|
||||
container.appendChild(this._domNode);
|
||||
|
||||
this.disposables = [this.rangeMap, this.gesture, this.scrollableElement];
|
||||
this.disposables = [this.rangeMap, this.gesture, this.scrollableElement, this.cache];
|
||||
|
||||
this.scrollableElement.onScroll(this.onScroll, this, this.disposables);
|
||||
domEvent(this.rowsContainer, TouchEventType.Change)(this.onTouchChange, this, this.disposables);
|
||||
|
||||
// Prevent the monaco-scrollable-element from scrolling
|
||||
// https://github.com/Microsoft/vscode/issues/44181
|
||||
domEvent(this.scrollableElement.getDomNode(), 'scroll')
|
||||
(e => (e.target as HTMLElement).scrollTop = 0, null, this.disposables);
|
||||
|
||||
const onDragOver = mapEvent(domEvent(this.rowsContainer, 'dragover'), e => new DragMouseEvent(e));
|
||||
onDragOver(this.onDragOver, this, this.disposables);
|
||||
|
||||
@@ -148,7 +155,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
const removeRange = intersect(previousRenderRange, deleteRange);
|
||||
|
||||
for (let i = removeRange.start; i < removeRange.end; i++) {
|
||||
this.removeItemFromDOM(this.items[i]);
|
||||
this.removeItemFromDOM(i);
|
||||
}
|
||||
|
||||
const previousRestRange: IRange = { start: start + deleteCount, end: this.items.length };
|
||||
@@ -181,19 +188,20 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
const removeRange = removeRanges[r];
|
||||
|
||||
for (let i = removeRange.start; i < removeRange.end; i++) {
|
||||
this.removeItemFromDOM(this.items[i]);
|
||||
this.removeItemFromDOM(i);
|
||||
}
|
||||
}
|
||||
|
||||
const unrenderedRestRanges = previousUnrenderedRestRanges.map(r => shift(r, delta));
|
||||
const elementsRange = { start, end: start + elements.length };
|
||||
const insertRanges = [elementsRange, ...unrenderedRestRanges].map(r => intersect(renderRange, r));
|
||||
const beforeElement = this.getNextToLastElement(insertRanges);
|
||||
|
||||
for (let r = 0; r < insertRanges.length; r++) {
|
||||
const insertRange = insertRanges[r];
|
||||
|
||||
for (let i = insertRange.start; i < insertRange.end; i++) {
|
||||
this.insertItemInDOM(this.items[i], i);
|
||||
this.insertItemInDOM(i, beforeElement);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,16 +260,17 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
const rangesToInsert = relativeComplement(renderRange, previousRenderRange);
|
||||
const rangesToRemove = relativeComplement(previousRenderRange, renderRange);
|
||||
const beforeElement = this.getNextToLastElement(rangesToInsert);
|
||||
|
||||
for (const range of rangesToInsert) {
|
||||
for (let i = range.start; i < range.end; i++) {
|
||||
this.insertItemInDOM(this.items[i], i);
|
||||
this.insertItemInDOM(i, beforeElement);
|
||||
}
|
||||
}
|
||||
|
||||
for (const range of rangesToRemove) {
|
||||
for (let i = range.start; i < range.end; i++) {
|
||||
this.removeItemFromDOM(this.items[i], );
|
||||
this.removeItemFromDOM(i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,28 +288,38 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
// DOM operations
|
||||
|
||||
private insertItemInDOM(item: IItem<T>, index: number): void {
|
||||
private insertItemInDOM(index: number, beforeElement: HTMLElement | null): void {
|
||||
const item = this.items[index];
|
||||
|
||||
if (!item.row) {
|
||||
item.row = this.cache.alloc(item.templateId);
|
||||
}
|
||||
|
||||
if (!item.row.domNode.parentElement) {
|
||||
this.rowsContainer.appendChild(item.row.domNode);
|
||||
if (beforeElement) {
|
||||
this.rowsContainer.insertBefore(item.row.domNode, beforeElement);
|
||||
} else {
|
||||
this.rowsContainer.appendChild(item.row.domNode);
|
||||
}
|
||||
}
|
||||
|
||||
const renderer = this.renderers.get(item.templateId);
|
||||
item.row.domNode.style.top = `${this.elementTop(index)}px`;
|
||||
item.row.domNode.style.height = `${item.size}px`;
|
||||
item.row.domNode.setAttribute('data-index', `${index}`);
|
||||
this.updateItemInDOM(item, index);
|
||||
|
||||
const renderer = this.renderers.get(item.templateId);
|
||||
renderer.renderElement(item.element, index, item.row.templateData);
|
||||
}
|
||||
|
||||
private updateItemInDOM(item: IItem<T>, index: number): void {
|
||||
item.row.domNode.style.top = `${this.elementTop(index)}px`;
|
||||
item.row.domNode.setAttribute('data-index', `${index}`);
|
||||
item.row.domNode.setAttribute('data-last-element', index === this.length - 1 ? 'true' : 'false');
|
||||
item.row.domNode.setAttribute('aria-setsize', `${this.length}`);
|
||||
item.row.domNode.setAttribute('aria-posinset', `${index + 1}`);
|
||||
}
|
||||
|
||||
private removeItemFromDOM(item: IItem<T>): void {
|
||||
private removeItemFromDOM(index: number): void {
|
||||
const item = this.items[index];
|
||||
this.cache.release(item.row);
|
||||
item.row = null;
|
||||
}
|
||||
@@ -448,6 +467,26 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
};
|
||||
}
|
||||
|
||||
private getNextToLastElement(ranges: IRange[]): HTMLElement | null {
|
||||
const lastRange = ranges[ranges.length - 1];
|
||||
|
||||
if (!lastRange) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const nextToLastItem = this.items[lastRange.end];
|
||||
|
||||
if (!nextToLastItem) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!nextToLastItem.row) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return nextToLastItem.row.domNode;
|
||||
}
|
||||
|
||||
// Dispose
|
||||
|
||||
dispose() {
|
||||
|
||||
@@ -15,11 +15,13 @@ import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import Event, { Emitter, EventBufferer, chain, mapEvent, anyEvent } from 'vs/base/common/event';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { IDelegate, IRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent } from './list';
|
||||
import { IDelegate, IRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IListOpenEvent } from './list';
|
||||
import { ListView, IListViewOptions } from './listView';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { clamp } from 'vs/base/common/numbers';
|
||||
|
||||
export interface IIdentityProvider<T> {
|
||||
(element: T): string;
|
||||
@@ -202,32 +204,6 @@ class FocusTrait<T> extends Trait<T> {
|
||||
}
|
||||
}
|
||||
|
||||
class Aria<T> implements IRenderer<T, HTMLElement>, ISpliceable<T> {
|
||||
|
||||
private length = 0;
|
||||
|
||||
get templateId(): string {
|
||||
return 'aria';
|
||||
}
|
||||
|
||||
splice(start: number, deleteCount: number, elements: T[]): void {
|
||||
this.length += elements.length - deleteCount;
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): HTMLElement {
|
||||
return container;
|
||||
}
|
||||
|
||||
renderElement(element: T, index: number, container: HTMLElement): void {
|
||||
container.setAttribute('aria-setsize', `${this.length}`);
|
||||
container.setAttribute('aria-posinset', `${index + 1}`);
|
||||
}
|
||||
|
||||
disposeTemplate(container: HTMLElement): void {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The TraitSpliceable is used as a util class to be able
|
||||
* to preserve traits across splice calls, given an identity
|
||||
@@ -260,6 +236,7 @@ function isInputElement(e: HTMLElement): boolean {
|
||||
class KeyboardController<T> implements IDisposable {
|
||||
|
||||
private disposables: IDisposable[];
|
||||
private openController: IOpenController;
|
||||
|
||||
constructor(
|
||||
private list: List<T>,
|
||||
@@ -269,6 +246,8 @@ class KeyboardController<T> implements IDisposable {
|
||||
const multipleSelectionSupport = !(options.multipleSelectionSupport === false);
|
||||
this.disposables = [];
|
||||
|
||||
this.openController = options.openController || DefaultOpenController;
|
||||
|
||||
const onKeyDown = chain(domEvent(view.domNode, 'keydown'))
|
||||
.filter(e => !isInputElement(e.target as HTMLElement))
|
||||
.map(e => new StandardKeyboardEvent(e));
|
||||
@@ -289,7 +268,10 @@ class KeyboardController<T> implements IDisposable {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.list.setSelection(this.list.getFocus());
|
||||
this.list.open(this.list.getFocus());
|
||||
|
||||
if (this.openController.shouldOpen(e.browserEvent)) {
|
||||
this.list.open(this.list.getFocus(), e.browserEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private onUpArrow(e: StandardKeyboardEvent): void {
|
||||
@@ -343,21 +325,84 @@ class KeyboardController<T> implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
function isSelectionSingleChangeEvent(event: IListMouseEvent<any> | IListTouchEvent<any>): boolean {
|
||||
class DOMFocusController<T> implements IDisposable {
|
||||
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
private list: List<T>,
|
||||
private view: ListView<T>
|
||||
) {
|
||||
this.disposables = [];
|
||||
|
||||
const onKeyDown = chain(domEvent(view.domNode, 'keydown'))
|
||||
.filter(e => !isInputElement(e.target as HTMLElement))
|
||||
.map(e => new StandardKeyboardEvent(e));
|
||||
|
||||
onKeyDown.filter(e => e.keyCode === KeyCode.Tab && !e.ctrlKey && !e.metaKey && !e.shiftKey && !e.altKey)
|
||||
.on(this.onTab, this, this.disposables);
|
||||
}
|
||||
|
||||
private onTab(e: StandardKeyboardEvent): void {
|
||||
if (e.target !== this.view.domNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const focus = this.list.getFocus();
|
||||
|
||||
if (focus.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const focusedDomElement = this.view.domElement(focus[0]);
|
||||
const tabIndexElement = focusedDomElement.querySelector('[tabIndex]');
|
||||
|
||||
if (!tabIndexElement || !(tabIndexElement instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
tabIndexElement.focus();
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
|
||||
export function isSelectionSingleChangeEvent(event: IListMouseEvent<any> | IListTouchEvent<any>): boolean {
|
||||
return platform.isMacintosh ? event.browserEvent.metaKey : event.browserEvent.ctrlKey;
|
||||
}
|
||||
|
||||
function isSelectionRangeChangeEvent(event: IListMouseEvent<any> | IListTouchEvent<any>): boolean {
|
||||
export function isSelectionRangeChangeEvent(event: IListMouseEvent<any> | IListTouchEvent<any>): boolean {
|
||||
return event.browserEvent.shiftKey;
|
||||
}
|
||||
|
||||
function isSelectionChangeEvent(event: IListMouseEvent<any> | IListTouchEvent<any>): boolean {
|
||||
return isSelectionSingleChangeEvent(event) || isSelectionRangeChangeEvent(event);
|
||||
function isMouseRightClick(event: UIEvent): boolean {
|
||||
return event instanceof MouseEvent && event.button === 2;
|
||||
}
|
||||
|
||||
const DefaultMultipleSelectionContoller = {
|
||||
isSelectionSingleChangeEvent,
|
||||
isSelectionRangeChangeEvent
|
||||
};
|
||||
|
||||
const DefaultOpenController = {
|
||||
shouldOpen: (event: UIEvent) => {
|
||||
if (event instanceof MouseEvent) {
|
||||
return !isMouseRightClick(event);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} as IOpenController;
|
||||
|
||||
class MouseController<T> implements IDisposable {
|
||||
|
||||
private multipleSelectionSupport: boolean;
|
||||
private multipleSelectionController: IMultipleSelectionController<T>;
|
||||
private openController: IOpenController;
|
||||
private didJustPressContextMenuKey: boolean = false;
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
@@ -397,7 +442,13 @@ class MouseController<T> implements IDisposable {
|
||||
private view: ListView<T>,
|
||||
private options: IListOptions<T> = {}
|
||||
) {
|
||||
this.multipleSelectionSupport = options.multipleSelectionSupport !== false;
|
||||
this.multipleSelectionSupport = !(options.multipleSelectionSupport === false);
|
||||
|
||||
if (this.multipleSelectionSupport) {
|
||||
this.multipleSelectionController = options.multipleSelectionController || DefaultMultipleSelectionContoller;
|
||||
}
|
||||
|
||||
this.openController = options.openController || DefaultOpenController;
|
||||
|
||||
view.onMouseDown(this.onMouseDown, this, this.disposables);
|
||||
view.onMouseClick(this.onPointer, this, this.disposables);
|
||||
@@ -407,6 +458,26 @@ class MouseController<T> implements IDisposable {
|
||||
Gesture.addTarget(view.domNode);
|
||||
}
|
||||
|
||||
private isSelectionSingleChangeEvent(event: IListMouseEvent<any> | IListTouchEvent<any>): boolean {
|
||||
if (this.multipleSelectionController) {
|
||||
return this.multipleSelectionController.isSelectionSingleChangeEvent(event);
|
||||
}
|
||||
|
||||
return platform.isMacintosh ? event.browserEvent.metaKey : event.browserEvent.ctrlKey;
|
||||
}
|
||||
|
||||
private isSelectionRangeChangeEvent(event: IListMouseEvent<any> | IListTouchEvent<any>): boolean {
|
||||
if (this.multipleSelectionController) {
|
||||
return this.multipleSelectionController.isSelectionRangeChangeEvent(event);
|
||||
}
|
||||
|
||||
return event.browserEvent.shiftKey;
|
||||
}
|
||||
|
||||
private isSelectionChangeEvent(event: IListMouseEvent<any> | IListTouchEvent<any>): boolean {
|
||||
return this.isSelectionSingleChangeEvent(event) || this.isSelectionRangeChangeEvent(event);
|
||||
}
|
||||
|
||||
private onMouseDown(e: IListMouseEvent<T> | IListTouchEvent<T>): void {
|
||||
if (this.options.focusOnMouseDown === false) {
|
||||
e.browserEvent.preventDefault();
|
||||
@@ -416,39 +487,48 @@ class MouseController<T> implements IDisposable {
|
||||
}
|
||||
|
||||
let reference = this.list.getFocus()[0];
|
||||
reference = reference === undefined ? this.list.getSelection()[0] : reference;
|
||||
const selection = this.list.getSelection();
|
||||
reference = reference === undefined ? selection[0] : reference;
|
||||
|
||||
if (this.multipleSelectionSupport && isSelectionRangeChangeEvent(e)) {
|
||||
if (this.multipleSelectionSupport && this.isSelectionRangeChangeEvent(e)) {
|
||||
return this.changeSelection(e, reference);
|
||||
}
|
||||
|
||||
const focus = e.index;
|
||||
this.list.setFocus([focus]);
|
||||
if (selection.every(s => s !== focus)) {
|
||||
this.list.setFocus([focus]);
|
||||
}
|
||||
|
||||
if (this.multipleSelectionSupport && isSelectionChangeEvent(e)) {
|
||||
if (this.multipleSelectionSupport && this.isSelectionChangeEvent(e)) {
|
||||
return this.changeSelection(e, reference);
|
||||
}
|
||||
|
||||
if (this.options.selectOnMouseDown) {
|
||||
if (this.options.selectOnMouseDown && !isMouseRightClick(e.browserEvent)) {
|
||||
this.list.setSelection([focus]);
|
||||
this.list.open([focus]);
|
||||
|
||||
if (this.openController.shouldOpen(e.browserEvent)) {
|
||||
this.list.open([focus], e.browserEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onPointer(e: IListMouseEvent<T>): void {
|
||||
if (this.multipleSelectionSupport && isSelectionChangeEvent(e)) {
|
||||
if (this.multipleSelectionSupport && this.isSelectionChangeEvent(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.options.selectOnMouseDown) {
|
||||
const focus = this.list.getFocus();
|
||||
this.list.setSelection(focus);
|
||||
this.list.open(focus);
|
||||
|
||||
if (this.openController.shouldOpen(e.browserEvent)) {
|
||||
this.list.open(focus, e.browserEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onDoubleClick(e: IListMouseEvent<T>): void {
|
||||
if (this.multipleSelectionSupport && isSelectionChangeEvent(e)) {
|
||||
if (this.multipleSelectionSupport && this.isSelectionChangeEvent(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -460,7 +540,7 @@ class MouseController<T> implements IDisposable {
|
||||
private changeSelection(e: IListMouseEvent<T> | IListTouchEvent<T>, reference: number | undefined): void {
|
||||
const focus = e.index;
|
||||
|
||||
if (isSelectionRangeChangeEvent(e) && reference !== undefined) {
|
||||
if (this.isSelectionRangeChangeEvent(e) && reference !== undefined) {
|
||||
const min = Math.min(reference, focus);
|
||||
const max = Math.max(reference, focus);
|
||||
const rangeSelection = range(min, max + 1);
|
||||
@@ -474,7 +554,7 @@ class MouseController<T> implements IDisposable {
|
||||
const newSelection = disjunction(rangeSelection, relativeComplement(selection, contiguousRange));
|
||||
this.list.setSelection(newSelection);
|
||||
|
||||
} else if (isSelectionSingleChangeEvent(e)) {
|
||||
} else if (this.isSelectionSingleChangeEvent(e)) {
|
||||
const selection = this.list.getSelection();
|
||||
const newSelection = selection.filter(i => i !== focus);
|
||||
|
||||
@@ -491,6 +571,15 @@ class MouseController<T> implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
export interface IMultipleSelectionController<T> {
|
||||
isSelectionSingleChangeEvent(event: IListMouseEvent<T> | IListTouchEvent<T>): boolean;
|
||||
isSelectionRangeChangeEvent(event: IListMouseEvent<T> | IListTouchEvent<T>): boolean;
|
||||
}
|
||||
|
||||
export interface IOpenController {
|
||||
shouldOpen(event: UIEvent): boolean;
|
||||
}
|
||||
|
||||
export interface IListOptions<T> extends IListViewOptions, IListStyles {
|
||||
identityProvider?: IIdentityProvider<T>;
|
||||
ariaLabel?: string;
|
||||
@@ -498,7 +587,10 @@ export interface IListOptions<T> extends IListViewOptions, IListStyles {
|
||||
selectOnMouseDown?: boolean;
|
||||
focusOnMouseDown?: boolean;
|
||||
keyboardSupport?: boolean;
|
||||
verticalScrollMode?: ScrollbarVisibility;
|
||||
multipleSelectionSupport?: boolean;
|
||||
multipleSelectionController?: IMultipleSelectionController<T>;
|
||||
openController?: IOpenController;
|
||||
}
|
||||
|
||||
export interface IListStyles {
|
||||
@@ -660,8 +752,9 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
private eventBufferer = new EventBufferer();
|
||||
private view: ListView<T>;
|
||||
private spliceable: ISpliceable<T>;
|
||||
private disposables: IDisposable[];
|
||||
protected disposables: IDisposable[];
|
||||
private styleElement: HTMLStyleElement;
|
||||
private mouseController: MouseController<T>;
|
||||
|
||||
@memoize get onFocusChange(): Event<IListEvent<T>> {
|
||||
return mapEvent(this.eventBufferer.wrapEvent(this.focus.onChange), e => this.toListEvent(e));
|
||||
@@ -673,9 +766,9 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
readonly onContextMenu: Event<IListContextMenuEvent<T>> = Event.None;
|
||||
|
||||
private _onOpen = new Emitter<number[]>();
|
||||
@memoize get onOpen(): Event<IListEvent<T>> {
|
||||
return mapEvent(this._onOpen.event, indexes => this.toListEvent({ indexes }));
|
||||
private _onOpen = new Emitter<IListOpenEvent<T>>();
|
||||
@memoize get onOpen(): Event<IListOpenEvent<T>> {
|
||||
return this._onOpen.event;
|
||||
}
|
||||
|
||||
private _onPin = new Emitter<number[]>();
|
||||
@@ -709,13 +802,12 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
renderers: IRenderer<T, any>[],
|
||||
options: IListOptions<T> = DefaultOptions
|
||||
) {
|
||||
const aria = new Aria();
|
||||
this.focus = new FocusTrait(i => this.getElementDomId(i));
|
||||
this.selection = new Trait('selected');
|
||||
|
||||
mixin(options, defaultStyles, false);
|
||||
|
||||
renderers = renderers.map(r => new PipelineRenderer(r.templateId, [aria, this.focus.renderer, this.selection.renderer, r]));
|
||||
renderers = renderers.map(r => new PipelineRenderer(r.templateId, [this.focus.renderer, this.selection.renderer, r]));
|
||||
|
||||
this.view = new ListView(container, delegate, renderers, options);
|
||||
this.view.domNode.setAttribute('role', 'tree');
|
||||
@@ -725,7 +817,6 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
this.styleElement = DOM.createStyleSheet(this.view.domNode);
|
||||
|
||||
this.spliceable = new CombinedSpliceable([
|
||||
aria,
|
||||
new TraitSpliceable(this.focus, this.view, options.identityProvider),
|
||||
new TraitSpliceable(this.selection, this.view, options.identityProvider),
|
||||
this.view
|
||||
@@ -736,15 +827,17 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
this.onDidFocus = mapEvent(domEvent(this.view.domNode, 'focus', true), () => null);
|
||||
this.onDidBlur = mapEvent(domEvent(this.view.domNode, 'blur', true), () => null);
|
||||
|
||||
this.disposables.push(new DOMFocusController(this, this.view));
|
||||
|
||||
if (typeof options.keyboardSupport !== 'boolean' || options.keyboardSupport) {
|
||||
const controller = new KeyboardController(this, this.view, options);
|
||||
this.disposables.push(controller);
|
||||
}
|
||||
|
||||
if (typeof options.mouseSupport !== 'boolean' || options.mouseSupport) {
|
||||
const controller = new MouseController(this, this.view, options);
|
||||
this.disposables.push(controller);
|
||||
this.onContextMenu = controller.onContextMenu;
|
||||
this.mouseController = new MouseController(this, this.view, options);
|
||||
this.disposables.push(this.mouseController);
|
||||
this.onContextMenu = this.mouseController.onContextMenu;
|
||||
}
|
||||
|
||||
this.onFocusChange(this._onFocusChange, this, this.disposables);
|
||||
@@ -790,6 +883,12 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
}
|
||||
|
||||
setSelection(indexes: number[]): void {
|
||||
for (const index of indexes) {
|
||||
if (index < 0 || index >= this.length) {
|
||||
throw new Error(`Invalid index ${index}`);
|
||||
}
|
||||
}
|
||||
|
||||
indexes = indexes.sort(numericSort);
|
||||
this.selection.set(indexes);
|
||||
}
|
||||
@@ -820,6 +919,12 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
}
|
||||
|
||||
setFocus(indexes: number[]): void {
|
||||
for (const index of indexes) {
|
||||
if (index < 0 || index >= this.length) {
|
||||
throw new Error(`Invalid index ${index}`);
|
||||
}
|
||||
}
|
||||
|
||||
indexes = indexes.sort(numericSort);
|
||||
this.focus.set(indexes);
|
||||
}
|
||||
@@ -903,17 +1008,18 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
}
|
||||
|
||||
reveal(index: number, relativeTop?: number): void {
|
||||
if (index < 0 || index >= this.length) {
|
||||
throw new Error(`Invalid index ${index}`);
|
||||
}
|
||||
|
||||
const scrollTop = this.view.getScrollTop();
|
||||
const elementTop = this.view.elementTop(index);
|
||||
const elementHeight = this.view.elementHeight(index);
|
||||
|
||||
if (isNumber(relativeTop)) {
|
||||
relativeTop = relativeTop < 0 ? 0 : relativeTop;
|
||||
relativeTop = relativeTop > 1 ? 1 : relativeTop;
|
||||
|
||||
// y = mx + b
|
||||
const m = elementHeight - this.view.renderHeight;
|
||||
this.view.setScrollTop(m * relativeTop + elementTop);
|
||||
this.view.setScrollTop(m * clamp(relativeTop, 0, 1) + elementTop);
|
||||
} else {
|
||||
const viewItemBottom = elementTop + elementHeight;
|
||||
const wrapperBottom = scrollTop + this.view.renderHeight;
|
||||
@@ -926,6 +1032,28 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the relative position of an element rendered in the list.
|
||||
* Returns `null` if the element isn't *entirely* in the visible viewport.
|
||||
*/
|
||||
getRelativeTop(index: number): number | null {
|
||||
if (index < 0 || index >= this.length) {
|
||||
throw new Error(`Invalid index ${index}`);
|
||||
}
|
||||
|
||||
const scrollTop = this.view.getScrollTop();
|
||||
const elementTop = this.view.elementTop(index);
|
||||
const elementHeight = this.view.elementHeight(index);
|
||||
|
||||
if (elementTop < scrollTop || elementTop + elementHeight > scrollTop + this.view.renderHeight) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// y = mx + b
|
||||
const m = elementHeight - this.view.renderHeight;
|
||||
return Math.abs((scrollTop - elementTop) / m);
|
||||
}
|
||||
|
||||
private getElementDomId(index: number): string {
|
||||
return `${this.idPrefix}_${index}`;
|
||||
}
|
||||
@@ -938,11 +1066,23 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
return this.view.domNode;
|
||||
}
|
||||
|
||||
open(indexes: number[]): void {
|
||||
this._onOpen.fire(indexes);
|
||||
open(indexes: number[], browserEvent?: UIEvent): void {
|
||||
for (const index of indexes) {
|
||||
if (index < 0 || index >= this.length) {
|
||||
throw new Error(`Invalid index ${index}`);
|
||||
}
|
||||
}
|
||||
|
||||
this._onOpen.fire({ indexes, elements: indexes.map(i => this.view.element(i)), browserEvent });
|
||||
}
|
||||
|
||||
pin(indexes: number[]): void {
|
||||
for (const index of indexes) {
|
||||
if (index < 0 || index >= this.length) {
|
||||
throw new Error(`Invalid index ${index}`);
|
||||
}
|
||||
}
|
||||
|
||||
this._onPin.fire(indexes);
|
||||
}
|
||||
|
||||
@@ -951,6 +1091,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
if (styles.listFocusBackground) {
|
||||
content.push(`.monaco-list.${this.idPrefix}:focus .monaco-list-row.focused { background-color: ${styles.listFocusBackground}; }`);
|
||||
content.push(`.monaco-list.${this.idPrefix}:focus .monaco-list-row.focused:hover { background-color: ${styles.listFocusBackground}; }`); // overwrite :hover style in this case!
|
||||
}
|
||||
|
||||
if (styles.listFocusForeground) {
|
||||
|
||||
@@ -82,7 +82,7 @@ export class RowCache<T> implements IDisposable {
|
||||
|
||||
this.cache.forEach((cachedRows, templateId) => {
|
||||
for (const cachedRow of cachedRows) {
|
||||
const renderer = this.renderers[templateId];
|
||||
const renderer = this.renderers.get(templateId);
|
||||
renderer.disposeTemplate(cachedRow.templateData);
|
||||
cachedRow.domNode = null;
|
||||
cachedRow.templateData = null;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"version": "3.1.0",
|
||||
"license": "MIT",
|
||||
"licenseDetail": [
|
||||
"The Source EULA (MIT)",
|
||||
"The Source EULA",
|
||||
"",
|
||||
"(c) 2012-2015 GitHub",
|
||||
"",
|
||||
|
||||
@@ -45,7 +45,9 @@ export class ProgressBar {
|
||||
private animationStopToken: ValueCallback;
|
||||
private progressBarBackground: Color;
|
||||
|
||||
constructor(builder: Builder, options?: IProgressBarOptions) {
|
||||
constructor(container: Builder, options?: IProgressBarOptions);
|
||||
constructor(container: HTMLElement, options?: IProgressBarOptions);
|
||||
constructor(container: any, options?: IProgressBarOptions) {
|
||||
this.options = options || Object.create(null);
|
||||
mixin(this.options, defaultOpts, false);
|
||||
|
||||
@@ -54,11 +56,13 @@ export class ProgressBar {
|
||||
|
||||
this.progressBarBackground = this.options.progressBarBackground;
|
||||
|
||||
this.create(builder);
|
||||
this.create(container);
|
||||
}
|
||||
|
||||
private create(parent: Builder): void {
|
||||
parent.div({ 'class': css_progress_container }, (builder) => {
|
||||
private create(container: Builder): void;
|
||||
private create(container: HTMLElement): void;
|
||||
private create(container: any): void {
|
||||
$(container).div({ 'class': css_progress_container }, (builder) => {
|
||||
this.element = builder.clone();
|
||||
|
||||
builder.div({ 'class': css_progress_bit }).on([DOM.EventType.ANIMATION_START, DOM.EventType.ANIMATION_END, DOM.EventType.ANIMATION_ITERATION], (e: Event) => {
|
||||
|
||||
@@ -1,243 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./resourceviewer';
|
||||
import nls = require('vs/nls');
|
||||
import mimes = require('vs/base/common/mime');
|
||||
import URI from 'vs/base/common/uri';
|
||||
import paths = require('vs/base/common/paths');
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { LRUCache } from 'vs/base/common/map';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
interface MapExtToMediaMimes {
|
||||
[index: string]: string;
|
||||
}
|
||||
|
||||
// Known media mimes that we can handle
|
||||
const mapExtToMediaMimes: MapExtToMediaMimes = {
|
||||
'.bmp': 'image/bmp',
|
||||
'.gif': 'image/gif',
|
||||
'.jpg': 'image/jpg',
|
||||
'.jpeg': 'image/jpg',
|
||||
'.jpe': 'image/jpg',
|
||||
'.png': 'image/png',
|
||||
'.tiff': 'image/tiff',
|
||||
'.tif': 'image/tiff',
|
||||
'.ico': 'image/x-icon',
|
||||
'.tga': 'image/x-tga',
|
||||
'.psd': 'image/vnd.adobe.photoshop',
|
||||
'.webp': 'image/webp',
|
||||
'.mid': 'audio/midi',
|
||||
'.midi': 'audio/midi',
|
||||
'.mp4a': 'audio/mp4',
|
||||
'.mpga': 'audio/mpeg',
|
||||
'.mp2': 'audio/mpeg',
|
||||
'.mp2a': 'audio/mpeg',
|
||||
'.mp3': 'audio/mpeg',
|
||||
'.m2a': 'audio/mpeg',
|
||||
'.m3a': 'audio/mpeg',
|
||||
'.oga': 'audio/ogg',
|
||||
'.ogg': 'audio/ogg',
|
||||
'.spx': 'audio/ogg',
|
||||
'.aac': 'audio/x-aac',
|
||||
'.wav': 'audio/x-wav',
|
||||
'.wma': 'audio/x-ms-wma',
|
||||
'.mp4': 'video/mp4',
|
||||
'.mp4v': 'video/mp4',
|
||||
'.mpg4': 'video/mp4',
|
||||
'.mpeg': 'video/mpeg',
|
||||
'.mpg': 'video/mpeg',
|
||||
'.mpe': 'video/mpeg',
|
||||
'.m1v': 'video/mpeg',
|
||||
'.m2v': 'video/mpeg',
|
||||
'.ogv': 'video/ogg',
|
||||
'.qt': 'video/quicktime',
|
||||
'.mov': 'video/quicktime',
|
||||
'.webm': 'video/webm',
|
||||
'.mkv': 'video/x-matroska',
|
||||
'.mk3d': 'video/x-matroska',
|
||||
'.mks': 'video/x-matroska',
|
||||
'.wmv': 'video/x-ms-wmv',
|
||||
'.flv': 'video/x-flv',
|
||||
'.avi': 'video/x-msvideo',
|
||||
'.movie': 'video/x-sgi-movie'
|
||||
};
|
||||
|
||||
export interface IResourceDescriptor {
|
||||
resource: URI;
|
||||
name: string;
|
||||
size: number;
|
||||
etag: string;
|
||||
mime: string;
|
||||
}
|
||||
|
||||
// Chrome is caching images very aggressively and so we use the ETag information to find out if
|
||||
// we need to bypass the cache or not. We could always bypass the cache everytime we show the image
|
||||
// however that has very bad impact on memory consumption because each time the image gets shown,
|
||||
// memory grows (see also https://github.com/electron/electron/issues/6275)
|
||||
const IMAGE_RESOURCE_ETAG_CACHE = new LRUCache<string, { etag: string, src: string }>(100);
|
||||
function imageSrc(descriptor: IResourceDescriptor): string {
|
||||
if (descriptor.resource.scheme === Schemas.data) {
|
||||
return descriptor.resource.toString(true /* skip encoding */);
|
||||
}
|
||||
|
||||
const src = descriptor.resource.toString();
|
||||
|
||||
let cached = IMAGE_RESOURCE_ETAG_CACHE.get(src);
|
||||
if (!cached) {
|
||||
cached = { etag: descriptor.etag, src };
|
||||
IMAGE_RESOURCE_ETAG_CACHE.set(src, cached);
|
||||
}
|
||||
|
||||
if (cached.etag !== descriptor.etag) {
|
||||
cached.etag = descriptor.etag;
|
||||
cached.src = `${src}?${Date.now()}`; // bypass cache with this trick
|
||||
}
|
||||
|
||||
return cached.src;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to actually render the given resource into the provided container. Will adjust scrollbar (if provided) automatically based on loading
|
||||
* progress of the binary resource.
|
||||
*/
|
||||
export class ResourceViewer {
|
||||
|
||||
private static readonly KB = 1024;
|
||||
private static readonly MB = ResourceViewer.KB * ResourceViewer.KB;
|
||||
private static readonly GB = ResourceViewer.MB * ResourceViewer.KB;
|
||||
private static readonly TB = ResourceViewer.GB * ResourceViewer.KB;
|
||||
|
||||
private static readonly MAX_IMAGE_SIZE = ResourceViewer.MB; // showing images inline is memory intense, so we have a limit
|
||||
|
||||
public static show(
|
||||
descriptor: IResourceDescriptor,
|
||||
container: Builder,
|
||||
scrollbar: DomScrollableElement,
|
||||
openExternal: (uri: URI) => void,
|
||||
metadataClb?: (meta: string) => void
|
||||
): void {
|
||||
|
||||
// Ensure CSS class
|
||||
$(container).setClass('monaco-resource-viewer');
|
||||
|
||||
// Lookup media mime if any
|
||||
let mime = descriptor.mime;
|
||||
if (!mime && descriptor.resource.scheme === Schemas.file) {
|
||||
const ext = paths.extname(descriptor.resource.toString());
|
||||
if (ext) {
|
||||
mime = mapExtToMediaMimes[ext.toLowerCase()];
|
||||
}
|
||||
}
|
||||
|
||||
if (!mime) {
|
||||
mime = mimes.MIME_BINARY;
|
||||
}
|
||||
|
||||
// Show Image inline unless they are large
|
||||
if (mime.indexOf('image/') >= 0) {
|
||||
if (ResourceViewer.inlineImage(descriptor)) {
|
||||
$(container)
|
||||
.empty()
|
||||
.addClass('image')
|
||||
.img({ src: imageSrc(descriptor) })
|
||||
.on(DOM.EventType.LOAD, (e, img) => {
|
||||
const imgElement = <HTMLImageElement>img.getHTMLElement();
|
||||
if (imgElement.naturalWidth > imgElement.width || imgElement.naturalHeight > imgElement.height) {
|
||||
$(container).addClass('oversized');
|
||||
|
||||
img.on(DOM.EventType.CLICK, (e, img) => {
|
||||
$(container).toggleClass('full-size');
|
||||
|
||||
scrollbar.scanDomNode();
|
||||
});
|
||||
}
|
||||
|
||||
if (metadataClb) {
|
||||
metadataClb(nls.localize('imgMeta', "{0}x{1} {2}", imgElement.naturalWidth, imgElement.naturalHeight, ResourceViewer.formatSize(descriptor.size)));
|
||||
}
|
||||
|
||||
scrollbar.scanDomNode();
|
||||
});
|
||||
} else {
|
||||
const imageContainer = $(container)
|
||||
.empty()
|
||||
.p({
|
||||
text: nls.localize('largeImageError', "The image is too large to display in the editor. ")
|
||||
});
|
||||
|
||||
if (descriptor.resource.scheme !== Schemas.data) {
|
||||
imageContainer.append($('a', {
|
||||
role: 'button',
|
||||
class: 'open-external',
|
||||
text: nls.localize('resourceOpenExternalButton', "Open image using external program?")
|
||||
}).on(DOM.EventType.CLICK, (e) => {
|
||||
openExternal(descriptor.resource);
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle generic Binary Files
|
||||
else {
|
||||
$(container)
|
||||
.empty()
|
||||
.span({
|
||||
text: nls.localize('nativeBinaryError', "The file will not be displayed in the editor because it is either binary, very large or uses an unsupported text encoding.")
|
||||
});
|
||||
|
||||
if (metadataClb) {
|
||||
metadataClb(ResourceViewer.formatSize(descriptor.size));
|
||||
}
|
||||
|
||||
scrollbar.scanDomNode();
|
||||
}
|
||||
}
|
||||
|
||||
private static inlineImage(descriptor: IResourceDescriptor): boolean {
|
||||
let skipInlineImage: boolean;
|
||||
|
||||
// Data URI
|
||||
if (descriptor.resource.scheme === Schemas.data) {
|
||||
const BASE64_MARKER = 'base64,';
|
||||
const base64MarkerIndex = descriptor.resource.path.indexOf(BASE64_MARKER);
|
||||
const hasData = base64MarkerIndex >= 0 && descriptor.resource.path.substring(base64MarkerIndex + BASE64_MARKER.length).length > 0;
|
||||
|
||||
skipInlineImage = !hasData || descriptor.size > ResourceViewer.MAX_IMAGE_SIZE || descriptor.resource.path.length > ResourceViewer.MAX_IMAGE_SIZE;
|
||||
}
|
||||
|
||||
// File URI
|
||||
else {
|
||||
skipInlineImage = typeof descriptor.size !== 'number' || descriptor.size > ResourceViewer.MAX_IMAGE_SIZE;
|
||||
}
|
||||
|
||||
return !skipInlineImage;
|
||||
}
|
||||
|
||||
private static formatSize(size: number): string {
|
||||
if (size < ResourceViewer.KB) {
|
||||
return nls.localize('sizeB', "{0}B", size);
|
||||
}
|
||||
|
||||
if (size < ResourceViewer.MB) {
|
||||
return nls.localize('sizeKB', "{0}KB", (size / ResourceViewer.KB).toFixed(2));
|
||||
}
|
||||
|
||||
if (size < ResourceViewer.GB) {
|
||||
return nls.localize('sizeMB', "{0}MB", (size / ResourceViewer.MB).toFixed(2));
|
||||
}
|
||||
|
||||
if (size < ResourceViewer.TB) {
|
||||
return nls.localize('sizeGB', "{0}GB", (size / ResourceViewer.GB).toFixed(2));
|
||||
}
|
||||
|
||||
return nls.localize('sizeTB', "{0}TB", (size / ResourceViewer.TB).toFixed(2));
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-resource-viewer:focus {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.monaco-resource-viewer {
|
||||
padding: 5px 0 0 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.monaco-resource-viewer.image {
|
||||
padding: 10px 10px 0 10px;
|
||||
background-position: 0 0, 8px 8px;
|
||||
background-size: 16px 16px;
|
||||
}
|
||||
|
||||
.monaco-resource-viewer.image.full-size {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.vs .monaco-resource-viewer.image {
|
||||
background-image:
|
||||
linear-gradient(45deg, rgb(230, 230, 230) 25%, transparent 25%, transparent 75%, rgb(230, 230, 230) 75%, rgb(230, 230, 230)),
|
||||
linear-gradient(45deg, rgb(230, 230, 230) 25%, transparent 25%, transparent 75%, rgb(230, 230, 230) 75%, rgb(230, 230, 230));
|
||||
}
|
||||
|
||||
.vs-dark .monaco-resource-viewer.image {
|
||||
background-image:
|
||||
linear-gradient(45deg, rgb(20, 20, 20) 25%, transparent 25%, transparent 75%, rgb(20, 20, 20) 75%, rgb(20, 20, 20)),
|
||||
linear-gradient(45deg, rgb(20, 20, 20) 25%, transparent 25%, transparent 75%, rgb(20, 20, 20) 75%, rgb(20, 20, 20));
|
||||
}
|
||||
|
||||
.monaco-resource-viewer img {
|
||||
max-width: 100%;
|
||||
max-height: calc(100% - 10px); /* somehow this prevents scrollbars from showing up */
|
||||
}
|
||||
|
||||
.monaco-resource-viewer.oversized img {
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
||||
.monaco-resource-viewer.full-size img {
|
||||
max-width: initial;
|
||||
max-height: initial;
|
||||
cursor: zoom-out;
|
||||
}
|
||||
|
||||
.monaco-resource-viewer .open-external,
|
||||
.monaco-resource-viewer .open-external:hover {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
@@ -35,6 +35,7 @@ export interface ISashEvent {
|
||||
currentX: number;
|
||||
startY: number;
|
||||
currentY: number;
|
||||
altKey: boolean;
|
||||
}
|
||||
|
||||
export interface ISashOptions {
|
||||
@@ -140,12 +141,14 @@ export class Sash {
|
||||
let mouseDownEvent = new StandardMouseEvent(e);
|
||||
let startX = mouseDownEvent.posx;
|
||||
let startY = mouseDownEvent.posy;
|
||||
const altKey = mouseDownEvent.altKey;
|
||||
|
||||
let startEvent: ISashEvent = {
|
||||
startX: startX,
|
||||
currentX: startX,
|
||||
startY: startY,
|
||||
currentY: startY
|
||||
currentY: startY,
|
||||
altKey
|
||||
};
|
||||
|
||||
this.$e.addClass('active');
|
||||
@@ -162,7 +165,8 @@ export class Sash {
|
||||
startX: startX,
|
||||
currentX: mouseMoveEvent.posx,
|
||||
startY: startY,
|
||||
currentY: mouseMoveEvent.posy
|
||||
currentY: mouseMoveEvent.posy,
|
||||
altKey
|
||||
};
|
||||
|
||||
this._onDidChange.fire(event);
|
||||
@@ -190,12 +194,15 @@ export class Sash {
|
||||
|
||||
let startX = event.pageX;
|
||||
let startY = event.pageY;
|
||||
const altKey = event.altKey;
|
||||
|
||||
|
||||
this._onDidStart.fire({
|
||||
startX: startX,
|
||||
currentX: startX,
|
||||
startY: startY,
|
||||
currentY: startY
|
||||
currentY: startY,
|
||||
altKey
|
||||
});
|
||||
|
||||
listeners.push(DOM.addDisposableListener(this.$e.getHTMLElement(), EventType.Change, (event: GestureEvent) => {
|
||||
@@ -204,7 +211,8 @@ export class Sash {
|
||||
startX: startX,
|
||||
currentX: event.pageX,
|
||||
startY: startY,
|
||||
currentY: event.pageY
|
||||
currentY: event.pageY,
|
||||
altKey
|
||||
});
|
||||
}
|
||||
}));
|
||||
@@ -368,4 +376,4 @@ export class VSash extends Disposable implements IVerticalSashLayoutProvider {
|
||||
this.sash.layout();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
}
|
||||
.monaco-scrollable-element > .invisible {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.monaco-scrollable-element > .invisible.fade {
|
||||
-webkit-transition: opacity 800ms linear;
|
||||
|
||||
@@ -330,7 +330,7 @@ export abstract class AbstractScrollableElement extends Widget {
|
||||
|
||||
// Convert vertical scrolling to horizontal if shift is held, this
|
||||
// is handled at a higher level on Mac
|
||||
const shiftConvert = !Platform.isMacintosh && e.browserEvent.shiftKey;
|
||||
const shiftConvert = !Platform.isMacintosh && e.browserEvent && e.browserEvent.shiftKey;
|
||||
if ((this._options.scrollYToX || shiftConvert) && !deltaX) {
|
||||
deltaX = deltaY;
|
||||
deltaY = 0;
|
||||
|
||||
@@ -7,17 +7,39 @@ import 'vs/css!./selectBox';
|
||||
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { deepClone } from 'vs/base/common/objects';
|
||||
import { deepClone, mixin } from 'vs/base/common/objects';
|
||||
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { IListStyles } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { SelectBoxNative } from 'vs/base/browser/ui/selectBox/selectBoxNative';
|
||||
import { SelectBoxList } from 'vs/base/browser/ui/selectBox/selectBoxCustom';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
|
||||
export interface ISelectBoxStyles {
|
||||
// Public SelectBox interface - Calls routed to appropriate select implementation class
|
||||
|
||||
export interface ISelectBoxDelegate {
|
||||
|
||||
// Public SelectBox Interface
|
||||
readonly onDidSelect: Event<ISelectData>;
|
||||
setOptions(options: string[], selected?: number, disabled?: number): void;
|
||||
select(index: number): void;
|
||||
focus(): void;
|
||||
blur(): void;
|
||||
dispose(): void;
|
||||
|
||||
// Delegated Widget interface
|
||||
render(container: HTMLElement): void;
|
||||
style(styles: ISelectBoxStyles): void;
|
||||
applyStyles(): void;
|
||||
}
|
||||
|
||||
export interface ISelectBoxStyles extends IListStyles {
|
||||
selectBackground?: Color;
|
||||
selectListBackground?: Color;
|
||||
selectForeground?: Color;
|
||||
selectBorder?: Color;
|
||||
focusBorder?: Color;
|
||||
}
|
||||
|
||||
export const defaultStyles = {
|
||||
@@ -31,114 +53,75 @@ export interface ISelectData {
|
||||
index: number;
|
||||
}
|
||||
|
||||
export class SelectBox extends Widget {
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
protected selectElement: HTMLSelectElement;
|
||||
export class SelectBox extends Widget implements ISelectBoxDelegate {
|
||||
protected options: string[];
|
||||
private selected: number;
|
||||
private _onDidSelect: Emitter<ISelectData>;
|
||||
private toDispose: IDisposable[];
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
protected selectElement: HTMLSelectElement;
|
||||
protected selectBackground: Color;
|
||||
protected selectForeground: Color;
|
||||
protected selectBorder: Color;
|
||||
private toDispose: IDisposable[];
|
||||
|
||||
constructor(options: string[], selected: number, styles: ISelectBoxStyles = deepClone(defaultStyles)) {
|
||||
private styles: ISelectBoxStyles;
|
||||
private selectBoxDelegate: ISelectBoxDelegate;
|
||||
|
||||
constructor(options: string[], selected: number, contextViewProvider: IContextViewProvider, styles: ISelectBoxStyles = deepClone(defaultStyles)) {
|
||||
super();
|
||||
|
||||
this.selectElement = document.createElement('select');
|
||||
this.selectElement.className = 'select-box';
|
||||
|
||||
this.setOptions(options, selected);
|
||||
this.toDispose = [];
|
||||
this._onDidSelect = new Emitter<ISelectData>();
|
||||
|
||||
this.selectBackground = styles.selectBackground;
|
||||
this.selectForeground = styles.selectForeground;
|
||||
this.selectBorder = styles.selectBorder;
|
||||
mixin(this.styles, defaultStyles, false);
|
||||
|
||||
this.toDispose.push(dom.addStandardDisposableListener(this.selectElement, 'change', (e) => {
|
||||
this.selectElement.title = e.target.value;
|
||||
this._onDidSelect.fire({
|
||||
index: e.target.selectedIndex,
|
||||
selected: e.target.value
|
||||
});
|
||||
}));
|
||||
this.toDispose.push(dom.addStandardDisposableListener(this.selectElement, 'keydown', (e) => {
|
||||
if (e.equals(KeyCode.Space) || e.equals(KeyCode.Enter)) {
|
||||
// Space is used to expand select box, do not propagate it (prevent action bar action run)
|
||||
e.stopPropagation();
|
||||
}
|
||||
}));
|
||||
// Instantiate select implementation based on platform
|
||||
if (isMacintosh) {
|
||||
this.selectBoxDelegate = new SelectBoxNative(options, selected, styles);
|
||||
} else {
|
||||
this.selectBoxDelegate = new SelectBoxList(options, selected, contextViewProvider, styles);
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
this.selectElement = (<any>this.selectBoxDelegate).selectElement;
|
||||
|
||||
this.toDispose.push(this.selectBoxDelegate);
|
||||
}
|
||||
|
||||
// Public SelectBox Methods - routed through delegate interface
|
||||
|
||||
public get onDidSelect(): Event<ISelectData> {
|
||||
return this._onDidSelect.event;
|
||||
return this.selectBoxDelegate.onDidSelect;
|
||||
}
|
||||
|
||||
public setOptions(options: string[], selected?: number, disabled?: number): void {
|
||||
if (!this.options || !arrays.equals(this.options, options)) {
|
||||
this.options = options;
|
||||
|
||||
this.selectElement.options.length = 0;
|
||||
let i = 0;
|
||||
this.options.forEach((option) => {
|
||||
this.selectElement.add(this.createOption(option, disabled === i++));
|
||||
});
|
||||
}
|
||||
this.select(selected);
|
||||
this.selectBoxDelegate.setOptions(options, selected, disabled);
|
||||
}
|
||||
|
||||
public select(index: number): void {
|
||||
if (index >= 0 && index < this.options.length) {
|
||||
this.selected = index;
|
||||
} else if (this.selected < 0) {
|
||||
this.selected = 0;
|
||||
}
|
||||
|
||||
this.selectElement.selectedIndex = this.selected;
|
||||
this.selectElement.title = this.options[this.selected];
|
||||
this.selectBoxDelegate.select(index);
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
if (this.selectElement) {
|
||||
this.selectElement.focus();
|
||||
}
|
||||
this.selectBoxDelegate.focus();
|
||||
}
|
||||
|
||||
public blur(): void {
|
||||
if (this.selectElement) {
|
||||
this.selectElement.blur();
|
||||
}
|
||||
this.selectBoxDelegate.blur();
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
dom.addClass(container, 'select-container');
|
||||
container.appendChild(this.selectElement);
|
||||
this.setOptions(this.options, this.selected);
|
||||
// Public Widget Methods - routed through delegate interface
|
||||
|
||||
this.applyStyles();
|
||||
public render(container: HTMLElement): void {
|
||||
this.selectBoxDelegate.render(container);
|
||||
}
|
||||
|
||||
public style(styles: ISelectBoxStyles): void {
|
||||
this.selectBackground = styles.selectBackground;
|
||||
this.selectForeground = styles.selectForeground;
|
||||
this.selectBorder = styles.selectBorder;
|
||||
|
||||
this.applyStyles();
|
||||
this.selectBoxDelegate.style(styles);
|
||||
}
|
||||
|
||||
protected applyStyles(): void {
|
||||
if (this.selectElement) {
|
||||
const background = this.selectBackground ? this.selectBackground.toString() : null;
|
||||
const foreground = this.selectForeground ? this.selectForeground.toString() : null;
|
||||
const border = this.selectBorder ? this.selectBorder.toString() : null;
|
||||
|
||||
this.selectElement.style.backgroundColor = background;
|
||||
this.selectElement.style.color = foreground;
|
||||
this.selectElement.style.borderColor = border;
|
||||
}
|
||||
public applyStyles(): void {
|
||||
this.selectBoxDelegate.applyStyles();
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
@@ -155,4 +138,4 @@ export class SelectBox extends Widget {
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
63
src/vs/base/browser/ui/selectBox/selectBoxCustom.css
Normal file
63
src/vs/base/browser/ui/selectBox/selectBoxCustom.css
Normal file
@@ -0,0 +1,63 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* Require .monaco-shell for ContextView dropdown */
|
||||
|
||||
.monaco-shell .select-box-dropdown-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-shell .select-box-dropdown-container.visible {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: left;
|
||||
width: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container {
|
||||
flex: 0 0 auto;
|
||||
align-self: flex-start;
|
||||
padding-bottom: 1px;
|
||||
padding-top: 1px;
|
||||
padding-left: 1px;
|
||||
padding-right: 1px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
-webkit-box-sizing: border-box;
|
||||
-o-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.monaco-shell.hc-black .select-box-dropdown-container > .select-box-dropdown-list-container {
|
||||
padding-bottom: 4px;
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row > .option-text {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
padding-left: 3.5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.monaco-shell .select-box-dropdown-container > .select-box-dropdown-container-width-control {
|
||||
flex: 1 1 auto;
|
||||
align-self: flex-start;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.monaco-shell .select-box-dropdown-container > .select-box-dropdown-container-width-control > .width-control-div {
|
||||
overflow: hidden;
|
||||
max-height: 0px;
|
||||
}
|
||||
|
||||
.monaco-shell .select-box-dropdown-container > .select-box-dropdown-container-width-control > .width-control-div > .option-text-width-control {
|
||||
padding-left: 4px;
|
||||
padding-right: 8px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
698
src/vs/base/browser/ui/selectBox/selectBoxCustom.ts
Normal file
698
src/vs/base/browser/ui/selectBox/selectBoxCustom.ts
Normal file
@@ -0,0 +1,698 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!./selectBoxCustom';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import Event, { Emitter, chain } from 'vs/base/common/event';
|
||||
import { KeyCode, KeyCodeUtils } from 'vs/base/common/keyCodes';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { List } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IDelegate, IRenderer } from 'vs/base/browser/ui/list/list';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { ISelectBoxDelegate, ISelectBoxStyles, ISelectData } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
const SELECT_OPTION_ENTRY_TEMPLATE_ID = 'selectOption.entry.template';
|
||||
|
||||
export interface ISelectOptionItem {
|
||||
optionText: string;
|
||||
optionDisabled: boolean;
|
||||
}
|
||||
|
||||
interface ISelectListTemplateData {
|
||||
root: HTMLElement;
|
||||
optionText: HTMLElement;
|
||||
disposables: IDisposable[];
|
||||
}
|
||||
|
||||
class SelectListRenderer implements IRenderer<ISelectOptionItem, ISelectListTemplateData> {
|
||||
|
||||
get templateId(): string { return SELECT_OPTION_ENTRY_TEMPLATE_ID; }
|
||||
|
||||
constructor() { }
|
||||
|
||||
renderTemplate(container: HTMLElement): any {
|
||||
const data = <ISelectListTemplateData>Object.create(null);
|
||||
data.disposables = [];
|
||||
data.root = container;
|
||||
data.optionText = dom.append(container, $('.option-text'));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
renderElement(element: ISelectOptionItem, index: number, templateData: ISelectListTemplateData): void {
|
||||
const data = <ISelectListTemplateData>templateData;
|
||||
const optionText = (<ISelectOptionItem>element).optionText;
|
||||
const optionDisabled = (<ISelectOptionItem>element).optionDisabled;
|
||||
|
||||
data.optionText.textContent = optionText;
|
||||
data.root.setAttribute('aria-label', nls.localize('selectAriaOption', "{0}", optionText));
|
||||
|
||||
// pseudo-select disabled option
|
||||
if (optionDisabled) {
|
||||
dom.addClass((<HTMLElement>data.root), 'option-disabled');
|
||||
}
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: ISelectListTemplateData): void {
|
||||
templateData.disposables = dispose(templateData.disposables);
|
||||
}
|
||||
}
|
||||
|
||||
export class SelectBoxList implements ISelectBoxDelegate, IDelegate<ISelectOptionItem> {
|
||||
|
||||
private static SELECT_DROPDOWN_BOTTOM_MARGIN = 10;
|
||||
|
||||
private _isVisible: boolean;
|
||||
// {{SQL CARBON EDIT}}
|
||||
public selectElement: HTMLSelectElement;
|
||||
private options: string[];
|
||||
private selected: number;
|
||||
private disabledOptionIndex: number;
|
||||
private _onDidSelect: Emitter<ISelectData>;
|
||||
private toDispose: IDisposable[];
|
||||
private styles: ISelectBoxStyles;
|
||||
private listRenderer: SelectListRenderer;
|
||||
private contextViewProvider: IContextViewProvider;
|
||||
private selectDropDownContainer: HTMLElement;
|
||||
private styleElement: HTMLStyleElement;
|
||||
private selectList: List<ISelectOptionItem>;
|
||||
private selectDropDownListContainer: HTMLElement;
|
||||
private widthControlElement: HTMLElement;
|
||||
private _currentSelection: number;
|
||||
|
||||
constructor(options: string[], selected: number, contextViewProvider: IContextViewProvider, styles: ISelectBoxStyles) {
|
||||
|
||||
this.toDispose = [];
|
||||
this._isVisible = false;
|
||||
|
||||
this.selectElement = document.createElement('select');
|
||||
this.selectElement.className = 'select-box';
|
||||
|
||||
this._onDidSelect = new Emitter<ISelectData>();
|
||||
this.styles = styles;
|
||||
|
||||
this.registerListeners();
|
||||
this.constructSelectDropDown(contextViewProvider);
|
||||
|
||||
this.setOptions(options, selected);
|
||||
}
|
||||
|
||||
// IDelegate - List renderer
|
||||
|
||||
getHeight(): number {
|
||||
return 18;
|
||||
}
|
||||
|
||||
getTemplateId(): string {
|
||||
return SELECT_OPTION_ENTRY_TEMPLATE_ID;
|
||||
}
|
||||
|
||||
private constructSelectDropDown(contextViewProvider: IContextViewProvider) {
|
||||
|
||||
// SetUp ContextView container to hold select Dropdown
|
||||
this.contextViewProvider = contextViewProvider;
|
||||
this.selectDropDownContainer = dom.$('.select-box-dropdown-container');
|
||||
|
||||
// Setup list for drop-down select
|
||||
this.createSelectList(this.selectDropDownContainer);
|
||||
|
||||
// Create span flex box item/div we can measure and control
|
||||
let widthControlOuterDiv = dom.append(this.selectDropDownContainer, $('.select-box-dropdown-container-width-control'));
|
||||
let widthControlInnerDiv = dom.append(widthControlOuterDiv, $('.width-control-div'));
|
||||
this.widthControlElement = document.createElement('span');
|
||||
this.widthControlElement.className = 'option-text-width-control';
|
||||
dom.append(widthControlInnerDiv, this.widthControlElement);
|
||||
|
||||
// Inline stylesheet for themes
|
||||
this.styleElement = dom.createStyleSheet(this.selectDropDownContainer);
|
||||
}
|
||||
|
||||
private registerListeners() {
|
||||
|
||||
// Parent native select keyboard listeners
|
||||
|
||||
this.toDispose.push(dom.addStandardDisposableListener(this.selectElement, 'change', (e) => {
|
||||
this.selectElement.title = e.target.value;
|
||||
this._onDidSelect.fire({
|
||||
index: e.target.selectedIndex,
|
||||
selected: e.target.value
|
||||
});
|
||||
}));
|
||||
|
||||
// Have to implement both keyboard and mouse controllers to handle disabled options
|
||||
// Intercept mouse events to override normal select actions on parents
|
||||
|
||||
this.toDispose.push(dom.addDisposableListener(this.selectElement, dom.EventType.CLICK, (e) => {
|
||||
dom.EventHelper.stop(e);
|
||||
|
||||
if (this._isVisible) {
|
||||
this.hideSelectDropDown(true);
|
||||
} else {
|
||||
this.showSelectDropDown();
|
||||
}
|
||||
}));
|
||||
|
||||
this.toDispose.push(dom.addDisposableListener(this.selectElement, dom.EventType.MOUSE_DOWN, (e) => {
|
||||
dom.EventHelper.stop(e);
|
||||
}));
|
||||
|
||||
// Intercept keyboard handling
|
||||
|
||||
this.toDispose.push(dom.addDisposableListener(this.selectElement, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
let showDropDown = false;
|
||||
|
||||
// Create and drop down select list on keyboard select
|
||||
if (isMacintosh) {
|
||||
if (event.keyCode === KeyCode.DownArrow || event.keyCode === KeyCode.UpArrow || event.keyCode === KeyCode.Space || event.keyCode === KeyCode.Enter) {
|
||||
showDropDown = true;
|
||||
}
|
||||
} else {
|
||||
if (event.keyCode === KeyCode.DownArrow && event.altKey || event.keyCode === KeyCode.UpArrow && event.altKey || event.keyCode === KeyCode.Space || event.keyCode === KeyCode.Enter) {
|
||||
showDropDown = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (showDropDown) {
|
||||
this.showSelectDropDown();
|
||||
dom.EventHelper.stop(e);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public get onDidSelect(): Event<ISelectData> {
|
||||
return this._onDidSelect.event;
|
||||
}
|
||||
|
||||
public setOptions(options: string[], selected?: number, disabled?: number): void {
|
||||
|
||||
if (!this.options || !arrays.equals(this.options, options)) {
|
||||
this.options = options;
|
||||
this.selectElement.options.length = 0;
|
||||
|
||||
let i = 0;
|
||||
this.options.forEach((option) => {
|
||||
this.selectElement.add(this.createOption(option, i, disabled === i++));
|
||||
});
|
||||
|
||||
// Mirror options in drop-down
|
||||
// Populate select list for non-native select mode
|
||||
if (this.selectList && !!this.options) {
|
||||
let listEntries: ISelectOptionItem[];
|
||||
|
||||
listEntries = [];
|
||||
if (disabled !== undefined) {
|
||||
this.disabledOptionIndex = disabled;
|
||||
}
|
||||
for (let index = 0; index < this.options.length; index++) {
|
||||
const element = this.options[index];
|
||||
let optionDisabled: boolean;
|
||||
index === this.disabledOptionIndex ? optionDisabled = true : optionDisabled = false;
|
||||
listEntries.push({ optionText: element, optionDisabled: optionDisabled });
|
||||
}
|
||||
|
||||
this.selectList.splice(0, this.selectList.length, listEntries);
|
||||
}
|
||||
}
|
||||
|
||||
if (selected !== undefined) {
|
||||
this.select(selected);
|
||||
}
|
||||
}
|
||||
|
||||
public select(index: number): void {
|
||||
|
||||
if (index >= 0 && index < this.options.length) {
|
||||
this.selected = index;
|
||||
} else if (this.selected < 0) {
|
||||
this.selected = 0;
|
||||
}
|
||||
|
||||
this.selectElement.selectedIndex = this.selected;
|
||||
this.selectElement.title = this.options[this.selected];
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
if (this.selectElement) {
|
||||
this.selectElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
public blur(): void {
|
||||
if (this.selectElement) {
|
||||
this.selectElement.blur();
|
||||
}
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
dom.addClass(container, 'select-container');
|
||||
container.appendChild(this.selectElement);
|
||||
this.setOptions(this.options, this.selected);
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
public style(styles: ISelectBoxStyles): void {
|
||||
|
||||
const content: string[] = [];
|
||||
|
||||
this.styles = styles;
|
||||
|
||||
// Style non-native select mode
|
||||
|
||||
if (this.styles.listFocusBackground) {
|
||||
content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.focused { background-color: ${this.styles.listFocusBackground} !important; }`);
|
||||
}
|
||||
|
||||
if (this.styles.listFocusForeground) {
|
||||
content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.focused:not(:hover) { color: ${this.styles.listFocusForeground} !important; }`);
|
||||
}
|
||||
|
||||
// Hover foreground - ignore for disabled options
|
||||
if (this.styles.listHoverForeground) {
|
||||
content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row:hover { color: ${this.styles.listHoverForeground} !important; }`);
|
||||
content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.option-disabled:hover { background-color: ${this.styles.listActiveSelectionForeground} !important; }`);
|
||||
}
|
||||
|
||||
// Hover background - ignore for disabled options
|
||||
if (this.styles.listHoverBackground) {
|
||||
content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row:not(.option-disabled):not(.focused):hover { background-color: ${this.styles.listHoverBackground} !important; }`);
|
||||
content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.option-disabled:hover { background-color: ${this.styles.selectBackground} !important; }`);
|
||||
}
|
||||
|
||||
// Match quickOpen outline styles - ignore for disabled options
|
||||
if (this.styles.listFocusOutline) {
|
||||
content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.focused { outline: 1.6px dotted ${this.styles.listFocusOutline} !important; outline-offset: -1.6px !important; }`);
|
||||
}
|
||||
|
||||
if (this.styles.listHoverOutline) {
|
||||
content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row:hover:not(.focused) { outline: 1.6px dashed ${this.styles.listHoverOutline} !important; outline-offset: -1.6px !important; }`);
|
||||
content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.option-disabled:hover { outline: none !important; }`);
|
||||
}
|
||||
|
||||
this.styleElement.innerHTML = content.join('\n');
|
||||
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
public applyStyles(): void {
|
||||
|
||||
// Style parent select
|
||||
|
||||
let background = null;
|
||||
|
||||
if (this.selectElement) {
|
||||
background = this.styles.selectBackground ? this.styles.selectBackground.toString() : null;
|
||||
const foreground = this.styles.selectForeground ? this.styles.selectForeground.toString() : null;
|
||||
const border = this.styles.selectBorder ? this.styles.selectBorder.toString() : null;
|
||||
|
||||
this.selectElement.style.backgroundColor = background;
|
||||
this.selectElement.style.color = foreground;
|
||||
this.selectElement.style.borderColor = border;
|
||||
}
|
||||
|
||||
// Style drop down select list (non-native mode only)
|
||||
|
||||
if (this.selectList) {
|
||||
this.selectList.style({});
|
||||
|
||||
let listBackground = this.styles.selectListBackground ? this.styles.selectListBackground.toString() : background;
|
||||
this.selectDropDownListContainer.style.backgroundColor = listBackground;
|
||||
const optionsBorder = this.styles.focusBorder ? this.styles.focusBorder.toString() : null;
|
||||
this.selectDropDownContainer.style.outlineColor = optionsBorder;
|
||||
this.selectDropDownContainer.style.outlineOffset = '-1px';
|
||||
}
|
||||
}
|
||||
|
||||
private createOption(value: string, index: number, disabled?: boolean): HTMLOptionElement {
|
||||
let option = document.createElement('option');
|
||||
option.value = value;
|
||||
option.text = value;
|
||||
option.disabled = disabled;
|
||||
|
||||
return option;
|
||||
}
|
||||
|
||||
// Non-native select list handling
|
||||
// ContextView dropdown methods
|
||||
|
||||
private showSelectDropDown() {
|
||||
if (!this.contextViewProvider || this._isVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isVisible = true;
|
||||
this.cloneElementFont(this.selectElement, this.selectDropDownContainer);
|
||||
this.contextViewProvider.showContextView({
|
||||
getAnchor: () => this.selectElement,
|
||||
render: (container: HTMLElement) => this.renderSelectDropDown(container),
|
||||
layout: () => this.layoutSelectDropDown(),
|
||||
onHide: () => {
|
||||
dom.toggleClass(this.selectDropDownContainer, 'visible', false);
|
||||
dom.toggleClass(this.selectElement, 'synthetic-focus', false);
|
||||
}
|
||||
});
|
||||
this._currentSelection = this.selected;
|
||||
}
|
||||
|
||||
private hideSelectDropDown(focusSelect: boolean) {
|
||||
if (!this.contextViewProvider || !this._isVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isVisible = false;
|
||||
|
||||
if (focusSelect) {
|
||||
this.selectElement.focus();
|
||||
}
|
||||
this.contextViewProvider.hideContextView();
|
||||
}
|
||||
|
||||
private renderSelectDropDown(container: HTMLElement): IDisposable {
|
||||
dom.append(container, this.selectDropDownContainer);
|
||||
this.layoutSelectDropDown();
|
||||
return null;
|
||||
}
|
||||
|
||||
private layoutSelectDropDown() {
|
||||
|
||||
// Layout ContextView drop down select list and container
|
||||
// Have to manage our vertical overflow, sizing
|
||||
// Need to be visible to measure
|
||||
|
||||
dom.toggleClass(this.selectDropDownContainer, 'visible', true);
|
||||
|
||||
const selectWidth = dom.getTotalWidth(this.selectElement);
|
||||
const selectPosition = dom.getDomNodePagePosition(this.selectElement);
|
||||
|
||||
// Set container height to max from select bottom to margin above status bar
|
||||
const statusBarHeight = dom.getTotalHeight(document.getElementById('workbench.parts.statusbar'));
|
||||
const maxSelectDropDownHeight = (window.innerHeight - selectPosition.top - selectPosition.height - statusBarHeight - SelectBoxList.SELECT_DROPDOWN_BOTTOM_MARGIN);
|
||||
|
||||
// SetUp list dimensions and layout - account for container padding
|
||||
if (this.selectList) {
|
||||
this.selectList.layout();
|
||||
let listHeight = this.selectList.contentHeight;
|
||||
const listContainerHeight = dom.getTotalHeight(this.selectDropDownListContainer);
|
||||
const totalVerticalListPadding = listContainerHeight - listHeight;
|
||||
|
||||
// Always show complete list items - never more than Max available vertical height
|
||||
if (listContainerHeight > maxSelectDropDownHeight) {
|
||||
listHeight = ((Math.floor((maxSelectDropDownHeight - totalVerticalListPadding) / this.getHeight())) * this.getHeight());
|
||||
}
|
||||
|
||||
this.selectList.layout(listHeight);
|
||||
this.selectList.domFocus();
|
||||
|
||||
// Finally set focus on selected item
|
||||
this.selectList.setFocus([this.selected]);
|
||||
this.selectList.reveal(this.selectList.getFocus()[0]);
|
||||
|
||||
// Set final container height after adjustments
|
||||
this.selectDropDownContainer.style.height = (listHeight + totalVerticalListPadding) + 'px';
|
||||
|
||||
// Determine optimal width - min(longest option), opt(parent select), max(ContextView controlled)
|
||||
const selectMinWidth = this.setWidthControlElement(this.widthControlElement);
|
||||
const selectOptimalWidth = Math.max(selectMinWidth, Math.round(selectWidth)).toString() + 'px';
|
||||
|
||||
this.selectDropDownContainer.style.minWidth = selectOptimalWidth;
|
||||
|
||||
// Maintain focus outline on parent select as well as list container - tabindex for focus
|
||||
this.selectDropDownListContainer.setAttribute('tabindex', '0');
|
||||
dom.toggleClass(this.selectElement, 'synthetic-focus', true);
|
||||
dom.toggleClass(this.selectDropDownContainer, 'synthetic-focus', true);
|
||||
}
|
||||
}
|
||||
|
||||
private setWidthControlElement(container: HTMLElement): number {
|
||||
let elementWidth = 0;
|
||||
|
||||
if (container && !!this.options) {
|
||||
let longest = 0;
|
||||
|
||||
for (let index = 0; index < this.options.length; index++) {
|
||||
if (this.options[index].length > this.options[longest].length) {
|
||||
longest = index;
|
||||
}
|
||||
}
|
||||
|
||||
container.innerHTML = this.options[longest];
|
||||
elementWidth = dom.getTotalWidth(container);
|
||||
}
|
||||
|
||||
return elementWidth;
|
||||
}
|
||||
|
||||
private cloneElementFont(source: HTMLElement, target: HTMLElement) {
|
||||
const fontSize = window.getComputedStyle(source, null).getPropertyValue('font-size');
|
||||
const fontFamily = window.getComputedStyle(source, null).getPropertyValue('font-family');
|
||||
target.style.fontFamily = fontFamily;
|
||||
target.style.fontSize = fontSize;
|
||||
}
|
||||
|
||||
private createSelectList(parent: HTMLElement): void {
|
||||
|
||||
// SetUp container for list
|
||||
this.selectDropDownListContainer = dom.append(parent, $('.select-box-dropdown-list-container'));
|
||||
|
||||
this.listRenderer = new SelectListRenderer();
|
||||
|
||||
this.selectList = new List(this.selectDropDownListContainer, this, [this.listRenderer], {
|
||||
useShadows: false,
|
||||
selectOnMouseDown: false,
|
||||
verticalScrollMode: ScrollbarVisibility.Visible,
|
||||
keyboardSupport: false,
|
||||
mouseSupport: false
|
||||
});
|
||||
|
||||
// SetUp list keyboard controller - control navigation, disabled items, focus
|
||||
const onSelectDropDownKeyDown = chain(domEvent(this.selectDropDownListContainer, 'keydown'))
|
||||
.filter(() => this.selectList.length > 0)
|
||||
.map(e => new StandardKeyboardEvent(e));
|
||||
|
||||
onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.Enter).on(e => this.onEnter(e), this, this.toDispose);
|
||||
onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.Escape).on(e => this.onEscape(e), this, this.toDispose);
|
||||
onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.UpArrow).on(this.onUpArrow, this, this.toDispose);
|
||||
onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.DownArrow).on(this.onDownArrow, this, this.toDispose);
|
||||
onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.PageDown).on(this.onPageDown, this, this.toDispose);
|
||||
onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.PageUp).on(this.onPageUp, this, this.toDispose);
|
||||
onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.Home).on(this.onHome, this, this.toDispose);
|
||||
onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.End).on(this.onEnd, this, this.toDispose);
|
||||
onSelectDropDownKeyDown.filter(e => (e.keyCode >= KeyCode.KEY_0 && e.keyCode <= KeyCode.KEY_Z) || (e.keyCode >= KeyCode.US_SEMICOLON && e.keyCode <= KeyCode.NUMPAD_DIVIDE)).on(this.onCharacter, this, this.toDispose);
|
||||
|
||||
// SetUp list mouse controller - control navigation, disabled items, focus
|
||||
|
||||
chain(domEvent(this.selectList.getHTMLElement(), 'mouseup'))
|
||||
.filter(() => this.selectList.length > 0)
|
||||
.on(e => this.onMouseUp(e), this, this.toDispose);
|
||||
|
||||
this.toDispose.push(this.selectList.onDidBlur(e => this.onListBlur()));
|
||||
}
|
||||
|
||||
// List methods
|
||||
|
||||
// List mouse controller - active exit, select option, fire onDidSelect, return focus to parent select
|
||||
private onMouseUp(e: MouseEvent): void {
|
||||
|
||||
// Check our mouse event is on an option (not scrollbar)
|
||||
if (!e.toElement.classList.contains('option-text')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const listRowElement = e.toElement.parentElement;
|
||||
const index = Number(listRowElement.getAttribute('data-index'));
|
||||
const disabled = listRowElement.classList.contains('option-disabled');
|
||||
|
||||
// Ignore mouse selection of disabled options
|
||||
if (index >= 0 && index < this.options.length && !disabled) {
|
||||
this.selected = index;
|
||||
this.select(this.selected);
|
||||
|
||||
this.selectList.setFocus([this.selected]);
|
||||
this.selectList.reveal(this.selectList.getFocus()[0]);
|
||||
this._onDidSelect.fire({
|
||||
index: this.selectElement.selectedIndex,
|
||||
selected: this.selectElement.title
|
||||
});
|
||||
|
||||
// Reset Selection Handler
|
||||
this._currentSelection = -1;
|
||||
this.hideSelectDropDown(true);
|
||||
}
|
||||
dom.EventHelper.stop(e);
|
||||
}
|
||||
|
||||
// List Exit - passive - hide drop-down, fire onDidSelect
|
||||
private onListBlur(): void {
|
||||
|
||||
if (this._currentSelection >= 0) {
|
||||
this.select(this._currentSelection);
|
||||
}
|
||||
|
||||
this._onDidSelect.fire({
|
||||
index: this.selectElement.selectedIndex,
|
||||
selected: this.selectElement.title
|
||||
});
|
||||
|
||||
this.hideSelectDropDown(false);
|
||||
}
|
||||
|
||||
// List keyboard controller
|
||||
// List exit - active - hide ContextView dropdown, return focus to parent select, fire onDidSelect
|
||||
private onEscape(e: StandardKeyboardEvent): void {
|
||||
dom.EventHelper.stop(e);
|
||||
this.select(this._currentSelection);
|
||||
|
||||
this.hideSelectDropDown(true);
|
||||
|
||||
this._onDidSelect.fire({
|
||||
index: this.selectElement.selectedIndex,
|
||||
selected: this.selectElement.title
|
||||
});
|
||||
}
|
||||
|
||||
// List exit - active - hide ContextView dropdown, return focus to parent select, fire onDidSelect
|
||||
private onEnter(e: StandardKeyboardEvent): void {
|
||||
dom.EventHelper.stop(e);
|
||||
|
||||
// Reset current selection
|
||||
this._currentSelection = -1;
|
||||
|
||||
this.hideSelectDropDown(true);
|
||||
this._onDidSelect.fire({
|
||||
index: this.selectElement.selectedIndex,
|
||||
selected: this.selectElement.title
|
||||
});
|
||||
}
|
||||
|
||||
// List navigation - have to handle a disabled option (jump over)
|
||||
private onDownArrow(): void {
|
||||
if (this.selected < this.options.length - 1) {
|
||||
// Skip disabled options
|
||||
if ((this.selected + 1) === this.disabledOptionIndex && this.options.length > this.selected + 2) {
|
||||
this.selected += 2;
|
||||
} else {
|
||||
this.selected++;
|
||||
}
|
||||
// Set focus/selection - only fire event when closing drop-down or on blur
|
||||
this.select(this.selected);
|
||||
this.selectList.setFocus([this.selected]);
|
||||
this.selectList.reveal(this.selectList.getFocus()[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private onUpArrow(): void {
|
||||
if (this.selected > 0) {
|
||||
// Skip disabled options
|
||||
if ((this.selected - 1) === this.disabledOptionIndex && this.selected > 1) {
|
||||
this.selected -= 2;
|
||||
} else {
|
||||
this.selected--;
|
||||
}
|
||||
// Set focus/selection - only fire event when closing drop-down or on blur
|
||||
this.select(this.selected);
|
||||
this.selectList.setFocus([this.selected]);
|
||||
this.selectList.reveal(this.selectList.getFocus()[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private onPageUp(e: StandardKeyboardEvent): void {
|
||||
dom.EventHelper.stop(e);
|
||||
|
||||
this.selectList.focusPreviousPage();
|
||||
|
||||
// Allow scrolling to settle
|
||||
setTimeout(() => {
|
||||
this.selected = this.selectList.getFocus()[0];
|
||||
|
||||
// Shift selection down if we land on a disabled option
|
||||
if (this.selected === this.disabledOptionIndex && this.selected < this.options.length - 1) {
|
||||
this.selected++;
|
||||
this.selectList.setFocus([this.selected]);
|
||||
}
|
||||
this.selectList.reveal(this.selected);
|
||||
this.select(this.selected);
|
||||
}, 1);
|
||||
}
|
||||
|
||||
private onPageDown(e: StandardKeyboardEvent): void {
|
||||
dom.EventHelper.stop(e);
|
||||
|
||||
this.selectList.focusNextPage();
|
||||
|
||||
// Allow scrolling to settle
|
||||
setTimeout(() => {
|
||||
this.selected = this.selectList.getFocus()[0];
|
||||
|
||||
// Shift selection up if we land on a disabled option
|
||||
if (this.selected === this.disabledOptionIndex && this.selected > 0) {
|
||||
this.selected--;
|
||||
this.selectList.setFocus([this.selected]);
|
||||
}
|
||||
this.selectList.reveal(this.selected);
|
||||
this.select(this.selected);
|
||||
}, 1);
|
||||
}
|
||||
|
||||
private onHome(e: StandardKeyboardEvent): void {
|
||||
dom.EventHelper.stop(e);
|
||||
|
||||
if (this.options.length < 2) {
|
||||
return;
|
||||
}
|
||||
this.selected = 0;
|
||||
if (this.selected === this.disabledOptionIndex && this.selected > 1) {
|
||||
this.selected++;
|
||||
}
|
||||
this.selectList.setFocus([this.selected]);
|
||||
this.selectList.reveal(this.selected);
|
||||
this.select(this.selected);
|
||||
}
|
||||
|
||||
private onEnd(e: StandardKeyboardEvent): void {
|
||||
dom.EventHelper.stop(e);
|
||||
|
||||
if (this.options.length < 2) {
|
||||
return;
|
||||
}
|
||||
this.selected = this.options.length - 1;
|
||||
if (this.selected === this.disabledOptionIndex && this.selected > 1) {
|
||||
this.selected--;
|
||||
}
|
||||
this.selectList.setFocus([this.selected]);
|
||||
this.selectList.reveal(this.selected);
|
||||
this.select(this.selected);
|
||||
}
|
||||
|
||||
// Mimic option first character navigation of native select
|
||||
private onCharacter(e: StandardKeyboardEvent): void {
|
||||
const ch = KeyCodeUtils.toString(e.keyCode);
|
||||
let optionIndex = -1;
|
||||
|
||||
for (let i = 0; i < this.options.length - 1; i++) {
|
||||
optionIndex = (i + this.selected + 1) % this.options.length;
|
||||
if (this.options[optionIndex].charAt(0).toUpperCase() === ch) {
|
||||
this.select(optionIndex);
|
||||
this.selectList.setFocus([optionIndex]);
|
||||
this.selectList.reveal(this.selectList.getFocus()[0]);
|
||||
dom.EventHelper.stop(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.hideSelectDropDown(false);
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
}
|
||||
}
|
||||
154
src/vs/base/browser/ui/selectBox/selectBoxNative.ts
Normal file
154
src/vs/base/browser/ui/selectBox/selectBoxNative.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { ISelectBoxDelegate, ISelectBoxStyles, ISelectData } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
|
||||
export class SelectBoxNative implements ISelectBoxDelegate {
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
public selectElement: HTMLSelectElement;
|
||||
private options: string[];
|
||||
private selected: number;
|
||||
private _onDidSelect: Emitter<ISelectData>;
|
||||
private toDispose: IDisposable[];
|
||||
private styles: ISelectBoxStyles;
|
||||
|
||||
constructor(options: string[], selected: number, styles: ISelectBoxStyles) {
|
||||
|
||||
this.toDispose = [];
|
||||
|
||||
this.selectElement = document.createElement('select');
|
||||
this.selectElement.className = 'select-box';
|
||||
|
||||
this._onDidSelect = new Emitter<ISelectData>();
|
||||
|
||||
this.styles = styles;
|
||||
|
||||
this.registerListeners();
|
||||
|
||||
this.setOptions(options, selected);
|
||||
}
|
||||
|
||||
private registerListeners() {
|
||||
|
||||
this.toDispose.push(dom.addStandardDisposableListener(this.selectElement, 'change', (e) => {
|
||||
this.selectElement.title = e.target.value;
|
||||
this._onDidSelect.fire({
|
||||
index: e.target.selectedIndex,
|
||||
selected: e.target.value
|
||||
});
|
||||
}));
|
||||
|
||||
this.toDispose.push(dom.addStandardDisposableListener(this.selectElement, 'keydown', (e) => {
|
||||
let showSelect = false;
|
||||
|
||||
if (isMacintosh) {
|
||||
if (e.keyCode === KeyCode.DownArrow || e.keyCode === KeyCode.UpArrow || e.keyCode === KeyCode.Space) {
|
||||
showSelect = true;
|
||||
}
|
||||
} else {
|
||||
if (e.keyCode === KeyCode.DownArrow && e.altKey || e.keyCode === KeyCode.Space || e.keyCode === KeyCode.Enter) {
|
||||
showSelect = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (showSelect) {
|
||||
// Space, Enter, is used to expand select box, do not propagate it (prevent action bar action run)
|
||||
e.stopPropagation();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public get onDidSelect(): Event<ISelectData> {
|
||||
return this._onDidSelect.event;
|
||||
}
|
||||
|
||||
public setOptions(options: string[], selected?: number, disabled?: number): void {
|
||||
|
||||
if (!this.options || !arrays.equals(this.options, options)) {
|
||||
this.options = options;
|
||||
this.selectElement.options.length = 0;
|
||||
|
||||
let i = 0;
|
||||
this.options.forEach((option) => {
|
||||
this.selectElement.add(this.createOption(option, i, disabled === i++));
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
if (selected !== undefined) {
|
||||
this.select(selected);
|
||||
}
|
||||
}
|
||||
|
||||
public select(index: number): void {
|
||||
if (index >= 0 && index < this.options.length) {
|
||||
this.selected = index;
|
||||
} else if (this.selected < 0) {
|
||||
this.selected = 0;
|
||||
}
|
||||
|
||||
this.selectElement.selectedIndex = this.selected;
|
||||
this.selectElement.title = this.options[this.selected];
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
if (this.selectElement) {
|
||||
this.selectElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
public blur(): void {
|
||||
if (this.selectElement) {
|
||||
this.selectElement.blur();
|
||||
}
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
dom.addClass(container, 'select-container');
|
||||
container.appendChild(this.selectElement);
|
||||
this.setOptions(this.options, this.selected);
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
public style(styles: ISelectBoxStyles): void {
|
||||
this.styles = styles;
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
public applyStyles(): void {
|
||||
|
||||
// Style native select
|
||||
if (this.selectElement) {
|
||||
const background = this.styles.selectBackground ? this.styles.selectBackground.toString() : null;
|
||||
const foreground = this.styles.selectForeground ? this.styles.selectForeground.toString() : null;
|
||||
const border = this.styles.selectBorder ? this.styles.selectBorder.toString() : null;
|
||||
|
||||
this.selectElement.style.backgroundColor = background;
|
||||
this.selectElement.style.color = foreground;
|
||||
this.selectElement.style.borderColor = border;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private createOption(value: string, index: number, disabled?: boolean): HTMLOptionElement {
|
||||
let option = document.createElement('option');
|
||||
option.value = value;
|
||||
option.text = value;
|
||||
option.disabled = disabled;
|
||||
|
||||
return option;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
}
|
||||
}
|
||||
149
src/vs/base/browser/ui/splitview/grid.ts
Normal file
149
src/vs/base/browser/ui/splitview/grid.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import Event, { anyEvent } from 'vs/base/common/event';
|
||||
import { Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||
import { append, $ } from 'vs/base/browser/dom';
|
||||
import { SplitView, IView } from 'vs/base/browser/ui/splitview/splitview';
|
||||
export { Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||
|
||||
export class GridNode implements IView {
|
||||
|
||||
get minimumSize(): number {
|
||||
let result = 0;
|
||||
|
||||
for (const child of this.children) {
|
||||
for (const grandchild of child.children) {
|
||||
result += grandchild.minimumSize;
|
||||
}
|
||||
}
|
||||
|
||||
return result === 0 ? 50 : result;
|
||||
}
|
||||
|
||||
readonly maximumSize = Number.MAX_VALUE;
|
||||
|
||||
private _onDidChange: Event<number | undefined> = Event.None;
|
||||
get onDidChange(): Event<number | undefined> {
|
||||
return this._onDidChange;
|
||||
}
|
||||
|
||||
protected orientation: Orientation | undefined;
|
||||
protected size: number | undefined;
|
||||
protected orthogonalSize: number | undefined;
|
||||
private splitview: SplitView | undefined;
|
||||
private children: GridNode[] = [];
|
||||
private color: string | undefined;
|
||||
|
||||
constructor(private parent?: GridNode, orthogonalSize?: number, color?: string) {
|
||||
this.orthogonalSize = orthogonalSize;
|
||||
this.color = color || `hsl(${Math.round(Math.random() * 360)}, 72%, 72%)`;
|
||||
}
|
||||
|
||||
render(container: HTMLElement): void {
|
||||
container = append(container, $('.node'));
|
||||
container.style.backgroundColor = this.color;
|
||||
|
||||
append(container, $('.action', { onclick: () => this.split(container, Orientation.HORIZONTAL) }, '⬌'));
|
||||
append(container, $('.action', { onclick: () => this.split(container, Orientation.VERTICAL) }, '⬍'));
|
||||
}
|
||||
|
||||
protected split(container: HTMLElement, orientation: Orientation): void {
|
||||
if (this.parent && this.parent.orientation === orientation) {
|
||||
const index = this.parent.children.indexOf(this);
|
||||
this.parent.addChild(this.size / 2, this.orthogonalSize, index + 1);
|
||||
} else {
|
||||
this.branch(container, orientation);
|
||||
}
|
||||
}
|
||||
|
||||
protected branch(container: HTMLElement, orientation: Orientation): void {
|
||||
this.orientation = orientation;
|
||||
container.innerHTML = '';
|
||||
|
||||
this.splitview = new SplitView(container, { orientation });
|
||||
this.layout(this.size);
|
||||
this.orthogonalLayout(this.orthogonalSize);
|
||||
|
||||
this.addChild(this.orthogonalSize / 2, this.size, 0, this.color);
|
||||
this.addChild(this.orthogonalSize / 2, this.size);
|
||||
}
|
||||
|
||||
layout(size: number): void {
|
||||
this.size = size;
|
||||
|
||||
for (const child of this.children) {
|
||||
child.orthogonalLayout(size);
|
||||
}
|
||||
}
|
||||
|
||||
orthogonalLayout(size: number): void {
|
||||
this.orthogonalSize = size;
|
||||
|
||||
if (this.splitview) {
|
||||
this.splitview.layout(size);
|
||||
}
|
||||
}
|
||||
|
||||
private addChild(size: number, orthogonalSize: number, index?: number, color?: string): void {
|
||||
const child = new GridNode(this, orthogonalSize, color);
|
||||
this.splitview.addView(child, size, index);
|
||||
|
||||
if (typeof index === 'number') {
|
||||
this.children.splice(index, 0, child);
|
||||
} else {
|
||||
this.children.push(child);
|
||||
}
|
||||
|
||||
this._onDidChange = anyEvent(...this.children.map(c => c.onDidChange));
|
||||
}
|
||||
}
|
||||
|
||||
export class RootGridNode extends GridNode {
|
||||
|
||||
private width: number;
|
||||
private height: number;
|
||||
|
||||
protected branch(container: HTMLElement, orientation: Orientation): void {
|
||||
if (orientation === Orientation.VERTICAL) {
|
||||
this.size = this.width;
|
||||
this.orthogonalSize = this.height;
|
||||
} else {
|
||||
this.size = this.height;
|
||||
this.orthogonalSize = this.width;
|
||||
}
|
||||
|
||||
super.branch(container, orientation);
|
||||
}
|
||||
|
||||
layoutBox(width: number, height: number): void {
|
||||
if (this.orientation === Orientation.VERTICAL) {
|
||||
this.layout(width);
|
||||
this.orthogonalLayout(height);
|
||||
} else if (this.orientation === Orientation.HORIZONTAL) {
|
||||
this.layout(height);
|
||||
this.orthogonalLayout(width);
|
||||
} else {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Grid {
|
||||
|
||||
private root: RootGridNode;
|
||||
|
||||
constructor(container: HTMLElement) {
|
||||
this.root = new RootGridNode();
|
||||
this.root.render(container);
|
||||
}
|
||||
|
||||
layout(width: number, height: number): void {
|
||||
this.root.layoutBox(width, height);
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,7 @@
|
||||
|
||||
/* TODO: actions should be part of the panel, but they aren't yet */
|
||||
.monaco-panel-view .panel:hover > .panel-header.expanded > .actions,
|
||||
.monaco-panel-view .panel > .panel-header.actions-always-visible.expanded > .actions,
|
||||
.monaco-panel-view .panel > .panel-header.focused.expanded > .actions {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
@@ -49,6 +49,10 @@ export abstract class Panel implements IView {
|
||||
private _onDidChange = new Emitter<number | undefined>();
|
||||
readonly onDidChange: Event<number | undefined> = this._onDidChange.event;
|
||||
|
||||
get element(): HTMLElement {
|
||||
return this.el;
|
||||
}
|
||||
|
||||
get draggableElement(): HTMLElement {
|
||||
return this.header;
|
||||
}
|
||||
|
||||
@@ -20,4 +20,5 @@
|
||||
|
||||
.monaco-split-view2.horizontal > .split-view-view {
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@@ -91,10 +91,13 @@ export class SplitView implements IDisposable {
|
||||
|
||||
private _onDidSashChange = new Emitter<void>();
|
||||
readonly onDidSashChange = this._onDidSashChange.event;
|
||||
private _onDidSashReset = new Emitter<void>();
|
||||
readonly onDidSashReset = this._onDidSashReset.event;
|
||||
|
||||
get length(): number {
|
||||
return this.viewItems.length;
|
||||
}
|
||||
|
||||
constructor(container: HTMLElement, options: ISplitViewOptions = {}) {
|
||||
this.orientation = types.isUndefined(options.orientation) ? Orientation.VERTICAL : options.orientation;
|
||||
|
||||
@@ -152,15 +155,17 @@ export class SplitView implements IDisposable {
|
||||
const onSashChangeDisposable = onChange(this.onSashChange, this);
|
||||
const onEnd = mapEvent<void, void>(sash.onDidEnd, () => null);
|
||||
const onEndDisposable = onEnd(() => this._onDidSashChange.fire());
|
||||
const onDidReset = mapEvent<void, void>(sash.onDidReset, () => null);
|
||||
const onDidResetDisposable = onDidReset(() => this._onDidSashReset.fire());
|
||||
|
||||
const disposable = combinedDisposable([onStartDisposable, onSashChangeDisposable, onEndDisposable, sash]);
|
||||
const disposable = combinedDisposable([onStartDisposable, onSashChangeDisposable, onEndDisposable, onDidResetDisposable, sash]);
|
||||
const sashItem: ISashItem = { sash, disposable };
|
||||
|
||||
this.sashItems.splice(index - 1, 0, sashItem);
|
||||
}
|
||||
|
||||
view.render(container, this.orientation);
|
||||
this.relayout();
|
||||
this.relayout(index);
|
||||
this.state = State.Idle;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-toolbar .dropdown > .dropdown-label:not(:empty) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.monaco-toolbar .toolbar-toggle-more {
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
|
||||
@@ -8,12 +8,10 @@
|
||||
import 'vs/css!./toolbar';
|
||||
import nls = require('vs/nls');
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import types = require('vs/base/common/types');
|
||||
import { Action, IActionRunner, IAction } from 'vs/base/common/actions';
|
||||
import { ActionBar, ActionsOrientation, IActionItemProvider, BaseActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IContextMenuProvider, DropdownMenu, IActionProvider, ILabelRenderer, IDropdownMenuOptions } from 'vs/base/browser/ui/dropdown/dropdown';
|
||||
import { ActionBar, ActionsOrientation, IActionItemProvider } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IContextMenuProvider, DropdownMenuActionItem } from 'vs/base/browser/ui/dropdown/dropdown';
|
||||
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
|
||||
export const CONTEXT = 'context.toolbar';
|
||||
@@ -181,83 +179,4 @@ class ToggleMenuAction extends Action {
|
||||
public set menuActions(actions: IAction[]) {
|
||||
this._menuActions = actions;
|
||||
}
|
||||
}
|
||||
|
||||
export class DropdownMenuActionItem extends BaseActionItem {
|
||||
private menuActionsOrProvider: any;
|
||||
private dropdownMenu: DropdownMenu;
|
||||
private contextMenuProvider: IContextMenuProvider;
|
||||
private actionItemProvider: IActionItemProvider;
|
||||
private keybindings: (action: IAction) => ResolvedKeybinding;
|
||||
private clazz: string;
|
||||
|
||||
constructor(action: IAction, menuActions: IAction[], contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, actionRunner: IActionRunner, keybindings: (action: IAction) => ResolvedKeybinding, clazz: string);
|
||||
constructor(action: IAction, actionProvider: IActionProvider, contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, actionRunner: IActionRunner, keybindings: (action: IAction) => ResolvedKeybinding, clazz: string);
|
||||
constructor(action: IAction, menuActionsOrProvider: any, contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, actionRunner: IActionRunner, keybindings: (action: IAction) => ResolvedKeybinding, clazz: string) {
|
||||
super(null, action);
|
||||
|
||||
this.menuActionsOrProvider = menuActionsOrProvider;
|
||||
this.contextMenuProvider = contextMenuProvider;
|
||||
this.actionItemProvider = actionItemProvider;
|
||||
this.actionRunner = actionRunner;
|
||||
this.keybindings = keybindings;
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
let labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable => {
|
||||
this.builder = $('a.action-label').attr({
|
||||
tabIndex: '0',
|
||||
role: 'button',
|
||||
'aria-haspopup': 'true',
|
||||
title: this._action.label || '',
|
||||
class: this.clazz
|
||||
});
|
||||
|
||||
this.builder.appendTo(el);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
let options: IDropdownMenuOptions = {
|
||||
contextMenuProvider: this.contextMenuProvider,
|
||||
labelRenderer: labelRenderer
|
||||
};
|
||||
|
||||
// Render the DropdownMenu around a simple action to toggle it
|
||||
if (types.isArray(this.menuActionsOrProvider)) {
|
||||
options.actions = this.menuActionsOrProvider;
|
||||
} else {
|
||||
options.actionProvider = this.menuActionsOrProvider;
|
||||
}
|
||||
|
||||
this.dropdownMenu = new DropdownMenu(container, options);
|
||||
|
||||
this.dropdownMenu.menuOptions = {
|
||||
actionItemProvider: this.actionItemProvider,
|
||||
actionRunner: this.actionRunner,
|
||||
getKeyBinding: this.keybindings,
|
||||
context: this._context
|
||||
};
|
||||
}
|
||||
|
||||
public setActionContext(newContext: any): void {
|
||||
super.setActionContext(newContext);
|
||||
|
||||
if (this.dropdownMenu) {
|
||||
this.dropdownMenu.menuOptions.context = newContext;
|
||||
}
|
||||
}
|
||||
|
||||
public show(): void {
|
||||
if (this.dropdownMenu) {
|
||||
this.dropdownMenu.show();
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.dropdownMenu.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user