Merge VS Code 1.31.1 (#4283)

This commit is contained in:
Matt Irvine
2019-03-15 13:09:45 -07:00
committed by GitHub
parent 7d31575149
commit 86bac90001
1716 changed files with 53308 additions and 48375 deletions

View File

@@ -14,7 +14,7 @@ class WindowManager {
// --- Zoom Level
private _zoomLevel: number = 0;
private _lastZoomLevelChangeTime: number = 0;
private readonly _onDidChangeZoomLevel: Emitter<number> = new Emitter<number>();
private readonly _onDidChangeZoomLevel = new Emitter<number>();
public readonly onDidChangeZoomLevel: Event<number> = this._onDidChangeZoomLevel.event;
public getZoomLevel(): number {
@@ -58,7 +58,7 @@ class WindowManager {
// --- Fullscreen
private _fullscreen: boolean;
private readonly _onDidChangeFullscreen: Emitter<void> = new Emitter<void>();
private readonly _onDidChangeFullscreen = new Emitter<void>();
public readonly onDidChangeFullscreen: Event<void> = this._onDidChangeFullscreen.event;
public setFullscreen(fullscreen: boolean): void {
@@ -75,7 +75,7 @@ class WindowManager {
// --- Accessibility
private _accessibilitySupport = platform.AccessibilitySupport.Unknown;
private readonly _onDidChangeAccessibilitySupport: Emitter<void> = new Emitter<void>();
private readonly _onDidChangeAccessibilitySupport = new Emitter<void>();
public readonly onDidChangeAccessibilitySupport: Event<void> = this._onDidChangeAccessibilitySupport.event;
public setAccessibilitySupport(accessibilitySupport: platform.AccessibilitySupport): void {
@@ -146,7 +146,8 @@ export const isOpera = (userAgent.indexOf('Opera') >= 0);
export const isFirefox = (userAgent.indexOf('Firefox') >= 0);
export const isWebKit = (userAgent.indexOf('AppleWebKit') >= 0);
export const isChrome = (userAgent.indexOf('Chrome') >= 0);
export const isSafari = (userAgent.indexOf('Chrome') === -1) && (userAgent.indexOf('Safari') >= 0);
export const isSafari = (!isChrome && (userAgent.indexOf('Safari') >= 0));
export const isWebkitWebView = (!isChrome && !isSafari && isWebKit);
export const isIPad = (userAgent.indexOf('iPad') >= 0);
export const isEdgeWebView = isEdge && (userAgent.indexOf('WebView/') >= 0);

View File

@@ -17,17 +17,17 @@ export interface IContextMenuEvent {
}
export class ContextSubMenu extends SubmenuAction {
constructor(label: string, public entries: (ContextSubMenu | IAction)[]) {
constructor(label: string, public entries: Array<ContextSubMenu | IAction>) {
super(label, entries, 'contextsubmenu');
}
}
export interface IContextMenuDelegate {
getAnchor(): HTMLElement | { x: number; y: number; width?: number; height?: number; };
getActions(): (IAction | ContextSubMenu)[];
getActionItem?(action: IAction): IActionItem;
getActions(): Array<IAction | ContextSubMenu>;
getActionItem?(action: IAction): IActionItem | null;
getActionsContext?(event?: IContextMenuEvent): any;
getKeyBinding?(action: IAction): ResolvedKeybinding;
getKeyBinding?(action: IAction): ResolvedKeybinding | undefined;
getMenuClassName?(): string;
onHide?(didCancel: boolean): void;
actionRunner?: IActionRunner;

View File

@@ -83,4 +83,30 @@ export function applyDragImage(event: DragEvent, label: string, clazz: string):
// Removes the element when the DND operation is done
setTimeout(() => document.body.removeChild(dragImage), 0);
}
}
}
export interface IDragAndDropData {
update(dataTransfer: DataTransfer): void;
getData(): any;
}
export class DragAndDropData<T> implements IDragAndDropData {
constructor(private data: T) { }
update(): void {
// noop
}
getData(): T {
return this.data;
}
}
export interface IStaticDND {
CurrentDragAndDropData: IDragAndDropData | undefined;
}
export const StaticDND: IStaticDND = {
CurrentDragAndDropData: undefined
};

View File

@@ -11,8 +11,9 @@ import { TimeoutTimer } from 'vs/base/common/async';
import { CharCode } from 'vs/base/common/charCode';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import * as platform from 'vs/base/common/platform';
import { coalesce } from 'vs/base/common/arrays';
export function clearNode(node: HTMLElement): void {
while (node.firstChild) {
@@ -146,10 +147,10 @@ const _manualClassList = new class implements IDomClassList {
toggleClass(node: HTMLElement, className: string, shouldHaveIt?: boolean): void {
this._findClassName(node, className);
if (this._lastStart !== -1 && (shouldHaveIt === void 0 || !shouldHaveIt)) {
if (this._lastStart !== -1 && (shouldHaveIt === undefined || !shouldHaveIt)) {
this.removeClass(node, className);
}
if (this._lastStart === -1 && (shouldHaveIt === void 0 || shouldHaveIt)) {
if (this._lastStart === -1 && (shouldHaveIt === undefined || shouldHaveIt)) {
this.addClass(node, className);
}
}
@@ -265,7 +266,7 @@ export let addStandardDisposableListener: IAddStandardDisposableListenerSignatur
export function addDisposableNonBubblingMouseOutListener(node: Element, handler: (event: MouseEvent) => void): IDisposable {
return addDisposableListener(node, 'mouseout', (e: MouseEvent) => {
// Mouse out bubbles, so this is an attempt to ignore faux mouse outs coming from children elements
let toElement: Node | null = <Node>(e.relatedTarget || e.toElement);
let toElement: Node | null = <Node>(e.relatedTarget || e.target);
while (toElement && toElement !== node) {
toElement = toElement.parentNode;
}
@@ -994,7 +995,7 @@ export function prepend<T extends Node>(parent: HTMLElement, child: T): T {
const SELECTOR_REGEX = /([\w\-]+)?(#([\w\-]+))?((.([\w\-]+))*)/;
export function $<T extends HTMLElement>(description: string, attrs?: { [key: string]: any; }, ...children: (Node | string)[]): T {
export function $<T extends HTMLElement>(description: string, attrs?: { [key: string]: any; }, ...children: Array<Node | string>): T {
let match = SELECTOR_REGEX.exec(description);
if (!match) {
@@ -1025,8 +1026,7 @@ export function $<T extends HTMLElement>(description: string, attrs?: { [key: st
}
});
children
.filter(child => !!child)
coalesce(children)
.forEach(child => {
if (child instanceof Node) {
result.appendChild(child);
@@ -1157,3 +1157,13 @@ export function windowOpenNoOpener(url: string): void {
}
}
}
export function animate(fn: () => void): IDisposable {
const step = () => {
fn();
stepDisposable = scheduleAtNextAnimationFrame(step);
};
let stepDisposable = scheduleAtNextAnimationFrame(step);
return toDisposable(() => stepDisposable.dispose());
}

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event, Emitter, mapEvent } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
export type EventHandler = HTMLElement | HTMLDocument | Window;
@@ -32,7 +32,7 @@ export interface CancellableEvent {
}
export function stop<T extends CancellableEvent>(event: Event<T>): Event<T> {
return mapEvent(event, e => {
return Event.map(event, e => {
e.preventDefault();
e.stopPropagation();
return e;

View File

@@ -92,12 +92,12 @@ export class GlobalMouseMoveMonitor<R> extends Disposable {
this.onStopCallback = onStopCallback;
let windowChain = IframeUtils.getSameOriginWindowChain();
for (let i = 0; i < windowChain.length; i++) {
this.hooks.push(dom.addDisposableThrottledListener(windowChain[i].window.document, 'mousemove',
for (const element of windowChain) {
this.hooks.push(dom.addDisposableThrottledListener(element.window.document, 'mousemove',
(data: R) => this.mouseMoveCallback!(data),
(lastEvent: R, currentEvent) => this.mouseMoveEventMerger!(lastEvent, currentEvent as MouseEvent)
));
this.hooks.push(dom.addDisposableListener(windowChain[i].window.document, 'mouseup', (e: MouseEvent) => this.stopMonitoring(true)));
this.hooks.push(dom.addDisposableListener(element.window.document, 'mouseup', (e: MouseEvent) => this.stopMonitoring(true)));
}
if (IframeUtils.hasDifferentOriginAncestor()) {

View File

@@ -6,7 +6,7 @@
import * as DOM from 'vs/base/browser/dom';
import { defaultGenerator } from 'vs/base/common/idGenerator';
import { escape } from 'vs/base/common/strings';
import { removeMarkdownEscapes, IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent';
import { removeMarkdownEscapes, IMarkdownString } from 'vs/base/common/htmlContent';
import * as marked from 'vs/base/common/marked/marked';
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { IDisposable } from 'vs/base/common/lifecycle';
@@ -24,7 +24,7 @@ export interface RenderOptions {
className?: string;
inline?: boolean;
actionHandler?: IContentActionHandler;
codeBlockRenderer?: (modeId: string, value: string) => Thenable<string>;
codeBlockRenderer?: (modeId: string, value: string) => Promise<string>;
codeBlockRenderCallback?: () => void;
}
@@ -92,7 +92,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: RenderOptions
// signal to code-block render that the
// element has been created
let signalInnerHTML: Function;
let signalInnerHTML: () => void;
const withInnerHTML = new Promise(c => signalInnerHTML = c);
const renderer = new marked.Renderer();
@@ -225,7 +225,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: RenderOptions
}
const markedOptions: marked.MarkedOptions = {
sanitize: markdown instanceof MarkdownString ? markdown.sanitize : true,
sanitize: true,
renderer
};

View File

@@ -111,8 +111,7 @@ export class IframeUtils {
let windowChain = this.getSameOriginWindowChain();
for (let i = 0; i < windowChain.length; i++) {
let windowChainEl = windowChain[i];
for (const windowChainEl of windowChain) {
if (windowChainEl.window === ancestorWindow) {
break;

View File

@@ -7,7 +7,7 @@ import 'vs/css!./actionbar';
import * as platform from 'vs/base/common/platform';
import * as nls from 'vs/nls';
import { Disposable, dispose } from 'vs/base/common/lifecycle';
import { SelectBox, ISelectBoxOptions } from 'vs/base/browser/ui/selectBox/selectBox';
import { SelectBox, ISelectOptionItem, ISelectBoxOptions } from 'vs/base/browser/ui/selectBox/selectBox';
import { IAction, IActionRunner, Action, IActionChangeEvent, ActionRunner, IRunEvent } from 'vs/base/common/actions';
import * as DOM from 'vs/base/browser/dom';
import * as types from 'vs/base/common/types';
@@ -60,24 +60,24 @@ export class BaseActionItem extends Disposable implements IActionItem {
}
private handleActionChangeEvent(event: IActionChangeEvent): void {
if (event.enabled !== void 0) {
if (event.enabled !== undefined) {
this.updateEnabled();
}
if (event.checked !== void 0) {
if (event.checked !== undefined) {
this.updateChecked();
}
if (event.class !== void 0) {
if (event.class !== undefined) {
this.updateClass();
}
if (event.label !== void 0) {
if (event.label !== undefined) {
this.updateLabel();
this.updateTooltip();
}
if (event.tooltip !== void 0) {
if (event.tooltip !== undefined) {
this.updateTooltip();
}
}
@@ -228,7 +228,7 @@ export class Separator extends Action {
export interface IActionItemOptions extends IBaseActionItemOptions {
icon?: boolean;
label?: boolean;
keybinding?: string;
keybinding?: string | null;
}
export class ActionItem extends BaseActionItem {
@@ -363,7 +363,7 @@ export interface ActionTrigger {
}
export interface IActionItemProvider {
(action: IAction): IActionItem;
(action: IAction): IActionItem | null;
}
export interface IActionBarOptions {
@@ -376,7 +376,7 @@ export interface IActionBarOptions {
triggerKeys?: ActionTrigger;
}
let defaultOptions: IActionBarOptions = {
const defaultOptions: IActionBarOptions = {
orientation: ActionsOrientation.HORIZONTAL,
context: null,
triggerKeys: {
@@ -473,7 +473,7 @@ export class ActionBar extends Disposable implements IActionRunner {
}
this._register(DOM.addDisposableListener(this.domNode, DOM.EventType.KEY_DOWN, e => {
let event = new StandardKeyboardEvent(e);
const event = new StandardKeyboardEvent(e);
let eventHandled = true;
if (event.equals(previousKey)) {
@@ -498,7 +498,7 @@ export class ActionBar extends Disposable implements IActionRunner {
}));
this._register(DOM.addDisposableListener(this.domNode, DOM.EventType.KEY_UP, e => {
let event = new StandardKeyboardEvent(e);
const event = new StandardKeyboardEvent(e);
// Run action on Enter/Space
if (this.isTriggerKeyEvent(event)) {
@@ -560,7 +560,7 @@ export class ActionBar extends Disposable implements IActionRunner {
private updateFocusedItem(): void {
for (let i = 0; i < this.actionsList.children.length; i++) {
let elem = this.actionsList.children[i];
const elem = this.actionsList.children[i];
if (DOM.isAncestor(document.activeElement, elem)) {
this.focusedItem = i;
break;
@@ -577,11 +577,11 @@ export class ActionBar extends Disposable implements IActionRunner {
this.items.forEach(i => i.setActionContext(context));
}
get actionRunner(): IActionRunner | undefined {
get actionRunner(): IActionRunner {
return this._actionRunner;
}
set actionRunner(actionRunner: IActionRunner | undefined) {
set actionRunner(actionRunner: IActionRunner) {
if (actionRunner) {
this._actionRunner = actionRunner;
this.items.forEach(item => item.actionRunner = actionRunner);
@@ -657,8 +657,8 @@ export class ActionBar extends Disposable implements IActionRunner {
pull(index: number): void {
if (index >= 0 && index < this.items.length) {
this.items.splice(index, 1);
this.actionsList.removeChild(this.actionsList.childNodes[index]);
dispose(this.items.splice(index, 1));
}
}
@@ -679,7 +679,7 @@ export class ActionBar extends Disposable implements IActionRunner {
focus(selectFirst?: boolean): void;
focus(arg?: any): void {
let selectFirst: boolean = false;
let index: number | undefined = void 0;
let index: number | undefined = undefined;
if (arg === undefined) {
selectFirst = true;
} else if (typeof arg === 'number') {
@@ -701,12 +701,12 @@ export class ActionBar extends Disposable implements IActionRunner {
}
}
private focusNext(): void {
protected focusNext(): void {
if (typeof this.focusedItem === 'undefined') {
this.focusedItem = this.items.length - 1;
}
let startIndex = this.focusedItem;
const startIndex = this.focusedItem;
let item: IActionItem;
do {
@@ -721,12 +721,12 @@ export class ActionBar extends Disposable implements IActionRunner {
this.updateFocus();
}
private focusPrevious(): void {
protected focusPrevious(): void {
if (typeof this.focusedItem === 'undefined') {
this.focusedItem = 0;
}
let startIndex = this.focusedItem;
const startIndex = this.focusedItem;
let item: IActionItem;
do {
@@ -752,9 +752,8 @@ export class ActionBar extends Disposable implements IActionRunner {
}
for (let i = 0; i < this.items.length; i++) {
let item = this.items[i];
let actionItem = item;
const item = this.items[i];
const actionItem = item;
if (i === this.focusedItem) {
if (types.isFunction(actionItem.isEnabled)) {
@@ -778,7 +777,7 @@ export class ActionBar extends Disposable implements IActionRunner {
}
// trigger action
let actionItem = this.items[this.focusedItem];
const actionItem = this.items[this.focusedItem];
if (actionItem instanceof BaseActionItem) {
const context = (actionItem._context === null || actionItem._context === undefined) ? event : actionItem._context;
this.run(actionItem._action, context);
@@ -793,7 +792,7 @@ export class ActionBar extends Disposable implements IActionRunner {
this._onDidCancel.fire();
}
run(action: IAction, context?: any): Thenable<void> {
run(action: IAction, context?: any): Promise<void> {
return this._actionRunner.run(action, context);
}
@@ -810,7 +809,7 @@ export class ActionBar extends Disposable implements IActionRunner {
export class SelectActionItem extends BaseActionItem {
protected selectBox: SelectBox;
constructor(ctx: any, action: IAction, options: string[], selected: number, contextViewProvider: IContextViewProvider, selectBoxOptions?: ISelectBoxOptions) {
constructor(ctx: any, action: IAction, options: ISelectOptionItem[], selected: number, contextViewProvider: IContextViewProvider, selectBoxOptions?: ISelectBoxOptions) {
super(ctx, action);
this.selectBox = new SelectBox(options, selected, contextViewProvider, undefined, selectBoxOptions);
@@ -819,8 +818,8 @@ export class SelectActionItem extends BaseActionItem {
this.registerListeners();
}
setOptions(options: string[], selected?: number, disabled?: number): void {
this.selectBox.setOptions(options, selected, disabled);
setOptions(options: ISelectOptionItem[], selected?: number): void {
this.selectBox.setOptions(options, selected);
}
select(index: number): void {

View File

@@ -11,7 +11,7 @@ import { Color } from 'vs/base/common/color';
import { mixin } from 'vs/base/common/objects';
import { Event as BaseEvent, Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { Gesture } from 'vs/base/browser/touch';
import { Gesture, EventType } from 'vs/base/browser/touch';
export interface IButtonOptions extends IButtonStyles {
title?: boolean;
@@ -65,14 +65,16 @@ export class Button extends Disposable {
Gesture.addTarget(this._element);
this._register(DOM.addDisposableListener(this._element, DOM.EventType.CLICK, e => {
if (!this.enabled) {
DOM.EventHelper.stop(e);
return;
}
[DOM.EventType.CLICK, EventType.Tap].forEach(eventType => {
this._register(DOM.addDisposableListener(this._element, eventType, e => {
if (!this.enabled) {
DOM.EventHelper.stop(e);
return;
}
this._onDidClick.fire(e);
}));
this._onDidClick.fire(e);
}));
});
this._register(DOM.addDisposableListener(this._element, DOM.EventType.KEY_DOWN, e => {
const event = new StandardKeyboardEvent(e);

View File

@@ -5,7 +5,7 @@
import { SplitView, Orientation, ISplitViewStyles, IView as ISplitViewView } from 'vs/base/browser/ui/splitview/splitview';
import { $ } from 'vs/base/browser/dom';
import { Event, mapEvent } from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { IView } from 'vs/base/browser/ui/grid/gridview';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Color } from 'vs/base/common/color';
@@ -39,8 +39,8 @@ function toSplitViewView(view: IView, getHeight: () => number): ISplitViewView {
element: view.element,
get maximumSize() { return view.maximumWidth; },
get minimumSize() { return view.minimumWidth; },
onDidChange: mapEvent(view.onDidChange, e => e && e.width),
layout: size => view.layout(size, getHeight())
onDidChange: Event.map(view.onDidChange, e => e && e.width),
layout: size => view.layout(size, getHeight(), Orientation.HORIZONTAL)
};
}
@@ -78,7 +78,7 @@ export class CenteredViewLayout {
this.resizeMargins();
}
} else {
this.view.layout(width, height);
this.view.layout(width, height, Orientation.HORIZONTAL);
}
this.didLayout = true;
}

View File

@@ -25,7 +25,7 @@ export const enum AnchorPosition {
export interface IDelegate {
getAnchor(): HTMLElement | IAnchor;
render(container: HTMLElement): IDisposable;
render(container: HTMLElement): IDisposable | null;
focus?(): void;
layout?(): void;
anchorAlignment?: AnchorAlignment; // default: left

View File

@@ -9,23 +9,8 @@
padding: 0;
}
.monaco-dropdown > .dropdown-label,
.monaco-dropdown > .dropdown-action {
.monaco-dropdown > .dropdown-label {
display: inline-block;
cursor: pointer;
height: 100%;
}
.monaco-dropdown > .dropdown-action {
vertical-align: top;
}
.monaco-dropdown > .dropdown-action > .action-label:hover {
color: inherit;
text-decoration: none;
}
.monaco-dropdown > .dropdown-action,
.monaco-dropdown > .dropdown-action > .action-label {
display: inline-block;
}

View File

@@ -16,7 +16,7 @@ import { IContextMenuDelegate } from 'vs/base/browser/contextmenu';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
export interface ILabelRenderer {
(container: HTMLElement): IDisposable;
(container: HTMLElement): IDisposable | null;
}
export interface IBaseDropdownOptions {
@@ -26,9 +26,9 @@ export interface IBaseDropdownOptions {
export class BaseDropdown extends ActionRunner {
private _element: HTMLElement;
private boxContainer: HTMLElement;
private _label: HTMLElement;
private contents: HTMLElement;
private boxContainer?: HTMLElement;
private _label?: HTMLElement;
private contents?: HTMLElement;
private visible: boolean;
constructor(container: HTMLElement, options: IBaseDropdownOptions) {
@@ -40,18 +40,18 @@ export class BaseDropdown extends ActionRunner {
let labelRenderer = options.labelRenderer;
if (!labelRenderer) {
labelRenderer = (container: HTMLElement): IDisposable => {
labelRenderer = (container: HTMLElement): IDisposable | null => {
container.textContent = options.label || '';
return null;
};
}
[EventType.CLICK, EventType.MOUSE_DOWN, GestureEventType.Tap].forEach(event => {
for (const event of [EventType.CLICK, EventType.MOUSE_DOWN, GestureEventType.Tap]) {
this._register(addDisposableListener(this._label, event, e => EventHelper.stop(e, true))); // prevent default click behaviour to trigger
});
}
[EventType.MOUSE_DOWN, GestureEventType.Tap].forEach(event => {
for (const event of [EventType.MOUSE_DOWN, GestureEventType.Tap]) {
this._register(addDisposableListener(this._label, event, e => {
if (e instanceof MouseEvent && e.detail > 1) {
return; // prevent multiple clicks to open multiple context menus (https://github.com/Microsoft/vscode/issues/41363)
@@ -63,7 +63,7 @@ export class BaseDropdown extends ActionRunner {
this.show();
}
}));
});
}
this._register(addDisposableListener(this._label, EventType.KEY_UP, e => {
const event = new StandardKeyboardEvent(e);
@@ -90,12 +90,14 @@ export class BaseDropdown extends ActionRunner {
return this._element;
}
get label(): HTMLElement {
get label() {
return this._label;
}
set tooltip(tooltip: string) {
this._label.title = tooltip;
if (this._label) {
this._label.title = tooltip;
}
}
show(): void {
@@ -116,17 +118,17 @@ export class BaseDropdown extends ActionRunner {
if (this.boxContainer) {
this.boxContainer.remove();
this.boxContainer = null;
this.boxContainer = undefined;
}
if (this.contents) {
this.contents.remove();
this.contents = null;
this.contents = undefined;
}
if (this._label) {
this._label.remove();
this._label = null;
this._label = undefined;
}
}
}
@@ -180,7 +182,7 @@ export class Dropdown extends BaseDropdown {
}
}
protected renderContents(container: HTMLElement): IDisposable {
protected renderContents(container: HTMLElement): IDisposable | null {
return null;
}
}
@@ -204,7 +206,7 @@ export class DropdownMenu extends BaseDropdown {
private _contextMenuProvider: IContextMenuProvider;
private _menuOptions: IMenuOptions;
private _actions: IAction[];
private actionProvider: IActionProvider;
private actionProvider?: IActionProvider;
private menuClassName: string;
constructor(container: HTMLElement, options: IDropdownMenuOptions) {
@@ -246,10 +248,10 @@ export class DropdownMenu extends BaseDropdown {
getActions: () => this.actions,
getActionsContext: () => this.menuOptions ? this.menuOptions.context : null,
getActionItem: action => this.menuOptions && this.menuOptions.actionItemProvider ? this.menuOptions.actionItemProvider(action) : null,
getKeyBinding: action => this.menuOptions && this.menuOptions.getKeyBinding ? this.menuOptions.getKeyBinding(action) : null,
getKeyBinding: action => this.menuOptions && this.menuOptions.getKeyBinding ? this.menuOptions.getKeyBinding(action) : undefined,
getMenuClassName: () => this.menuClassName,
onHide: () => this.onHide(),
actionRunner: this.menuOptions ? this.menuOptions.actionRunner : null,
actionRunner: this.menuOptions ? this.menuOptions.actionRunner : undefined,
anchorAlignment: this.menuOptions.anchorAlignment
});
}
@@ -268,14 +270,14 @@ export class DropdownMenuActionItem extends BaseActionItem {
private menuActionsOrProvider: any;
private dropdownMenu: DropdownMenu;
private contextMenuProvider: IContextMenuProvider;
private actionItemProvider: IActionItemProvider;
private keybindings: (action: IAction) => ResolvedKeybinding;
private actionItemProvider?: IActionItemProvider;
private keybindings?: (action: IAction) => ResolvedKeybinding | undefined;
private clazz: string;
private anchorAlignmentProvider: (() => AnchorAlignment) | undefined;
constructor(action: IAction, menuActions: IAction[], contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, actionRunner: IActionRunner, keybindings: (action: IAction) => ResolvedKeybinding, clazz: string, anchorAlignmentProvider?: () => AnchorAlignment);
constructor(action: IAction, actionProvider: IActionProvider, contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, actionRunner: IActionRunner, keybindings: (action: IAction) => ResolvedKeybinding, clazz: string, anchorAlignmentProvider?: () => AnchorAlignment);
constructor(action: IAction, menuActionsOrProvider: any, contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider, actionRunner: IActionRunner, keybindings: (action: IAction) => ResolvedKeybinding, clazz: string, anchorAlignmentProvider?: () => AnchorAlignment) {
constructor(action: IAction, menuActions: IAction[], contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string, anchorAlignmentProvider?: () => AnchorAlignment);
constructor(action: IAction, actionProvider: IActionProvider, contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding) | undefined, clazz: string, anchorAlignmentProvider?: () => AnchorAlignment);
constructor(action: IAction, menuActionsOrProvider: any, contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string, anchorAlignmentProvider?: () => AnchorAlignment) {
super(null, action);
this.menuActionsOrProvider = menuActionsOrProvider;
@@ -288,7 +290,7 @@ export class DropdownMenuActionItem extends BaseActionItem {
}
render(container: HTMLElement): void {
const labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable => {
const labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable | null => {
this.element = append(el, $('a.action-label.icon'));
addClasses(this.element, this.clazz);
@@ -327,7 +329,7 @@ export class DropdownMenuActionItem extends BaseActionItem {
this.dropdownMenu.menuOptions = {
...this.dropdownMenu.menuOptions,
get anchorAlignment(): AnchorAlignment {
return that.anchorAlignmentProvider();
return that.anchorAlignmentProvider!();
}
};
}

View File

@@ -32,7 +32,7 @@ export interface IFindInputOptions extends IFindInputStyles {
}
export interface IFindInputStyles extends IInputBoxStyles {
inputActiveOptionBorder?: Color;
inputActiveOptionBorder?: Color | null;
}
const NLS_DEFAULT_LABEL = nls.localize('defaultLabel', "input");
@@ -44,24 +44,24 @@ export class FindInput extends Widget {
private contextViewProvider: IContextViewProvider;
private width: number;
private placeholder: string;
private validation: IInputValidator;
private validation?: IInputValidator;
private label: string;
private fixFocusOnOptionClickEnabled = true;
private inputActiveOptionBorder: Color;
private inputBackground: Color;
private inputForeground: Color;
private inputBorder: Color;
private inputActiveOptionBorder?: Color | null;
private inputBackground?: Color | null;
private inputForeground?: Color | null;
private inputBorder?: Color | null;
private inputValidationInfoBorder: Color;
private inputValidationInfoBackground: Color;
private inputValidationInfoForeground: Color;
private inputValidationWarningBorder: Color;
private inputValidationWarningBackground: Color;
private inputValidationWarningForeground: Color;
private inputValidationErrorBorder: Color;
private inputValidationErrorBackground: Color;
private inputValidationErrorForeground: Color;
private inputValidationInfoBorder?: Color | null;
private inputValidationInfoBackground?: Color | null;
private inputValidationInfoForeground?: Color | null;
private inputValidationWarningBorder?: Color | null;
private inputValidationWarningBackground?: Color | null;
private inputValidationWarningForeground?: Color | null;
private inputValidationErrorBorder?: Color | null;
private inputValidationErrorBackground?: Color | null;
private inputValidationErrorForeground?: Color | null;
private regex: RegexCheckbox;
private wholeWords: WholeWordsCheckbox;
@@ -90,7 +90,7 @@ export class FindInput extends Widget {
private _onRegexKeyDown = this._register(new Emitter<IKeyboardEvent>());
public readonly onRegexKeyDown: Event<IKeyboardEvent> = this._onRegexKeyDown.event;
constructor(parent: HTMLElement, contextViewProvider: IContextViewProvider, private readonly _showOptionButtons: boolean, options?: IFindInputOptions) {
constructor(parent: HTMLElement | null, contextViewProvider: IContextViewProvider, private readonly _showOptionButtons: boolean, options: IFindInputOptions) {
super();
this.contextViewProvider = contextViewProvider;
this.width = options.width || 100;
@@ -113,15 +113,9 @@ export class FindInput extends Widget {
this.inputValidationErrorBackground = options.inputValidationErrorBackground;
this.inputValidationErrorForeground = options.inputValidationErrorForeground;
this.regex = null;
this.wholeWords = null;
this.caseSensitive = null;
this.domNode = null;
this.inputBox = null;
this.buildDomNode(options.appendCaseSensitiveLabel || '', options.appendWholeWordsLabel || '', options.appendRegexLabel || '', options.history || [], !!options.flexibleHeight);
this.buildDomNode(options.appendCaseSensitiveLabel || '', options.appendWholeWordsLabel || '', options.appendRegexLabel || '', options.history, options.flexibleHeight);
if (Boolean(parent)) {
if (parent) {
parent.appendChild(this.domNode);
}
@@ -208,7 +202,7 @@ export class FindInput extends Widget {
protected applyStyles(): void {
if (this.domNode) {
const checkBoxStyles: ICheckboxStyles = {
inputActiveOptionBorder: this.inputActiveOptionBorder,
inputActiveOptionBorder: this.inputActiveOptionBorder || undefined,
};
this.regex.style(checkBoxStyles);
this.wholeWords.style(checkBoxStyles);
@@ -298,7 +292,7 @@ export class FindInput extends Widget {
placeholder: this.placeholder || '',
ariaLabel: this.label || '',
validationOptions: {
validation: this.validation || null
validation: this.validation
},
inputBackground: this.inputBackground,
inputForeground: this.inputForeground,
@@ -319,7 +313,7 @@ export class FindInput extends Widget {
this.regex = this._register(new RegexCheckbox({
appendTitle: appendRegexLabel,
isChecked: false,
inputActiveOptionBorder: this.inputActiveOptionBorder
inputActiveOptionBorder: this.inputActiveOptionBorder || undefined
}));
this._register(this.regex.onChange(viaKeyboard => {
this._onDidOptionChange.fire(viaKeyboard);
@@ -336,7 +330,7 @@ export class FindInput extends Widget {
this.wholeWords = this._register(new WholeWordsCheckbox({
appendTitle: appendWholeWordsLabel,
isChecked: false,
inputActiveOptionBorder: this.inputActiveOptionBorder
inputActiveOptionBorder: this.inputActiveOptionBorder || undefined
}));
this._register(this.wholeWords.onChange(viaKeyboard => {
this._onDidOptionChange.fire(viaKeyboard);
@@ -350,7 +344,7 @@ export class FindInput extends Widget {
this.caseSensitive = this._register(new CaseSensitiveCheckbox({
appendTitle: appendCaseSensitiveLabel,
isChecked: false,
inputActiveOptionBorder: this.inputActiveOptionBorder
inputActiveOptionBorder: this.inputActiveOptionBorder || undefined
}));
this._register(this.caseSensitive.onChange(viaKeyboard => {
this._onDidOptionChange.fire(viaKeyboard);
@@ -370,7 +364,7 @@ export class FindInput extends Widget {
if (event.equals(KeyCode.LeftArrow) || event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Escape)) {
let index = indexes.indexOf(<HTMLElement>document.activeElement);
if (index >= 0) {
let newIndex: number;
let newIndex: number = -1;
if (event.equals(KeyCode.RightArrow)) {
newIndex = (index + 1) % indexes.length;
} else if (event.equals(KeyCode.LeftArrow)) {
@@ -405,19 +399,27 @@ export class FindInput extends Widget {
}
public validate(): void {
this.inputBox.validate();
if (this.inputBox) {
this.inputBox.validate();
}
}
public showMessage(message: InputBoxMessage): void {
this.inputBox.showMessage(message);
if (this.inputBox) {
this.inputBox.showMessage(message);
}
}
public clearMessage(): void {
this.inputBox.hideMessage();
if (this.inputBox) {
this.inputBox.hideMessage();
}
}
private clearValidation(): void {
this.inputBox.hideMessage();
if (this.inputBox) {
this.inputBox.hideMessage();
}
}
public dispose(): void {

View File

@@ -8,7 +8,9 @@ import { Orientation } from 'vs/base/browser/ui/sash/sash';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { tail2 as tail, equals } from 'vs/base/common/arrays';
import { orthogonal, IView, GridView, Sizing as GridViewSizing, Box, IGridViewStyles } from './gridview';
import { Event } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { $ } from 'vs/base/browser/dom';
import { LayoutPriority } from 'vs/base/browser/ui/splitview/splitview';
export { Orientation } from './gridview';
@@ -186,6 +188,7 @@ export interface IGridStyles extends IGridViewStyles { }
export interface IGridOptions {
styles?: IGridStyles;
proportionalLayout?: boolean;
}
export class Grid<T extends IView> implements IDisposable {
@@ -646,3 +649,63 @@ export function createSerializedGrid(gridDescriptor: GridDescriptor): ISerialize
height: height || 1
};
}
export class View implements IView {
readonly element = $('.grid-view-view');
private visible = false;
private width: number | undefined;
private height: number | undefined;
private orientation: Orientation = Orientation.HORIZONTAL;
get minimumWidth(): number { return this.visible ? this.view.minimumWidth : 0; }
get maximumWidth(): number { return this.visible ? this.view.maximumWidth : (this.orientation === Orientation.HORIZONTAL ? 0 : Number.POSITIVE_INFINITY); }
get minimumHeight(): number { return this.visible ? this.view.minimumHeight : 0; }
get maximumHeight(): number { return this.visible ? this.view.maximumHeight : (this.orientation === Orientation.VERTICAL ? 0 : Number.POSITIVE_INFINITY); }
private onDidChangeVisibility = new Emitter<{ width: number; height: number; } | undefined>();
readonly onDidChange: Event<{ width: number; height: number; } | undefined>;
get priority(): LayoutPriority | undefined { return this.view.priority; }
get snapSize(): number | undefined { return this.visible ? this.view.snapSize : undefined; }
constructor(private view: IView) {
this.show();
this.onDidChange = Event.any(this.onDidChangeVisibility.event, Event.filter(view.onDidChange, () => this.visible));
}
show(): void {
if (this.visible) {
return;
}
this.visible = true;
this.element.appendChild(this.view.element);
this.onDidChangeVisibility.fire(typeof this.width === 'number' ? { width: this.width, height: this.height! } : undefined);
}
hide(): void {
if (!this.visible) {
return;
}
this.visible = false;
this.element.removeChild(this.view.element);
this.onDidChangeVisibility.fire(undefined);
}
layout(width: number, height: number, orientation: Orientation): void {
this.orientation = orientation;
if (!this.visible) {
return;
}
this.view.layout(width, height, orientation);
this.width = width;
this.height = height;
}
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./gridview';
import { Event, anyEvent, Emitter, mapEvent, Relay } from 'vs/base/common/event';
import { Event, Emitter, Relay } from 'vs/base/common/event';
import { Orientation, Sash } from 'vs/base/browser/ui/sash/sash';
import { SplitView, IView as ISplitView, Sizing, LayoutPriority, ISplitViewStyles } from 'vs/base/browser/ui/splitview/splitview';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
@@ -21,10 +21,10 @@ export interface IView {
readonly maximumWidth: number;
readonly minimumHeight: number;
readonly maximumHeight: number;
readonly onDidChange: Event<{ width: number; height: number; }>;
readonly onDidChange: Event<{ width: number; height: number; } | undefined>;
readonly priority?: LayoutPriority;
readonly snapSize?: number;
layout(width: number, height: number): void;
layout(width: number, height: number, orientation: Orientation): void;
}
export function orthogonal(orientation: Orientation): Orientation {
@@ -147,10 +147,10 @@ class BranchNode implements ISplitView, IDisposable {
this._orthogonalSize = orthogonalSize;
this.element = $('.monaco-grid-branch-node');
this.splitview = new SplitView(this.element, { orientation, styles });
this.splitview = new SplitView(this.element, { orientation, styles, proportionalLayout });
this.splitview.layout(size);
const onDidSashReset = mapEvent(this.splitview.onDidSashReset, i => [i]);
const onDidSashReset = Event.map(this.splitview.onDidSashReset, i => [i]);
this.splitviewSashResetDisposable = onDidSashReset(this._onDidSashReset.fire, this._onDidSashReset);
}
@@ -300,15 +300,15 @@ class BranchNode implements ISplitView, IDisposable {
}
private onDidChildrenChange(): void {
const onDidChildrenChange = anyEvent(...this.children.map(c => c.onDidChange));
const onDidChildrenChange = Event.map(Event.any(...this.children.map(c => c.onDidChange)), () => undefined);
this.childrenChangeDisposable.dispose();
this.childrenChangeDisposable = onDidChildrenChange(this._onDidChange.fire, this._onDidChange);
const onDidChildrenSashReset = anyEvent(...this.children.map((c, i) => mapEvent(c.onDidSashReset, location => [i, ...location])));
const onDidChildrenSashReset = Event.any(...this.children.map((c, i) => Event.map(c.onDidSashReset, location => [i, ...location])));
this.childrenSashResetDisposable.dispose();
this.childrenSashResetDisposable = onDidChildrenSashReset(this._onDidSashReset.fire, this._onDidSashReset);
this._onDidChange.fire();
this._onDidChange.fire(undefined);
}
trySet2x2(other: BranchNode): IDisposable {
@@ -348,8 +348,8 @@ class BranchNode implements ISplitView, IDisposable {
mySash.linkedSash = otherSash;
otherSash.linkedSash = mySash;
this._onDidChange.fire();
other._onDidChange.fire();
this._onDidChange.fire(undefined);
other._onDidChange.fire(undefined);
return toDisposable(() => {
mySash.linkedSash = otherSash.linkedSash = undefined;
@@ -391,7 +391,7 @@ class LeafNode implements ISplitView, IDisposable {
set linkedWidthNode(node: LeafNode | undefined) {
this._onDidLinkedWidthNodeChange.input = node ? node._onDidViewChange : Event.None;
this._linkedWidthNode = node;
this._onDidSetLinkedNode.fire();
this._onDidSetLinkedNode.fire(undefined);
}
private _onDidLinkedHeightNodeChange = new Relay<number | undefined>();
@@ -400,7 +400,7 @@ class LeafNode implements ISplitView, IDisposable {
set linkedHeightNode(node: LeafNode | undefined) {
this._onDidLinkedHeightNodeChange.input = node ? node._onDidViewChange : Event.None;
this._linkedHeightNode = node;
this._onDidSetLinkedNode.fire();
this._onDidSetLinkedNode.fire(undefined);
}
private _onDidSetLinkedNode = new Emitter<number | undefined>();
@@ -414,8 +414,8 @@ class LeafNode implements ISplitView, IDisposable {
) {
this._orthogonalSize = orthogonalSize;
this._onDidViewChange = mapEvent(this.view.onDidChange, this.orientation === Orientation.HORIZONTAL ? e => e && e.width : e => e && e.height);
this.onDidChange = anyEvent(this._onDidViewChange, this._onDidSetLinkedNode.event, this._onDidLinkedWidthNodeChange.event, this._onDidLinkedHeightNodeChange.event);
this._onDidViewChange = Event.map(this.view.onDidChange, e => e && (this.orientation === Orientation.VERTICAL ? e.width : e.height));
this.onDidChange = Event.any(this._onDidViewChange, this._onDidSetLinkedNode.event, this._onDidLinkedWidthNodeChange.event, this._onDidLinkedHeightNodeChange.event);
}
get width(): number {
@@ -480,12 +480,12 @@ class LeafNode implements ISplitView, IDisposable {
layout(size: number): void {
this._size = size;
return this.view.layout(this.width, this.height);
return this.view.layout(this.width, this.height, orthogonal(this.orientation));
}
orthogonalLayout(size: number): void {
this._orthogonalSize = size;
return this.view.layout(this.width, this.height);
return this.view.layout(this.width, this.height, orthogonal(this.orientation));
}
dispose(): void { }
@@ -547,7 +547,7 @@ export class GridView implements IDisposable {
this._root = root;
this.element.appendChild(root.element);
this.onDidSashResetRelay.input = root.onDidSashReset;
this._onDidChange.input = mapEvent(root.onDidChange, () => undefined); // TODO
this._onDidChange.input = Event.map(root.onDidChange, () => undefined); // TODO
}
get orientation(): Orientation {
@@ -802,8 +802,7 @@ export class GridView implements IDisposable {
const children: GridNode[] = [];
let offset = 0;
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i];
for (const child of node.children) {
const childOrientation = orthogonal(orientation);
const childBox: Box = orientation === Orientation.HORIZONTAL
? { top: box.top, left: box.left + offset, width: child.width, height: box.height }

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable } from 'vs/base/common/lifecycle';
import * as dom from 'vs/base/browser/dom';
import * as objects from 'vs/base/common/objects';
import { renderOcticons } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
@@ -14,7 +13,7 @@ export interface IHighlight {
end: number;
}
export class HighlightedLabel implements IDisposable {
export class HighlightedLabel {
private domNode: HTMLElement;
private text: string;
@@ -33,7 +32,7 @@ export class HighlightedLabel implements IDisposable {
return this.domNode;
}
set(text: string, highlights: IHighlight[] = [], title: string = '', escapeNewLines?: boolean) {
set(text: string | undefined, highlights: IHighlight[] = [], title: string = '', escapeNewLines?: boolean) {
if (!text) {
text = '';
}
@@ -58,12 +57,10 @@ export class HighlightedLabel implements IDisposable {
private render() {
dom.clearNode(this.domNode);
let htmlContent: string[] = [],
highlight: IHighlight,
pos = 0;
let htmlContent: string[] = [];
let pos = 0;
for (let i = 0; i < this.highlights.length; i++) {
highlight = this.highlights[i];
for (const highlight of this.highlights) {
if (highlight.end === highlight.start) {
continue;
}
@@ -93,11 +90,6 @@ export class HighlightedLabel implements IDisposable {
this.didEverRender = true;
}
dispose() {
this.text = null!; // StrictNullOverride: nulling out ok in dispose
this.highlights = null!; // StrictNullOverride: nulling out ok in dispose
}
static escapeNewLines(text: string, highlights: IHighlight[]): string {
let total = 0;

View File

@@ -7,7 +7,7 @@ import 'vs/css!./iconlabel';
import * as dom from 'vs/base/browser/dom';
import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
import { IMatch } from 'vs/base/common/filters';
import { IDisposable, combinedDisposable, Disposable } from 'vs/base/common/lifecycle';
import { Disposable } from 'vs/base/common/lifecycle';
export interface IIconLabelCreationOptions {
supportHighlights?: boolean;
@@ -100,13 +100,13 @@ export class IconLabel extends Disposable {
this.labelDescriptionContainer = this._register(new FastLabelNode(dom.append(this.domNode.element, dom.$('.monaco-icon-label-description-container'))));
if (options && options.supportHighlights) {
this.labelNode = this._register(new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name')), !options.donotSupportOcticons));
this.labelNode = new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name')), !options.donotSupportOcticons);
} else {
this.labelNode = this._register(new FastLabelNode(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name'))));
}
if (options && options.supportDescriptionHighlights) {
this.descriptionNodeFactory = () => this._register(new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description')), !options.donotSupportOcticons));
this.descriptionNodeFactory = () => new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description')), !options.donotSupportOcticons);
} else {
this.descriptionNodeFactory = () => this._register(new FastLabelNode(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description'))));
}
@@ -116,13 +116,7 @@ export class IconLabel extends Disposable {
return this.domNode.element;
}
onClick(callback: (event: MouseEvent) => void): IDisposable {
return combinedDisposable([
dom.addDisposableListener(this.labelDescriptionContainer.element, dom.EventType.CLICK, (e: MouseEvent) => callback(e)),
]);
}
setValue(label?: string, description?: string, options?: IIconLabelValueOptions): void {
setLabel(label?: string, description?: string, options?: IIconLabelValueOptions): void {
const classes = ['monaco-icon-label'];
if (options) {
if (options.extraClasses) {
@@ -138,7 +132,7 @@ export class IconLabel extends Disposable {
this.domNode.title = options && options.title ? options.title : '';
if (this.labelNode instanceof HighlightedLabel) {
this.labelNode.set(label || '', options ? options.matches : void 0, options && options.title ? options.title : void 0, options && options.labelEscapeNewLines);
this.labelNode.set(label || '', options ? options.matches : undefined, options && options.title ? options.title : undefined, options && options.labelEscapeNewLines);
} else {
this.labelNode.textContent = label || '';
}
@@ -149,7 +143,7 @@ export class IconLabel extends Disposable {
}
if (this.descriptionNode instanceof HighlightedLabel) {
this.descriptionNode.set(description || '', options ? options.descriptionMatches : void 0);
this.descriptionNode.set(description || '', options ? options.descriptionMatches : undefined);
if (options && options.descriptionTitle) {
this.descriptionNode.element.title = options.descriptionTitle;
} else {
@@ -163,4 +157,3 @@ export class IconLabel extends Disposable {
}
}
}

View File

@@ -23,46 +23,46 @@ import { IHistoryNavigationWidget } from 'vs/base/browser/history';
const $ = dom.$;
export interface IInputOptions extends IInputBoxStyles {
placeholder?: string;
ariaLabel?: string;
type?: string;
validationOptions?: IInputValidationOptions;
flexibleHeight?: boolean;
actions?: IAction[];
readonly placeholder?: string;
readonly ariaLabel?: string;
readonly type?: string;
readonly validationOptions?: IInputValidationOptions;
readonly flexibleHeight?: boolean;
readonly actions?: IAction[];
// {{SQL CARBON EDIT}} Candidate for addition to vscode
min?: string;
max?: string;
useDefaultValidation?: boolean;
readonly min?: string;
readonly max?: string;
readonly useDefaultValidation?: boolean;
}
export interface IInputBoxStyles {
inputBackground?: Color;
inputForeground?: Color;
inputBorder?: Color;
inputValidationInfoBorder?: Color;
inputValidationInfoBackground?: Color;
inputValidationInfoForeground?: Color;
inputValidationWarningBorder?: Color;
inputValidationWarningBackground?: Color;
inputValidationWarningForeground?: Color;
inputValidationErrorBorder?: Color;
inputValidationErrorBackground?: Color;
inputValidationErrorForeground?: Color;
readonly inputBackground?: Color | null;
readonly inputForeground?: Color | null;
readonly inputBorder?: Color | null;
readonly inputValidationInfoBorder?: Color | null;
readonly inputValidationInfoBackground?: Color | null;
readonly inputValidationInfoForeground?: Color | null;
readonly inputValidationWarningBorder?: Color | null;
readonly inputValidationWarningBackground?: Color | null;
readonly inputValidationWarningForeground?: Color | null;
readonly inputValidationErrorBorder?: Color | null;
readonly inputValidationErrorBackground?: Color | null;
readonly inputValidationErrorForeground?: Color | null;
}
export interface IInputValidator {
(value: string): IMessage;
(value: string): IMessage | null;
}
export interface IMessage {
content: string;
formatContent?: boolean; // defaults to false
type?: MessageType;
readonly content: string;
readonly formatContent?: boolean; // defaults to false
readonly type?: MessageType;
}
export interface IInputValidationOptions {
validation: IInputValidator;
validation?: IInputValidator;
}
export const enum MessageType {
@@ -89,34 +89,35 @@ export const defaultOpts = {
};
export class InputBox extends Widget {
private contextViewProvider: IContextViewProvider;
private contextViewProvider?: IContextViewProvider;
element: HTMLElement;
private input: HTMLInputElement;
private mirror: HTMLElement;
private actionbar: ActionBar;
private actionbar?: ActionBar;
private options: IInputOptions;
private message: IMessage;
private message: IMessage | null;
private placeholder: string;
private ariaLabel: string;
private validation: IInputValidator;
private state = 'idle';
private cachedHeight: number;
private validation?: IInputValidator;
private state: string | null = 'idle';
private cachedHeight: number | null;
// {{SQL CARBON EDIT}}
// {{SQL CARBON EDIT}} - Add showValidationMessage and set inputBackground, inputForeground, and inputBorder as protected
protected showValidationMessage: boolean;
protected inputBackground: Color;
protected inputForeground: Color;
protected inputBorder: Color;
protected inputBackground?: Color | null;
protected inputForeground?: Color | null;
protected inputBorder?: Color | null;
// {{SQL CARBON EDIT}} - End
private inputValidationInfoBorder: Color;
private inputValidationInfoBackground: Color;
private inputValidationInfoForeground: Color;
private inputValidationWarningBorder: Color;
private inputValidationWarningBackground: Color;
private inputValidationWarningForeground: Color;
private inputValidationErrorBorder: Color;
private inputValidationErrorBackground: Color;
private inputValidationErrorForeground: Color;
private inputValidationInfoBorder?: Color | null;
private inputValidationInfoBackground?: Color | null;
private inputValidationInfoForeground?: Color | null;
private inputValidationWarningBorder?: Color | null;
private inputValidationWarningBackground?: Color | null;
private inputValidationWarningForeground?: Color | null;
private inputValidationErrorBorder?: Color | null;
private inputValidationErrorBackground?: Color | null;
private inputValidationErrorForeground?: Color | null;
private _onDidChange = this._register(new Emitter<string>());
public readonly onDidChange: Event<string> = this._onDidChange.event;
@@ -124,7 +125,7 @@ export class InputBox extends Widget {
private _onDidHeightChange = this._register(new Emitter<number>());
public readonly onDidHeightChange: Event<number> = this._onDidHeightChange.event;
constructor(container: HTMLElement, contextViewProvider: IContextViewProvider, options?: IInputOptions) {
constructor(container: HTMLElement, contextViewProvider: IContextViewProvider | undefined, options?: IInputOptions) {
super();
this.contextViewProvider = contextViewProvider;
@@ -392,7 +393,7 @@ export class InputBox extends Widget {
return errorMsg ? errorMsg.type !== MessageType.ERROR : true;
}
private stylesForType(type: MessageType): { border: Color; background: Color; foreground: Color } {
private stylesForType(type: MessageType | undefined): { border: Color | undefined | null; background: Color | undefined | null; foreground: Color | undefined | null } {
switch (type) {
case MessageType.INFO: return { border: this.inputValidationInfoBorder, background: this.inputValidationInfoBackground, foreground: this.inputValidationInfoForeground };
case MessageType.WARNING: return { border: this.inputValidationWarningBorder, background: this.inputValidationWarningBackground, foreground: this.inputValidationWarningForeground };
@@ -400,7 +401,7 @@ export class InputBox extends Widget {
}
}
private classForType(type: MessageType): string {
private classForType(type: MessageType | undefined): string {
switch (type) {
case MessageType.INFO: return 'info';
case MessageType.WARNING: return 'warning';
@@ -423,6 +424,10 @@ export class InputBox extends Widget {
getAnchor: () => this.element,
anchorAlignment: AnchorAlignment.RIGHT,
render: (container: HTMLElement) => {
if (!this.message) {
return null;
}
div = dom.append(container, $('.monaco-inputbox-container'));
layout();
@@ -465,7 +470,7 @@ export class InputBox extends Widget {
this.validate();
this.updateMirror();
if (this.state === 'open') {
if (this.state === 'open' && this.contextViewProvider) {
this.contextViewProvider.layout();
}
}
@@ -541,15 +546,13 @@ export class InputBox extends Widget {
public dispose(): void {
this._hideMessage();
this.element = null;
this.input = null;
this.contextViewProvider = null;
this.element = null!; // StrictNullOverride: nulling out ok in dispose
this.input = null!; // StrictNullOverride: nulling out ok in dispose
this.contextViewProvider = undefined;
this.message = null;
this.placeholder = null;
this.ariaLabel = null;
this.validation = null;
this.validation = undefined;
this.state = null;
this.actionbar = null;
this.actionbar = undefined;
super.dispose();
}
@@ -563,7 +566,7 @@ export class HistoryInputBox extends InputBox implements IHistoryNavigationWidge
private readonly history: HistoryNavigator<string>;
constructor(container: HTMLElement, contextViewProvider: IContextViewProvider, options: IHistoryInputOptions) {
constructor(container: HTMLElement, contextViewProvider: IContextViewProvider | undefined, options: IHistoryInputOptions) {
super(container, contextViewProvider, options);
this.history = new HistoryNavigator<string>(options.history, 100);
}
@@ -614,7 +617,7 @@ export class HistoryInputBox extends InputBox implements IHistoryNavigationWidge
this.history.clear();
}
private getCurrentValue(): string {
private getCurrentValue(): string | null {
let currentValue = this.history.current();
if (!currentValue) {
currentValue = this.history.last();
@@ -623,11 +626,11 @@ export class HistoryInputBox extends InputBox implements IHistoryNavigationWidge
return currentValue;
}
private getPreviousValue(): string {
private getPreviousValue(): string | null {
return this.history.previous() || this.history.first();
}
private getNextValue(): string {
private getNextValue(): string | null {
return this.history.next() || this.history.last();
}
}

View File

@@ -4,12 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./keybindingLabel';
import { IDisposable } from 'vs/base/common/lifecycle';
import { equals } from 'vs/base/common/objects';
import { OperatingSystem } from 'vs/base/common/platform';
import { ResolvedKeybinding, ResolvedKeybindingPart } from 'vs/base/common/keyCodes';
import { UILabelProvider } from 'vs/base/common/keybindingLabels';
import * as dom from 'vs/base/browser/dom';
import { localize } from 'vs/nls';
const $ = dom.$;
@@ -26,14 +26,18 @@ export interface Matches {
chordPart: PartMatches;
}
export class KeybindingLabel implements IDisposable {
export interface KeybindingLabelOptions {
renderUnboundKeybindings: boolean;
}
export class KeybindingLabel {
private domNode: HTMLElement;
private keybinding: ResolvedKeybinding;
private matches: Matches;
private matches: Matches | undefined;
private didEverRender: boolean;
constructor(container: HTMLElement, private os: OperatingSystem) {
constructor(container: HTMLElement, private os: OperatingSystem, private options?: KeybindingLabelOptions) {
this.domNode = dom.append(container, $('.monaco-keybinding'));
this.didEverRender = false;
container.appendChild(this.domNode);
@@ -43,7 +47,7 @@ export class KeybindingLabel implements IDisposable {
return this.domNode;
}
set(keybinding: ResolvedKeybinding, matches: Matches) {
set(keybinding: ResolvedKeybinding, matches?: Matches) {
if (this.didEverRender && this.keybinding === keybinding && KeybindingLabel.areSame(this.matches, matches)) {
return;
}
@@ -66,6 +70,8 @@ export class KeybindingLabel implements IDisposable {
this.renderPart(this.domNode, chordPart, this.matches ? this.matches.chordPart : null);
}
this.domNode.title = this.keybinding.getAriaLabel() || '';
} else if (this.options && this.options.renderUnboundKeybindings) {
this.renderUnbound(this.domNode);
}
this.didEverRender = true;
@@ -98,10 +104,11 @@ export class KeybindingLabel implements IDisposable {
}
}
dispose() {
private renderUnbound(parent: HTMLElement): void {
dom.append(parent, $('span.monaco-keybinding-key', undefined, localize('unbound', "Unbound")));
}
private static areSame(a: Matches, b: Matches): boolean {
private static areSame(a: Matches | undefined, b: Matches | undefined): boolean {
if (a === b || (!a && !b)) {
return true;
}

View File

@@ -29,6 +29,11 @@
height: 100%;
}
.monaco-list.horizontal-scrolling .monaco-list-rows {
width: auto;
min-width: 100%;
}
.monaco-list-row {
position: absolute;
-moz-box-sizing: border-box;
@@ -52,4 +57,138 @@
/* Focus */
.monaco-list.element-focused, .monaco-list.selection-single, .monaco-list.selection-multiple {
outline: 0 !important;
}
/* Dnd */
.monaco-list-drag-image {
display: inline-block;
padding: 1px 7px;
border-radius: 10px;
font-size: 12px;
position: absolute;
}
/* Type filter */
.monaco-list-type-filter {
display: flex;
align-items: center;
position: absolute;
border-radius: 2px;
padding: 0px 3px;
max-width: calc(100% - 10px);
text-overflow: ellipsis;
overflow: hidden;
text-align: right;
box-sizing: border-box;
cursor: all-scroll;
font-size: 13px;
line-height: 18px;
height: 20px;
z-index: 1;
top: 4px;
}
.monaco-list-type-filter.dragging {
transition: top 0.2s, left 0.2s;
}
.monaco-list-type-filter.ne {
right: 4px;
}
.monaco-list-type-filter.nw {
left: 4px;
}
.monaco-list-type-filter > .controls {
display: flex;
align-items: center;
box-sizing: border-box;
transition: width 0.2s;
width: 0;
}
.monaco-list-type-filter.dragging > .controls,
.monaco-list-type-filter:hover > .controls {
width: 36px;
}
.monaco-list-type-filter > .controls > * {
box-sizing: border-box;
width: 16px;
height: 16px;
margin: 0 0 0 2px;
flex-shrink: 0;
}
.monaco-list-type-filter > .controls > .filter {
-webkit-appearance: none;
width: 16px;
height: 16px;
background: url("media/no-filter.svg");
background-position: 50% 50%;
cursor: pointer;
}
.monaco-list-type-filter > .controls > .filter:checked {
background-image: url("media/filter.svg");
}
.vs-dark .monaco-list-type-filter > .controls > .filter {
background-image: url("media/no-filter-dark.svg");
}
.vs-dark .monaco-list-type-filter > .controls > .filter:checked {
background-image: url("media/filter-dark.svg");
}
.hc-black .monaco-list-type-filter > .controls > .filter {
background-image: url("media/no-filter-hc.svg");
}
.hc-black .monaco-list-type-filter > .controls > .filter:checked {
background-image: url("media/filter-hc.svg");
}
.monaco-list-type-filter > .controls > .clear {
border: none;
background: url("media/close.svg");
cursor: pointer;
}
.vs-dark .monaco-list-type-filter > .controls > .clear {
background-image: url("media/close-dark.svg");
}
.hc-black .monaco-list-type-filter > .controls > .clear {
background-image: url("media/close-hc.svg");
}
.monaco-list-type-filter-message {
position: absolute;
box-sizing: border-box;
width: 100%;
height: 100%;
top: 0;
left: 0;
padding: 40px 1em 1em 1em;
text-align: center;
white-space: normal;
opacity: 0.7;
pointer-events: none;
}
.monaco-list-type-filter-message:empty {
display: none;
}
/* Electron */
.monaco-list-type-filter {
cursor: -webkit-grab;
}
.monaco-list-type-filter.dragging {
cursor: -webkit-grabbing;
}

View File

@@ -4,6 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import { GestureEvent } from 'vs/base/browser/touch';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IDragAndDropData } from 'vs/base/browser/dnd';
export interface IListVirtualDelegate<T> {
getHeight(element: T): number;
@@ -15,7 +17,7 @@ export interface IListRenderer<T, TTemplateData> {
templateId: string;
renderTemplate(container: HTMLElement): TTemplateData;
renderElement(element: T, index: number, templateData: TTemplateData): void;
disposeElement(element: T, index: number, templateData: TTemplateData): void;
disposeElement?(element: T, index: number, templateData: TTemplateData): void;
disposeTemplate(templateData: TTemplateData): void;
}
@@ -43,6 +45,12 @@ export interface IListGestureEvent<T> {
index: number | undefined;
}
export interface IListDragEvent<T> {
browserEvent: DragEvent;
element: T | undefined;
index: number | undefined;
}
export interface IListContextMenuEvent<T> {
browserEvent: UIEvent;
element: T | undefined;
@@ -52,4 +60,47 @@ export interface IListContextMenuEvent<T> {
export interface IIdentityProvider<T> {
getId(element: T): { toString(): string; };
}
}
export enum ListAriaRootRole {
/** default tree structure role */
TREE = 'tree',
/** role='tree' can interfere with screenreaders reading nested elements inside the tree row. Use FORM in that case. */
FORM = 'form'
}
export interface IKeyboardNavigationLabelProvider<T> {
/**
* Return a keyboard navigation label which will be used by the
* list for filtering/navigating. Return `undefined` to make an
* element always match.
*/
getKeyboardNavigationLabel(element: T): { toString(): string | undefined; } | undefined;
mightProducePrintableCharacter?(event: IKeyboardEvent): boolean;
}
export const enum ListDragOverEffect {
Copy,
Move
}
export interface IListDragOverReaction {
accept: boolean;
effect?: ListDragOverEffect;
feedback?: number[]; // use -1 for entire list
}
export const ListDragOverReactions = {
reject(): IListDragOverReaction { return { accept: false }; },
accept(): IListDragOverReaction { return { accept: true }; },
};
export interface IListDragAndDrop<T> {
getDragURI(element: T): string | null;
getDragLabel?(elements: T[]): string | undefined;
onDragStart?(data: IDragAndDropData, originalEvent: DragEvent): void;
onDragOver(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | IListDragOverReaction;
drop(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void;
}

View File

@@ -9,7 +9,7 @@ import { range } from 'vs/base/common/arrays';
import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent } from './list';
import { List, IListStyles, IListOptions } from './listWidget';
import { IPagedModel } from 'vs/base/common/paging';
import { Event, mapEvent } from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
export interface IPagedRenderer<TElement, TTemplateData> extends IListRenderer<TElement, TTemplateData> {
@@ -58,10 +58,6 @@ class PagedRenderer<TElement, TTemplateData> implements IListRenderer<number, IT
promise.then(entry => this.renderer.renderElement(entry, index, data.data!));
}
disposeElement(): void {
// noop
}
disposeTemplate(data: ITemplateData<TTemplateData>): void {
if (data.disposable) {
data.disposable.dispose();
@@ -118,23 +114,23 @@ export class PagedList<T> implements IDisposable {
}
get onFocusChange(): Event<IListEvent<T>> {
return mapEvent(this.list.onFocusChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
return Event.map(this.list.onFocusChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
}
get onOpen(): Event<IListEvent<T>> {
return mapEvent(this.list.onOpen, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));
return Event.map(this.list.onDidOpen, ({ 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 }));
return Event.map(this.list.onSelectionChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
}
get onPin(): Event<IListEvent<T>> {
return mapEvent(this.list.onPin, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
return Event.map(this.list.onPin, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
}
get onContextMenu(): Event<IListContextMenuEvent<T>> {
return mapEvent(this.list.onContextMenu, ({ element, index, anchor, browserEvent }) => (typeof element === 'undefined' ? { element, index, anchor, browserEvent } : { element: this._model.get(element), index, anchor, browserEvent }));
return Event.map(this.list.onContextMenu, ({ element, index, anchor, browserEvent }) => (typeof element === 'undefined' ? { element, index, anchor, browserEvent } : { element: this._model.get(element), index, anchor, browserEvent }));
}
get model(): IPagedModel<T> {
@@ -194,8 +190,8 @@ export class PagedList<T> implements IDisposable {
return this.list.getSelection();
}
layout(height?: number): void {
this.list.layout(height);
layout(height?: number, width?: number): void {
this.list.layout(height, width);
}
reveal(index: number, relativeTop?: number): void {

View File

@@ -4,34 +4,24 @@
*--------------------------------------------------------------------------------------------*/
import { getOrDefault } from 'vs/base/common/objects';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { Gesture, EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch';
import * as DOM from 'vs/base/browser/dom';
import { Event, mapEvent, filterEvent, Emitter, latch } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { domEvent } from 'vs/base/browser/event';
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { ScrollEvent, ScrollbarVisibility, INewScrollDimensions } from 'vs/base/common/scrollable';
import { RangeMap, shift } from './rangeMap';
import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListTouchEvent, IListGestureEvent } from './list';
import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListTouchEvent, IListGestureEvent, IListDragEvent, IListDragAndDrop, ListDragOverEffect } from './list';
import { RowCache, IRow } from './rowCache';
import { isWindows } from 'vs/base/common/platform';
import * as browser from 'vs/base/browser/browser';
import { ISpliceable } from 'vs/base/common/sequence';
import { memoize } from 'vs/base/common/decorators';
import { DragMouseEvent } from 'vs/base/browser/mouseEvent';
import { Range, IRange } from 'vs/base/common/range';
function canUseTranslate3d(): boolean {
if (browser.isFirefox) {
return false;
}
if (browser.getZoomLevel() !== 0) {
return false;
}
return true;
}
import { equals, distinct } from 'vs/base/common/arrays';
import { DataTransfers, StaticDND, IDragAndDropData } from 'vs/base/browser/dnd';
import { disposableTimeout, Delayer } from 'vs/base/common/async';
interface IItem<T> {
readonly id: string;
@@ -39,25 +29,117 @@ interface IItem<T> {
readonly templateId: string;
row: IRow | null;
size: number;
width: number | undefined;
hasDynamicHeight: boolean;
renderWidth: number | undefined;
lastDynamicHeightWidth: number | undefined;
uri: string | undefined;
dropTarget: boolean;
dragStartDisposable: IDisposable;
}
export interface IListViewOptions {
export interface IListViewDragAndDrop<T> extends IListDragAndDrop<T> {
getDragElements(element: T): T[];
}
export interface IListViewOptions<T> {
readonly dnd?: IListViewDragAndDrop<T>;
readonly useShadows?: boolean;
readonly verticalScrollMode?: ScrollbarVisibility;
readonly setRowLineHeight?: boolean;
readonly supportDynamicHeights?: boolean;
readonly mouseSupport?: boolean;
readonly horizontalScrolling?: boolean;
}
const DefaultOptions = {
useShadows: true,
verticalScrollMode: ScrollbarVisibility.Auto,
setRowLineHeight: true,
supportDynamicHeights: false
supportDynamicHeights: false,
dnd: {
getDragElements(e) { return [e]; },
getDragURI() { return null; },
onDragStart(): void { },
onDragOver() { return false; },
drop() { }
},
horizontalScrolling: false
};
export class ElementsDragAndDropData<T> implements IDragAndDropData {
readonly elements: T[];
constructor(elements: T[]) {
this.elements = elements;
}
update(): void { }
getData(): any {
return this.elements;
}
}
export class ExternalElementsDragAndDropData<T> implements IDragAndDropData {
readonly elements: T[];
constructor(elements: T[]) {
this.elements = elements;
}
update(): void { }
getData(): any {
return this.elements;
}
}
export class DesktopDragAndDropData implements IDragAndDropData {
readonly types: any[];
readonly files: any[];
constructor() {
this.types = [];
this.files = [];
}
update(dataTransfer: DataTransfer): void {
if (dataTransfer.types) {
this.types.splice(0, this.types.length, ...dataTransfer.types);
}
if (dataTransfer.files) {
this.files.splice(0, this.files.length);
for (let i = 0; i < dataTransfer.files.length; i++) {
const file = dataTransfer.files.item(i);
if (file && (file.size || file.type)) {
this.files.push(file);
}
}
}
}
getData(): any {
return {
types: this.types,
files: this.files
};
}
}
function equalsDragFeedback(f1: number[] | undefined, f2: number[] | undefined): boolean {
if (Array.isArray(f1) && Array.isArray(f2)) {
return equals(f1, f2!);
}
return f1 === f2;
}
export class ListView<T> implements ISpliceable<T>, IDisposable {
readonly domNode: HTMLElement;
@@ -75,24 +157,50 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
private scrollableElement: ScrollableElement;
private _scrollHeight: number;
private scrollableElementUpdateDisposable: IDisposable | null = null;
private scrollableElementWidthDelayer = new Delayer<void>(50);
private splicing = false;
private dragAndDropScrollInterval: number;
private dragAndDropScrollTimeout: number;
private dragAndDropMouseY: number;
private dragOverAnimationDisposable: IDisposable | undefined;
private dragOverAnimationStopDisposable: IDisposable = Disposable.None;
private dragOverMouseY: number;
private setRowLineHeight: boolean;
private supportDynamicHeights: boolean;
private horizontalScrolling: boolean;
private scrollWidth: number | undefined;
private canUseTranslate3d: boolean | undefined = undefined;
private dnd: IListViewDragAndDrop<T>;
private canDrop: boolean = false;
private currentDragData: IDragAndDropData | undefined;
private currentDragFeedback: number[] | undefined;
private currentDragFeedbackDisposable: IDisposable = Disposable.None;
private onDragLeaveTimeout: IDisposable = Disposable.None;
private disposables: IDisposable[];
private _onDidChangeContentHeight = new Emitter<number>();
readonly onDidChangeContentHeight: Event<number> = latch(this._onDidChangeContentHeight.event);
readonly onDidChangeContentHeight: Event<number> = Event.latch(this._onDidChangeContentHeight.event);
get contentHeight(): number { return this.rangeMap.size; }
readonly onDidScroll: Event<void>;
// private _onDragStart = new Emitter<{ element: T, uri: string, event: DragEvent }>();
// readonly onDragStart = this._onDragStart.event;
// readonly onDragOver: Event<IListDragEvent<T>>;
// readonly onDragLeave: Event<void>;
// readonly onDrop: Event<IListDragEvent<T>>;
// readonly onDragEnd: Event<void>;
constructor(
container: HTMLElement,
private virtualDelegate: IListVirtualDelegate<T>,
renderers: IListRenderer<any /* TODO@joao */, any>[],
options: IListViewOptions = DefaultOptions
options: IListViewOptions<T> = DefaultOptions
) {
if (options.horizontalScrolling && options.supportDynamicHeights) {
throw new Error('Horizontal scrolling and dynamic heights not supported simultaneously');
}
this.items = [];
this.itemId = 0;
this.rangeMap = new RangeMap();
@@ -110,13 +218,16 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.domNode.className = 'monaco-list';
DOM.toggleClass(this.domNode, 'mouse-support', typeof options.mouseSupport === 'boolean' ? options.mouseSupport : true);
this.horizontalScrolling = getOrDefault(options, o => o.horizontalScrolling, DefaultOptions.horizontalScrolling);
DOM.toggleClass(this.domNode, 'horizontal-scrolling', this.horizontalScrolling);
this.rowsContainer = document.createElement('div');
this.rowsContainer.className = 'monaco-list-rows';
Gesture.addTarget(this.rowsContainer);
this.scrollableElement = new ScrollableElement(this.rowsContainer, {
alwaysConsumeMouseWheel: true,
horizontal: ScrollbarVisibility.Hidden,
horizontal: this.horizontalScrolling ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden,
vertical: getOrDefault(options, o => o.verticalScrollMode, DefaultOptions.verticalScrollMode),
useShadows: getOrDefault(options, o => o.useShadows, DefaultOptions.useShadows)
});
@@ -126,6 +237,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.disposables = [this.rangeMap, this.gesture, this.scrollableElement, this.cache];
this.onDidScroll = Event.signal(this.scrollableElement.onScroll);
this.scrollableElement.onScroll(this.onScroll, this, this.disposables);
domEvent(this.rowsContainer, TouchEventType.Change)(this.onTouchChange, this, this.disposables);
@@ -134,11 +246,14 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
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);
Event.map(domEvent(this.domNode, 'dragover'), e => this.toDragEvent(e))(this.onDragOver, this, this.disposables);
Event.map(domEvent(this.domNode, 'drop'), e => this.toDragEvent(e))(this.onDrop, this, this.disposables);
domEvent(this.domNode, 'dragleave')(this.onDragLeave, this, this.disposables);
domEvent(window, 'dragend')(this.onDragEnd, this, this.disposables);
this.setRowLineHeight = getOrDefault(options, o => o.setRowLineHeight, DefaultOptions.setRowLineHeight);
this.supportDynamicHeights = getOrDefault(options, o => o.supportDynamicHeights, DefaultOptions.supportDynamicHeights);
this.dnd = getOrDefault<IListViewOptions<T>, IListViewDragAndDrop<T>>(options, o => o.dnd, DefaultOptions.dnd);
this.layout();
}
@@ -176,9 +291,13 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
element,
templateId: this.virtualDelegate.getTemplateId(element),
size: this.virtualDelegate.getHeight(element),
width: undefined,
hasDynamicHeight: !!this.virtualDelegate.hasDynamicHeight && this.virtualDelegate.hasDynamicHeight(element),
renderWidth: undefined,
row: null
lastDynamicHeightWidth: undefined,
row: null,
uri: undefined,
dropTarget: false,
dragStartDisposable: Disposable.None
}));
let deleted: IItem<T>[];
@@ -222,7 +341,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
}
}
this.updateScrollHeight();
this.eventuallyUpdateScrollDimensions();
if (this.supportDynamicHeights) {
this.rerender(this.scrollTop, this.renderHeight);
@@ -231,18 +350,62 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
return deleted.map(i => i.element);
}
private updateScrollHeight(): void {
private eventuallyUpdateScrollDimensions(): void {
this._scrollHeight = this.contentHeight;
this.rowsContainer.style.height = `${this._scrollHeight}px`;
if (!this.scrollableElementUpdateDisposable) {
this.scrollableElementUpdateDisposable = DOM.scheduleAtNextAnimationFrame(() => {
this.scrollableElement.setScrollDimensions({ scrollHeight: this._scrollHeight });
this.scrollableElement.setScrollDimensions({ scrollHeight: this.scrollHeight });
this.updateScrollWidth();
this.scrollableElementUpdateDisposable = null;
});
}
}
private eventuallyUpdateScrollWidth(): void {
if (!this.horizontalScrolling) {
return;
}
this.scrollableElementWidthDelayer.trigger(() => this.updateScrollWidth());
}
private updateScrollWidth(): void {
if (!this.horizontalScrolling) {
return;
}
if (this.items.length === 0) {
this.scrollableElement.setScrollDimensions({ scrollWidth: 0 });
}
let scrollWidth = 0;
for (const item of this.items) {
if (typeof item.width !== 'undefined') {
scrollWidth = Math.max(scrollWidth, item.width);
}
}
this.scrollWidth = scrollWidth;
this.scrollableElement.setScrollDimensions({ scrollWidth: scrollWidth + 10 });
}
updateWidth(index: number): void {
if (!this.horizontalScrolling || typeof this.scrollWidth === 'undefined') {
return;
}
const item = this.items[index];
this.measureItemWidth(item);
if (typeof item.width !== 'undefined' && item.width > this.scrollWidth) {
this.scrollWidth = item.width;
this.scrollableElement.setScrollDimensions({ scrollWidth: this.scrollWidth + 10 });
}
}
get length(): number {
return this.items.length;
}
@@ -277,31 +440,37 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
return this.rangeMap.indexAfter(position);
}
layout(height?: number): void {
layout(height?: number, width?: number): void {
let scrollDimensions: INewScrollDimensions = {
height: height || DOM.getContentHeight(this.domNode)
height: typeof height === 'number' ? height : DOM.getContentHeight(this.domNode)
};
if (this.scrollableElementUpdateDisposable) {
this.scrollableElementUpdateDisposable.dispose();
this.scrollableElementUpdateDisposable = null;
scrollDimensions.scrollHeight = this._scrollHeight;
scrollDimensions.scrollHeight = this.scrollHeight;
}
this.scrollableElement.setScrollDimensions(scrollDimensions);
}
layoutWidth(width: number): void {
this.renderWidth = width;
if (typeof width !== 'undefined') {
this.renderWidth = width;
if (this.supportDynamicHeights) {
this.rerender(this.scrollTop, this.renderHeight);
if (this.supportDynamicHeights) {
this.rerender(this.scrollTop, this.renderHeight);
}
if (this.horizontalScrolling) {
this.scrollableElement.setScrollDimensions({
width: typeof width === 'number' ? width : DOM.getContentWidth(this.domNode)
});
}
}
}
// Render
private render(renderTop: number, renderHeight: number): void {
private render(renderTop: number, renderHeight: number, renderLeft: number, scrollWidth: number): void {
const previousRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight);
const renderRange = this.getRenderRange(renderTop, renderHeight);
@@ -321,14 +490,33 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
}
}
if (canUseTranslate3d() && !isWindows /* Windows: translate3d breaks subpixel-antialias (ClearType) unless a background is defined */) {
const transform = `translate3d(0px, -${renderTop}px, 0px)`;
const canUseTranslate3d = !isWindows && !browser.isFirefox && browser.getZoomLevel() === 0;
if (canUseTranslate3d) {
const transform = `translate3d(-${renderLeft}px, -${renderTop}px, 0px)`;
this.rowsContainer.style.transform = transform;
this.rowsContainer.style.webkitTransform = transform;
if (canUseTranslate3d !== this.canUseTranslate3d) {
this.rowsContainer.style.left = '0';
this.rowsContainer.style.top = '0';
}
} else {
this.rowsContainer.style.left = `-${renderLeft}px`;
this.rowsContainer.style.top = `-${renderTop}px`;
if (canUseTranslate3d !== this.canUseTranslate3d) {
this.rowsContainer.style.transform = '';
this.rowsContainer.style.webkitTransform = '';
}
}
if (this.horizontalScrolling) {
this.rowsContainer.style.width = `${Math.max(scrollWidth, this.renderWidth)}px`;
}
this.canUseTranslate3d = canUseTranslate3d;
this.lastRenderTop = renderTop;
this.lastRenderHeight = renderHeight;
}
@@ -353,7 +541,48 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.updateItemInDOM(item, index);
const renderer = this.renderers.get(item.templateId);
renderer.renderElement(item.element, index, item.row.templateData);
if (!renderer) {
throw new Error(`No renderer found for template id ${item.templateId}`);
}
if (renderer) {
renderer.renderElement(item.element, index, item.row.templateData);
}
const uri = this.dnd.getDragURI(item.element);
item.dragStartDisposable.dispose();
item.row.domNode!.draggable = !!uri;
if (uri) {
const onDragStart = domEvent(item.row.domNode!, 'dragstart');
item.dragStartDisposable = onDragStart(event => this.onDragStart(item.element, uri, event));
}
if (this.horizontalScrolling) {
this.measureItemWidth(item);
this.eventuallyUpdateScrollWidth();
}
}
private measureItemWidth(item: IItem<T>): void {
if (!item.row || !item.row.domNode) {
return;
}
item.row.domNode.style.width = 'fit-content';
item.width = DOM.getContentWidth(item.row.domNode);
const style = window.getComputedStyle(item.row.domNode);
if (style.paddingLeft) {
item.width += parseFloat(style.paddingLeft);
}
if (style.paddingRight) {
item.width += parseFloat(style.paddingRight);
}
item.row.domNode.style.width = '';
}
private updateItemInDOM(item: IItem<T>, index: number): void {
@@ -368,18 +597,24 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
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}`);
DOM.toggleClass(item.row!.domNode!, 'drop-target', item.dropTarget);
}
private removeItemFromDOM(index: number): void {
const item = this.items[index];
const renderer = this.renderers.get(item.templateId);
item.dragStartDisposable.dispose();
if (renderer.disposeElement) {
const renderer = this.renderers.get(item.templateId);
if (renderer && renderer.disposeElement) {
renderer.disposeElement(item.element, index, item.row!.templateData);
}
this.cache.release(item.row!);
item.row = null;
if (this.horizontalScrolling) {
this.eventuallyUpdateScrollWidth();
}
}
getScrollTop(): number {
@@ -391,7 +626,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
if (this.scrollableElementUpdateDisposable) {
this.scrollableElementUpdateDisposable.dispose();
this.scrollableElementUpdateDisposable = null;
this.scrollableElement.setScrollDimensions({ scrollHeight: this._scrollHeight });
this.scrollableElement.setScrollDimensions({ scrollHeight: this.scrollHeight });
}
this.scrollableElement.setScrollPosition({ scrollTop });
@@ -406,22 +641,22 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
}
get scrollHeight(): number {
return this._scrollHeight;
return this._scrollHeight + (this.horizontalScrolling ? 10 : 0);
}
// Events
@memoize get onMouseClick(): Event<IListMouseEvent<T>> { return mapEvent(domEvent(this.domNode, 'click'), e => this.toMouseEvent(e)); }
@memoize get onMouseDblClick(): Event<IListMouseEvent<T>> { return mapEvent(domEvent(this.domNode, 'dblclick'), e => this.toMouseEvent(e)); }
@memoize get onMouseMiddleClick(): Event<IListMouseEvent<T>> { return filterEvent(mapEvent(domEvent(this.domNode, 'auxclick'), e => this.toMouseEvent(e as MouseEvent)), e => e.browserEvent.button === 1); }
@memoize get onMouseUp(): Event<IListMouseEvent<T>> { return mapEvent(domEvent(this.domNode, 'mouseup'), e => this.toMouseEvent(e)); }
@memoize get onMouseDown(): Event<IListMouseEvent<T>> { return mapEvent(domEvent(this.domNode, 'mousedown'), e => this.toMouseEvent(e)); }
@memoize get onMouseOver(): Event<IListMouseEvent<T>> { return mapEvent(domEvent(this.domNode, 'mouseover'), e => this.toMouseEvent(e)); }
@memoize get onMouseMove(): Event<IListMouseEvent<T>> { return mapEvent(domEvent(this.domNode, 'mousemove'), e => this.toMouseEvent(e)); }
@memoize get onMouseOut(): Event<IListMouseEvent<T>> { return mapEvent(domEvent(this.domNode, 'mouseout'), e => this.toMouseEvent(e)); }
@memoize get onContextMenu(): Event<IListMouseEvent<T>> { return mapEvent(domEvent(this.domNode, 'contextmenu'), e => this.toMouseEvent(e)); }
@memoize get onTouchStart(): Event<IListTouchEvent<T>> { return mapEvent(domEvent(this.domNode, 'touchstart'), e => this.toTouchEvent(e)); }
@memoize get onTap(): Event<IListGestureEvent<T>> { return mapEvent(domEvent(this.rowsContainer, TouchEventType.Tap), e => this.toGestureEvent(e)); }
@memoize get onMouseClick(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'click'), e => this.toMouseEvent(e)); }
@memoize get onMouseDblClick(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'dblclick'), e => this.toMouseEvent(e)); }
@memoize get onMouseMiddleClick(): Event<IListMouseEvent<T>> { return Event.filter(Event.map(domEvent(this.domNode, 'auxclick'), e => this.toMouseEvent(e as MouseEvent)), e => e.browserEvent.button === 1); }
@memoize get onMouseUp(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'mouseup'), e => this.toMouseEvent(e)); }
@memoize get onMouseDown(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'mousedown'), e => this.toMouseEvent(e)); }
@memoize get onMouseOver(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'mouseover'), e => this.toMouseEvent(e)); }
@memoize get onMouseMove(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'mousemove'), e => this.toMouseEvent(e)); }
@memoize get onMouseOut(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'mouseout'), e => this.toMouseEvent(e)); }
@memoize get onContextMenu(): Event<IListMouseEvent<T>> { return Event.map(domEvent(this.domNode, 'contextmenu'), e => this.toMouseEvent(e)); }
@memoize get onTouchStart(): Event<IListTouchEvent<T>> { return Event.map(domEvent(this.domNode, 'touchstart'), e => this.toTouchEvent(e)); }
@memoize get onTap(): Event<IListGestureEvent<T>> { return Event.map(domEvent(this.rowsContainer, TouchEventType.Tap), e => this.toGestureEvent(e)); }
private toMouseEvent(browserEvent: MouseEvent): IListMouseEvent<T> {
const index = this.getItemIndexFromEventTarget(browserEvent.target || null);
@@ -444,9 +679,16 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
return { browserEvent, index, element };
}
private toDragEvent(browserEvent: DragEvent): IListDragEvent<T> {
const index = this.getItemIndexFromEventTarget(browserEvent.target || null);
const item = typeof index === 'undefined' ? undefined : this.items[index];
const element = item && item.element;
return { browserEvent, index, element };
}
private onScroll(e: ScrollEvent): void {
try {
this.render(e.scrollTop, e.height);
this.render(e.scrollTop, e.height, e.scrollLeft, e.scrollWidth);
if (this.supportDynamicHeights) {
this.rerender(e.scrollTop, e.height);
@@ -464,55 +706,218 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.scrollTop -= event.translationY;
}
private onDragOver(event: DragMouseEvent): void {
this.setupDragAndDropScrollInterval();
this.dragAndDropMouseY = event.posy;
}
// DND
private setupDragAndDropScrollInterval(): void {
const viewTop = DOM.getTopLeftOffset(this.domNode).top;
private onDragStart(element: T, uri: string, event: DragEvent): void {
if (!event.dataTransfer) {
return;
}
if (!this.dragAndDropScrollInterval) {
this.dragAndDropScrollInterval = window.setInterval(() => {
if (this.dragAndDropMouseY === undefined) {
return;
}
const elements = this.dnd.getDragElements(element);
var diff = this.dragAndDropMouseY - viewTop;
var scrollDiff = 0;
var upperLimit = this.renderHeight - 35;
event.dataTransfer.effectAllowed = 'copyMove';
event.dataTransfer.setData(DataTransfers.RESOURCES, JSON.stringify([uri]));
if (diff < 35) {
scrollDiff = Math.max(-14, 0.2 * (diff - 35));
} else if (diff > upperLimit) {
scrollDiff = Math.min(14, 0.2 * (diff - upperLimit));
}
if (event.dataTransfer.setDragImage) {
let label: string | undefined;
this.scrollTop += scrollDiff;
}, 10);
if (this.dnd.getDragLabel) {
label = this.dnd.getDragLabel(elements);
}
this.cancelDragAndDropScrollTimeout();
if (typeof label === 'undefined') {
label = String(elements.length);
}
this.dragAndDropScrollTimeout = window.setTimeout(() => {
this.cancelDragAndDropScrollInterval();
this.dragAndDropScrollTimeout = -1;
}, 1000);
const dragImage = DOM.$('.monaco-list-drag-image');
dragImage.textContent = label;
document.body.appendChild(dragImage);
event.dataTransfer.setDragImage(dragImage, -10, -10);
setTimeout(() => document.body.removeChild(dragImage), 0);
}
this.currentDragData = new ElementsDragAndDropData(elements);
StaticDND.CurrentDragAndDropData = new ExternalElementsDragAndDropData(elements);
if (this.dnd.onDragStart) {
this.dnd.onDragStart(this.currentDragData, event);
}
}
private cancelDragAndDropScrollInterval(): void {
if (this.dragAndDropScrollInterval) {
window.clearInterval(this.dragAndDropScrollInterval);
this.dragAndDropScrollInterval = -1;
private onDragOver(event: IListDragEvent<T>): boolean {
this.onDragLeaveTimeout.dispose();
if (StaticDND.CurrentDragAndDropData && StaticDND.CurrentDragAndDropData.getData() === 'vscode-ui') {
return false;
}
this.cancelDragAndDropScrollTimeout();
this.setupDragAndDropScrollTopAnimation(event.browserEvent);
if (!event.browserEvent.dataTransfer) {
return false;
}
// Drag over from outside
if (!this.currentDragData) {
if (StaticDND.CurrentDragAndDropData) {
// Drag over from another list
this.currentDragData = StaticDND.CurrentDragAndDropData;
} else {
// Drag over from the desktop
if (!event.browserEvent.dataTransfer.types) {
return false;
}
this.currentDragData = new DesktopDragAndDropData();
}
}
const result = this.dnd.onDragOver(this.currentDragData, event.element, event.index, event.browserEvent);
this.canDrop = typeof result === 'boolean' ? result : result.accept;
if (!this.canDrop) {
this.currentDragFeedback = undefined;
this.currentDragFeedbackDisposable.dispose();
return false;
}
event.browserEvent.dataTransfer.dropEffect = (typeof result !== 'boolean' && result.effect === ListDragOverEffect.Copy) ? 'copy' : 'move';
let feedback: number[];
if (typeof result !== 'boolean' && result.feedback) {
feedback = result.feedback;
} else {
if (typeof event.index === 'undefined') {
feedback = [-1];
} else {
feedback = [event.index];
}
}
// sanitize feedback list
feedback = distinct(feedback).filter(i => i >= -1 && i < this.length).sort();
feedback = feedback[0] === -1 ? [-1] : feedback;
if (feedback.length === 0) {
throw new Error('Invalid empty feedback list');
}
if (equalsDragFeedback(this.currentDragFeedback, feedback)) {
return true;
}
this.currentDragFeedback = feedback;
this.currentDragFeedbackDisposable.dispose();
if (feedback[0] === -1) { // entire list feedback
DOM.addClass(this.domNode, 'drop-target');
this.currentDragFeedbackDisposable = toDisposable(() => DOM.removeClass(this.domNode, 'drop-target'));
} else {
for (const index of feedback) {
const item = this.items[index]!;
item.dropTarget = true;
if (item.row && item.row.domNode) {
DOM.addClass(item.row.domNode, 'drop-target');
}
}
this.currentDragFeedbackDisposable = toDisposable(() => {
for (const index of feedback) {
const item = this.items[index]!;
item.dropTarget = false;
if (item.row && item.row.domNode) {
DOM.removeClass(item.row.domNode, 'drop-target');
}
}
});
}
return true;
}
private cancelDragAndDropScrollTimeout(): void {
if (this.dragAndDropScrollTimeout) {
window.clearTimeout(this.dragAndDropScrollTimeout);
this.dragAndDropScrollTimeout = -1;
private onDragLeave(): void {
this.onDragLeaveTimeout.dispose();
this.onDragLeaveTimeout = disposableTimeout(() => this.clearDragOverFeedback(), 100);
}
private onDrop(event: IListDragEvent<T>): void {
if (!this.canDrop) {
return;
}
const dragData = this.currentDragData;
this.teardownDragAndDropScrollTopAnimation();
this.clearDragOverFeedback();
this.currentDragData = undefined;
StaticDND.CurrentDragAndDropData = undefined;
if (!dragData || !event.browserEvent.dataTransfer) {
return;
}
event.browserEvent.preventDefault();
dragData.update(event.browserEvent.dataTransfer);
this.dnd.drop(dragData, event.element, event.index, event.browserEvent);
}
private onDragEnd(): void {
this.canDrop = false;
this.teardownDragAndDropScrollTopAnimation();
this.clearDragOverFeedback();
this.currentDragData = undefined;
StaticDND.CurrentDragAndDropData = undefined;
}
private clearDragOverFeedback(): void {
this.currentDragFeedback = undefined;
this.currentDragFeedbackDisposable.dispose();
this.currentDragFeedbackDisposable = Disposable.None;
}
// DND scroll top animation
private setupDragAndDropScrollTopAnimation(event: DragEvent): void {
if (!this.dragOverAnimationDisposable) {
const viewTop = DOM.getTopLeftOffset(this.domNode).top;
this.dragOverAnimationDisposable = DOM.animate(this.animateDragAndDropScrollTop.bind(this, viewTop));
}
this.dragOverAnimationStopDisposable.dispose();
this.dragOverAnimationStopDisposable = disposableTimeout(() => {
if (this.dragOverAnimationDisposable) {
this.dragOverAnimationDisposable.dispose();
this.dragOverAnimationDisposable = undefined;
}
}, 1000);
this.dragOverMouseY = event.pageY;
}
private animateDragAndDropScrollTop(viewTop: number): void {
if (this.dragOverMouseY === undefined) {
return;
}
const diff = this.dragOverMouseY - viewTop;
const upperLimit = this.renderHeight - 35;
if (diff < 35) {
this.scrollTop += Math.max(-14, Math.floor(0.3 * (diff - 35)));
} else if (diff > upperLimit) {
this.scrollTop += Math.min(14, Math.floor(0.3 * (diff - upperLimit)));
}
}
private teardownDragAndDropScrollTopAnimation(): void {
this.dragOverAnimationStopDisposable.dispose();
if (this.dragOverAnimationDisposable) {
this.dragOverAnimationDisposable.dispose();
this.dragOverAnimationDisposable = undefined;
}
}
@@ -554,12 +959,15 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
// Let's remember the second element's position, this helps in scrolling up
// and preserving a linear upwards scroll movement
let secondElementIndex: number | undefined;
let secondElementTopDelta: number | undefined;
let anchorElementIndex: number | undefined;
let anchorElementTopDelta: number | undefined;
if (previousRenderRange.end - previousRenderRange.start > 1) {
secondElementIndex = previousRenderRange.start + 1;
secondElementTopDelta = this.elementTop(secondElementIndex) - renderTop;
if (renderTop === this.elementTop(previousRenderRange.start)) {
anchorElementIndex = previousRenderRange.start;
anchorElementTopDelta = 0;
} else if (previousRenderRange.end - previousRenderRange.start > 1) {
anchorElementIndex = previousRenderRange.start + 1;
anchorElementTopDelta = this.elementTop(anchorElementIndex) - renderTop;
}
let heightDiff = 0;
@@ -582,7 +990,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
if (!didChange) {
if (heightDiff !== 0) {
this.updateScrollHeight();
this.eventuallyUpdateScrollDimensions();
}
const unrenderRanges = Range.relativeComplement(previousRenderRange, renderRange);
@@ -595,14 +1003,25 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
}
}
const renderRanges = Range.relativeComplement(renderRange, previousRenderRange);
for (const range of renderRanges) {
for (let i = range.start; i < range.end; i++) {
const afterIndex = i + 1;
const beforeRow = afterIndex < this.items.length ? this.items[afterIndex].row : null;
const beforeElement = beforeRow ? beforeRow.domNode : null;
this.insertItemInDOM(i, beforeElement);
}
}
for (let i = renderRange.start; i < renderRange.end; i++) {
if (this.items[i].row) {
this.updateItemInDOM(this.items[i], i);
}
}
if (typeof secondElementIndex === 'number') {
this.scrollTop = this.elementTop(secondElementIndex) - secondElementTopDelta!;
if (typeof anchorElementIndex === 'number') {
this.scrollTop = this.elementTop(anchorElementIndex) - anchorElementTopDelta!;
}
this._onDidChangeContentHeight.fire(this.contentHeight);
@@ -614,7 +1033,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
private probeDynamicHeight(index: number): number {
const item = this.items[index];
if (!item.hasDynamicHeight || item.renderWidth === this.renderWidth) {
if (!item.hasDynamicHeight || item.lastDynamicHeightWidth === this.renderWidth) {
return 0;
}
@@ -624,9 +1043,11 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
row.domNode!.style.height = '';
this.rowsContainer.appendChild(row.domNode!);
renderer.renderElement(item.element, index, row.templateData);
if (renderer) {
renderer.renderElement(item.element, index, row.templateData);
}
item.size = row.domNode!.offsetHeight;
item.renderWidth = this.renderWidth;
item.lastDynamicHeightWidth = this.renderWidth;
this.rowsContainer.removeChild(row.domNode!);
this.cache.release(row);
@@ -660,7 +1081,9 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
for (const item of this.items) {
if (item.row) {
const renderer = this.renderers.get(item.row.templateId);
renderer.disposeTemplate(item.row.templateData);
if (renderer) {
renderer.disposeTemplate(item.row.templateData);
}
}
}

View File

@@ -13,17 +13,19 @@ import * as DOM from 'vs/base/browser/dom';
import * as platform from 'vs/base/common/platform';
import { Gesture } from 'vs/base/browser/touch';
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 { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { Event, Emitter, EventBufferer } from 'vs/base/common/event';
import { domEvent } from 'vs/base/browser/event';
import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider } from './list';
import { ListView, IListViewOptions } from './listView';
import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider, IKeyboardNavigationLabelProvider, IListDragAndDrop, IListDragOverReaction, ListAriaRootRole } from './list';
import { ListView, IListViewOptions, IListViewDragAndDrop } 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 { CombinedSpliceable } from 'vs/base/browser/ui/list/splice';
import { clamp } from 'vs/base/common/numbers';
import { matchesPrefix } from 'vs/base/common/filters';
import { IDragAndDropData } from 'vs/base/browser/dnd';
interface ITraitChangeEvent {
indexes: number[];
@@ -66,15 +68,10 @@ class TraitRenderer<T> implements IListRenderer<T, ITraitTemplateData>
this.trait.renderIndex(index, templateData);
}
disposeElement(): void {
// noop
}
splice(start: number, deleteCount: number, insertCount: number): void {
const rendered: IRenderedContainer[] = [];
for (let i = 0; i < this.renderedElements.length; i++) {
const renderedElement = this.renderedElements[i];
for (const renderedElement of this.renderedElements) {
if (renderedElement.index < start) {
rendered.push(renderedElement);
@@ -216,7 +213,7 @@ class TraitSpliceable<T> implements ISpliceable<T> {
splice(start: number, deleteCount: number, elements: T[]): void {
if (!this.identityProvider) {
return this.trait.splice(start, deleteCount, elements.map(e => false));
return this.trait.splice(start, deleteCount, elements.map(() => false));
}
const pastElementsWithTrait = this.trait.get().map(i => this.identityProvider!.getId(this.view.element(i)).toString());
@@ -245,7 +242,7 @@ class KeyboardController<T> implements IDisposable {
this.openController = options.openController || DefaultOpenController;
const onKeyDown = chain(domEvent(view.domNode, 'keydown'))
const onKeyDown = Event.chain(domEvent(view.domNode, 'keydown'))
.filter(e => !isInputElement(e.target as HTMLElement))
.map(e => new StandardKeyboardEvent(e));
@@ -322,6 +319,106 @@ class KeyboardController<T> implements IDisposable {
}
}
enum TypeLabelControllerState {
Idle,
Typing
}
export function mightProducePrintableCharacter(event: IKeyboardEvent): boolean {
if (event.ctrlKey || event.metaKey || event.altKey) {
return false;
}
return (event.keyCode >= KeyCode.KEY_A && event.keyCode <= KeyCode.KEY_Z)
|| (event.keyCode >= KeyCode.KEY_0 && event.keyCode <= KeyCode.KEY_9)
|| (event.keyCode >= KeyCode.US_SEMICOLON && event.keyCode <= KeyCode.US_QUOTE);
}
class TypeLabelController<T> implements IDisposable {
private enabled = false;
private state: TypeLabelControllerState = TypeLabelControllerState.Idle;
private enabledDisposables: IDisposable[] = [];
private disposables: IDisposable[] = [];
constructor(
private list: List<T>,
private view: ListView<T>,
private keyboardNavigationLabelProvider: IKeyboardNavigationLabelProvider<T>
) {
list.onDidUpdateOptions(this.onDidUpdateListOptions, this, this.disposables);
this.onDidUpdateListOptions(list.options);
}
private onDidUpdateListOptions(options: IListOptions<T>): void {
const enableKeyboardNavigation = typeof options.enableKeyboardNavigation === 'undefined' ? true : !!options.enableKeyboardNavigation;
if (enableKeyboardNavigation) {
this.enable();
} else {
this.disable();
}
}
private enable(): void {
if (this.enabled) {
return;
}
const onChar = Event.chain(domEvent(this.view.domNode, 'keydown'))
.filter(e => !isInputElement(e.target as HTMLElement))
.map(event => new StandardKeyboardEvent(event))
.filter(this.keyboardNavigationLabelProvider.mightProducePrintableCharacter ? e => this.keyboardNavigationLabelProvider.mightProducePrintableCharacter!(e) : e => mightProducePrintableCharacter(e))
.map(event => event.browserEvent.key)
.event;
const onClear = Event.debounce<string, null>(onChar, () => null, 800);
const onInput = Event.reduce<string | null, string | null>(Event.any(onChar, onClear), (r, i) => i === null ? null : ((r || '') + i));
onInput(this.onInput, this, this.enabledDisposables);
this.enabled = true;
}
private disable(): void {
if (!this.enabled) {
return;
}
this.enabledDisposables = dispose(this.enabledDisposables);
this.enabled = false;
}
private onInput(word: string | null): void {
if (!word) {
this.state = TypeLabelControllerState.Idle;
return;
}
const focus = this.list.getFocus();
const start = focus.length > 0 ? focus[0] : 0;
const delta = this.state === TypeLabelControllerState.Idle ? 1 : 0;
this.state = TypeLabelControllerState.Typing;
for (let i = 0; i < this.list.length; i++) {
const index = (start + i + delta) % this.list.length;
const label = this.keyboardNavigationLabelProvider.getKeyboardNavigationLabel(this.view.element(index));
const labelStr = label && label.toString();
if (typeof labelStr === 'undefined' || matchesPrefix(word, labelStr)) {
this.list.setFocus([index]);
this.list.reveal(index);
return;
}
}
}
dispose() {
this.disable();
this.disposables = dispose(this.disposables);
}
}
class DOMFocusController<T> implements IDisposable {
private disposables: IDisposable[] = [];
@@ -332,7 +429,7 @@ class DOMFocusController<T> implements IDisposable {
) {
this.disposables = [];
const onKeyDown = chain(domEvent(view.domNode, 'keydown'))
const onKeyDown = Event.chain(domEvent(view.domNode, 'keydown'))
.filter(e => !isInputElement(e.target as HTMLElement))
.map(e => new StandardKeyboardEvent(e));
@@ -408,14 +505,14 @@ const DefaultOpenController: IOpenController = {
class MouseController<T> implements IDisposable {
private multipleSelectionSupport: boolean;
private multipleSelectionController: IMultipleSelectionController<T>;
readonly multipleSelectionController: IMultipleSelectionController<T>;
private openController: IOpenController;
private disposables: IDisposable[] = [];
constructor(
private list: List<T>,
private view: ListView<T>,
private options: IListOptions<T> = {}
options: IListOptions<T> = {}
) {
this.multipleSelectionSupport = !(options.multipleSelectionSupport === false);
@@ -426,7 +523,9 @@ class MouseController<T> implements IDisposable {
this.openController = options.openController || DefaultOpenController;
view.onMouseDown(this.onMouseDown, this, this.disposables);
view.onContextMenu(this.onContextMenu, this, this.disposables);
view.onMouseClick(this.onPointer, this, this.disposables);
list.onMouseMiddleClick(this.onPointer, this, this.disposables);
view.onMouseDblClick(this.onDoubleClick, this, this.disposables);
view.onTouchStart(this.onMouseDown, this, this.disposables);
view.onTap(this.onPointer, this, this.disposables);
@@ -454,12 +553,20 @@ class MouseController<T> implements IDisposable {
}
private onMouseDown(e: IListMouseEvent<T> | IListTouchEvent<T>): void {
if (this.options.focusOnMouseDown === false) {
e.browserEvent.preventDefault();
e.browserEvent.stopPropagation();
} else if (document.activeElement !== e.browserEvent.target) {
if (document.activeElement !== e.browserEvent.target) {
this.view.domNode.focus();
}
}
private onContextMenu(e: IListMouseEvent<T> | IListTouchEvent<T>): void {
const focus = typeof e.index === 'undefined' ? [] : [e.index];
this.list.setFocus(focus, e.browserEvent);
}
private onPointer(e: IListMouseEvent<T>): void {
if (isInputElement(e.browserEvent.target as HTMLElement)) {
return;
}
let reference = this.list.getFocus()[0];
const selection = this.list.getSelection();
@@ -477,15 +584,13 @@ class MouseController<T> implements IDisposable {
return this.changeSelection(e, reference);
}
if (selection.every(s => s !== focus)) {
this.list.setFocus([focus], e.browserEvent);
}
if (this.multipleSelectionSupport && this.isSelectionChangeEvent(e)) {
return this.changeSelection(e, reference);
}
if (this.options.selectOnMouseDown && !isMouseRightClick(e.browserEvent)) {
this.list.setFocus([focus], e.browserEvent);
if (!isMouseRightClick(e.browserEvent)) {
this.list.setSelection([focus], e.browserEvent);
if (this.openController.shouldOpen(e.browserEvent)) {
@@ -494,22 +599,11 @@ class MouseController<T> implements IDisposable {
}
}
private onPointer(e: IListMouseEvent<T>): void {
if (this.multipleSelectionSupport && this.isSelectionChangeEvent(e)) {
private onDoubleClick(e: IListMouseEvent<T>): void {
if (isInputElement(e.browserEvent.target as HTMLElement)) {
return;
}
if (!this.options.selectOnMouseDown) {
const focus = this.list.getFocus();
this.list.setSelection(focus, e.browserEvent);
if (this.openController.shouldOpen(e.browserEvent)) {
this.list.open(focus, e.browserEvent);
}
}
}
private onDoubleClick(e: IListMouseEvent<T>): void {
if (this.multipleSelectionSupport && this.isSelectionChangeEvent(e)) {
return;
}
@@ -576,9 +670,14 @@ export interface IAccessibilityProvider<T> {
* Returning null will not disable ARIA for the element. Instead it is up to the screen reader
* to compute a meaningful label based on the contents of the element in the DOM
*
* See also: https://www.w3.org/TR/wai-aria/states_and_properties#aria-label
* See also: https://www.w3.org/TR/wai-aria/#aria-label
*/
getAriaLabel(element: T): string | null;
/**
* https://www.w3.org/TR/wai-aria/#aria-level
*/
getAriaLevel?(element: T): number | undefined;
}
export class DefaultStyleController implements IStyleController {
@@ -608,11 +707,17 @@ export class DefaultStyleController implements IStyleController {
}
if (styles.listFocusAndSelectionBackground) {
content.push(`.monaco-list${suffix}:focus .monaco-list-row.selected.focused { background-color: ${styles.listFocusAndSelectionBackground}; }`);
content.push(`
.monaco-list-drag-image,
.monaco-list${suffix}:focus .monaco-list-row.selected.focused { background-color: ${styles.listFocusAndSelectionBackground}; }
`);
}
if (styles.listFocusAndSelectionForeground) {
content.push(`.monaco-list${suffix}:focus .monaco-list-row.selected.focused { color: ${styles.listFocusAndSelectionForeground}; }`);
content.push(`
.monaco-list-drag-image,
.monaco-list${suffix}:focus .monaco-list-row.selected.focused { color: ${styles.listFocusAndSelectionForeground}; }
`);
}
if (styles.listInactiveFocusBackground) {
@@ -630,11 +735,11 @@ export class DefaultStyleController implements IStyleController {
}
if (styles.listHoverBackground) {
content.push(`.monaco-list${suffix} .monaco-list-row:hover { background-color: ${styles.listHoverBackground}; }`);
content.push(`.monaco-list${suffix}:not(.drop-target) .monaco-list-row:hover:not(.selected):not(.focused) { background-color: ${styles.listHoverBackground}; }`);
}
if (styles.listHoverForeground) {
content.push(`.monaco-list${suffix} .monaco-list-row:hover { color: ${styles.listHoverForeground}; }`);
content.push(`.monaco-list${suffix} .monaco-list-row:hover:not(.selected):not(.focused) { color: ${styles.listHoverForeground}; }`);
}
if (styles.listSelectionOutline) {
@@ -642,7 +747,10 @@ export class DefaultStyleController implements IStyleController {
}
if (styles.listFocusOutline) {
content.push(`.monaco-list${suffix}:focus .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; }`);
content.push(`
.monaco-list-drag-image,
.monaco-list${suffix}:focus .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; }
`);
}
if (styles.listInactiveFocusOutline) {
@@ -653,6 +761,29 @@ export class DefaultStyleController implements IStyleController {
content.push(`.monaco-list${suffix} .monaco-list-row:hover { outline: 1px dashed ${styles.listHoverOutline}; outline-offset: -1px; }`);
}
if (styles.listDropBackground) {
content.push(`
.monaco-list${suffix}.drop-target,
.monaco-list${suffix} .monaco-list-row.drop-target { background-color: ${styles.listDropBackground} !important; color: inherit !important; }
`);
}
if (styles.listFilterWidgetBackground) {
content.push(`.monaco-list-type-filter { background-color: ${styles.listFilterWidgetBackground} }`);
}
if (styles.listFilterWidgetOutline) {
content.push(`.monaco-list-type-filter { border: 1px solid ${styles.listFilterWidgetOutline}; }`);
}
if (styles.listFilterWidgetNoMatchesOutline) {
content.push(`.monaco-list-type-filter.no-matches { border: 1px solid ${styles.listFilterWidgetNoMatchesOutline}; }`);
}
if (styles.listMatchesShadow) {
content.push(`.monaco-list-type-filter { box-shadow: 1px 1px 1px ${styles.listMatchesShadow}; }`);
}
const newStyles = content.join('\n');
if (newStyles !== this.styleElement.innerHTML) {
this.styleElement.innerHTML = newStyles;
@@ -660,19 +791,27 @@ export class DefaultStyleController implements IStyleController {
}
}
export interface IListOptions<T> extends IListViewOptions, IListStyles {
identityProvider?: IIdentityProvider<T>;
ariaLabel?: string;
mouseSupport?: boolean;
selectOnMouseDown?: boolean;
focusOnMouseDown?: boolean;
keyboardSupport?: boolean;
verticalScrollMode?: ScrollbarVisibility;
multipleSelectionSupport?: boolean;
multipleSelectionController?: IMultipleSelectionController<T>;
openController?: IOpenController;
styleController?: IStyleController;
accessibilityProvider?: IAccessibilityProvider<T>;
export interface IListOptions<T> extends IListStyles {
readonly identityProvider?: IIdentityProvider<T>;
readonly dnd?: IListDragAndDrop<T>;
readonly enableKeyboardNavigation?: boolean;
readonly keyboardNavigationLabelProvider?: IKeyboardNavigationLabelProvider<T>;
readonly ariaRole?: ListAriaRootRole;
readonly ariaLabel?: string;
readonly keyboardSupport?: boolean;
readonly multipleSelectionSupport?: boolean;
readonly multipleSelectionController?: IMultipleSelectionController<T>;
readonly openController?: IOpenController;
readonly styleController?: IStyleController;
readonly accessibilityProvider?: IAccessibilityProvider<T>;
// list view options
readonly useShadows?: boolean;
readonly verticalScrollMode?: ScrollbarVisibility;
readonly setRowLineHeight?: boolean;
readonly supportDynamicHeights?: boolean;
readonly mouseSupport?: boolean;
readonly horizontalScrolling?: boolean;
}
export interface IListStyles {
@@ -692,6 +831,10 @@ export interface IListStyles {
listInactiveFocusOutline?: Color;
listSelectionOutline?: Color;
listHoverOutline?: Color;
listFilterWidgetBackground?: Color;
listFilterWidgetOutline?: Color;
listFilterWidgetNoMatchesOutline?: Color;
listMatchesShadow?: Color;
}
const defaultStyles: IListStyles = {
@@ -705,10 +848,17 @@ const defaultStyles: IListStyles = {
listDropBackground: Color.fromHex('#383B3D')
};
const DefaultOptions: IListOptions<any> = {
const DefaultOptions = {
keyboardSupport: true,
mouseSupport: true,
multipleSelectionSupport: true
multipleSelectionSupport: true,
dnd: {
getDragURI() { return null; },
onDragStart(): void { },
onDragOver() { return false; },
drop() { }
},
ariaRootRole: ListAriaRootRole.TREE
};
// TODO@Joao: move these utils into a SortedArray class
@@ -819,7 +969,11 @@ class PipelineRenderer<T> implements IListRenderer<T, any> {
let i = 0;
for (const renderer of this.renderers) {
renderer.disposeElement(element, index, templateData[i++]);
if (renderer.disposeElement) {
renderer.disposeElement(element, index, templateData[i]);
}
i += 1;
}
}
@@ -836,9 +990,7 @@ class AccessibiltyRenderer<T> implements IListRenderer<T, HTMLElement> {
templateId: string = 'a18n';
constructor(private accessibilityProvider: IAccessibilityProvider<T>) {
}
constructor(private accessibilityProvider: IAccessibilityProvider<T>) { }
renderTemplate(container: HTMLElement): HTMLElement {
return container;
@@ -852,10 +1004,14 @@ class AccessibiltyRenderer<T> implements IListRenderer<T, HTMLElement> {
} else {
container.removeAttribute('aria-label');
}
}
disposeElement(element: T, index: number, container: HTMLElement): void {
// noop
const ariaLevel = this.accessibilityProvider.getAriaLevel && this.accessibilityProvider.getAriaLevel(element);
if (typeof ariaLevel === 'number') {
container.setAttribute('aria-level', `${ariaLevel}`);
} else {
container.removeAttribute('aria-level');
}
}
disposeTemplate(templateData: any): void {
@@ -863,6 +1019,47 @@ class AccessibiltyRenderer<T> implements IListRenderer<T, HTMLElement> {
}
}
class ListViewDragAndDrop<T> implements IListViewDragAndDrop<T> {
constructor(private list: List<T>, private dnd: IListDragAndDrop<T>) { }
getDragElements(element: T): T[] {
const selection = this.list.getSelectedElements();
const elements = selection.indexOf(element) > -1 ? selection : [element];
return elements;
}
getDragURI(element: T): string | null {
return this.dnd.getDragURI(element);
}
getDragLabel?(elements: T[]): string | undefined {
if (this.dnd.getDragLabel) {
return this.dnd.getDragLabel(elements);
}
return undefined;
}
onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {
if (this.dnd.onDragStart) {
this.dnd.onDragStart(data, originalEvent);
}
}
onDragOver(data: IDragAndDropData, targetElement: T, targetIndex: number, originalEvent: DragEvent): boolean | IListDragOverReaction {
return this.dnd.onDragOver(data, targetElement, targetIndex, originalEvent);
}
drop(data: IDragAndDropData, targetElement: T, targetIndex: number, originalEvent: DragEvent): void {
this.dnd.drop(data, targetElement, targetIndex, originalEvent);
}
}
export interface IListOptionsUpdate {
readonly enableKeyboardNavigation?: boolean;
}
export class List<T> implements ISpliceable<T>, IDisposable {
private static InstanceCount = 0;
@@ -873,26 +1070,36 @@ export class List<T> implements ISpliceable<T>, IDisposable {
private eventBufferer = new EventBufferer();
private view: ListView<T>;
private spliceable: ISpliceable<T>;
protected disposables: IDisposable[];
private styleElement: HTMLStyleElement;
private styleController: IStyleController;
private mouseController: MouseController<T> | undefined;
get multipleSelectionController(): IMultipleSelectionController<T> {
return (this.mouseController && this.mouseController.multipleSelectionController) || DefaultMultipleSelectionContoller;
}
private _onDidUpdateOptions = new Emitter<IListOptions<T>>();
readonly onDidUpdateOptions = this._onDidUpdateOptions.event;
protected disposables: IDisposable[];
@memoize get onFocusChange(): Event<IListEvent<T>> {
return mapEvent(this.eventBufferer.wrapEvent(this.focus.onChange), e => this.toListEvent(e));
return Event.map(this.eventBufferer.wrapEvent(this.focus.onChange), e => this.toListEvent(e));
}
@memoize get onSelectionChange(): Event<IListEvent<T>> {
return mapEvent(this.eventBufferer.wrapEvent(this.selection.onChange), e => this.toListEvent(e));
return Event.map(this.eventBufferer.wrapEvent(this.selection.onChange), e => this.toListEvent(e));
}
private _onOpen = new Emitter<IListEvent<T>>();
readonly onOpen: Event<IListEvent<T>> = this._onOpen.event;
private _onDidOpen = new Emitter<IListEvent<T>>();
readonly onDidOpen: Event<IListEvent<T>> = this._onDidOpen.event;
private _onPin = new Emitter<number[]>();
@memoize get onPin(): Event<IListEvent<T>> {
return mapEvent(this._onPin.event, indexes => this.toListEvent({ indexes }));
return Event.map(this._onPin.event, indexes => this.toListEvent({ indexes }));
}
get onDidScroll(): Event<void> { return this.view.onDidScroll; }
get onMouseClick(): Event<IListMouseEvent<T>> { return this.view.onMouseClick; }
get onMouseDblClick(): Event<IListMouseEvent<T>> { return this.view.onMouseDblClick; }
get onMouseMiddleClick(): Event<IListMouseEvent<T>> { return this.view.onMouseMiddleClick; }
@@ -906,7 +1113,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
private didJustPressContextMenuKey: boolean = false;
@memoize get onContextMenu(): Event<IListContextMenuEvent<T>> {
const fromKeydown = chain(domEvent(this.view.domNode, 'keydown'))
const fromKeydown = Event.chain(domEvent(this.view.domNode, 'keydown'))
.map(e => new StandardKeyboardEvent(e))
.filter(e => this.didJustPressContextMenuKey = e.keyCode === KeyCode.ContextMenu || (e.shiftKey && e.keyCode === KeyCode.F10))
.filter(e => { e.preventDefault(); e.stopPropagation(); return false; })
@@ -918,7 +1125,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
})
.event;
const fromKeyup = chain(domEvent(this.view.domNode, 'keyup'))
const fromKeyup = Event.chain(domEvent(this.view.domNode, 'keyup'))
.filter(() => {
const didJustPressContextMenuKey = this.didJustPressContextMenuKey;
this.didJustPressContextMenuKey = false;
@@ -934,12 +1141,12 @@ export class List<T> implements ISpliceable<T>, IDisposable {
.filter(({ anchor }) => !!anchor)
.event;
const fromMouse = chain(this.view.onContextMenu)
const fromMouse = Event.chain(this.view.onContextMenu)
.filter(() => !this.didJustPressContextMenuKey)
.map(({ element, index, browserEvent }) => ({ element, index, anchor: { x: browserEvent.clientX + 1, y: browserEvent.clientY }, browserEvent }))
.event;
return anyEvent<IListContextMenuEvent<T>>(fromKeydown, fromKeyup, fromMouse);
return Event.any<IListContextMenuEvent<T>>(fromKeydown, fromKeyup, fromMouse);
}
get onKeyDown(): Event<KeyboardEvent> { return domEvent(this.view.domNode, 'keydown'); }
@@ -956,60 +1163,86 @@ export class List<T> implements ISpliceable<T>, IDisposable {
container: HTMLElement,
virtualDelegate: IListVirtualDelegate<T>,
renderers: IListRenderer<any /* TODO@joao */, any>[],
options: IListOptions<T> = DefaultOptions
private _options: IListOptions<T> = DefaultOptions
) {
this.focus = new FocusTrait(i => this.getElementDomId(i));
this.selection = new Trait('selected');
mixin(options, defaultStyles, false);
mixin(_options, defaultStyles, false);
const baseRenderers: IListRenderer<T, ITraitTemplateData>[] = [this.focus.renderer, this.selection.renderer];
if (options.accessibilityProvider) {
baseRenderers.push(new AccessibiltyRenderer<T>(options.accessibilityProvider));
if (_options.accessibilityProvider) {
baseRenderers.push(new AccessibiltyRenderer<T>(_options.accessibilityProvider));
}
renderers = renderers.map(r => new PipelineRenderer(r.templateId, [...baseRenderers, r]));
this.view = new ListView(container, virtualDelegate, renderers, options);
this.view.domNode.setAttribute('role', 'tree');
const viewOptions: IListViewOptions<T> = {
..._options,
dnd: _options.dnd && new ListViewDragAndDrop(this, _options.dnd)
};
this.view = new ListView(container, virtualDelegate, renderers, viewOptions);
if (typeof _options.ariaRole !== 'string') {
this.view.domNode.setAttribute('role', ListAriaRootRole.TREE);
} else {
this.view.domNode.setAttribute('role', _options.ariaRole);
}
DOM.addClass(this.view.domNode, this.idPrefix);
this.view.domNode.tabIndex = 0;
this.styleElement = DOM.createStyleSheet(this.view.domNode);
this.styleController = options.styleController || new DefaultStyleController(this.styleElement, this.idPrefix);
this.styleController = _options.styleController || new DefaultStyleController(this.styleElement, this.idPrefix);
this.spliceable = new CombinedSpliceable([
new TraitSpliceable(this.focus, this.view, options.identityProvider),
new TraitSpliceable(this.selection, this.view, options.identityProvider),
new TraitSpliceable(this.focus, this.view, _options.identityProvider),
new TraitSpliceable(this.selection, this.view, _options.identityProvider),
this.view
]);
this.disposables = [this.focus, this.selection, this.view, this._onDidDispose];
this.onDidFocus = mapEvent(domEvent(this.view.domNode, 'focus', true), () => null!);
this.onDidBlur = mapEvent(domEvent(this.view.domNode, 'blur', true), () => null!);
this.onDidFocus = Event.map(domEvent(this.view.domNode, 'focus', true), () => null!);
this.onDidBlur = Event.map(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);
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 : true) {
this.disposables.push(new MouseController(this, this.view, options));
if (_options.keyboardNavigationLabelProvider) {
const controller = new TypeLabelController(this, this.view, _options.keyboardNavigationLabelProvider);
this.disposables.push(controller);
}
if (typeof _options.mouseSupport === 'boolean' ? _options.mouseSupport : true) {
this.mouseController = new MouseController(this, this.view, _options);
this.disposables.push(this.mouseController);
}
this.onFocusChange(this._onFocusChange, this, this.disposables);
this.onSelectionChange(this._onSelectionChange, this, this.disposables);
if (options.ariaLabel) {
this.view.domNode.setAttribute('aria-label', localize('aria list', "{0}. Use the navigation keys to navigate.", options.ariaLabel));
if (_options.ariaLabel) {
this.view.domNode.setAttribute('aria-label', localize('aria list', "{0}. Use the navigation keys to navigate.", _options.ariaLabel));
}
this.style(options);
this.style(_options);
}
updateOptions(optionsUpdate: IListOptionsUpdate = {}): void {
this._options = { ...this._options, ...optionsUpdate };
this._onDidUpdateOptions.fire(this._options);
}
get options(): IListOptions<T> {
return this._options;
}
splice(start: number, deleteCount: number, elements: T[] = []): void {
@@ -1028,6 +1261,14 @@ export class List<T> implements ISpliceable<T>, IDisposable {
this.eventBufferer.bufferEvents(() => this.spliceable.splice(start, deleteCount, elements));
}
updateWidth(index: number): void {
this.view.updateWidth(index);
}
element(index: number): T {
return this.view.element(index);
}
get length(): number {
return this.view.length;
}
@@ -1060,12 +1301,8 @@ export class List<T> implements ISpliceable<T>, IDisposable {
this.view.domNode.focus();
}
layout(height?: number): void {
this.view.layout(height);
}
layoutWidth(width: number): void {
this.view.layoutWidth(width);
layout(height?: number, width?: number): void {
this.view.layout(height, width);
}
setSelection(indexes: number[], browserEvent?: UIEvent): void {
@@ -1098,41 +1335,54 @@ export class List<T> implements ISpliceable<T>, IDisposable {
this.focus.set(indexes, browserEvent);
}
focusNext(n = 1, loop = false, browserEvent?: UIEvent): void {
focusNext(n = 1, loop = false, browserEvent?: UIEvent, filter?: (element: T) => boolean): void {
if (this.length === 0) { return; }
const focus = this.focus.get();
let index = focus.length > 0 ? focus[0] + n : 0;
this.setFocus(loop ? [index % this.length] : [Math.min(index, this.length - 1)], browserEvent);
const index = this.findNextIndex(focus.length > 0 ? focus[0] + n : 0, loop, filter);
if (index > -1) {
this.setFocus([index], browserEvent);
}
}
focusPrevious(n = 1, loop = false, browserEvent?: UIEvent): void {
focusPrevious(n = 1, loop = false, browserEvent?: UIEvent, filter?: (element: T) => boolean): void {
if (this.length === 0) { return; }
const focus = this.focus.get();
let index = focus.length > 0 ? focus[0] - n : 0;
if (loop && index < 0) { index = (this.length + (index % this.length)) % this.length; }
this.setFocus([Math.max(index, 0)], browserEvent);
const index = this.findPreviousIndex(focus.length > 0 ? focus[0] - n : 0, loop, filter);
if (index > -1) {
this.setFocus([index], browserEvent);
}
}
focusNextPage(browserEvent?: UIEvent): void {
focusNextPage(browserEvent?: UIEvent, filter?: (element: T) => boolean): void {
let lastPageIndex = this.view.indexAt(this.view.getScrollTop() + this.view.renderHeight);
lastPageIndex = lastPageIndex === 0 ? 0 : lastPageIndex - 1;
const lastPageElement = this.view.element(lastPageIndex);
const currentlyFocusedElement = this.getFocusedElements()[0];
if (currentlyFocusedElement !== lastPageElement) {
this.setFocus([lastPageIndex], browserEvent);
const lastGoodPageIndex = this.findPreviousIndex(lastPageIndex, false, filter);
if (lastGoodPageIndex > -1 && currentlyFocusedElement !== this.view.element(lastGoodPageIndex)) {
this.setFocus([lastGoodPageIndex], browserEvent);
} else {
this.setFocus([lastPageIndex], browserEvent);
}
} else {
const previousScrollTop = this.view.getScrollTop();
this.view.setScrollTop(previousScrollTop + this.view.renderHeight - this.view.elementHeight(lastPageIndex));
if (this.view.getScrollTop() !== previousScrollTop) {
// Let the scroll event listener run
setTimeout(() => this.focusNextPage(browserEvent), 0);
setTimeout(() => this.focusNextPage(browserEvent, filter), 0);
}
}
}
focusPreviousPage(browserEvent?: UIEvent): void {
focusPreviousPage(browserEvent?: UIEvent, filter?: (element: T) => boolean): void {
let firstPageIndex: number;
const scrollTop = this.view.getScrollTop();
@@ -1146,26 +1396,78 @@ export class List<T> implements ISpliceable<T>, IDisposable {
const currentlyFocusedElement = this.getFocusedElements()[0];
if (currentlyFocusedElement !== firstPageElement) {
this.setFocus([firstPageIndex], browserEvent);
const firstGoodPageIndex = this.findNextIndex(firstPageIndex, false, filter);
if (firstGoodPageIndex > -1 && currentlyFocusedElement !== this.view.element(firstGoodPageIndex)) {
this.setFocus([firstGoodPageIndex], browserEvent);
} else {
this.setFocus([firstPageIndex], browserEvent);
}
} else {
const previousScrollTop = scrollTop;
this.view.setScrollTop(scrollTop - this.view.renderHeight);
if (this.view.getScrollTop() !== previousScrollTop) {
// Let the scroll event listener run
setTimeout(() => this.focusPreviousPage(browserEvent), 0);
setTimeout(() => this.focusPreviousPage(browserEvent, filter), 0);
}
}
}
focusLast(browserEvent?: UIEvent): void {
focusLast(browserEvent?: UIEvent, filter?: (element: T) => boolean): void {
if (this.length === 0) { return; }
this.setFocus([this.length - 1], browserEvent);
const index = this.findPreviousIndex(this.length - 1, false, filter);
if (index > -1) {
this.setFocus([index], browserEvent);
}
}
focusFirst(browserEvent?: UIEvent): void {
focusFirst(browserEvent?: UIEvent, filter?: (element: T) => boolean): void {
if (this.length === 0) { return; }
this.setFocus([0], browserEvent);
const index = this.findNextIndex(0, false, filter);
if (index > -1) {
this.setFocus([index], browserEvent);
}
}
private findNextIndex(index: number, loop = false, filter?: (element: T) => boolean): number {
for (let i = 0; i < this.length; i++) {
if (index >= this.length && !loop) {
return -1;
}
index = index % this.length;
if (!filter || filter(this.element(index))) {
return index;
}
index++;
}
return -1;
}
private findPreviousIndex(index: number, loop = false, filter?: (element: T) => boolean): number {
for (let i = 0; i < this.length; i++) {
if (index < 0 && !loop) {
return -1;
}
index = (this.length + (index % this.length)) % this.length;
if (!filter || filter(this.element(index))) {
return index;
}
index--;
}
return -1;
}
getFocus(): number[] {
@@ -1242,7 +1544,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
}
}
this._onOpen.fire({ indexes, elements: indexes.map(i => this.view.element(i)), browserEvent });
this._onDidOpen.fire({ indexes, elements: indexes.map(i => this.view.element(i)), browserEvent });
}
pin(indexes: number[]): void {
@@ -1288,7 +1590,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
this._onDidDispose.fire();
this.disposables = dispose(this.disposables);
this._onOpen.dispose();
this._onDidOpen.dispose();
this._onPin.dispose();
this._onDidDispose.dispose();
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#e8e8e8" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg>

After

Width:  |  Height:  |  Size: 307 B

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg7320"
version="1.1"
enable-background="new 3 3 16 16"
viewBox="3 3 16 16"
height="16"
width="16">
<metadata
id="metadata7326">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs7324" />
<polygon
style="fill:#ffffff;fill-opacity:1"
id="polygon7318"
points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"
fill="#424242" />
</svg>

After

Width:  |  Height:  |  Size: 1006 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#424242" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg>

After

Width:  |  Height:  |  Size: 307 B

View File

@@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.0002 10H6.00024V12H10.0002V10Z" fill="#E8E8E8"/>
<path d="M11.9998 7H3.99976V9H11.9998V7Z" fill="#E8E8E8"/>
<path d="M14 4H2V6H14V4Z" fill="#E8E8E8"/>
</svg>

After

Width:  |  Height:  |  Size: 267 B

View File

@@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.0002 10H6.00024V12H10.0002V10Z" fill="white"/>
<path d="M11.9998 7H3.99976V9H11.9998V7Z" fill="white"/>
<path d="M14 4H2V6H14V4Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 261 B

View File

@@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.0002 10H6.00024V12H10.0002V10Z" fill="#424242"/>
<path d="M11.9998 7H3.99976V9H11.9998V7Z" fill="#424242"/>
<path d="M14 4H2V6H14V4Z" fill="#424242"/>
</svg>

After

Width:  |  Height:  |  Size: 267 B

View File

@@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 9.99951H2V11.9995H6V9.99951Z" fill="#E8E8E8"/>
<path d="M10 7H2V9H10V7Z" fill="#E8E8E8"/>
<path d="M14 4H2V6H14V4Z" fill="#E8E8E8"/>
</svg>

After

Width:  |  Height:  |  Size: 248 B

View File

@@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 9.99951H2V11.9995H6V9.99951Z" fill="white"/>
<path d="M10 7H2V9H10V7Z" fill="white"/>
<path d="M14 4H2V6H14V4Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 242 B

View File

@@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 9.99951H2V11.9995H6V9.99951Z" fill="#4B4B4B"/>
<path d="M10 7H2V9H10V7Z" fill="#4B4B4B"/>
<path d="M14 4H2V6H14V4Z" fill="#4B4B4B"/>
</svg>

After

Width:  |  Height:  |  Size: 248 B

View File

@@ -38,7 +38,7 @@ export class RowCache<T> implements IDisposable {
if (!result) {
const domNode = $('.monaco-list-row');
const renderer = this.renderers.get(templateId);
const renderer = this.getRenderer(templateId);
const templateData = renderer.renderTemplate(domNode);
result = { domNode, templateId, templateData };
}
@@ -86,7 +86,7 @@ export class RowCache<T> implements IDisposable {
this.cache.forEach((cachedRows, templateId) => {
for (const cachedRow of cachedRows) {
const renderer = this.renderers.get(templateId);
const renderer = this.getRenderer(templateId);
renderer.disposeTemplate(cachedRow.templateData);
cachedRow.domNode = null;
cachedRow.templateData = null;
@@ -101,4 +101,12 @@ export class RowCache<T> implements IDisposable {
this.cache.clear();
this.renderers = null!; // StrictNullOverride: nulling out ok in dispose
}
private getRenderer(templateId: string): IListRenderer<T, any> {
const renderer = this.renderers.get(templateId);
if (!renderer) {
throw new Error(`No renderer found for ${templateId}`);
}
return renderer;
}
}

View File

@@ -117,7 +117,7 @@
/* Context Menu */
.context-view.monaco-menu-container {
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Ubuntu", "Droid Sans", sans-serif;
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
outline: 0;
border: none;
-webkit-animation: fadeIn 0.083s linear;
@@ -154,7 +154,6 @@
flex-shrink: 1;
box-sizing: border-box;
height: 30px;
-webkit-app-region: no-drag;
overflow: hidden;
flex-wrap: wrap;
}
@@ -183,7 +182,7 @@
}
.menubar .menubar-menu-items-holder.monaco-menu-container {
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Ubuntu", "Droid Sans", sans-serif;
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
outline: 0;
border: none;
}

View File

@@ -15,9 +15,10 @@ import { RunOnceScheduler } from 'vs/base/common/async';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Color } from 'vs/base/common/color';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable';
import { Event, Emitter } from 'vs/base/common/event';
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
import { isLinux } from 'vs/base/common/platform';
export const MENU_MNEMONIC_REGEX: RegExp = /\(&{1,2}(.)\)|&{1,2}(.)/;
export const MENU_ESCAPED_MNEMONIC_REGEX: RegExp = /(?:&amp;){1,2}(.)/;
@@ -26,7 +27,7 @@ export interface IMenuOptions {
context?: any;
actionItemProvider?: IActionItemProvider;
actionRunner?: IActionRunner;
getKeyBinding?: (action: IAction) => ResolvedKeybinding;
getKeyBinding?: (action: IAction) => ResolvedKeybinding | undefined;
ariaLabel?: string;
enableMnemonics?: boolean;
anchorAlignment?: AnchorAlignment;
@@ -44,7 +45,7 @@ export interface IMenuStyles {
}
export class SubmenuAction extends Action {
constructor(label: string, public entries: (SubmenuAction | IAction)[], cssClass?: string) {
constructor(label: string, public entries: Array<SubmenuAction | IAction>, cssClass?: string) {
super(!!cssClass ? cssClass : 'submenu', label, '', true);
}
}
@@ -59,6 +60,7 @@ export class Menu extends ActionBar {
private menuDisposables: IDisposable[];
private scrollableElement: DomScrollableElement;
private menuElement: HTMLElement;
private scrollTopHold: number | undefined;
private readonly _onScroll: Emitter<void>;
@@ -94,48 +96,73 @@ export class Menu extends ActionBar {
const key = KeyCodeUtils.fromString(e.key);
if (this.mnemonics.has(key)) {
EventHelper.stop(e, true);
const actions = this.mnemonics.get(key);
const actions = this.mnemonics.get(key)!;
if (actions.length === 1) {
if (actions[0] instanceof SubmenuActionItem) {
this.focusItemByElement(actions[0].container);
}
actions[0].onClick(event);
actions[0].onClick(e);
}
if (actions.length > 1) {
const action = actions.shift();
this.focusItemByElement(action.container);
if (action) {
this.focusItemByElement(action.container);
actions.push(action);
}
actions.push(action);
this.mnemonics.set(key, actions);
}
}
}));
}
if (isLinux) {
this._register(addDisposableListener(menuElement, EventType.KEY_DOWN, e => {
const event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.Home) || event.equals(KeyCode.PageUp)) {
this.focusedItem = this.items.length - 1;
this.focusNext();
EventHelper.stop(e, true);
} else if (event.equals(KeyCode.End) || event.equals(KeyCode.PageDown)) {
this.focusedItem = 0;
this.focusPrevious();
EventHelper.stop(e, true);
}
}));
}
this._register(addDisposableListener(this.domNode, EventType.MOUSE_OUT, e => {
let relatedTarget = e.relatedTarget as HTMLElement;
if (!isAncestor(relatedTarget, this.domNode)) {
this.focusedItem = undefined;
this.scrollTopHold = this.menuElement.scrollTop;
this.updateFocus();
e.stopPropagation();
}
}));
this._register(addDisposableListener(this.domNode, EventType.MOUSE_UP, e => {
// Absorb clicks in menu dead space https://github.com/Microsoft/vscode/issues/63575
EventHelper.stop(e, true);
}));
this._register(addDisposableListener(this.actionsList, EventType.MOUSE_OVER, e => {
let target = e.target as HTMLElement;
if (!target || !isAncestor(target, this.actionsList) || target === this.actionsList) {
return;
}
while (target.parentElement !== this.actionsList) {
while (target.parentElement !== this.actionsList && target.parentElement !== null) {
target = target.parentElement;
}
if (hasClass(target, 'action-item')) {
const lastFocusedItem = this.focusedItem;
this.scrollTopHold = this.menuElement.scrollTop;
this.setFocusedItem(target);
if (lastFocusedItem !== this.focusedItem) {
@@ -171,7 +198,11 @@ export class Menu extends ActionBar {
this._onScroll.fire();
}, this, this.menuDisposables);
this._register(addDisposableListener(this.menuElement, EventType.SCROLL, (e) => {
this._register(addDisposableListener(this.menuElement, EventType.SCROLL, (e: ScrollEvent) => {
if (this.scrollTopHold !== undefined) {
this.menuElement.scrollTop = this.scrollTopHold;
this.scrollTopHold = undefined;
}
this.scrollableElement.scanDomNode();
}));
@@ -261,7 +292,7 @@ export class Menu extends ActionBar {
if (mnemonic && menuActionItem.isEnabled()) {
let actionItems: MenuActionItem[] = [];
if (this.mnemonics.has(mnemonic)) {
actionItems = this.mnemonics.get(mnemonic);
actionItems = this.mnemonics.get(mnemonic)!;
}
actionItems.push(menuActionItem);
@@ -276,7 +307,11 @@ export class Menu extends ActionBar {
if (options.getKeyBinding) {
const keybinding = options.getKeyBinding(action);
if (keybinding) {
menuItemOptions.keybinding = keybinding.getLabel();
const keybindingLabel = keybinding.getLabel();
if (keybindingLabel) {
menuItemOptions.keybinding = keybindingLabel;
}
}
}
@@ -287,7 +322,7 @@ export class Menu extends ActionBar {
if (mnemonic && menuActionItem.isEnabled()) {
let actionItems: MenuActionItem[] = [];
if (this.mnemonics.has(mnemonic)) {
actionItems = this.mnemonics.get(mnemonic);
actionItems = this.mnemonics.get(mnemonic)!;
}
actionItems.push(menuActionItem);
@@ -342,6 +377,10 @@ class MenuActionItem extends BaseActionItem {
render(container: HTMLElement): void {
super.render(container);
if (!this.element) {
return;
}
this.container = container;
this.item = append(this.element, $('a.action-menu-item'));
@@ -439,7 +478,7 @@ class MenuActionItem extends BaseActionItem {
removeClasses(this.item, this.cssClass);
}
if (this.options.icon) {
this.cssClass = this.getAction().class;
this.cssClass = this.getAction().class || '';
addClass(this.label, 'icon');
if (this.cssClass) {
addClasses(this.label, this.cssClass);
@@ -452,11 +491,17 @@ class MenuActionItem extends BaseActionItem {
updateEnabled(): void {
if (this.getAction().enabled) {
removeClass(this.element, 'disabled');
if (this.element) {
removeClass(this.element, 'disabled');
}
removeClass(this.item, 'disabled');
this.item.tabIndex = 0;
} else {
addClass(this.element, 'disabled');
if (this.element) {
addClass(this.element, 'disabled');
}
addClass(this.item, 'disabled');
removeTabIndexAndUpdateFocus(this.item);
}
@@ -483,7 +528,7 @@ class MenuActionItem extends BaseActionItem {
return;
}
const isSelected = hasClass(this.element, 'focused');
const isSelected = this.element && hasClass(this.element, 'focused');
const fgColor = isSelected && this.menuStyle.selectionForegroundColor ? this.menuStyle.selectionForegroundColor : this.menuStyle.foregroundColor;
const bgColor = isSelected && this.menuStyle.selectionBackgroundColor ? this.menuStyle.selectionBackgroundColor : this.menuStyle.backgroundColor;
const border = isSelected && this.menuStyle.selectionBorderColor ? `1px solid ${this.menuStyle.selectionBorderColor}` : null;
@@ -501,8 +546,8 @@ class MenuActionItem extends BaseActionItem {
}
class SubmenuActionItem extends MenuActionItem {
private mysubmenu: Menu;
private submenuContainer: HTMLElement;
private mysubmenu: Menu | null;
private submenuContainer: HTMLElement | undefined;
private submenuIndicator: HTMLElement;
private submenuDisposables: IDisposable[] = [];
private mouseOver: boolean;
@@ -525,7 +570,7 @@ class SubmenuActionItem extends MenuActionItem {
}, 250);
this.hideScheduler = new RunOnceScheduler(() => {
if ((!isAncestor(document.activeElement, this.element) && this.parentData.submenu === this.mysubmenu)) {
if (this.element && (!isAncestor(document.activeElement, this.element) && this.parentData.submenu === this.mysubmenu)) {
this.parentData.parent.focus(false);
this.cleanupExistingSubmenu(true);
}
@@ -535,6 +580,10 @@ class SubmenuActionItem extends MenuActionItem {
render(container: HTMLElement): void {
super.render(container);
if (!this.element) {
return;
}
addClass(this.item, 'monaco-submenu-item');
this.item.setAttribute('aria-haspopup', 'true');
@@ -570,7 +619,7 @@ class SubmenuActionItem extends MenuActionItem {
}));
this._register(addDisposableListener(this.element, EventType.FOCUS_OUT, e => {
if (!isAncestor(document.activeElement, this.element)) {
if (this.element && !isAncestor(document.activeElement, this.element)) {
this.hideScheduler.schedule();
}
}));
@@ -597,16 +646,20 @@ class SubmenuActionItem extends MenuActionItem {
private cleanupExistingSubmenu(force: boolean): void {
if (this.parentData.submenu && (force || (this.parentData.submenu !== this.mysubmenu))) {
this.parentData.submenu.dispose();
this.parentData.submenu = null;
this.parentData.submenu = undefined;
if (this.submenuContainer) {
this.submenuDisposables = dispose(this.submenuDisposables);
this.submenuContainer = null;
this.submenuContainer = undefined;
}
}
}
private createSubmenu(selectFirstItem = true): void {
if (!this.element) {
return;
}
if (!this.parentData.submenu) {
this.submenuContainer = append(this.element, $('div.monaco-submenu'));
addClasses(this.submenuContainer, 'menubar-menu-items-holder', 'context-view');
@@ -618,13 +671,15 @@ class SubmenuActionItem extends MenuActionItem {
const boundingRect = this.element.getBoundingClientRect();
const childBoundingRect = this.submenuContainer.getBoundingClientRect();
const computedStyles = getComputedStyle(this.parentData.parent.domNode);
const paddingTop = parseFloat(computedStyles.paddingTop || '0') || 0;
if (window.innerWidth <= boundingRect.right + childBoundingRect.width) {
this.submenuContainer.style.left = '10px';
this.submenuContainer.style.top = `${this.element.offsetTop - this.parentData.parent.scrollOffset + boundingRect.height}px`;
} else {
this.submenuContainer.style.left = `${this.element.offsetWidth}px`;
this.submenuContainer.style.top = `${this.element.offsetTop - this.parentData.parent.scrollOffset}px`;
this.submenuContainer.style.top = `${this.element.offsetTop - this.parentData.parent.scrollOffset - paddingTop}px`;
}
this.submenuDisposables.push(addDisposableListener(this.submenuContainer, EventType.KEY_UP, e => {
@@ -633,11 +688,14 @@ class SubmenuActionItem extends MenuActionItem {
EventHelper.stop(e, true);
this.parentData.parent.focus();
this.parentData.submenu.dispose();
this.parentData.submenu = null;
if (this.parentData.submenu) {
this.parentData.submenu.dispose();
this.parentData.submenu = undefined;
}
this.submenuDisposables = dispose(this.submenuDisposables);
this.submenuContainer = null;
this.submenuContainer = undefined;
}
}));
@@ -651,11 +709,14 @@ class SubmenuActionItem extends MenuActionItem {
this.submenuDisposables.push(this.parentData.submenu.onDidCancel(() => {
this.parentData.parent.focus();
this.parentData.submenu.dispose();
this.parentData.submenu = null;
if (this.parentData.submenu) {
this.parentData.submenu.dispose();
this.parentData.submenu = undefined;
}
this.submenuDisposables = dispose(this.submenuDisposables);
this.submenuContainer = null;
this.submenuContainer = undefined;
}));
this.parentData.submenu.focus(selectFirstItem);
@@ -673,7 +734,7 @@ class SubmenuActionItem extends MenuActionItem {
return;
}
const isSelected = hasClass(this.element, 'focused');
const isSelected = this.element && hasClass(this.element, 'focused');
const fgColor = isSelected && this.menuStyle.selectionForegroundColor ? this.menuStyle.selectionForegroundColor : this.menuStyle.foregroundColor;
this.submenuIndicator.style.backgroundColor = fgColor ? `${fgColor}` : null;
@@ -695,7 +756,7 @@ class SubmenuActionItem extends MenuActionItem {
if (this.submenuContainer) {
this.submenuDisposables = dispose(this.submenuDisposables);
this.submenuContainer = null;
this.submenuContainer = undefined;
}
}
}

View File

@@ -59,9 +59,9 @@ export class MenuBar extends Disposable {
index: number;
holder?: HTMLElement;
widget?: Menu;
};
} | undefined;
private focusToReturn: HTMLElement;
private focusToReturn: HTMLElement | undefined;
private menuUpdater: RunOnceScheduler;
// Input-related
@@ -80,7 +80,7 @@ export class MenuBar extends Disposable {
private numMenusShown: number;
private menuStyle: IMenuStyles;
private overflowLayoutScheduled: IDisposable;
private overflowLayoutScheduled: IDisposable | null;
constructor(private container: HTMLElement, private options: IMenuBarOptions = {}) {
super();
@@ -118,7 +118,7 @@ export class MenuBar extends Disposable {
} else if (event.equals(KeyCode.Escape) && this.isFocused && !this.isOpen) {
this.setUnfocusedState();
} else if (!this.isOpen && !event.ctrlKey && this.options.enableMnemonics && this.mnemonicsInUse && this.mnemonics.has(key)) {
const menuIndex = this.mnemonics.get(key);
const menuIndex = this.mnemonics.get(key)!;
this.onMenuTriggered(menuIndex, false);
} else {
eventHandled = false;
@@ -152,7 +152,7 @@ export class MenuBar extends Disposable {
if (event.relatedTarget) {
if (!this.container.contains(event.relatedTarget as HTMLElement)) {
this.focusToReturn = null;
this.focusToReturn = undefined;
this.setUnfocusedState();
}
}
@@ -171,7 +171,7 @@ export class MenuBar extends Disposable {
this.mnemonicsInUse = true;
this.updateMnemonicVisibility(true);
const menuIndex = this.mnemonics.get(key);
const menuIndex = this.mnemonics.get(key)!;
this.onMenuTriggered(menuIndex, false);
}));
@@ -223,7 +223,7 @@ export class MenuBar extends Disposable {
Gesture.addTarget(buttonElement);
this._register(DOM.addDisposableListener(buttonElement, EventType.Tap, (e: GestureEvent) => {
// Ignore this touch if the menu is touched
if (this.isOpen && this.focusedMenu.holder && DOM.isAncestor(e.initialTarget as HTMLElement, this.focusedMenu.holder)) {
if (this.isOpen && this.focusedMenu && this.focusedMenu.holder && DOM.isAncestor(e.initialTarget as HTMLElement, this.focusedMenu.holder)) {
return;
}
@@ -307,7 +307,7 @@ export class MenuBar extends Disposable {
Gesture.addTarget(buttonElement);
this._register(DOM.addDisposableListener(buttonElement, EventType.Tap, (e: GestureEvent) => {
// Ignore this touch if the menu is touched
if (this.isOpen && this.focusedMenu.holder && DOM.isAncestor(e.initialTarget as HTMLElement, this.focusedMenu.holder)) {
if (this.isOpen && this.focusedMenu && this.focusedMenu.holder && DOM.isAncestor(e.initialTarget as HTMLElement, this.focusedMenu.holder)) {
return;
}
@@ -377,7 +377,9 @@ export class MenuBar extends Disposable {
DOM.removeNode(this.overflowMenu.titleElement);
DOM.removeNode(this.overflowMenu.buttonElement);
this.overflowLayoutScheduled = dispose(this.overflowLayoutScheduled);
if (this.overflowLayoutScheduled) {
this.overflowLayoutScheduled = dispose(this.overflowLayoutScheduled);
}
}
blur(): void {
@@ -439,7 +441,7 @@ export class MenuBar extends Disposable {
this.overflowMenu.actions = [];
for (let idx = this.numMenusShown; idx < this.menuCache.length; idx++) {
this.overflowMenu.actions.push(new SubmenuAction(this.menuCache[idx].label, this.menuCache[idx].actions));
this.overflowMenu.actions.push(new SubmenuAction(this.menuCache[idx].label, this.menuCache[idx].actions || []));
}
DOM.removeNode(this.overflowMenu.buttonElement);
@@ -496,7 +498,7 @@ export class MenuBar extends Disposable {
if (!this.overflowLayoutScheduled) {
this.overflowLayoutScheduled = DOM.scheduleAtNextAnimationFrame(() => {
this.updateOverflowAction();
this.overflowLayoutScheduled = void 0;
this.overflowLayoutScheduled = null;
});
}
@@ -518,6 +520,8 @@ export class MenuBar extends Disposable {
if (this.container.style.display !== 'flex') {
this.container.style.display = 'flex';
this._onVisibilityChange.fire(true);
this.updateOverflowAction();
}
}
@@ -556,11 +560,11 @@ export class MenuBar extends Disposable {
}
if (isFocused) {
this.focusedMenu = null;
this.focusedMenu = undefined;
if (this.focusToReturn) {
this.focusToReturn.focus();
this.focusToReturn = null;
this.focusToReturn = undefined;
}
}
@@ -584,11 +588,11 @@ export class MenuBar extends Disposable {
}
}
this.focusedMenu = null;
this.focusedMenu = undefined;
if (this.focusToReturn) {
this.focusToReturn.focus();
this.focusToReturn = null;
this.focusToReturn = undefined;
}
}
@@ -814,7 +818,10 @@ export class MenuBar extends Disposable {
}
if (this.focusedMenu.holder) {
DOM.removeClass(this.focusedMenu.holder.parentElement, 'open');
if (this.focusedMenu.holder.parentElement) {
DOM.removeClass(this.focusedMenu.holder.parentElement, 'open');
}
this.focusedMenu.holder.remove();
}
@@ -829,6 +836,11 @@ export class MenuBar extends Disposable {
private showCustomMenu(menuIndex: number, selectFirst = true): void {
const actualMenuIndex = menuIndex >= this.numMenusShown ? MenuBar.OVERFLOW_INDEX : menuIndex;
const customMenu = actualMenuIndex === MenuBar.OVERFLOW_INDEX ? this.overflowMenu : this.menuCache[actualMenuIndex];
if (!customMenu.actions) {
return;
}
const menuHolder = $('div.menubar-menu-items-holder');
DOM.addClass(customMenu.buttonElement, 'open');
@@ -851,12 +863,6 @@ export class MenuBar extends Disposable {
this.focusState = MenubarState.FOCUSED;
}));
this._register(menuWidget.onDidBlur(() => {
setTimeout(() => {
this.cleanupCustomMenu();
}, 100);
}));
if (actualMenuIndex !== menuIndex) {
menuWidget.trigger(menuIndex - this.numMenusShown);
} else {
@@ -897,7 +903,7 @@ class ModifierKeyEmitter extends Emitter<IModifierKeyStatus> {
ctrlKey: false
};
this._subscriptions.push(domEvent(document.body, 'keydown')(e => {
this._subscriptions.push(domEvent(document.body, 'keydown', true)(e => {
const event = new StandardKeyboardEvent(e);
if (e.altKey && !this._keyStatus.altKey) {
@@ -920,7 +926,8 @@ class ModifierKeyEmitter extends Emitter<IModifierKeyStatus> {
this.fire(this._keyStatus);
}
}));
this._subscriptions.push(domEvent(document.body, 'keyup')(e => {
this._subscriptions.push(domEvent(document.body, 'keyup', true)(e => {
if (!e.altKey && this._keyStatus.altKey) {
this._keyStatus.lastKeyReleased = 'alt';
} else if (!e.ctrlKey && this._keyStatus.ctrlKey) {
@@ -943,11 +950,11 @@ class ModifierKeyEmitter extends Emitter<IModifierKeyStatus> {
this.fire(this._keyStatus);
}
}));
this._subscriptions.push(domEvent(document.body, 'mousedown')(e => {
this._subscriptions.push(domEvent(document.body, 'mousedown', true)(e => {
this._keyStatus.lastKeyPressed = undefined;
}));
this._subscriptions.push(domEvent(window, 'blur')(e => {
this._keyStatus.lastKeyPressed = undefined;
this._keyStatus.lastKeyReleased = undefined;

View File

@@ -1 +0,0 @@
If you intend to install Octicons locally, install `octicons-local.ttf`. It should appear as “github-octicons” in your font list. It is specially designed not to conflict with GitHub's web fonts.

View File

@@ -1,140 +0,0 @@
{
"registrations": [
{
"component": {
"type": "other",
"other": {
"name": "octicons-code",
"version": "3.1.0",
"downloadUrl": "https://registry.npmjs.org/octicons/-/octicons-3.1.0.tgz"
}
},
"licenseDetail": [
"The MIT License (MIT)",
"",
"(c) 2012-2015 GitHub",
"",
"Permission is hereby granted, free of charge, to any person obtaining a copy",
"of this software and associated documentation files (the \"Software\"), to deal",
"in the Software without restriction, including without limitation the rights",
"to use, copy, modify, merge, publish, distribute, sublicense, and/or sell",
"copies of the Software, and to permit persons to whom the Software is",
"furnished to do so, subject to the following conditions:",
"",
"The above copyright notice and this permission notice shall be included in",
"all copies or substantial portions of the Software.",
"",
"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR",
"IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,",
"FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE",
"AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER",
"LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,",
"OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN",
"THE SOFTWARE."
],
"license": "MIT",
"version": "3.1.0"
},
{
"component": {
"type": "other",
"other": {
"name": "octicons-font",
"version": "3.1.0",
"downloadUrl": "https://registry.npmjs.org/octicons/-/octicons-3.1.0.tgz"
}
},
"licenseDetail": [
"(c) 2012-2015 GitHub",
"",
"SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007",
"",
"PREAMBLE",
"The goals of the Open Font License (OFL) are to stimulate worldwide",
"development of collaborative font projects, to support the font creation",
"efforts of academic and linguistic communities, and to provide a free and",
"open framework in which fonts may be shared and improved in partnership",
"with others.",
"",
"The OFL allows the licensed fonts to be used, studied, modified and",
"redistributed freely as long as they are not sold by themselves. The",
"fonts, including any derivative works, can be bundled, embedded,",
"redistributed and/or sold with any software provided that any reserved",
"names are not used by derivative works. The fonts and derivatives,",
"however, cannot be released under any other type of license. The",
"requirement for fonts to remain under this license does not apply",
"to any document created using the fonts or their derivatives.",
"",
"DEFINITIONS",
"\"Font Software\" refers to the set of files released by the Copyright",
"Holder(s) under this license and clearly marked as such. This may",
"include source files, build scripts and documentation.",
"",
"\"Reserved Font Name\" refers to any names specified as such after the",
"copyright statement(s).",
"",
"\"Original Version\" refers to the collection of Font Software components as",
"distributed by the Copyright Holder(s).",
"",
"\"Modified Version\" refers to any derivative made by adding to, deleting,",
"or substituting -- in part or in whole -- any of the components of the",
"Original Version, by changing formats or by porting the Font Software to a",
"new environment.",
"",
"\"Author\" refers to any designer, engineer, programmer, technical",
"writer or other person who contributed to the Font Software.",
"",
"PERMISSION & CONDITIONS",
"Permission is hereby granted, free of charge, to any person obtaining",
"a copy of the Font Software, to use, study, copy, merge, embed, modify,",
"redistribute, and sell modified and unmodified copies of the Font",
"Software, subject to the following conditions:",
"",
"1) Neither the Font Software nor any of its individual components,",
"in Original or Modified Versions, may be sold by itself.",
"",
"2) Original or Modified Versions of the Font Software may be bundled,",
"redistributed and/or sold with any software, provided that each copy",
"contains the above copyright notice and this license. These can be",
"included either as stand-alone text files, human-readable headers or",
"in the appropriate machine-readable metadata fields within text or",
"binary files as long as those fields can be easily viewed by the user.",
"",
"3) No Modified Version of the Font Software may use the Reserved Font",
"Name(s) unless explicit written permission is granted by the corresponding",
"Copyright Holder. This restriction only applies to the primary font name as",
"presented to the users.",
"",
"4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font",
"Software shall not be used to promote, endorse or advertise any",
"Modified Version, except to acknowledge the contribution(s) of the",
"Copyright Holder(s) and the Author(s) or with their explicit written",
"permission.",
"",
"5) The Font Software, modified or unmodified, in part or in whole,",
"must be distributed entirely under this license, and must not be",
"distributed under any other license. The requirement for fonts to",
"remain under this license does not apply to any document created",
"using the Font Software.",
"",
"TERMINATION",
"This license becomes null and void if any of the above conditions are",
"not met.",
"",
"DISCLAIMER",
"THE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,",
"EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF",
"MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT",
"OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE",
"COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,",
"INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL",
"DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING",
"FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM",
"OTHER DEALINGS IN THE FONT SOFTWARE."
],
"license": "SIL OFL 1.1",
"version": "3.1.0"
}
],
"version": 1
}

View File

@@ -1,233 +1,243 @@
/*! *****************************************************************************
(c) 2012-2015 GitHub
When using the GitHub logos, be sure to follow the GitHub logo guidelines (https://github.com/logos)
Font License: SIL OFL 1.1 (http://scripts.sil.org/OFL)
Applies to all font files
Code License: MIT (http://choosealicense.com/licenses/mit/)
Applies to all other files
***************************************************************************** */
@font-face {
font-family: 'octicons';
src: url('octicons.eot?#iefix') format('embedded-opentype'),
url('octicons.woff') format('woff'),
url('octicons.ttf') format('truetype'),
url('octicons.svg#octicons') format('svg');
font-weight: normal;
font-style: normal;
font-family: "octicons";
src: url("./octicons.ttf?4cd2299755e93a2430ba5703f4476584") format("truetype"),
url("./octicons.svg?4cd2299755e93a2430ba5703f4476584#octicons") format("svg");
}
/*
.octicon is optimized for 16px.
.mega-octicon is optimized for 32px but can be used larger.
*/
.octicon, .mega-octicon {
font: normal normal normal 16px/1 octicons;
display: inline-block;
text-decoration: none;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
font: normal normal normal 16px/1 octicons;
display: inline-block;
text-decoration: none;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.mega-octicon { font-size: 32px; }
.octicon-alert:before { content: '\f02d'} /*  */
.octicon-arrow-down:before { content: '\f03f'} /*  */
.octicon-arrow-left:before { content: '\f040'} /*  */
.octicon-arrow-right:before { content: '\f03e'} /*  */
.octicon-arrow-small-down:before { content: '\f0a0'} /*  */
.octicon-arrow-small-left:before { content: '\f0a1'} /*  */
.octicon-arrow-small-right:before { content: '\f071'} /*  */
.octicon-arrow-small-up:before { content: '\f09f'} /*  */
.octicon-arrow-up:before { content: '\f03d'} /*  */
.octicon-microscope:before,
.octicon-beaker:before { content: '\f0dd'} /*  */
.octicon-bell:before { content: '\f0de'} /*  */
.octicon-book:before { content: '\f007'} /*  */
.octicon-bookmark:before { content: '\f07b'} /*  */
.octicon-briefcase:before { content: '\f0d3'} /*  */
.octicon-broadcast:before { content: '\f048'} /*  */
.octicon-browser:before { content: '\f0c5'} /*  */
.octicon-bug:before { content: '\f091'} /*  */
.octicon-calendar:before { content: '\f068'} /*  */
.octicon-check:before { content: '\f03a'} /*  */
.octicon-checklist:before { content: '\f076'} /*  */
.octicon-chevron-down:before { content: '\f0a3'} /*  */
.octicon-chevron-left:before { content: '\f0a4'} /*  */
.octicon-chevron-right:before { content: '\f078'} /*  */
.octicon-chevron-up:before { content: '\f0a2'} /*  */
.octicon-circle-slash:before { content: '\f084'} /*  */
.octicon-circuit-board:before { content: '\f0d6'} /*  */
.octicon-clippy:before { content: '\f035'} /*  */
.octicon-clock:before { content: '\f046'} /*  */
.octicon-cloud-download:before { content: '\f00b'} /*  */
.octicon-cloud-upload:before { content: '\f00c'} /*  */
.octicon-code:before { content: '\f05f'} /*  */
.octicon-color-mode:before { content: '\f065'} /*  */
.octicon-comment-add:before,
.octicon-comment:before { content: '\f02b'} /*  */
.octicon-comment-discussion:before { content: '\f04f'} /*  */
.octicon-credit-card:before { content: '\f045'} /*  */
.octicon-dash:before { content: '\f0ca'} /*  */
.octicon-dashboard:before { content: '\f07d'} /*  */
.octicon-database:before { content: '\f096'} /*  */
.octicon-clone:before,
.octicon-desktop-download:before { content: '\f0dc'} /*  */
.octicon-device-camera:before { content: '\f056'} /*  */
.octicon-device-camera-video:before { content: '\f057'} /*  */
.octicon-device-desktop:before { content: '\f27c'} /*  */
.octicon-device-mobile:before { content: '\f038'} /*  */
.octicon-diff:before { content: '\f04d'} /*  */
.octicon-diff-added:before { content: '\f06b'} /*  */
.octicon-diff-ignored:before { content: '\f099'} /*  */
.octicon-diff-modified:before { content: '\f06d'} /*  */
.octicon-diff-removed:before { content: '\f06c'} /*  */
.octicon-diff-renamed:before { content: '\f06e'} /*  */
.octicon-ellipsis:before { content: '\f09a'} /*  */
.octicon-eye-unwatch:before,
.octicon-eye-watch:before,
.octicon-eye:before { content: '\f04e'} /*  */
.octicon-file-binary:before { content: '\f094'} /*  */
.octicon-file-code:before { content: '\f010'} /*  */
.octicon-file-directory:before { content: '\f016'} /*  */
.octicon-file-media:before { content: '\f012'} /*  */
.octicon-file-pdf:before { content: '\f014'} /*  */
.octicon-file-submodule:before { content: '\f017'} /*  */
.octicon-file-symlink-directory:before { content: '\f0b1'} /*  */
.octicon-file-symlink-file:before { content: '\f0b0'} /*  */
.octicon-file-text:before { content: '\f011'} /*  */
.octicon-file-zip:before { content: '\f013'} /*  */
.octicon-flame:before { content: '\f0d2'} /*  */
.octicon-fold:before { content: '\f0cc'} /*  */
.octicon-gear:before { content: '\f02f'} /*  */
.octicon-gift:before { content: '\f042'} /*  */
.octicon-gist:before { content: '\f00e'} /*  */
.octicon-gist-secret:before { content: '\f08c'} /*  */
.octicon-git-branch-create:before,
.octicon-git-branch-delete:before,
.octicon-git-branch:before { content: '\f020'} /*  */
.octicon-git-commit:before { content: '\f01f'} /*  */
.octicon-git-compare:before { content: '\f0ac'} /*  */
.octicon-git-merge:before { content: '\f023'} /*  */
.octicon-git-pull-request-abandoned:before,
.octicon-git-pull-request:before { content: '\f009'} /*  */
.octicon-globe:before { content: '\f0b6'} /*  */
.octicon-graph:before { content: '\f043'} /*  */
.octicon-heart:before { content: '\2665'} /* ♥ */
.octicon-history:before { content: '\f07e'} /*  */
.octicon-home:before { content: '\f08d'} /*  */
.octicon-horizontal-rule:before { content: '\f070'} /*  */
.octicon-hubot:before { content: '\f09d'} /*  */
.octicon-inbox:before { content: '\f0cf'} /*  */
.octicon-info:before { content: '\f059'} /*  */
.octicon-issue-closed:before { content: '\f028'} /*  */
.octicon-issue-opened:before { content: '\f026'} /*  */
.octicon-issue-reopened:before { content: '\f027'} /*  */
.octicon-jersey:before { content: '\f019'} /*  */
.octicon-key:before { content: '\f049'} /*  */
.octicon-keyboard:before { content: '\f00d'} /*  */
.octicon-law:before { content: '\f0d8'} /*  */
.octicon-light-bulb:before { content: '\f000'} /*  */
.octicon-link:before { content: '\f05c'} /*  */
.octicon-link-external:before { content: '\f07f'} /*  */
.octicon-list-ordered:before { content: '\f062'} /*  */
.octicon-list-unordered:before { content: '\f061'} /*  */
.octicon-location:before { content: '\f060'} /*  */
.octicon-gist-private:before,
.octicon-mirror-private:before,
.octicon-git-fork-private:before,
.octicon-lock:before { content: '\f06a'} /*  */
.octicon-logo-github:before { content: '\f092'} /*  */
.octicon-mail:before { content: '\f03b'} /*  */
.octicon-mail-read:before { content: '\f03c'} /*  */
.octicon-mail-reply:before { content: '\f051'} /*  */
.octicon-mark-github:before { content: '\f00a'} /*  */
.octicon-markdown:before { content: '\f0c9'} /*  */
.octicon-megaphone:before { content: '\f077'} /*  */
.octicon-mention:before { content: '\f0be'} /*  */
.octicon-milestone:before { content: '\f075'} /*  */
.octicon-mirror-public:before,
.octicon-mirror:before { content: '\f024'} /*  */
.octicon-mortar-board:before { content: '\f0d7'} /*  */
.octicon-mute:before { content: '\f080'} /*  */
.octicon-no-newline:before { content: '\f09c'} /*  */
.octicon-octoface:before { content: '\f008'} /*  */
.octicon-organization:before { content: '\f037'} /*  */
.octicon-package:before { content: '\f0c4'} /*  */
.octicon-paintcan:before { content: '\f0d1'} /*  */
.octicon-pencil:before { content: '\f058'} /*  */
.octicon-person-add:before,
.octicon-person-follow:before,
.octicon-person:before { content: '\f018'} /*  */
.octicon-pin:before { content: '\f041'} /*  */
.octicon-plug:before { content: '\f0d4'} /*  */
.octicon-repo-create:before,
.octicon-gist-new:before,
.octicon-file-directory-create:before,
.octicon-file-add:before,
.octicon-plus:before { content: '\f05d'} /*  */
.octicon-primitive-dot:before { content: '\f052'} /*  */
.octicon-primitive-square:before { content: '\f053'} /*  */
.octicon-pulse:before { content: '\f085'} /*  */
.octicon-question:before { content: '\f02c'} /*  */
.octicon-quote:before { content: '\f063'} /*  */
.octicon-radio-tower:before { content: '\f030'} /*  */
.octicon-repo-delete:before,
.octicon-repo:before { content: '\f001'} /*  */
.octicon-repo-clone:before { content: '\f04c'} /*  */
.octicon-repo-force-push:before { content: '\f04a'} /*  */
.octicon-gist-fork:before,
.octicon-repo-forked:before { content: '\f002'} /*  */
.octicon-repo-pull:before { content: '\f006'} /*  */
.octicon-repo-push:before { content: '\f005'} /*  */
.octicon-rocket:before { content: '\f033'} /*  */
.octicon-rss:before { content: '\f034'} /*  */
.octicon-ruby:before { content: '\f047'} /*  */
.octicon-screen-full:before { content: '\f066'} /*  */
.octicon-screen-normal:before { content: '\f067'} /*  */
.octicon-search-save:before,
.octicon-search:before { content: '\f02e'} /*  */
.octicon-server:before { content: '\f097'} /*  */
.octicon-settings:before { content: '\f07c'} /*  */
.octicon-shield:before { content: '\f0e1'} /*  */
.octicon-log-in:before,
.octicon-sign-in:before { content: '\f036'} /*  */
.octicon-log-out:before,
.octicon-sign-out:before { content: '\f032'} /*  */
.octicon-squirrel:before { content: '\f0b2'} /*  */
.octicon-star-add:before,
.octicon-star-delete:before,
.octicon-star:before { content: '\f02a'} /*  */
.octicon-stop:before { content: '\f08f'} /*  */
.octicon-repo-sync:before,
.octicon-sync:before { content: '\f087'} /*  */
.octicon-tag-remove:before,
.octicon-tag-add:before,
.octicon-tag:before { content: '\f015'} /*  */
.octicon-telescope:before { content: '\f088'} /*  */
.octicon-terminal:before { content: '\f0c8'} /*  */
.octicon-three-bars:before { content: '\f05e'} /*  */
.octicon-thumbsdown:before { content: '\f0db'} /*  */
.octicon-thumbsup:before { content: '\f0da'} /*  */
.octicon-tools:before { content: '\f031'} /*  */
.octicon-trashcan:before { content: '\f0d0'} /*  */
.octicon-triangle-down:before { content: '\f05b'} /*  */
.octicon-triangle-left:before { content: '\f044'} /*  */
.octicon-triangle-right:before { content: '\f05a'} /*  */
.octicon-triangle-up:before { content: '\f0aa'} /*  */
.octicon-unfold:before { content: '\f039'} /*  */
.octicon-unmute:before { content: '\f0ba'} /*  */
.octicon-versions:before { content: '\f064'} /*  */
.octicon-watch:before { content: '\f0e0'} /*  */
.octicon-remove-close:before,
.octicon-x:before { content: '\f081'} /*  */
.octicon-zap:before { content: '\26A1'} /* ⚡ */
.octicon-alert:before { content: "\f02d" }
.octicon-arrow-down:before { content: "\f03f" }
.octicon-arrow-left:before { content: "\f040" }
.octicon-arrow-right:before { content: "\f03e" }
.octicon-arrow-small-down:before { content: "\f0a0" }
.octicon-arrow-small-left:before { content: "\f0a1" }
.octicon-arrow-small-right:before { content: "\f071" }
.octicon-arrow-small-up:before { content: "\f09f" }
.octicon-arrow-up:before { content: "\f03d" }
.octicon-beaker:before { content: "\f0dd" }
.octicon-bell:before { content: "\f0de" }
.octicon-bold:before { content: "\f282" }
.octicon-book:before { content: "\f007" }
.octicon-bookmark:before { content: "\f07b" }
.octicon-briefcase:before { content: "\f0d3" }
.octicon-broadcast:before { content: "\f048" }
.octicon-browser:before { content: "\f0c5" }
.octicon-bug:before { content: "\f091" }
.octicon-calendar:before { content: "\f068" }
.octicon-check:before { content: "\f03a" }
.octicon-checklist:before { content: "\f076" }
.octicon-chevron-down:before { content: "\f0a3" }
.octicon-chevron-left:before { content: "\f0a4" }
.octicon-chevron-right:before { content: "\f078" }
.octicon-chevron-up:before { content: "\f0a2" }
.octicon-circle-slash:before { content: "\f084" }
.octicon-circuit-board:before { content: "\f0d6" }
.octicon-clippy:before { content: "\f035" }
.octicon-clock:before { content: "\f046" }
.octicon-clone:before { content: "\f0dc" }
.octicon-cloud-download:before { content: "\f00b" }
.octicon-cloud-upload:before { content: "\f00c" }
.octicon-code:before { content: "\f05f" }
.octicon-color-mode:before { content: "\f065" }
.octicon-comment-add:before { content: "\f02b" }
.octicon-comment-discussion:before { content: "\f04f" }
.octicon-comment:before { content: "\f02b" }
.octicon-credit-card:before { content: "\f045" }
.octicon-dash:before { content: "\f0ca" }
.octicon-dashboard:before { content: "\f07d" }
.octicon-database:before { content: "\f096" }
.octicon-desktop-download:before { content: "\f0dc" }
.octicon-device-camera-video:before { content: "\f057" }
.octicon-device-camera:before { content: "\f056" }
.octicon-device-desktop:before { content: "\f27c" }
.octicon-device-mobile:before { content: "\f038" }
.octicon-diff-added:before { content: "\f06b" }
.octicon-diff-ignored:before { content: "\f099" }
.octicon-diff-modified:before { content: "\f06d" }
.octicon-diff-removed:before { content: "\f06c" }
.octicon-diff-renamed:before { content: "\f06e" }
.octicon-diff:before { content: "\f04d" }
.octicon-ellipsis:before { content: "\f09a" }
.octicon-eye-unwatch:before { content: "\f04e" }
.octicon-eye-watch:before { content: "\f04e" }
.octicon-eye:before { content: "\f04e" }
.octicon-file-add:before { content: "\f05d" }
.octicon-file-binary:before { content: "\f094" }
.octicon-file-code:before { content: "\f010" }
.octicon-file-directory-create:before { content: "\f05d" }
.octicon-file-directory:before { content: "\f016" }
.octicon-file-media:before { content: "\f012" }
.octicon-file-pdf:before { content: "\f014" }
.octicon-file-submodule:before { content: "\f017" }
.octicon-file-symlink-directory:before { content: "\f0b1" }
.octicon-file-symlink-file:before { content: "\f0b0" }
.octicon-file-text:before { content: "\f283" }
.octicon-file-zip:before { content: "\f013" }
.octicon-file:before { content: "\f283" }
.octicon-flame:before { content: "\f0d2" }
.octicon-fold:before { content: "\f0cc" }
.octicon-gear:before { content: "\f02f" }
.octicon-gift:before { content: "\f042" }
.octicon-gist-fork:before { content: "\f002" }
.octicon-gist-new:before { content: "\f05d" }
.octicon-gist-private:before { content: "\f06a" }
.octicon-gist-secret:before { content: "\f08c" }
.octicon-gist:before { content: "\f00e" }
.octicon-git-branch-create:before { content: "\f020" }
.octicon-git-branch-delete:before { content: "\f020" }
.octicon-git-branch:before { content: "\f020" }
.octicon-git-commit:before { content: "\f01f" }
.octicon-git-compare:before { content: "\f0ac" }
.octicon-git-fork-private:before { content: "\f06a" }
.octicon-git-merge:before { content: "\f023" }
.octicon-git-pull-request-abandoned:before { content: "\f009" }
.octicon-git-pull-request:before { content: "\f009" }
.octicon-globe:before { content: "\f0b6" }
.octicon-grabber:before { content: "\f284" }
.octicon-graph:before { content: "\f043" }
.octicon-heart:before { content: "\2665" }
.octicon-history:before { content: "\f07e" }
.octicon-home:before { content: "\f08d" }
.octicon-horizontal-rule:before { content: "\f070" }
.octicon-hubot:before { content: "\f09d" }
.octicon-inbox:before { content: "\f0cf" }
.octicon-info:before { content: "\f059" }
.octicon-issue-closed:before { content: "\f028" }
.octicon-issue-opened:before { content: "\f026" }
.octicon-issue-reopened:before { content: "\f027" }
.octicon-italic:before { content: "\f285" }
.octicon-jersey:before { content: "\f019" }
.octicon-kebab-horizontal:before { content: "\f286" }
.octicon-kebab-vertical:before { content: "\f287" }
.octicon-key:before { content: "\f049" }
.octicon-keyboard:before { content: "\f00d" }
.octicon-law:before { content: "\f0d8" }
.octicon-light-bulb:before { content: "\f000" }
.octicon-link-external:before { content: "\f07f" }
.octicon-link:before { content: "\f05c" }
.octicon-list-ordered:before { content: "\f062" }
.octicon-list-unordered:before { content: "\f061" }
.octicon-location:before { content: "\f060" }
.octicon-lock:before { content: "\f06a" }
.octicon-log-in:before { content: "\f036" }
.octicon-log-out:before { content: "\f032" }
.octicon-logo-gist:before { content: "\f288" }
.octicon-logo-github:before { content: "\f092" }
.octicon-mail-read:before { content: "\f03c" }
.octicon-mail-reply:before { content: "\f28c" }
.octicon-mail:before { content: "\f03b" }
.octicon-mark-github:before { content: "\f00a" }
.octicon-markdown:before { content: "\f0c9" }
.octicon-megaphone:before { content: "\f077" }
.octicon-mention:before { content: "\f0be" }
.octicon-microscope:before { content: "\f0dd" }
.octicon-milestone:before { content: "\f075" }
.octicon-mirror-private:before { content: "\f06a" }
.octicon-mirror-public:before { content: "\f024" }
.octicon-mirror:before { content: "\f024" }
.octicon-mortar-board:before { content: "\f0d7" }
.octicon-mute:before { content: "\f080" }
.octicon-no-newline:before { content: "\f09c" }
.octicon-note:before { content: "\f289" }
.octicon-octoface:before { content: "\f008" }
.octicon-organization:before { content: "\f037" }
.octicon-organization-filled:before { content: "\26a2" }
.octicon-organization-outline:before { content: "\f037" }
.octicon-package:before { content: "\f0c4" }
.octicon-paintcan:before { content: "\f0d1" }
.octicon-pencil:before { content: "\f058" }
.octicon-person-add:before { content: "\f018" }
.octicon-person-follow:before { content: "\f018" }
.octicon-person:before { content: "\f018" }
.octicon-person-filled:before { content: "\26a3" }
.octicon-person-outline:before { content: "\f018" }
.octicon-pin:before { content: "\f041" }
.octicon-plug:before { content: "\f0d4" }
.octicon-plus-small:before { content: "\f28a" }
.octicon-plus:before { content: "\f05d" }
.octicon-primitive-dot:before { content: "\f052" }
.octicon-primitive-square:before { content: "\f053" }
.octicon-project:before { content: "\f28b" }
.octicon-pulse:before { content: "\f085" }
.octicon-question:before { content: "\f02c" }
.octicon-quote:before { content: "\f063" }
.octicon-radio-tower:before { content: "\f030" }
.octicon-remove-close:before { content: "\f081" }
.octicon-reply:before { content: "\f28c" }
.octicon-repo-clone:before { content: "\f04c" }
.octicon-repo-create:before { content: "\f05d" }
.octicon-repo-delete:before { content: "\f001" }
.octicon-repo-force-push:before { content: "\f04a" }
.octicon-repo-forked:before { content: "\f002" }
.octicon-repo-pull:before { content: "\f006" }
.octicon-repo-push:before { content: "\f005" }
.octicon-repo-sync:before { content: "\f087" }
.octicon-repo:before { content: "\f001" }
.octicon-report:before { content: "\f28d" }
.octicon-rocket:before { content: "\f033" }
.octicon-rss:before { content: "\f034" }
.octicon-ruby:before { content: "\f047" }
.octicon-screen-full:before { content: "\f066" }
.octicon-screen-normal:before { content: "\f067" }
.octicon-search-save:before { content: "\f02e" }
.octicon-search:before { content: "\f02e" }
.octicon-server:before { content: "\f097" }
.octicon-settings:before { content: "\f07c" }
.octicon-shield:before { content: "\f0e1" }
.octicon-sign-in:before { content: "\f036" }
.octicon-sign-out:before { content: "\f032" }
.octicon-smiley:before { content: "\f27d" }
.octicon-squirrel:before { content: "\f0b2" }
.octicon-star-add:before { content: "\f02a" }
.octicon-star-delete:before { content: "\f02a" }
.octicon-star:before { content: "\f02a" }
.octicon-stop:before { content: "\f08f" }
.octicon-sync:before { content: "\f087" }
.octicon-tag-add:before { content: "\f015" }
.octicon-tag-remove:before { content: "\f015" }
.octicon-tag:before { content: "\f015" }
.octicon-tasklist:before { content: "\f27e" }
.octicon-telescope:before { content: "\f088" }
.octicon-terminal:before { content: "\f0c8" }
.octicon-text-size:before { content: "\f27f" }
.octicon-three-bars:before { content: "\f05e" }
.octicon-thumbsdown:before { content: "\f0db" }
.octicon-thumbsup:before { content: "\f0da" }
.octicon-tools:before { content: "\f031" }
.octicon-trashcan:before { content: "\f0d0" }
.octicon-triangle-down:before { content: "\f05b" }
.octicon-triangle-left:before { content: "\f044" }
.octicon-triangle-right:before { content: "\f05a" }
.octicon-triangle-up:before { content: "\f0aa" }
.octicon-unfold:before { content: "\f039" }
.octicon-unmute:before { content: "\f0ba" }
.octicon-unverified:before { content: "\f280" }
.octicon-verified:before { content: "\f281" }
.octicon-versions:before { content: "\f064" }
.octicon-watch:before { content: "\f0e0" }
.octicon-x:before { content: "\f081" }
.octicon-zap:before { content: "\26a1" }
.octicon-archive:before { content: "\f101" }
.octicon-arrow-both:before { content: "\f102" }
.octicon-eye-closed:before { content: "\f103" }
.octicon-fold-down:before { content: "\f104" }
.octicon-fold-up:before { content: "\f105" }
.octicon-github-action:before { content: "\f106" }
.octicon-play:before { content: "\f107" }
.octicon-request-changes:before { content: "\f108" }

View File

@@ -1,220 +0,0 @@
@octicons-font-path: ".";
@octicons-version: "396334ee3da78f4302d25c758ae3e3ce5dc3c97d";
@font-face {
font-family: 'octicons';
src: ~"url('@{octicons-font-path}/octicons.eot?#iefix&v=@{octicons-version}') format('embedded-opentype')",
~"url('@{octicons-font-path}/octicons.woff?v=@{octicons-version}') format('woff')",
~"url('@{octicons-font-path}/octicons.ttf?v=@{octicons-version}') format('truetype')",
~"url('@{octicons-font-path}/octicons.svg?v=@{octicons-version}#octicons') format('svg')";
font-weight: normal;
font-style: normal;
}
// .octicon is optimized for 16px.
// .mega-octicon is optimized for 32px but can be used larger.
.octicon, .mega-octicon {
font: normal normal normal 16px/1 octicons;
display: inline-block;
text-decoration: none;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.mega-octicon { font-size: 32px; }
.octicon-alert:before { content: '\f02d'} /*  */
.octicon-arrow-down:before { content: '\f03f'} /*  */
.octicon-arrow-left:before { content: '\f040'} /*  */
.octicon-arrow-right:before { content: '\f03e'} /*  */
.octicon-arrow-small-down:before { content: '\f0a0'} /*  */
.octicon-arrow-small-left:before { content: '\f0a1'} /*  */
.octicon-arrow-small-right:before { content: '\f071'} /*  */
.octicon-arrow-small-up:before { content: '\f09f'} /*  */
.octicon-arrow-up:before { content: '\f03d'} /*  */
.octicon-microscope:before,
.octicon-beaker:before { content: '\f0dd'} /*  */
.octicon-bell:before { content: '\f0de'} /*  */
.octicon-book:before { content: '\f007'} /*  */
.octicon-bookmark:before { content: '\f07b'} /*  */
.octicon-briefcase:before { content: '\f0d3'} /*  */
.octicon-broadcast:before { content: '\f048'} /*  */
.octicon-browser:before { content: '\f0c5'} /*  */
.octicon-bug:before { content: '\f091'} /*  */
.octicon-calendar:before { content: '\f068'} /*  */
.octicon-check:before { content: '\f03a'} /*  */
.octicon-checklist:before { content: '\f076'} /*  */
.octicon-chevron-down:before { content: '\f0a3'} /*  */
.octicon-chevron-left:before { content: '\f0a4'} /*  */
.octicon-chevron-right:before { content: '\f078'} /*  */
.octicon-chevron-up:before { content: '\f0a2'} /*  */
.octicon-circle-slash:before { content: '\f084'} /*  */
.octicon-circuit-board:before { content: '\f0d6'} /*  */
.octicon-clippy:before { content: '\f035'} /*  */
.octicon-clock:before { content: '\f046'} /*  */
.octicon-cloud-download:before { content: '\f00b'} /*  */
.octicon-cloud-upload:before { content: '\f00c'} /*  */
.octicon-code:before { content: '\f05f'} /*  */
.octicon-color-mode:before { content: '\f065'} /*  */
.octicon-comment-add:before,
.octicon-comment:before { content: '\f02b'} /*  */
.octicon-comment-discussion:before { content: '\f04f'} /*  */
.octicon-credit-card:before { content: '\f045'} /*  */
.octicon-dash:before { content: '\f0ca'} /*  */
.octicon-dashboard:before { content: '\f07d'} /*  */
.octicon-database:before { content: '\f096'} /*  */
.octicon-clone:before,
.octicon-desktop-download:before { content: '\f0dc'} /*  */
.octicon-device-camera:before { content: '\f056'} /*  */
.octicon-device-camera-video:before { content: '\f057'} /*  */
.octicon-device-desktop:before { content: '\f27c'} /*  */
.octicon-device-mobile:before { content: '\f038'} /*  */
.octicon-diff:before { content: '\f04d'} /*  */
.octicon-diff-added:before { content: '\f06b'} /*  */
.octicon-diff-ignored:before { content: '\f099'} /*  */
.octicon-diff-modified:before { content: '\f06d'} /*  */
.octicon-diff-removed:before { content: '\f06c'} /*  */
.octicon-diff-renamed:before { content: '\f06e'} /*  */
.octicon-ellipsis:before { content: '\f09a'} /*  */
.octicon-eye-unwatch:before,
.octicon-eye-watch:before,
.octicon-eye:before { content: '\f04e'} /*  */
.octicon-file-binary:before { content: '\f094'} /*  */
.octicon-file-code:before { content: '\f010'} /*  */
.octicon-file-directory:before { content: '\f016'} /*  */
.octicon-file-media:before { content: '\f012'} /*  */
.octicon-file-pdf:before { content: '\f014'} /*  */
.octicon-file-submodule:before { content: '\f017'} /*  */
.octicon-file-symlink-directory:before { content: '\f0b1'} /*  */
.octicon-file-symlink-file:before { content: '\f0b0'} /*  */
.octicon-file-text:before { content: '\f011'} /*  */
.octicon-file-zip:before { content: '\f013'} /*  */
.octicon-flame:before { content: '\f0d2'} /*  */
.octicon-fold:before { content: '\f0cc'} /*  */
.octicon-gear:before { content: '\f02f'} /*  */
.octicon-gift:before { content: '\f042'} /*  */
.octicon-gist:before { content: '\f00e'} /*  */
.octicon-gist-secret:before { content: '\f08c'} /*  */
.octicon-git-branch-create:before,
.octicon-git-branch-delete:before,
.octicon-git-branch:before { content: '\f020'} /*  */
.octicon-git-commit:before { content: '\f01f'} /*  */
.octicon-git-compare:before { content: '\f0ac'} /*  */
.octicon-git-merge:before { content: '\f023'} /*  */
.octicon-git-pull-request-abandoned:before,
.octicon-git-pull-request:before { content: '\f009'} /*  */
.octicon-globe:before { content: '\f0b6'} /*  */
.octicon-graph:before { content: '\f043'} /*  */
.octicon-heart:before { content: '\2665'} /* ♥ */
.octicon-history:before { content: '\f07e'} /*  */
.octicon-home:before { content: '\f08d'} /*  */
.octicon-horizontal-rule:before { content: '\f070'} /*  */
.octicon-hubot:before { content: '\f09d'} /*  */
.octicon-inbox:before { content: '\f0cf'} /*  */
.octicon-info:before { content: '\f059'} /*  */
.octicon-issue-closed:before { content: '\f028'} /*  */
.octicon-issue-opened:before { content: '\f026'} /*  */
.octicon-issue-reopened:before { content: '\f027'} /*  */
.octicon-jersey:before { content: '\f019'} /*  */
.octicon-key:before { content: '\f049'} /*  */
.octicon-keyboard:before { content: '\f00d'} /*  */
.octicon-law:before { content: '\f0d8'} /*  */
.octicon-light-bulb:before { content: '\f000'} /*  */
.octicon-link:before { content: '\f05c'} /*  */
.octicon-link-external:before { content: '\f07f'} /*  */
.octicon-list-ordered:before { content: '\f062'} /*  */
.octicon-list-unordered:before { content: '\f061'} /*  */
.octicon-location:before { content: '\f060'} /*  */
.octicon-gist-private:before,
.octicon-mirror-private:before,
.octicon-git-fork-private:before,
.octicon-lock:before { content: '\f06a'} /*  */
.octicon-logo-github:before { content: '\f092'} /*  */
.octicon-mail:before { content: '\f03b'} /*  */
.octicon-mail-read:before { content: '\f03c'} /*  */
.octicon-mail-reply:before { content: '\f051'} /*  */
.octicon-mark-github:before { content: '\f00a'} /*  */
.octicon-markdown:before { content: '\f0c9'} /*  */
.octicon-megaphone:before { content: '\f077'} /*  */
.octicon-mention:before { content: '\f0be'} /*  */
.octicon-milestone:before { content: '\f075'} /*  */
.octicon-mirror-public:before,
.octicon-mirror:before { content: '\f024'} /*  */
.octicon-mortar-board:before { content: '\f0d7'} /*  */
.octicon-mute:before { content: '\f080'} /*  */
.octicon-no-newline:before { content: '\f09c'} /*  */
.octicon-octoface:before { content: '\f008'} /*  */
.octicon-organization:before { content: '\f037'} /*  */
.octicon-package:before { content: '\f0c4'} /*  */
.octicon-paintcan:before { content: '\f0d1'} /*  */
.octicon-pencil:before { content: '\f058'} /*  */
.octicon-person-add:before,
.octicon-person-follow:before,
.octicon-person:before { content: '\f018'} /*  */
.octicon-pin:before { content: '\f041'} /*  */
.octicon-plug:before { content: '\f0d4'} /*  */
.octicon-repo-create:before,
.octicon-gist-new:before,
.octicon-file-directory-create:before,
.octicon-file-add:before,
.octicon-plus:before { content: '\f05d'} /*  */
.octicon-primitive-dot:before { content: '\f052'} /*  */
.octicon-primitive-square:before { content: '\f053'} /*  */
.octicon-pulse:before { content: '\f085'} /*  */
.octicon-question:before { content: '\f02c'} /*  */
.octicon-quote:before { content: '\f063'} /*  */
.octicon-radio-tower:before { content: '\f030'} /*  */
.octicon-repo-delete:before,
.octicon-repo:before { content: '\f001'} /*  */
.octicon-repo-clone:before { content: '\f04c'} /*  */
.octicon-repo-force-push:before { content: '\f04a'} /*  */
.octicon-gist-fork:before,
.octicon-repo-forked:before { content: '\f002'} /*  */
.octicon-repo-pull:before { content: '\f006'} /*  */
.octicon-repo-push:before { content: '\f005'} /*  */
.octicon-rocket:before { content: '\f033'} /*  */
.octicon-rss:before { content: '\f034'} /*  */
.octicon-ruby:before { content: '\f047'} /*  */
.octicon-screen-full:before { content: '\f066'} /*  */
.octicon-screen-normal:before { content: '\f067'} /*  */
.octicon-search-save:before,
.octicon-search:before { content: '\f02e'} /*  */
.octicon-server:before { content: '\f097'} /*  */
.octicon-settings:before { content: '\f07c'} /*  */
.octicon-shield:before { content: '\f0e1'} /*  */
.octicon-log-in:before,
.octicon-sign-in:before { content: '\f036'} /*  */
.octicon-log-out:before,
.octicon-sign-out:before { content: '\f032'} /*  */
.octicon-squirrel:before { content: '\f0b2'} /*  */
.octicon-star-add:before,
.octicon-star-delete:before,
.octicon-star:before { content: '\f02a'} /*  */
.octicon-stop:before { content: '\f08f'} /*  */
.octicon-repo-sync:before,
.octicon-sync:before { content: '\f087'} /*  */
.octicon-tag-remove:before,
.octicon-tag-add:before,
.octicon-tag:before { content: '\f015'} /*  */
.octicon-telescope:before { content: '\f088'} /*  */
.octicon-terminal:before { content: '\f0c8'} /*  */
.octicon-three-bars:before { content: '\f05e'} /*  */
.octicon-thumbsdown:before { content: '\f0db'} /*  */
.octicon-thumbsup:before { content: '\f0da'} /*  */
.octicon-tools:before { content: '\f031'} /*  */
.octicon-trashcan:before { content: '\f0d0'} /*  */
.octicon-triangle-down:before { content: '\f05b'} /*  */
.octicon-triangle-left:before { content: '\f044'} /*  */
.octicon-triangle-right:before { content: '\f05a'} /*  */
.octicon-triangle-up:before { content: '\f0aa'} /*  */
.octicon-unfold:before { content: '\f039'} /*  */
.octicon-unmute:before { content: '\f0ba'} /*  */
.octicon-versions:before { content: '\f064'} /*  */
.octicon-watch:before { content: '\f0e0'} /*  */
.octicon-remove-close:before,
.octicon-x:before { content: '\f081'} /*  */
.octicon-zap:before { content: '\26A1'} /* ⚡ */

View File

@@ -1,220 +0,0 @@
$octicons-font-path: "." !default;
$octicons-version: "396334ee3da78f4302d25c758ae3e3ce5dc3c97d";
@font-face {
font-family: 'octicons';
src: url('#{$octicons-font-path}/octicons.eot?#iefix&v=#{$octicons-version}') format('embedded-opentype'),
url('#{$octicons-font-path}/octicons.woff?v=#{$octicons-version}') format('woff'),
url('#{$octicons-font-path}/octicons.ttf?v=#{$octicons-version}') format('truetype'),
url('#{$octicons-font-path}/octicons.svg?v=#{$octicons-version}#octicons') format('svg');
font-weight: normal;
font-style: normal;
}
// .octicon is optimized for 16px.
// .mega-octicon is optimized for 32px but can be used larger.
.octicon, .mega-octicon {
font: normal normal normal 16px/1 octicons;
display: inline-block;
text-decoration: none;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.mega-octicon { font-size: 32px; }
.octicon-alert:before { content: '\f02d'} /*  */
.octicon-arrow-down:before { content: '\f03f'} /*  */
.octicon-arrow-left:before { content: '\f040'} /*  */
.octicon-arrow-right:before { content: '\f03e'} /*  */
.octicon-arrow-small-down:before { content: '\f0a0'} /*  */
.octicon-arrow-small-left:before { content: '\f0a1'} /*  */
.octicon-arrow-small-right:before { content: '\f071'} /*  */
.octicon-arrow-small-up:before { content: '\f09f'} /*  */
.octicon-arrow-up:before { content: '\f03d'} /*  */
.octicon-microscope:before,
.octicon-beaker:before { content: '\f0dd'} /*  */
.octicon-bell:before { content: '\f0de'} /*  */
.octicon-book:before { content: '\f007'} /*  */
.octicon-bookmark:before { content: '\f07b'} /*  */
.octicon-briefcase:before { content: '\f0d3'} /*  */
.octicon-broadcast:before { content: '\f048'} /*  */
.octicon-browser:before { content: '\f0c5'} /*  */
.octicon-bug:before { content: '\f091'} /*  */
.octicon-calendar:before { content: '\f068'} /*  */
.octicon-check:before { content: '\f03a'} /*  */
.octicon-checklist:before { content: '\f076'} /*  */
.octicon-chevron-down:before { content: '\f0a3'} /*  */
.octicon-chevron-left:before { content: '\f0a4'} /*  */
.octicon-chevron-right:before { content: '\f078'} /*  */
.octicon-chevron-up:before { content: '\f0a2'} /*  */
.octicon-circle-slash:before { content: '\f084'} /*  */
.octicon-circuit-board:before { content: '\f0d6'} /*  */
.octicon-clippy:before { content: '\f035'} /*  */
.octicon-clock:before { content: '\f046'} /*  */
.octicon-cloud-download:before { content: '\f00b'} /*  */
.octicon-cloud-upload:before { content: '\f00c'} /*  */
.octicon-code:before { content: '\f05f'} /*  */
.octicon-color-mode:before { content: '\f065'} /*  */
.octicon-comment-add:before,
.octicon-comment:before { content: '\f02b'} /*  */
.octicon-comment-discussion:before { content: '\f04f'} /*  */
.octicon-credit-card:before { content: '\f045'} /*  */
.octicon-dash:before { content: '\f0ca'} /*  */
.octicon-dashboard:before { content: '\f07d'} /*  */
.octicon-database:before { content: '\f096'} /*  */
.octicon-clone:before,
.octicon-desktop-download:before { content: '\f0dc'} /*  */
.octicon-device-camera:before { content: '\f056'} /*  */
.octicon-device-camera-video:before { content: '\f057'} /*  */
.octicon-device-desktop:before { content: '\f27c'} /*  */
.octicon-device-mobile:before { content: '\f038'} /*  */
.octicon-diff:before { content: '\f04d'} /*  */
.octicon-diff-added:before { content: '\f06b'} /*  */
.octicon-diff-ignored:before { content: '\f099'} /*  */
.octicon-diff-modified:before { content: '\f06d'} /*  */
.octicon-diff-removed:before { content: '\f06c'} /*  */
.octicon-diff-renamed:before { content: '\f06e'} /*  */
.octicon-ellipsis:before { content: '\f09a'} /*  */
.octicon-eye-unwatch:before,
.octicon-eye-watch:before,
.octicon-eye:before { content: '\f04e'} /*  */
.octicon-file-binary:before { content: '\f094'} /*  */
.octicon-file-code:before { content: '\f010'} /*  */
.octicon-file-directory:before { content: '\f016'} /*  */
.octicon-file-media:before { content: '\f012'} /*  */
.octicon-file-pdf:before { content: '\f014'} /*  */
.octicon-file-submodule:before { content: '\f017'} /*  */
.octicon-file-symlink-directory:before { content: '\f0b1'} /*  */
.octicon-file-symlink-file:before { content: '\f0b0'} /*  */
.octicon-file-text:before { content: '\f011'} /*  */
.octicon-file-zip:before { content: '\f013'} /*  */
.octicon-flame:before { content: '\f0d2'} /*  */
.octicon-fold:before { content: '\f0cc'} /*  */
.octicon-gear:before { content: '\f02f'} /*  */
.octicon-gift:before { content: '\f042'} /*  */
.octicon-gist:before { content: '\f00e'} /*  */
.octicon-gist-secret:before { content: '\f08c'} /*  */
.octicon-git-branch-create:before,
.octicon-git-branch-delete:before,
.octicon-git-branch:before { content: '\f020'} /*  */
.octicon-git-commit:before { content: '\f01f'} /*  */
.octicon-git-compare:before { content: '\f0ac'} /*  */
.octicon-git-merge:before { content: '\f023'} /*  */
.octicon-git-pull-request-abandoned:before,
.octicon-git-pull-request:before { content: '\f009'} /*  */
.octicon-globe:before { content: '\f0b6'} /*  */
.octicon-graph:before { content: '\f043'} /*  */
.octicon-heart:before { content: '\2665'} /* ♥ */
.octicon-history:before { content: '\f07e'} /*  */
.octicon-home:before { content: '\f08d'} /*  */
.octicon-horizontal-rule:before { content: '\f070'} /*  */
.octicon-hubot:before { content: '\f09d'} /*  */
.octicon-inbox:before { content: '\f0cf'} /*  */
.octicon-info:before { content: '\f059'} /*  */
.octicon-issue-closed:before { content: '\f028'} /*  */
.octicon-issue-opened:before { content: '\f026'} /*  */
.octicon-issue-reopened:before { content: '\f027'} /*  */
.octicon-jersey:before { content: '\f019'} /*  */
.octicon-key:before { content: '\f049'} /*  */
.octicon-keyboard:before { content: '\f00d'} /*  */
.octicon-law:before { content: '\f0d8'} /*  */
.octicon-light-bulb:before { content: '\f000'} /*  */
.octicon-link:before { content: '\f05c'} /*  */
.octicon-link-external:before { content: '\f07f'} /*  */
.octicon-list-ordered:before { content: '\f062'} /*  */
.octicon-list-unordered:before { content: '\f061'} /*  */
.octicon-location:before { content: '\f060'} /*  */
.octicon-gist-private:before,
.octicon-mirror-private:before,
.octicon-git-fork-private:before,
.octicon-lock:before { content: '\f06a'} /*  */
.octicon-logo-github:before { content: '\f092'} /*  */
.octicon-mail:before { content: '\f03b'} /*  */
.octicon-mail-read:before { content: '\f03c'} /*  */
.octicon-mail-reply:before { content: '\f051'} /*  */
.octicon-mark-github:before { content: '\f00a'} /*  */
.octicon-markdown:before { content: '\f0c9'} /*  */
.octicon-megaphone:before { content: '\f077'} /*  */
.octicon-mention:before { content: '\f0be'} /*  */
.octicon-milestone:before { content: '\f075'} /*  */
.octicon-mirror-public:before,
.octicon-mirror:before { content: '\f024'} /*  */
.octicon-mortar-board:before { content: '\f0d7'} /*  */
.octicon-mute:before { content: '\f080'} /*  */
.octicon-no-newline:before { content: '\f09c'} /*  */
.octicon-octoface:before { content: '\f008'} /*  */
.octicon-organization:before { content: '\f037'} /*  */
.octicon-package:before { content: '\f0c4'} /*  */
.octicon-paintcan:before { content: '\f0d1'} /*  */
.octicon-pencil:before { content: '\f058'} /*  */
.octicon-person-add:before,
.octicon-person-follow:before,
.octicon-person:before { content: '\f018'} /*  */
.octicon-pin:before { content: '\f041'} /*  */
.octicon-plug:before { content: '\f0d4'} /*  */
.octicon-repo-create:before,
.octicon-gist-new:before,
.octicon-file-directory-create:before,
.octicon-file-add:before,
.octicon-plus:before { content: '\f05d'} /*  */
.octicon-primitive-dot:before { content: '\f052'} /*  */
.octicon-primitive-square:before { content: '\f053'} /*  */
.octicon-pulse:before { content: '\f085'} /*  */
.octicon-question:before { content: '\f02c'} /*  */
.octicon-quote:before { content: '\f063'} /*  */
.octicon-radio-tower:before { content: '\f030'} /*  */
.octicon-repo-delete:before,
.octicon-repo:before { content: '\f001'} /*  */
.octicon-repo-clone:before { content: '\f04c'} /*  */
.octicon-repo-force-push:before { content: '\f04a'} /*  */
.octicon-gist-fork:before,
.octicon-repo-forked:before { content: '\f002'} /*  */
.octicon-repo-pull:before { content: '\f006'} /*  */
.octicon-repo-push:before { content: '\f005'} /*  */
.octicon-rocket:before { content: '\f033'} /*  */
.octicon-rss:before { content: '\f034'} /*  */
.octicon-ruby:before { content: '\f047'} /*  */
.octicon-screen-full:before { content: '\f066'} /*  */
.octicon-screen-normal:before { content: '\f067'} /*  */
.octicon-search-save:before,
.octicon-search:before { content: '\f02e'} /*  */
.octicon-server:before { content: '\f097'} /*  */
.octicon-settings:before { content: '\f07c'} /*  */
.octicon-shield:before { content: '\f0e1'} /*  */
.octicon-log-in:before,
.octicon-sign-in:before { content: '\f036'} /*  */
.octicon-log-out:before,
.octicon-sign-out:before { content: '\f032'} /*  */
.octicon-squirrel:before { content: '\f0b2'} /*  */
.octicon-star-add:before,
.octicon-star-delete:before,
.octicon-star:before { content: '\f02a'} /*  */
.octicon-stop:before { content: '\f08f'} /*  */
.octicon-repo-sync:before,
.octicon-sync:before { content: '\f087'} /*  */
.octicon-tag-remove:before,
.octicon-tag-add:before,
.octicon-tag:before { content: '\f015'} /*  */
.octicon-telescope:before { content: '\f088'} /*  */
.octicon-terminal:before { content: '\f0c8'} /*  */
.octicon-three-bars:before { content: '\f05e'} /*  */
.octicon-thumbsdown:before { content: '\f0db'} /*  */
.octicon-thumbsup:before { content: '\f0da'} /*  */
.octicon-tools:before { content: '\f031'} /*  */
.octicon-trashcan:before { content: '\f0d0'} /*  */
.octicon-triangle-down:before { content: '\f05b'} /*  */
.octicon-triangle-left:before { content: '\f044'} /*  */
.octicon-triangle-right:before { content: '\f05a'} /*  */
.octicon-triangle-up:before { content: '\f0aa'} /*  */
.octicon-unfold:before { content: '\f039'} /*  */
.octicon-unmute:before { content: '\f0ba'} /*  */
.octicon-versions:before { content: '\f064'} /*  */
.octicon-watch:before { content: '\f0e0'} /*  */
.octicon-remove-close:before,
.octicon-x:before { content: '\f081'} /*  */
.octicon-zap:before { content: '\26A1'} /* ⚡ */

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 131 KiB

View File

@@ -1,217 +0,0 @@
@font-face {
font-family: 'octicons';
src: font-url('octicons.eot?#iefix') format('embedded-opentype'),
font-url('octicons.woff') format('woff'),
font-url('octicons.ttf') format('truetype'),
font-url('octicons.svg#octicons') format('svg');
font-weight: normal;
font-style: normal;
}
// .octicon is optimized for 16px.
// .mega-octicon is optimized for 32px but can be used larger.
.octicon, .mega-octicon {
font: normal normal normal 16px/1 octicons;
display: inline-block;
text-decoration: none;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.mega-octicon { font-size: 32px; }
.octicon-alert:before { content: '\f02d'} /*  */
.octicon-arrow-down:before { content: '\f03f'} /*  */
.octicon-arrow-left:before { content: '\f040'} /*  */
.octicon-arrow-right:before { content: '\f03e'} /*  */
.octicon-arrow-small-down:before { content: '\f0a0'} /*  */
.octicon-arrow-small-left:before { content: '\f0a1'} /*  */
.octicon-arrow-small-right:before { content: '\f071'} /*  */
.octicon-arrow-small-up:before { content: '\f09f'} /*  */
.octicon-arrow-up:before { content: '\f03d'} /*  */
.octicon-microscope:before,
.octicon-beaker:before { content: '\f0dd'} /*  */
.octicon-bell:before { content: '\f0de'} /*  */
.octicon-book:before { content: '\f007'} /*  */
.octicon-bookmark:before { content: '\f07b'} /*  */
.octicon-briefcase:before { content: '\f0d3'} /*  */
.octicon-broadcast:before { content: '\f048'} /*  */
.octicon-browser:before { content: '\f0c5'} /*  */
.octicon-bug:before { content: '\f091'} /*  */
.octicon-calendar:before { content: '\f068'} /*  */
.octicon-check:before { content: '\f03a'} /*  */
.octicon-checklist:before { content: '\f076'} /*  */
.octicon-chevron-down:before { content: '\f0a3'} /*  */
.octicon-chevron-left:before { content: '\f0a4'} /*  */
.octicon-chevron-right:before { content: '\f078'} /*  */
.octicon-chevron-up:before { content: '\f0a2'} /*  */
.octicon-circle-slash:before { content: '\f084'} /*  */
.octicon-circuit-board:before { content: '\f0d6'} /*  */
.octicon-clippy:before { content: '\f035'} /*  */
.octicon-clock:before { content: '\f046'} /*  */
.octicon-cloud-download:before { content: '\f00b'} /*  */
.octicon-cloud-upload:before { content: '\f00c'} /*  */
.octicon-code:before { content: '\f05f'} /*  */
.octicon-color-mode:before { content: '\f065'} /*  */
.octicon-comment-add:before,
.octicon-comment:before { content: '\f02b'} /*  */
.octicon-comment-discussion:before { content: '\f04f'} /*  */
.octicon-credit-card:before { content: '\f045'} /*  */
.octicon-dash:before { content: '\f0ca'} /*  */
.octicon-dashboard:before { content: '\f07d'} /*  */
.octicon-database:before { content: '\f096'} /*  */
.octicon-clone:before,
.octicon-desktop-download:before { content: '\f0dc'} /*  */
.octicon-device-camera:before { content: '\f056'} /*  */
.octicon-device-camera-video:before { content: '\f057'} /*  */
.octicon-device-desktop:before { content: '\f27c'} /*  */
.octicon-device-mobile:before { content: '\f038'} /*  */
.octicon-diff:before { content: '\f04d'} /*  */
.octicon-diff-added:before { content: '\f06b'} /*  */
.octicon-diff-ignored:before { content: '\f099'} /*  */
.octicon-diff-modified:before { content: '\f06d'} /*  */
.octicon-diff-removed:before { content: '\f06c'} /*  */
.octicon-diff-renamed:before { content: '\f06e'} /*  */
.octicon-ellipsis:before { content: '\f09a'} /*  */
.octicon-eye-unwatch:before,
.octicon-eye-watch:before,
.octicon-eye:before { content: '\f04e'} /*  */
.octicon-file-binary:before { content: '\f094'} /*  */
.octicon-file-code:before { content: '\f010'} /*  */
.octicon-file-directory:before { content: '\f016'} /*  */
.octicon-file-media:before { content: '\f012'} /*  */
.octicon-file-pdf:before { content: '\f014'} /*  */
.octicon-file-submodule:before { content: '\f017'} /*  */
.octicon-file-symlink-directory:before { content: '\f0b1'} /*  */
.octicon-file-symlink-file:before { content: '\f0b0'} /*  */
.octicon-file-text:before { content: '\f011'} /*  */
.octicon-file-zip:before { content: '\f013'} /*  */
.octicon-flame:before { content: '\f0d2'} /*  */
.octicon-fold:before { content: '\f0cc'} /*  */
.octicon-gear:before { content: '\f02f'} /*  */
.octicon-gift:before { content: '\f042'} /*  */
.octicon-gist:before { content: '\f00e'} /*  */
.octicon-gist-secret:before { content: '\f08c'} /*  */
.octicon-git-branch-create:before,
.octicon-git-branch-delete:before,
.octicon-git-branch:before { content: '\f020'} /*  */
.octicon-git-commit:before { content: '\f01f'} /*  */
.octicon-git-compare:before { content: '\f0ac'} /*  */
.octicon-git-merge:before { content: '\f023'} /*  */
.octicon-git-pull-request-abandoned:before,
.octicon-git-pull-request:before { content: '\f009'} /*  */
.octicon-globe:before { content: '\f0b6'} /*  */
.octicon-graph:before { content: '\f043'} /*  */
.octicon-heart:before { content: '\2665'} /* ♥ */
.octicon-history:before { content: '\f07e'} /*  */
.octicon-home:before { content: '\f08d'} /*  */
.octicon-horizontal-rule:before { content: '\f070'} /*  */
.octicon-hubot:before { content: '\f09d'} /*  */
.octicon-inbox:before { content: '\f0cf'} /*  */
.octicon-info:before { content: '\f059'} /*  */
.octicon-issue-closed:before { content: '\f028'} /*  */
.octicon-issue-opened:before { content: '\f026'} /*  */
.octicon-issue-reopened:before { content: '\f027'} /*  */
.octicon-jersey:before { content: '\f019'} /*  */
.octicon-key:before { content: '\f049'} /*  */
.octicon-keyboard:before { content: '\f00d'} /*  */
.octicon-law:before { content: '\f0d8'} /*  */
.octicon-light-bulb:before { content: '\f000'} /*  */
.octicon-link:before { content: '\f05c'} /*  */
.octicon-link-external:before { content: '\f07f'} /*  */
.octicon-list-ordered:before { content: '\f062'} /*  */
.octicon-list-unordered:before { content: '\f061'} /*  */
.octicon-location:before { content: '\f060'} /*  */
.octicon-gist-private:before,
.octicon-mirror-private:before,
.octicon-git-fork-private:before,
.octicon-lock:before { content: '\f06a'} /*  */
.octicon-logo-github:before { content: '\f092'} /*  */
.octicon-mail:before { content: '\f03b'} /*  */
.octicon-mail-read:before { content: '\f03c'} /*  */
.octicon-mail-reply:before { content: '\f051'} /*  */
.octicon-mark-github:before { content: '\f00a'} /*  */
.octicon-markdown:before { content: '\f0c9'} /*  */
.octicon-megaphone:before { content: '\f077'} /*  */
.octicon-mention:before { content: '\f0be'} /*  */
.octicon-milestone:before { content: '\f075'} /*  */
.octicon-mirror-public:before,
.octicon-mirror:before { content: '\f024'} /*  */
.octicon-mortar-board:before { content: '\f0d7'} /*  */
.octicon-mute:before { content: '\f080'} /*  */
.octicon-no-newline:before { content: '\f09c'} /*  */
.octicon-octoface:before { content: '\f008'} /*  */
.octicon-organization:before { content: '\f037'} /*  */
.octicon-package:before { content: '\f0c4'} /*  */
.octicon-paintcan:before { content: '\f0d1'} /*  */
.octicon-pencil:before { content: '\f058'} /*  */
.octicon-person-add:before,
.octicon-person-follow:before,
.octicon-person:before { content: '\f018'} /*  */
.octicon-pin:before { content: '\f041'} /*  */
.octicon-plug:before { content: '\f0d4'} /*  */
.octicon-repo-create:before,
.octicon-gist-new:before,
.octicon-file-directory-create:before,
.octicon-file-add:before,
.octicon-plus:before { content: '\f05d'} /*  */
.octicon-primitive-dot:before { content: '\f052'} /*  */
.octicon-primitive-square:before { content: '\f053'} /*  */
.octicon-pulse:before { content: '\f085'} /*  */
.octicon-question:before { content: '\f02c'} /*  */
.octicon-quote:before { content: '\f063'} /*  */
.octicon-radio-tower:before { content: '\f030'} /*  */
.octicon-repo-delete:before,
.octicon-repo:before { content: '\f001'} /*  */
.octicon-repo-clone:before { content: '\f04c'} /*  */
.octicon-repo-force-push:before { content: '\f04a'} /*  */
.octicon-gist-fork:before,
.octicon-repo-forked:before { content: '\f002'} /*  */
.octicon-repo-pull:before { content: '\f006'} /*  */
.octicon-repo-push:before { content: '\f005'} /*  */
.octicon-rocket:before { content: '\f033'} /*  */
.octicon-rss:before { content: '\f034'} /*  */
.octicon-ruby:before { content: '\f047'} /*  */
.octicon-screen-full:before { content: '\f066'} /*  */
.octicon-screen-normal:before { content: '\f067'} /*  */
.octicon-search-save:before,
.octicon-search:before { content: '\f02e'} /*  */
.octicon-server:before { content: '\f097'} /*  */
.octicon-settings:before { content: '\f07c'} /*  */
.octicon-shield:before { content: '\f0e1'} /*  */
.octicon-log-in:before,
.octicon-sign-in:before { content: '\f036'} /*  */
.octicon-log-out:before,
.octicon-sign-out:before { content: '\f032'} /*  */
.octicon-squirrel:before { content: '\f0b2'} /*  */
.octicon-star-add:before,
.octicon-star-delete:before,
.octicon-star:before { content: '\f02a'} /*  */
.octicon-stop:before { content: '\f08f'} /*  */
.octicon-repo-sync:before,
.octicon-sync:before { content: '\f087'} /*  */
.octicon-tag-remove:before,
.octicon-tag-add:before,
.octicon-tag:before { content: '\f015'} /*  */
.octicon-telescope:before { content: '\f088'} /*  */
.octicon-terminal:before { content: '\f0c8'} /*  */
.octicon-three-bars:before { content: '\f05e'} /*  */
.octicon-thumbsdown:before { content: '\f0db'} /*  */
.octicon-thumbsup:before { content: '\f0da'} /*  */
.octicon-tools:before { content: '\f031'} /*  */
.octicon-trashcan:before { content: '\f0d0'} /*  */
.octicon-triangle-down:before { content: '\f05b'} /*  */
.octicon-triangle-left:before { content: '\f044'} /*  */
.octicon-triangle-right:before { content: '\f05a'} /*  */
.octicon-triangle-up:before { content: '\f0aa'} /*  */
.octicon-unfold:before { content: '\f039'} /*  */
.octicon-unmute:before { content: '\f0ba'} /*  */
.octicon-versions:before { content: '\f064'} /*  */
.octicon-watch:before { content: '\f0e0'} /*  */
.octicon-remove-close:before,
.octicon-x:before { content: '\f081'} /*  */
.octicon-zap:before { content: '\26A1'} /* ⚡ */

View File

@@ -34,8 +34,8 @@ export class HorizontalScrollbar extends AbstractScrollbar {
className: 'left-arrow',
top: scrollbarDelta,
left: arrowDelta,
bottom: void 0,
right: void 0,
bottom: undefined,
right: undefined,
bgWidth: options.arrowSize,
bgHeight: options.horizontalScrollbarSize,
onActivate: () => this._host.onMouseWheel(new StandardWheelEvent(null, 1, 0)),
@@ -44,8 +44,8 @@ export class HorizontalScrollbar extends AbstractScrollbar {
this._createArrow({
className: 'right-arrow',
top: scrollbarDelta,
left: void 0,
bottom: void 0,
left: undefined,
bottom: undefined,
right: arrowDelta,
bgWidth: options.arrowSize,
bgHeight: options.horizontalScrollbarSize,

View File

@@ -281,6 +281,7 @@ export abstract class AbstractScrollableElement extends Widget {
let massagedOptions = resolveOptions(newOptions);
this._options.handleMouseWheel = massagedOptions.handleMouseWheel;
this._options.mouseWheelScrollSensitivity = massagedOptions.mouseWheelScrollSensitivity;
this._options.fastScrollSensitivity = massagedOptions.fastScrollSensitivity;
this._setListeningToMouseWheel(this._options.handleMouseWheel);
if (!this._options.lazyRender) {
@@ -340,6 +341,12 @@ export abstract class AbstractScrollableElement extends Widget {
deltaY = 0;
}
if (e.browserEvent && e.browserEvent.altKey) {
// fastScrolling
deltaX = deltaX * this._options.fastScrollSensitivity;
deltaY = deltaY * this._options.fastScrollSensitivity;
}
const futureScrollPosition = this._scrollable.getFutureScrollPosition();
let desiredScrollPosition: INewScrollPosition = {};
@@ -540,6 +547,7 @@ function resolveOptions(opts: ScrollableElementCreationOptions): ScrollableEleme
alwaysConsumeMouseWheel: (typeof opts.alwaysConsumeMouseWheel !== 'undefined' ? opts.alwaysConsumeMouseWheel : false),
scrollYToX: (typeof opts.scrollYToX !== 'undefined' ? opts.scrollYToX : false),
mouseWheelScrollSensitivity: (typeof opts.mouseWheelScrollSensitivity !== 'undefined' ? opts.mouseWheelScrollSensitivity : 1),
fastScrollSensitivity: (typeof opts.fastScrollSensitivity !== 'undefined' ? opts.fastScrollSensitivity : 5),
mouseWheelSmoothScroll: (typeof opts.mouseWheelSmoothScroll !== 'undefined' ? opts.mouseWheelSmoothScroll : true),
arrowSize: (typeof opts.arrowSize !== 'undefined' ? opts.arrowSize : 11),

View File

@@ -50,6 +50,11 @@ export interface ScrollableElementCreationOptions {
* Defaults to 1.
*/
mouseWheelScrollSensitivity?: number;
/**
* FastScrolling mulitplier speed when pressing `Alt`
* Defaults to 5.
*/
fastScrollSensitivity?: number;
/**
* Height for vertical arrows (top/bottom) and width for horizontal arrows (left/right).
* Defaults to 11.
@@ -107,6 +112,7 @@ export interface ScrollableElementCreationOptions {
export interface ScrollableElementChangeOptions {
handleMouseWheel?: boolean;
mouseWheelScrollSensitivity?: number;
fastScrollSensitivity: number;
}
export interface ScrollableElementResolvedOptions {
@@ -118,6 +124,7 @@ export interface ScrollableElementResolvedOptions {
scrollYToX: boolean;
alwaysConsumeMouseWheel: boolean;
mouseWheelScrollSensitivity: number;
fastScrollSensitivity: number;
mouseWheelSmoothScroll: boolean;
arrowSize: number;
listenOnDomNode: HTMLElement | null;

View File

@@ -35,8 +35,8 @@ export class VerticalScrollbar extends AbstractScrollbar {
className: 'up-arrow',
top: arrowDelta,
left: scrollbarDelta,
bottom: void 0,
right: void 0,
bottom: undefined,
right: undefined,
bgWidth: options.verticalScrollbarSize,
bgHeight: options.arrowSize,
onActivate: () => this._host.onMouseWheel(new StandardWheelEvent(null, 0, 1)),
@@ -44,10 +44,10 @@ export class VerticalScrollbar extends AbstractScrollbar {
this._createArrow({
className: 'down-arrow',
top: void 0,
top: undefined,
left: scrollbarDelta,
bottom: arrowDelta,
right: void 0,
right: undefined,
bgWidth: options.verticalScrollbarSize,
bgHeight: options.arrowSize,
onActivate: () => this._host.onMouseWheel(new StandardWheelEvent(null, 0, -1)),

View File

@@ -14,7 +14,7 @@ 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';
import { IContentActionHandler } from 'vs/base/browser/htmlContentRenderer';
// Public SelectBox interface - Calls routed to appropriate select implementation class
@@ -22,10 +22,9 @@ export interface ISelectBoxDelegate {
// Public SelectBox Interface
readonly onDidSelect: Event<ISelectData>;
setOptions(options: string[], selected?: number, disabled?: number): void;
setOptions(options: ISelectOptionItem[], selected?: number);
select(index: number): void;
setAriaLabel(label: string);
setDetailsProvider(provider: (index: number) => { details: string, isMarkdown: boolean });
focus(): void;
blur(): void;
dispose(): void;
@@ -37,16 +36,25 @@ export interface ISelectBoxDelegate {
}
export interface ISelectBoxOptions {
useCustomDrawn?: boolean;
ariaLabel?: string;
minBottomMargin?: number;
hasDetails?: boolean;
markdownActionHandler?: IContentActionHandler;
}
// Utilize optionItem interface to capture all option parameters
export interface ISelectOptionItem {
text: string;
decoratorRight?: string;
description?: string;
descriptionIsMarkdown?: boolean;
isDisabled?: boolean;
}
export interface ISelectBoxStyles extends IListStyles {
selectBackground?: Color;
selectListBackground?: Color;
selectForeground?: Color;
decoratorRightForeground?: Color;
selectBorder?: Color;
selectListBorder?: Color;
focusBorder?: Color;
@@ -72,13 +80,13 @@ export class SelectBox extends Widget implements ISelectBoxDelegate {
private styles: ISelectBoxStyles;
private selectBoxDelegate: ISelectBoxDelegate;
constructor(options: string[], selected: number, contextViewProvider: IContextViewProvider, styles: ISelectBoxStyles = deepClone(defaultStyles), selectBoxOptions?: ISelectBoxOptions) {
constructor(options: ISelectOptionItem[], selected: number, contextViewProvider: IContextViewProvider, styles: ISelectBoxStyles = deepClone(defaultStyles), selectBoxOptions?: ISelectBoxOptions) {
super();
mixin(this.styles, defaultStyles, false);
// Instantiate select implementation based on platform
if (isMacintosh && !(selectBoxOptions && selectBoxOptions.hasDetails)) {
// Default to native SelectBox for OSX unless overridden
if (isMacintosh && !(selectBoxOptions && selectBoxOptions.useCustomDrawn)) {
this.selectBoxDelegate = new SelectBoxNative(options, selected, styles, selectBoxOptions);
} else {
this.selectBoxDelegate = new SelectBoxList(options, selected, contextViewProvider, styles, selectBoxOptions);
@@ -96,8 +104,8 @@ export class SelectBox extends Widget implements ISelectBoxDelegate {
return this.selectBoxDelegate.onDidSelect;
}
public setOptions(options: string[], selected?: number, disabled?: number): void {
this.selectBoxDelegate.setOptions(options, selected, disabled);
public setOptions(options: ISelectOptionItem[], selected?: number): void {
this.selectBoxDelegate.setOptions(options, selected);
}
public select(index: number): void {
@@ -108,10 +116,6 @@ export class SelectBox extends Widget implements ISelectBoxDelegate {
this.selectBoxDelegate.setAriaLabel(label);
}
public setDetailsProvider(provider: (index: number) => { details: string, isMarkdown: boolean }): void {
this.selectBoxDelegate.setDetailsProvider(provider);
}
public focus(): void {
this.selectBoxDelegate.focus();
}

View File

@@ -80,8 +80,18 @@
overflow: hidden;
padding-left: 3.5px;
white-space: nowrap;
float: left;
}
.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row > .option-decorator-right {
text-overflow: ellipsis;
overflow: hidden;
padding-right: 10px;
white-space: nowrap;
float: right;
}
/* Accepted CSS hiding technique for accessibility reader text */
/* https://webaim.org/techniques/css/invisiblecontent/ */

View File

@@ -6,7 +6,7 @@
import 'vs/css!./selectBoxCustom';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Event, Emitter, chain } from 'vs/base/common/event';
import { Event, Emitter } 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';
@@ -16,7 +16,7 @@ import { List } from 'vs/base/browser/ui/list/listWidget';
import { IListVirtualDelegate, IListRenderer, IListEvent } from 'vs/base/browser/ui/list/list';
import { domEvent } from 'vs/base/browser/event';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { ISelectBoxDelegate, ISelectBoxOptions, ISelectBoxStyles, ISelectData } from 'vs/base/browser/ui/selectBox/selectBox';
import { ISelectBoxDelegate, ISelectOptionItem, ISelectBoxOptions, ISelectBoxStyles, ISelectData } from 'vs/base/browser/ui/selectBox/selectBox';
import { isMacintosh } from 'vs/base/common/platform';
import { renderMarkdown } from 'vs/base/browser/htmlContentRenderer';
@@ -24,16 +24,11 @@ const $ = dom.$;
const SELECT_OPTION_ENTRY_TEMPLATE_ID = 'selectOption.entry.template';
export interface ISelectOptionItem {
optionText: string;
optionDescriptionText?: string;
optionDisabled: boolean;
}
interface ISelectListTemplateData {
root: HTMLElement;
optionText: HTMLElement;
optionDescriptionText: HTMLElement;
text: HTMLElement;
itemDescription: HTMLElement;
decoratorRight: HTMLElement;
disposables: IDisposable[];
}
@@ -47,29 +42,32 @@ class SelectListRenderer implements IListRenderer<ISelectOptionItem, ISelectList
const data = <ISelectListTemplateData>Object.create(null);
data.disposables = [];
data.root = container;
data.optionText = dom.append(container, $('.option-text'));
data.optionDescriptionText = dom.append(container, $('.option-text-description'));
dom.addClass(data.optionDescriptionText, 'visually-hidden');
data.text = dom.append(container, $('.option-text'));
data.decoratorRight = dom.append(container, $('.option-decorator-right'));
data.itemDescription = dom.append(container, $('.option-text-description'));
dom.addClass(data.itemDescription, 'visually-hidden');
return data;
}
renderElement(element: ISelectOptionItem, index: number, templateData: ISelectListTemplateData): void {
const data = <ISelectListTemplateData>templateData;
const optionText = (<ISelectOptionItem>element).optionText;
const optionDisabled = (<ISelectOptionItem>element).optionDisabled;
const text = (<ISelectOptionItem>element).text;
const decoratorRight = (<ISelectOptionItem>element).decoratorRight;
const isDisabled = (<ISelectOptionItem>element).isDisabled;
data.optionText.textContent = optionText;
data.text.textContent = text;
data.decoratorRight.innerText = (!!decoratorRight ? decoratorRight : '');
if (typeof element.optionDescriptionText === 'string') {
const optionDescriptionId = (optionText.replace(/ /g, '_').toLowerCase() + '_description_' + data.root.id);
data.optionText.setAttribute('aria-describedby', optionDescriptionId);
data.optionDescriptionText.id = optionDescriptionId;
data.optionDescriptionText.innerText = element.optionDescriptionText;
if (typeof element.description === 'string') {
const itemDescriptionId = (text.replace(/ /g, '_').toLowerCase() + '_description_' + data.root.id);
data.text.setAttribute('aria-describedby', itemDescriptionId);
data.itemDescription.id = itemDescriptionId;
data.itemDescription.innerText = element.description;
}
// pseudo-select disabled option
if (optionDisabled) {
if (isDisabled) {
dom.addClass((<HTMLElement>data.root), 'option-disabled');
} else {
// Make sure we do class removal from prior template rendering
@@ -77,10 +75,6 @@ class SelectListRenderer implements IListRenderer<ISelectOptionItem, ISelectList
}
}
disposeElement(): void {
// noop
}
disposeTemplate(templateData: ISelectListTemplateData): void {
templateData.disposables = dispose(templateData.disposables);
}
@@ -96,9 +90,8 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
private selectBoxOptions: ISelectBoxOptions;
// {{SQL CARBON EDIT}}
public selectElement: HTMLSelectElement;
private options: string[];
private options: ISelectOptionItem[];
private selected: number;
private disabledOptionIndex: number;
private readonly _onDidSelect: Emitter<ISelectData>;
private toDispose: IDisposable[];
private styles: ISelectBoxStyles;
@@ -111,13 +104,13 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
private widthControlElement: HTMLElement;
private _currentSelection: number;
private _dropDownPosition: AnchorPosition;
private detailsProvider: (index: number) => { details: string, isMarkdown: boolean };
private _hasDetails: boolean = false;
private selectionDetailsPane: HTMLElement;
private _skipLayout: boolean = false;
private _sticky: boolean = false; // for dev purposes only
constructor(options: string[], selected: number, contextViewProvider: IContextViewProvider, styles: ISelectBoxStyles, selectBoxOptions?: ISelectBoxOptions) {
constructor(options: ISelectOptionItem[], selected: number, contextViewProvider: IContextViewProvider, styles: ISelectBoxStyles, selectBoxOptions?: ISelectBoxOptions) {
this.toDispose = [];
this._isVisible = false;
@@ -252,19 +245,18 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
return this._onDidSelect.event;
}
public setOptions(options: string[], selected?: number, disabled?: number): void {
public setOptions(options: ISelectOptionItem[], selected?: number): void {
if (!this.options || !arrays.equals(this.options, options)) {
this.options = options;
this.selectElement.options.length = 0;
this._hasDetails = false;
let i = 0;
this.options.forEach((option) => {
this.selectElement.add(this.createOption(option, i, disabled === i++));
this.options.forEach((option, index) => {
this.selectElement.add(this.createOption(option.text, index, option.isDisabled));
if (typeof option.description === 'string') {
this._hasDetails = true;
}
});
if (disabled !== undefined) {
this.disabledOptionIndex = disabled;
}
}
if (selected !== undefined) {
@@ -280,18 +272,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
// Mirror options in drop-down
// Populate select list for non-native select mode
if (this.selectList && !!this.options) {
let listEntries: ISelectOptionItem[];
listEntries = [];
for (let index = 0; index < this.options.length; index++) {
const element = this.options[index];
let optionDisabled: boolean;
index === this.disabledOptionIndex ? optionDisabled = true : optionDisabled = false;
const optionDescription = this.detailsProvider ? this.detailsProvider(index) : { details: undefined, isMarkdown: false };
listEntries.push({ optionText: element, optionDisabled: optionDisabled, optionDescriptionText: optionDescription.details });
}
this.selectList.splice(0, this.selectList.length, listEntries);
this.selectList.splice(0, this.selectList.length, this.options);
}
}
@@ -315,10 +296,6 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
this.selectElement.setAttribute('aria-label', this.selectBoxOptions.ariaLabel);
}
public setDetailsProvider(provider: (index: number) => { details: string, isMarkdown: boolean }): void {
this.detailsProvider = provider;
}
public focus(): void {
if (this.selectElement) {
this.selectElement.focus();
@@ -353,6 +330,10 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
content.push(`.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.focused:not(:hover) { color: ${this.styles.listFocusForeground} !important; }`);
}
if (this.styles.decoratorRightForeground) {
content.push(`.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row .option-decorator-right { color: ${this.styles.decoratorRightForeground} !important; }`);
}
if (this.styles.selectBackground && this.styles.selectBorder && !this.styles.selectBorder.equals(this.styles.selectBackground)) {
content.push(`.monaco-select-box-dropdown-container { border: 1px solid ${this.styles.selectBorder} } `);
content.push(`.monaco-select-box-dropdown-container > .select-box-details-pane.border-top { border-top: 1px solid ${this.styles.selectBorder} } `);
@@ -533,23 +514,16 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
// Iterate over detailed descriptions, find max height
private measureMaxDetailsHeight(): number {
if (!this.detailsProvider) {
return 0;
}
let maxDetailsPaneHeight = 0;
let description = { details: '', isMarkdown: false };
this.options.forEach((option, index) => {
this.selectionDetailsPane.innerText = '';
description = this.detailsProvider ? this.detailsProvider(index) : { details: '', isMarkdown: false };
if (description.details) {
if (description.isMarkdown) {
this.selectionDetailsPane.appendChild(this.renderDescriptionMarkdown(description.details));
if (option.description) {
if (option.descriptionIsMarkdown) {
this.selectionDetailsPane.appendChild(this.renderDescriptionMarkdown(option.description));
} else {
this.selectionDetailsPane.innerText = description.details;
this.selectionDetailsPane.innerText = option.description;
}
this.selectionDetailsPane.style.display = 'block';
} else {
@@ -562,18 +536,19 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
});
// Reset description to selected
description = this.detailsProvider ? this.detailsProvider(this.selected) : { details: '', isMarkdown: false };
this.selectionDetailsPane.innerText = '';
if (description.details) {
if (description.isMarkdown) {
this.selectionDetailsPane.appendChild(this.renderDescriptionMarkdown(description.details));
this.selectionDetailsPane.innerText = '';
const description = this.options[this.selected].description || null;
const descriptionIsMarkdown = this.options[this.selected].descriptionIsMarkdown || null;
if (description) {
if (descriptionIsMarkdown) {
this.selectionDetailsPane.appendChild(this.renderDescriptionMarkdown(description));
} else {
this.selectionDetailsPane.innerText = description.details;
this.selectionDetailsPane.innerText = description;
}
this.selectionDetailsPane.style.display = 'block';
}
return maxDetailsPaneHeight;
}
@@ -610,7 +585,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
this.selectList.layout();
let listHeight = this.selectList.contentHeight;
const maxDetailsPaneHeight = this.measureMaxDetailsHeight();
const maxDetailsPaneHeight = this._hasDetails ? this.measureMaxDetailsHeight() : 0;
const minRequiredDropDownHeight = listHeight + verticalPadding + maxDetailsPaneHeight;
const maxVisibleOptionsBelow = ((Math.floor((maxSelectDropDownHeightBelow - verticalPadding - maxDetailsPaneHeight) / this.getHeight())));
@@ -702,7 +677,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
this.selectList.reveal(this.selectList.getFocus()[0] || 0);
}
if (this.detailsProvider) {
if (this._hasDetails) {
// Leave the selectDropDownContainer to size itself according to children (list + details) - #57447
this.selectList.getHTMLElement().style.height = (listHeight + verticalPadding) + 'px';
} else {
@@ -727,14 +702,18 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
if (container && !!this.options) {
let longest = 0;
let longestLength = 0;
for (let index = 0; index < this.options.length; index++) {
if (this.options[index].length > this.options[longest].length) {
this.options.forEach((option, index) => {
const len = option.text.length + (!!option.decoratorRight ? option.decoratorRight.length : 0);
if (len > longestLength) {
longest = index;
longestLength = len;
}
}
});
container.innerHTML = this.options[longest];
container.innerHTML = this.options[longest].text + (!!this.options[longest].decoratorRight ? (this.options[longest].decoratorRight + ' ') : '');
elementWidth = dom.getTotalWidth(container);
}
@@ -763,14 +742,13 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
this.selectList = new List(this.selectDropDownListContainer, this, [this.listRenderer], {
ariaLabel: this.selectBoxOptions.ariaLabel,
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'))
const onSelectDropDownKeyDown = Event.chain(domEvent(this.selectDropDownListContainer, 'keydown'))
.filter(() => this.selectList.length > 0)
.map(e => new StandardKeyboardEvent(e));
@@ -786,7 +764,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
// SetUp list mouse controller - control navigation, disabled items, focus
chain(domEvent(this.selectList.getHTMLElement(), 'mouseup'))
Event.chain(domEvent(this.selectList.getHTMLElement(), 'mouseup'))
.filter(() => this.selectList.length > 0)
.on(e => this.onMouseUp(e), this, this.toDispose);
@@ -809,12 +787,18 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
dom.EventHelper.stop(e);
// Check our mouse event is on an option (not scrollbar)
if (!e.toElement.classList.contains('option-text')) {
const target = <Element>e.target;
if (!target) {
return;
}
const listRowElement = e.toElement.parentElement;
// Check our mouse event is on an option (not scrollbar)
if (!!target.classList.contains('slider')) {
return;
}
const listRowElement = target.closest('.monaco-list-row');
if (!listRowElement) {
return;
}
@@ -839,7 +823,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
this._onDidSelect.fire({
index: this.selectElement.selectedIndex,
selected: this.options[this.selected]
selected: this.options[this.selected].text
});
}
}
@@ -871,9 +855,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
}
};
const renderedMarkdown = renderMarkdown({ value: text }, {
actionHandler: this.selectBoxOptions.markdownActionHandler
});
const renderedMarkdown = renderMarkdown({ value: text });
renderedMarkdown.classList.add('select-box-description-markdown');
cleanRenderedMarkdown(renderedMarkdown);
@@ -884,18 +866,20 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
// List Focus Change - passive - update details pane with newly focused element's data
private onListFocus(e: IListEvent<ISelectOptionItem>) {
// Skip during initial layout
if (!this._isVisible) {
if (!this._isVisible || !this._hasDetails) {
return;
}
this.selectionDetailsPane.innerText = '';
const selectedIndex = e.indexes[0];
let description = this.detailsProvider ? this.detailsProvider(selectedIndex) : { details: '', isMarkdown: false };
if (description.details) {
if (description.isMarkdown) {
this.selectionDetailsPane.appendChild(this.renderDescriptionMarkdown(description.details));
const description = this.options[selectedIndex].description || null;
const descriptionIsMarkdown = this.options[selectedIndex].descriptionIsMarkdown || null;
if (description) {
if (descriptionIsMarkdown) {
this.selectionDetailsPane.appendChild(this.renderDescriptionMarkdown(description));
} else {
this.selectionDetailsPane.innerText = description.details;
this.selectionDetailsPane.innerText = description;
}
this.selectionDetailsPane.style.display = 'block';
} else {
@@ -929,7 +913,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
this._currentSelection = this.selected;
this._onDidSelect.fire({
index: this.selectElement.selectedIndex,
selected: this.options[this.selected]
selected: this.options[this.selected].text
});
}
@@ -939,14 +923,18 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
// 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) {
const nextOptionDisabled = this.options[this.selected + 1].isDisabled;
if (nextOptionDisabled && this.options.length > this.selected + 2) {
this.selected += 2;
} else if ((this.selected + 1) === this.disabledOptionIndex) {
} else if (nextOptionDisabled) {
return;
} 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]);
@@ -957,7 +945,8 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
private onUpArrow(): void {
if (this.selected > 0) {
// Skip disabled options
if ((this.selected - 1) === this.disabledOptionIndex && this.selected > 1) {
const previousOptionDisabled = this.options[this.selected - 1].isDisabled;
if (previousOptionDisabled && this.selected > 1) {
this.selected -= 2;
} else {
this.selected--;
@@ -979,7 +968,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
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) {
if (this.options[this.selected].isDisabled && this.selected < this.options.length - 1) {
this.selected++;
this.selectList.setFocus([this.selected]);
}
@@ -998,7 +987,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
this.selected = this.selectList.getFocus()[0];
// Shift selection up if we land on a disabled option
if (this.selected === this.disabledOptionIndex && this.selected > 0) {
if (this.options[this.selected].isDisabled && this.selected > 0) {
this.selected--;
this.selectList.setFocus([this.selected]);
}
@@ -1014,7 +1003,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
return;
}
this.selected = 0;
if (this.selected === this.disabledOptionIndex && this.selected > 1) {
if (this.options[this.selected].isDisabled && this.selected > 1) {
this.selected++;
}
this.selectList.setFocus([this.selected]);
@@ -1029,7 +1018,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
return;
}
this.selected = this.options.length - 1;
if (this.selected === this.disabledOptionIndex && this.selected > 1) {
if (this.options[this.selected].isDisabled && this.selected > 1) {
this.selected--;
}
this.selectList.setFocus([this.selected]);
@@ -1044,7 +1033,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate<I
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) {
if (this.options[optionIndex].text.charAt(0).toUpperCase() === ch && !this.options[optionIndex].isDisabled) {
this.select(optionIndex);
this.selectList.setFocus([optionIndex]);
this.selectList.reveal(this.selectList.getFocus()[0]);

View File

@@ -8,7 +8,7 @@ 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, ISelectBoxOptions, ISelectBoxStyles, ISelectData } from 'vs/base/browser/ui/selectBox/selectBox';
import { ISelectBoxDelegate, ISelectOptionItem, ISelectBoxOptions, ISelectBoxStyles, ISelectData } from 'vs/base/browser/ui/selectBox/selectBox';
import { isMacintosh } from 'vs/base/common/platform';
export class SelectBoxNative implements ISelectBoxDelegate {
@@ -16,17 +16,18 @@ export class SelectBoxNative implements ISelectBoxDelegate {
// {{SQL CARBON EDIT}}
public selectElement: HTMLSelectElement;
private selectBoxOptions: ISelectBoxOptions;
private options: string[];
private options: ISelectOptionItem[];
private selected: number;
private readonly _onDidSelect: Emitter<ISelectData>;
private toDispose: IDisposable[];
private styles: ISelectBoxStyles;
constructor(options: string[], selected: number, styles: ISelectBoxStyles, selectBoxOptions?: ISelectBoxOptions) {
constructor(options: ISelectOptionItem[], selected: number, styles: ISelectBoxStyles, selectBoxOptions?: ISelectBoxOptions) {
this.toDispose = [];
this.selectBoxOptions = selectBoxOptions || Object.create(null);
this.options = [];
this.selectElement = document.createElement('select');
// Workaround for Electron 2.x
@@ -84,15 +85,14 @@ export class SelectBoxNative implements ISelectBoxDelegate {
return this._onDidSelect.event;
}
public setOptions(options: string[], selected?: number, disabled?: number): void {
public setOptions(options: ISelectOptionItem[], selected?: 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++));
this.options.forEach((option, index) => {
this.selectElement.add(this.createOption(option.text, index, option.isDisabled));
});
}
@@ -103,7 +103,9 @@ export class SelectBoxNative implements ISelectBoxDelegate {
}
public select(index: number): void {
if (index >= 0 && index < this.options.length) {
if (this.options.length === 0) {
this.selected = 0;
} else if (index >= 0 && index < this.options.length) {
this.selected = index;
} else if (index > this.options.length - 1) {
// Adjust index to end of list
@@ -114,7 +116,11 @@ export class SelectBoxNative implements ISelectBoxDelegate {
}
this.selectElement.selectedIndex = this.selected;
this.selectElement.title = this.options[this.selected];
if ((this.selected < this.options.length) && typeof this.options[this.selected].text === 'string') {
this.selectElement.title = this.options[this.selected].text;
} else {
this.selectElement.title = '';
}
}
public setAriaLabel(label: string): void {
@@ -122,10 +128,6 @@ export class SelectBoxNative implements ISelectBoxDelegate {
this.selectElement.setAttribute('aria-label', label);
}
public setDetailsProvider(provider: any): void {
console.error('details are not available for native select boxes');
}
public focus(): void {
if (this.selectElement) {
this.selectElement.focus();

View File

@@ -5,7 +5,7 @@
import 'vs/css!./panelview';
import { IDisposable, dispose, combinedDisposable, Disposable } from 'vs/base/common/lifecycle';
import { Event, Emitter, chain, filterEvent } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { domEvent } from 'vs/base/browser/event';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
@@ -77,7 +77,7 @@ export abstract class Panel implements IView {
set minimumBodySize(size: number) {
this._minimumBodySize = size;
this._onDidChange.fire();
this._onDidChange.fire(undefined);
}
get maximumBodySize(): number {
@@ -86,7 +86,7 @@ export abstract class Panel implements IView {
set maximumBodySize(size: number) {
this._maximumBodySize = size;
this._onDidChange.fire();
this._onDidChange.fire(undefined);
}
private get headerSize(): number {
@@ -109,6 +109,8 @@ export abstract class Panel implements IView {
return headerSize + maximumBodySize;
}
width: number;
constructor(options: IPanelOptions = {}) {
this._expanded = typeof options.expanded === 'undefined' ? true : !!options.expanded;
this.ariaHeaderLabel = options.ariaHeaderLabel || '';
@@ -122,14 +124,16 @@ export abstract class Panel implements IView {
return this._expanded;
}
setExpanded(expanded: boolean): void {
setExpanded(expanded: boolean): boolean {
if (this._expanded === !!expanded) {
return;
return false;
}
this._expanded = !!expanded;
this.updateHeader();
this._onDidChange.fire(expanded ? this.expandedSize : undefined);
return true;
}
get headerVisible(): boolean {
@@ -143,7 +147,7 @@ export abstract class Panel implements IView {
this._headerVisible = !!visible;
this.updateHeader();
this._onDidChange.fire();
this._onDidChange.fire(undefined);
}
render(): void {
@@ -160,7 +164,7 @@ export abstract class Panel implements IView {
this.updateHeader();
const onHeaderKeyDown = chain(domEvent(this.header, 'keydown'))
const onHeaderKeyDown = Event.chain(domEvent(this.header, 'keydown'))
.map(e => new StandardKeyboardEvent(e));
onHeaderKeyDown.filter(e => e.keyCode === KeyCode.Enter || e.keyCode === KeyCode.Space)
@@ -186,12 +190,12 @@ export abstract class Panel implements IView {
this.renderBody(body);
}
layout(size: number): void {
layout(height: number): void {
const headerSize = this.headerVisible ? Panel.HEADER_SIZE : 0;
if (this.isExpanded()) {
this.layoutBody(size - headerSize);
this.expandedSize = size;
this.layoutBody(height - headerSize, this.width);
this.expandedSize = height;
}
}
@@ -222,7 +226,7 @@ export abstract class Panel implements IView {
protected abstract renderHeader(container: HTMLElement): void;
protected abstract renderBody(container: HTMLElement): void;
protected abstract layoutBody(size: number): void;
protected abstract layoutBody(height: number, width: number): void;
dispose(): void {
this.disposables = dispose(this.disposables);
@@ -367,6 +371,7 @@ export class PanelView extends Disposable {
private dndContext: IDndContext = { draggable: null };
private el: HTMLElement;
private panelItems: IPanelItem[] = [];
private width: number;
private splitview: SplitView;
private animationTimer: number | null = null;
@@ -391,11 +396,12 @@ export class PanelView extends Disposable {
let shouldAnimate = false;
disposables.push(scheduleAtNextAnimationFrame(() => shouldAnimate = true));
filterEvent(panel.onDidChange, () => shouldAnimate)
Event.filter(panel.onDidChange, () => shouldAnimate)
(this.setupAnimation, this, disposables);
const panelItem = { panel, disposable: combinedDisposable(disposables) };
this.panelItems.splice(index, 0, panelItem);
panel.width = this.width;
this.splitview.addView(panel, size, index);
if (this.dnd) {
@@ -451,8 +457,14 @@ export class PanelView extends Disposable {
return this.splitview.getViewSize(index);
}
layout(size: number): void {
this.splitview.layout(size);
layout(height: number, width: number): void {
this.width = width;
for (const panelItem of this.panelItems) {
panelItem.panel.width = width;
}
this.splitview.layout(height);
}
private setupAnimation(): void {

View File

@@ -5,7 +5,7 @@
import 'vs/css!./splitview';
import { IDisposable, combinedDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle';
import { Event, mapEvent, Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import * as types from 'vs/base/common/types';
import * as dom from 'vs/base/browser/dom';
import { clamp } from 'vs/base/common/numbers';
@@ -238,11 +238,11 @@ export class SplitView extends Disposable {
? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY, alt: e.altKey } as ISashEvent)
: (e: IBaseSashEvent) => ({ sash, start: e.startX, current: e.currentX, alt: e.altKey } as ISashEvent);
const onStart = mapEvent(sash.onDidStart, sashEventMapper);
const onStart = Event.map(sash.onDidStart, sashEventMapper);
const onStartDisposable = onStart(this.onSashStart, this);
const onChange = mapEvent(sash.onDidChange, sashEventMapper);
const onChange = Event.map(sash.onDidChange, sashEventMapper);
const onChangeDisposable = onChange(this.onSashChange, this);
const onEnd = mapEvent(sash.onDidEnd, () => firstIndex(this.sashItems, item => item.sash === sash));
const onEnd = Event.map(sash.onDidEnd, () => firstIndex(this.sashItems, item => item.sash === sash));
const onEndDisposable = onEnd(this.onSashEnd, this);
const onDidResetDisposable = sash.onDidReset(() => this._onDidSashReset.fire(firstIndex(this.sashItems, item => item.sash === sash)));

View File

@@ -18,7 +18,7 @@ export interface IToolBarOptions {
orientation?: ActionsOrientation;
actionItemProvider?: IActionItemProvider;
ariaLabel?: string;
getKeyBinding?: (action: IAction) => ResolvedKeybinding;
getKeyBinding?: (action: IAction) => ResolvedKeybinding | undefined;
actionRunner?: IActionRunner;
toggleMenuTitle?: string;
anchorAlignmentProvider?: () => AnchorAlignment;
@@ -31,7 +31,7 @@ export class ToolBar extends Disposable {
private options: IToolBarOptions;
private actionBar: ActionBar;
private toggleMenuAction: ToggleMenuAction;
private toggleMenuActionItem: DropdownMenuActionItem;
private toggleMenuActionItem?: DropdownMenuActionItem;
private hasSecondaryActions: boolean;
private lookupKeybindings: boolean;
@@ -72,9 +72,9 @@ export class ToolBar extends Disposable {
'toolbar-toggle-more',
this.options.anchorAlignmentProvider
);
this.toggleMenuActionItem.setActionContext(this.actionBar.context);
this.toggleMenuActionItem!.setActionContext(this.actionBar.context);
return this.toggleMenuActionItem;
return this.toggleMenuActionItem || null;
}
return options.actionItemProvider ? options.actionItemProvider(action) : null;
@@ -118,8 +118,8 @@ export class ToolBar extends Disposable {
let primaryActionsToSet = primaryActions ? primaryActions.slice(0) : [];
// Inject additional action to open secondary actions if present
this.hasSecondaryActions = secondaryActions && secondaryActions.length > 0;
if (this.hasSecondaryActions) {
this.hasSecondaryActions = !!(secondaryActions && secondaryActions.length > 0);
if (this.hasSecondaryActions && secondaryActions) {
this.toggleMenuAction.menuActions = secondaryActions.slice(0);
primaryActionsToSet.push(this.toggleMenuAction);
}
@@ -132,10 +132,10 @@ export class ToolBar extends Disposable {
};
}
private getKeybindingLabel(action: IAction): string {
const key = this.lookupKeybindings ? this.options.getKeyBinding(action) : void 0;
private getKeybindingLabel(action: IAction): string | undefined {
const key = this.lookupKeybindings && this.options.getKeyBinding ? this.options.getKeyBinding(action) : undefined;
return key ? key.getLabel() : void 0;
return (key && key.getLabel()) || undefined;
}
addPrimaryAction(primaryAction: IAction): () => void {
@@ -157,7 +157,7 @@ export class ToolBar extends Disposable {
dispose(): void {
if (this.toggleMenuActionItem) {
this.toggleMenuActionItem.dispose();
this.toggleMenuActionItem = void 0;
this.toggleMenuActionItem = undefined;
}
super.dispose();
@@ -173,7 +173,7 @@ class ToggleMenuAction extends Action {
constructor(toggleDropdownMenu: () => void, title?: string) {
title = title || nls.localize('moreActions', "More Actions...");
super(ToggleMenuAction.ID, title, null, true);
super(ToggleMenuAction.ID, title, undefined, true);
this.toggleDropdownMenu = toggleDropdownMenu;
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,64 +3,81 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ComposedTreeDelegate, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
import { ComposedTreeDelegate, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
import { ObjectTree, IObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree';
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree';
import { IListVirtualDelegate, IIdentityProvider, IListDragAndDrop, IListDragOverReaction } from 'vs/base/browser/ui/list/list';
import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop } from 'vs/base/browser/ui/tree/tree';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Emitter, Event, mapEvent } from 'vs/base/common/event';
import { timeout, always } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { timeout, always, CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
import { IListStyles } from 'vs/base/browser/ui/list/listWidget';
import { toggleClass } from 'vs/base/browser/dom';
import { Iterator } from 'vs/base/common/iterator';
import { IDragAndDropData } from 'vs/base/browser/dnd';
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors';
import { toggleClass } from 'vs/base/browser/dom';
export interface IDataSource<T extends NonNullable<any>> {
hasChildren(element: T | null): boolean;
getChildren(element: T | null): Thenable<T[]>;
const enum AsyncDataTreeNodeState {
Uninitialized = 'uninitialized',
Loaded = 'loaded',
Loading = 'loading'
}
enum AsyncDataTreeNodeState {
Uninitialized,
Loaded,
Loading,
Slow
}
interface IAsyncDataTreeNode<T extends NonNullable<any>> {
element: T | null;
readonly parent: IAsyncDataTreeNode<T> | null;
interface IAsyncDataTreeNode<TInput, T> {
element: TInput | T;
readonly parent: IAsyncDataTreeNode<TInput, T> | null;
readonly children: IAsyncDataTreeNode<TInput, T>[];
readonly id?: string | null;
readonly children?: IAsyncDataTreeNode<T>[];
state: AsyncDataTreeNodeState;
hasChildren: boolean;
needsRefresh: boolean;
slow: boolean;
disposed: boolean;
}
function isAncestor<TInput, T>(ancestor: IAsyncDataTreeNode<TInput, T>, descendant: IAsyncDataTreeNode<TInput, T>): boolean {
if (!descendant.parent) {
return false;
} else if (descendant.parent === ancestor) {
return true;
} else {
return isAncestor(ancestor, descendant.parent);
}
}
function intersects<TInput, T>(node: IAsyncDataTreeNode<TInput, T>, other: IAsyncDataTreeNode<TInput, T>): boolean {
return node === other || isAncestor(node, other) || isAncestor(other, node);
}
interface IDataTreeListTemplateData<T> {
templateData: T;
}
class AsyncDataTreeNodeWrapper<T, TFilterData> implements ITreeNode<T, TFilterData> {
class AsyncDataTreeNodeWrapper<TInput, T, TFilterData> implements ITreeNode<TInput | T, TFilterData> {
get element(): T { return this.node.element!.element!; }
get element(): T { return this.node.element!.element as T; }
get parent(): ITreeNode<T, TFilterData> | undefined { return this.node.parent && new AsyncDataTreeNodeWrapper(this.node.parent); }
get children(): ITreeNode<T, TFilterData>[] { return this.node.children.map(node => new AsyncDataTreeNodeWrapper(node)); }
get depth(): number { return this.node.depth; }
get visibleChildrenCount(): number { return this.node.visibleChildrenCount; }
get visibleChildIndex(): number { return this.node.visibleChildIndex; }
get collapsible(): boolean { return this.node.collapsible; }
get collapsed(): boolean { return this.node.collapsed; }
get visible(): boolean { return this.node.visible; }
get filterData(): TFilterData | undefined { return this.node.filterData; }
constructor(private node: ITreeNode<IAsyncDataTreeNode<T> | null, TFilterData>) { }
constructor(private node: ITreeNode<IAsyncDataTreeNode<TInput, T> | null, TFilterData>) { }
}
class DataTreeRenderer<T, TFilterData, TTemplateData> implements ITreeRenderer<IAsyncDataTreeNode<T>, TFilterData, IDataTreeListTemplateData<TTemplateData>> {
class DataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements ITreeRenderer<IAsyncDataTreeNode<TInput, T>, TFilterData, IDataTreeListTemplateData<TTemplateData>> {
readonly templateId: string;
private renderedNodes = new Map<IAsyncDataTreeNode<T>, IDataTreeListTemplateData<TTemplateData>>();
private renderedNodes = new Map<IAsyncDataTreeNode<TInput, T>, IDataTreeListTemplateData<TTemplateData>>();
private disposables: IDisposable[] = [];
constructor(
private renderer: ITreeRenderer<T, TFilterData, TTemplateData>,
readonly onDidChangeTwistieState: Event<IAsyncDataTreeNode<T>>
readonly onDidChangeTwistieState: Event<IAsyncDataTreeNode<TInput, T>>
) {
this.templateId = renderer.templateId;
}
@@ -70,17 +87,19 @@ class DataTreeRenderer<T, TFilterData, TTemplateData> implements ITreeRenderer<I
return { templateData };
}
renderElement(node: ITreeNode<IAsyncDataTreeNode<T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>): void {
renderElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>): void {
this.renderer.renderElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData);
}
renderTwistie(element: IAsyncDataTreeNode<T>, twistieElement: HTMLElement): boolean {
toggleClass(twistieElement, 'loading', element.state === AsyncDataTreeNodeState.Slow);
renderTwistie(element: IAsyncDataTreeNode<TInput, T>, twistieElement: HTMLElement): boolean {
toggleClass(twistieElement, 'loading', element.slow);
return false;
}
disposeElement(node: ITreeNode<IAsyncDataTreeNode<T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>): void {
this.renderer.disposeElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData);
disposeElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>): void {
if (this.renderer.disposeElement) {
this.renderer.disposeElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData);
}
}
disposeTemplate(templateData: IDataTreeListTemplateData<TTemplateData>): void {
@@ -93,24 +112,24 @@ class DataTreeRenderer<T, TFilterData, TTemplateData> implements ITreeRenderer<I
}
}
function asTreeEvent<T>(e: ITreeEvent<IAsyncDataTreeNode<T>>): ITreeEvent<T> {
function asTreeEvent<TInput, T>(e: ITreeEvent<IAsyncDataTreeNode<TInput, T>>): ITreeEvent<T> {
return {
browserEvent: e.browserEvent,
elements: e.elements.map(e => e.element!)
elements: e.elements.map(e => e.element as T)
};
}
function asTreeMouseEvent<T>(e: ITreeMouseEvent<IAsyncDataTreeNode<T>>): ITreeMouseEvent<T> {
function asTreeMouseEvent<TInput, T>(e: ITreeMouseEvent<IAsyncDataTreeNode<TInput, T>>): ITreeMouseEvent<T> {
return {
browserEvent: e.browserEvent,
element: e.element && e.element.element!
element: e.element && e.element.element as T
};
}
function asTreeContextMenuEvent<T>(e: ITreeContextMenuEvent<IAsyncDataTreeNode<T>>): ITreeContextMenuEvent<T> {
function asTreeContextMenuEvent<TInput, T>(e: ITreeContextMenuEvent<IAsyncDataTreeNode<TInput, T>>): ITreeContextMenuEvent<T> {
return {
browserEvent: e.browserEvent,
element: e.element && e.element.element!,
element: e.element && e.element.element as T,
anchor: e.anchor
};
}
@@ -125,14 +144,56 @@ export interface IChildrenResolutionEvent<T> {
readonly reason: ChildrenResolutionReason;
}
function asObjectTreeOptions<T, TFilterData>(options?: IAsyncDataTreeOptions<T, TFilterData>): IObjectTreeOptions<IAsyncDataTreeNode<T>, TFilterData> | undefined {
function asAsyncDataTreeDragAndDropData<TInput, T>(data: IDragAndDropData): IDragAndDropData {
if (data instanceof ElementsDragAndDropData) {
const nodes = (data as ElementsDragAndDropData<IAsyncDataTreeNode<TInput, T>>).elements;
return new ElementsDragAndDropData(nodes.map(node => node.element));
}
return data;
}
class AsyncDataTreeNodeListDragAndDrop<TInput, T> implements IListDragAndDrop<IAsyncDataTreeNode<TInput, T>> {
constructor(private dnd: ITreeDragAndDrop<T>) { }
getDragURI(node: IAsyncDataTreeNode<TInput, T>): string | null {
return this.dnd.getDragURI(node.element as T);
}
getDragLabel(nodes: IAsyncDataTreeNode<TInput, T>[]): string | undefined {
if (this.dnd.getDragLabel) {
return this.dnd.getDragLabel(nodes.map(node => node.element as T));
}
return undefined;
}
onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {
if (this.dnd.onDragStart) {
this.dnd.onDragStart(asAsyncDataTreeDragAndDropData(data), originalEvent);
}
}
onDragOver(data: IDragAndDropData, targetNode: IAsyncDataTreeNode<TInput, T> | undefined, targetIndex: number | undefined, originalEvent: DragEvent, raw = true): boolean | IListDragOverReaction {
return this.dnd.onDragOver(asAsyncDataTreeDragAndDropData(data), targetNode && targetNode.element as T, targetIndex, originalEvent);
}
drop(data: IDragAndDropData, targetNode: IAsyncDataTreeNode<TInput, T> | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void {
this.dnd.drop(asAsyncDataTreeDragAndDropData(data), targetNode && targetNode.element as T, targetIndex, originalEvent);
}
}
function asObjectTreeOptions<TInput, T, TFilterData>(options?: IAsyncDataTreeOptions<T, TFilterData>): IObjectTreeOptions<IAsyncDataTreeNode<TInput, T>, TFilterData> | undefined {
return options && {
...options,
collapseByDefault: true,
identityProvider: options.identityProvider && {
getId(el) {
return options.identityProvider!.getId(el.element!);
return options.identityProvider!.getId(el.element as T);
}
},
dnd: options.dnd && new AsyncDataTreeNodeListDragAndDrop(options.dnd),
multipleSelectionController: options.multipleSelectionController && {
isSelectionSingleChangeEvent(e) {
return options.multipleSelectionController!.isSelectionSingleChangeEvent({ ...e, element: e.element } as any);
@@ -143,90 +204,137 @@ function asObjectTreeOptions<T, TFilterData>(options?: IAsyncDataTreeOptions<T,
},
accessibilityProvider: options.accessibilityProvider && {
getAriaLabel(e) {
return options.accessibilityProvider!.getAriaLabel(e.element!);
return options.accessibilityProvider!.getAriaLabel(e.element as T);
}
},
filter: options.filter && {
filter(element, parentVisibility) {
return options.filter!.filter(element.element!, parentVisibility);
filter(e, parentVisibility) {
return options.filter!.filter(e.element as T, parentVisibility);
}
}
},
keyboardNavigationLabelProvider: options.keyboardNavigationLabelProvider && {
getKeyboardNavigationLabel(e) {
return options.keyboardNavigationLabelProvider!.getKeyboardNavigationLabel(e.element as T);
}
},
sorter: undefined
};
}
function asTreeElement<T>(node: IAsyncDataTreeNode<T>): ITreeElement<IAsyncDataTreeNode<T>> {
function asTreeElement<TInput, T>(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): ITreeElement<IAsyncDataTreeNode<TInput, T>> {
let collapsed: boolean | undefined;
if (viewStateContext && node.id) {
collapsed = viewStateContext.viewState.expanded.indexOf(node.id) === -1;
}
return {
element: node,
children: Iterator.map(Iterator.fromArray(node.children!), asTreeElement)
children: Iterator.map(Iterator.fromArray(node.children), child => asTreeElement(child, viewStateContext)),
collapsible: node.hasChildren,
collapsed
};
}
export interface IAsyncDataTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
export interface IAsyncDataTreeOptionsUpdate extends IAbstractTreeOptionsUpdate { }
export interface IAsyncDataTreeOptions<T, TFilterData = void> extends IAsyncDataTreeOptionsUpdate, IAbstractTreeOptions<T, TFilterData> {
identityProvider?: IIdentityProvider<T>;
sorter?: ITreeSorter<T>;
autoExpandSingleChildren?: boolean;
}
export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> implements IDisposable {
export interface IAsyncDataTreeViewState {
readonly focus: string[];
readonly selection: string[];
readonly expanded: string[];
}
interface IAsyncDataTreeViewStateContext<TInput, T> {
readonly viewState: IAsyncDataTreeViewState;
readonly selection: IAsyncDataTreeNode<TInput, T>[];
readonly focus: IAsyncDataTreeNode<TInput, T>[];
}
export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable {
private readonly tree: ObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData>;
private readonly root: IAsyncDataTreeNode<TInput, T>;
private readonly renderedNodes = new Map<null | T, IAsyncDataTreeNode<TInput, T>>();
private readonly sorter?: ITreeSorter<T>;
private readonly subTreeRefreshPromises = new Map<IAsyncDataTreeNode<TInput, T>, Promise<void>>();
private readonly refreshPromises = new Map<IAsyncDataTreeNode<TInput, T>, CancelablePromise<T[]>>();
private readonly tree: ObjectTree<IAsyncDataTreeNode<T>, TFilterData>;
private readonly root: IAsyncDataTreeNode<T>;
private readonly nodes = new Map<T | null, IAsyncDataTreeNode<T>>();
private readonly refreshPromises = new Map<IAsyncDataTreeNode<T>, Thenable<void>>();
private readonly identityProvider?: IIdentityProvider<T>;
private readonly autoExpandSingleChildren: boolean;
private readonly _onDidChangeNodeState = new Emitter<IAsyncDataTreeNode<T>>();
private readonly _onDidRender = new Emitter<void>();
private readonly _onDidChangeNodeSlowState = new Emitter<IAsyncDataTreeNode<TInput, T>>();
protected readonly disposables: IDisposable[] = [];
get onDidChangeFocus(): Event<ITreeEvent<T>> { return mapEvent(this.tree.onDidChangeFocus, asTreeEvent); }
get onDidChangeSelection(): Event<ITreeEvent<T>> { return mapEvent(this.tree.onDidChangeSelection, asTreeEvent); }
get onDidChangeCollapseState(): Event<T> { return mapEvent(this.tree.onDidChangeCollapseState, e => e.element!.element!); }
get onDidScroll(): Event<void> { return this.tree.onDidScroll; }
private readonly _onDidResolveChildren = new Emitter<IChildrenResolutionEvent<T>>();
readonly onDidResolveChildren: Event<IChildrenResolutionEvent<T>> = this._onDidResolveChildren.event;
get onDidChangeFocus(): Event<ITreeEvent<T>> { return Event.map(this.tree.onDidChangeFocus, asTreeEvent); }
get onDidChangeSelection(): Event<ITreeEvent<T>> { return Event.map(this.tree.onDidChangeSelection, asTreeEvent); }
get onDidOpen(): Event<ITreeEvent<T>> { return Event.map(this.tree.onDidOpen, asTreeEvent); }
get onMouseClick(): Event<ITreeMouseEvent<T>> { return mapEvent(this.tree.onMouseClick, asTreeMouseEvent); }
get onMouseDblClick(): Event<ITreeMouseEvent<T>> { return mapEvent(this.tree.onMouseDblClick, asTreeMouseEvent); }
get onContextMenu(): Event<ITreeContextMenuEvent<T>> { return mapEvent(this.tree.onContextMenu, asTreeContextMenuEvent); }
get onMouseClick(): Event<ITreeMouseEvent<T>> { return Event.map(this.tree.onMouseClick, asTreeMouseEvent); }
get onMouseDblClick(): Event<ITreeMouseEvent<T>> { return Event.map(this.tree.onMouseDblClick, asTreeMouseEvent); }
get onContextMenu(): Event<ITreeContextMenuEvent<T>> { return Event.map(this.tree.onContextMenu, asTreeContextMenuEvent); }
get onDidFocus(): Event<void> { return this.tree.onDidFocus; }
get onDidBlur(): Event<void> { return this.tree.onDidBlur; }
get filterOnType(): boolean { return this.tree.filterOnType; }
get openOnSingleClick(): boolean { return this.tree.openOnSingleClick; }
get onDidDispose(): Event<void> { return this.tree.onDidDispose; }
constructor(
container: HTMLElement,
delegate: IListVirtualDelegate<T>,
renderers: ITreeRenderer<any /* TODO@joao */, TFilterData, any>[],
private dataSource: IDataSource<T>,
options?: IAsyncDataTreeOptions<T, TFilterData>
private dataSource: IAsyncDataSource<TInput, T>,
options: IAsyncDataTreeOptions<T, TFilterData> = {}
) {
this.identityProvider = options && options.identityProvider;
this.identityProvider = options.identityProvider;
this.autoExpandSingleChildren = typeof options.autoExpandSingleChildren === 'undefined' ? false : options.autoExpandSingleChildren;
this.sorter = options.sorter;
const objectTreeDelegate = new ComposedTreeDelegate<T | null, IAsyncDataTreeNode<T>>(delegate);
const objectTreeRenderers = renderers.map(r => new DataTreeRenderer(r, this._onDidChangeNodeState.event));
const objectTreeOptions = asObjectTreeOptions(options) || {};
objectTreeOptions.collapseByDefault = true;
const objectTreeDelegate = new ComposedTreeDelegate<TInput | T, IAsyncDataTreeNode<TInput, T>>(delegate);
const objectTreeRenderers = renderers.map(r => new DataTreeRenderer(r, this._onDidChangeNodeSlowState.event));
const objectTreeOptions = asObjectTreeOptions<TInput, T, TFilterData>(options) || {};
this.tree = new ObjectTree(container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions);
this.root = {
element: null,
element: undefined!,
parent: null,
children: [],
state: AsyncDataTreeNodeState.Uninitialized,
hasChildren: true,
needsRefresh: false,
disposed: false,
slow: false
};
if (this.identityProvider) {
this.root = {
...this.root,
id: null,
children: [],
id: null
};
}
this.nodes.set(null, this.root);
this.renderedNodes.set(null, this.root);
this.tree.onDidChangeCollapseState(this._onDidChangeCollapseState, this, this.disposables);
}
updateOptions(options: IAsyncDataTreeOptionsUpdate = {}): void {
this.tree.updateOptions(options);
}
// Widget
getHTMLElement(): HTMLElement {
@@ -261,50 +369,110 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
this.tree.domFocus();
}
layout(height?: number): void {
this.tree.layout(height);
layout(height?: number, width?: number): void {
this.tree.layout(height, width);
}
style(styles: IListStyles): void {
this.tree.style(styles);
}
// Data Tree
// Model
refresh(element: T | null, recursive = true): Thenable<void> {
return this.refreshNode(this.getDataNode(element), recursive, ChildrenResolutionReason.Refresh);
getInput(): TInput | undefined {
return this.root.element as TInput;
}
async setInput(input: TInput, viewState?: IAsyncDataTreeViewState): Promise<void> {
this.refreshPromises.forEach(promise => promise.cancel());
this.refreshPromises.clear();
this.root.element = input!;
const viewStateContext = viewState && { viewState, focus: [], selection: [] } as IAsyncDataTreeViewStateContext<TInput, T>;
await this.updateChildren(input, true, viewStateContext);
if (viewStateContext) {
this.tree.setFocus(viewStateContext.focus);
this.tree.setSelection(viewStateContext.selection);
}
}
async updateChildren(element: TInput | T = this.root.element, recursive = true, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
if (typeof this.root.element === 'undefined') {
throw new Error('Tree input not set');
}
if (this.root.state === AsyncDataTreeNodeState.Loading) {
await this.subTreeRefreshPromises.get(this.root)!;
await Event.toPromise(this._onDidRender.event);
}
await this.refreshAndRenderNode(this.getDataNode(element), recursive, ChildrenResolutionReason.Refresh, viewStateContext);
}
hasNode(element: TInput | T): boolean {
return element === this.root.element || this.renderedNodes.has(element as T);
}
// View
refresh(element: T): void {
const node = this.getDataNode(element);
this.tree.refresh(node);
}
updateWidth(element: T): void {
const node = this.getDataNode(element);
this.tree.updateWidth(node);
}
// Tree
getNode(element: T | null): ITreeNode<T | null, TFilterData> {
getNode(element: TInput | T = this.root.element): ITreeNode<TInput | T, TFilterData> {
const dataNode = this.getDataNode(element);
const node = this.tree.getNode(dataNode === this.root ? null : dataNode);
return new AsyncDataTreeNodeWrapper<T | null, TFilterData>(node);
return new AsyncDataTreeNodeWrapper<TInput, T, TFilterData>(node);
}
collapse(element: T): boolean {
return this.tree.collapse(this.getDataNode(element));
collapse(element: T, recursive: boolean = false): boolean {
const node = this.getDataNode(element);
return this.tree.collapse(node === this.root ? null : node, recursive);
}
async expand(element: T): Promise<boolean> {
async expand(element: T, recursive: boolean = false): Promise<boolean> {
if (typeof this.root.element === 'undefined') {
throw new Error('Tree input not set');
}
if (this.root.state === AsyncDataTreeNodeState.Loading) {
await this.subTreeRefreshPromises.get(this.root)!;
await Event.toPromise(this._onDidRender.event);
}
const node = this.getDataNode(element);
if (!this.tree.isCollapsed(node)) {
if (node !== this.root && node.state !== AsyncDataTreeNodeState.Loading && !this.tree.isCollapsed(node)) {
return false;
}
this.tree.expand(node);
const result = this.tree.expand(node === this.root ? null : node, recursive);
if (node.state !== AsyncDataTreeNodeState.Loaded) {
await this.refreshNode(node, false, ChildrenResolutionReason.Expand);
if (node.state === AsyncDataTreeNodeState.Loading) {
await this.subTreeRefreshPromises.get(node)!;
await Event.toPromise(this._onDidRender.event);
}
return true;
return result;
}
toggleCollapsed(element: T): void {
this.tree.toggleCollapsed(this.getDataNode(element));
toggleCollapsed(element: T, recursive: boolean = false): boolean {
return this.tree.toggleCollapsed(this.getDataNode(element), recursive);
}
expandAll(): void {
this.tree.expandAll();
}
collapseAll(): void {
@@ -319,8 +487,8 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
return this.tree.isCollapsed(this.getDataNode(element));
}
isExpanded(element: T): boolean {
return this.tree.isExpanded(this.getDataNode(element));
toggleKeyboardNavigation(): void {
this.tree.toggleKeyboardNavigation();
}
refilter(): void {
@@ -334,7 +502,7 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
getSelection(): T[] {
const nodes = this.tree.getSelection();
return nodes.map(n => n!.element!);
return nodes.map(n => n!.element as T);
}
setFocus(elements: T[], browserEvent?: UIEvent): void {
@@ -368,7 +536,7 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
getFocus(): T[] {
const nodes = this.tree.getFocus();
return nodes.map(n => n!.element!);
return nodes.map(n => n!.element as T);
}
open(elements: T[]): void {
@@ -386,21 +554,15 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
// Tree navigation
getParentElement(element: T): T | null {
getParentElement(element: T): TInput | T {
const node = this.tree.getParentElement(this.getDataNode(element));
return node && node.element;
return (node && node.element)!;
}
getFirstElementChild(element: T | null = null): T | null | undefined {
getFirstElementChild(element: TInput | T = this.root.element): TInput | T | undefined {
const dataNode = this.getDataNode(element);
const node = this.tree.getFirstElementChild(dataNode === this.root ? null : dataNode);
return node && node.element;
}
getLastElementAncestor(element: T | null = null): T | null | undefined {
const dataNode = this.getDataNode(element);
const node = this.tree.getLastElementAncestor(dataNode === this.root ? null : dataNode);
return node && node.element;
return (node && node.element)!;
}
// List
@@ -411,175 +573,287 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
// Implementation
private getDataNode(element: T | null): IAsyncDataTreeNode<T> {
const node: IAsyncDataTreeNode<T> = this.nodes.get(element);
private getDataNode(element: TInput | T): IAsyncDataTreeNode<TInput, T> {
const node: IAsyncDataTreeNode<TInput, T> | undefined = this.renderedNodes.get((element === this.root.element ? null : element) as T);
if (typeof node === 'undefined') {
if (!node) {
throw new Error(`Data tree node not found: ${element}`);
}
return node;
}
private async refreshNode(node: IAsyncDataTreeNode<T>, recursive: boolean, reason: ChildrenResolutionReason): Promise<void> {
await this._refreshNode(node, recursive, reason);
private async refreshAndRenderNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, reason: ChildrenResolutionReason, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
await this.refreshNode(node, recursive, viewStateContext);
this.render(node, viewStateContext);
if (recursive && node.children) {
await Promise.all(node.children.map(child => this.refreshNode(child, recursive, reason)));
if (node !== this.root && this.autoExpandSingleChildren && reason === ChildrenResolutionReason.Expand) {
const treeNode = this.tree.getNode(node);
const visibleChildren = treeNode.children.filter(node => node.visible);
if (visibleChildren.length === 1) {
await this.tree.expand(visibleChildren[0].element, false);
}
}
}
private _refreshNode(node: IAsyncDataTreeNode<T>, recursive: boolean, reason: ChildrenResolutionReason): Thenable<void> {
private async refreshNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
if (node.disposed) {
console.error('Async data tree node is disposed');
return;
}
let result: Promise<void> | undefined;
this.subTreeRefreshPromises.forEach((refreshPromise, refreshNode) => {
if (!result && intersects(refreshNode, node)) {
result = refreshPromise.then(() => this.refreshNode(node, recursive, viewStateContext));
}
});
if (result) {
return result;
}
result = this.doRefreshSubTree(node, recursive, viewStateContext);
this.subTreeRefreshPromises.set(node, result);
try {
await result;
} finally {
this.subTreeRefreshPromises.delete(node);
}
}
private async doRefreshSubTree(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
node.state = AsyncDataTreeNodeState.Loading;
try {
await this.doRefreshNode(node, recursive, viewStateContext);
if (recursive) {
const childrenToRefresh = node.children
.filter(child => {
if (child.needsRefresh) {
child.needsRefresh = false;
return true;
}
// TODO@joao: is this still needed?
if (child.hasChildren && child.state === AsyncDataTreeNodeState.Loaded) {
return true;
}
if (!viewStateContext || !child.id) {
return false;
}
return viewStateContext.viewState.expanded.indexOf(child.id) > -1;
});
await Promise.all(childrenToRefresh.map(child => this.doRefreshSubTree(child, recursive, viewStateContext)));
}
} finally {
node.state = AsyncDataTreeNodeState.Loaded;
}
}
private async doRefreshNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
node.hasChildren = !!this.dataSource.hasChildren(node.element!);
let childrenPromise: Promise<T[]>;
if (!node.hasChildren) {
childrenPromise = Promise.resolve([]);
} else {
const slowTimeout = timeout(800);
slowTimeout.then(() => {
node.slow = true;
this._onDidChangeNodeSlowState.fire(node);
}, _ => null);
childrenPromise = always(this.doGetChildren(node), () => slowTimeout.cancel());
}
try {
const children = await childrenPromise;
this.setChildren(node, children, recursive, viewStateContext);
} catch (err) {
node.needsRefresh = true;
if (node !== this.root) {
this.tree.collapse(node === this.root ? null : node);
}
if (isPromiseCanceledError(err)) {
return;
}
throw err;
} finally {
if (node.slow) {
node.slow = false;
this._onDidChangeNodeSlowState.fire(node);
}
}
}
private doGetChildren(node: IAsyncDataTreeNode<TInput, T>): Promise<T[]> {
let result = this.refreshPromises.get(node);
if (result) {
return result;
}
result = this.doRefresh(node, recursive, reason);
result = createCancelablePromise(async () => {
const children = await this.dataSource.getChildren(node.element!);
if (this.sorter) {
children.sort(this.sorter.compare.bind(this.sorter));
}
return children;
});
this.refreshPromises.set(node, result);
return always(result, () => this.refreshPromises.delete(node));
}
private doRefresh(node: IAsyncDataTreeNode<T>, recursive: boolean, reason: ChildrenResolutionReason): Thenable<void> {
const hasChildren = !!this.dataSource.hasChildren(node.element);
if (!hasChildren) {
this.setChildren(node, [], recursive);
return Promise.resolve();
} else if (node !== this.root && (!this.tree.isCollapsible(node) || this.tree.isCollapsed(node))) {
return Promise.resolve();
} else {
node.state = AsyncDataTreeNodeState.Loading;
this._onDidChangeNodeState.fire(node);
const slowTimeout = timeout(800);
slowTimeout.then(() => {
node.state = AsyncDataTreeNodeState.Slow;
this._onDidChangeNodeState.fire(node);
}, _ => null);
return this.dataSource.getChildren(node.element)
.then(children => {
slowTimeout.cancel();
node.state = AsyncDataTreeNodeState.Loaded;
this._onDidChangeNodeState.fire(node);
this.setChildren(node, children, recursive);
this._onDidResolveChildren.fire({ element: node.element, reason });
}, err => {
slowTimeout.cancel();
node.state = AsyncDataTreeNodeState.Uninitialized;
this._onDidChangeNodeState.fire(node);
if (node !== this.root) {
this.tree.collapse(node);
}
return Promise.reject(err);
});
}
}
private _onDidChangeCollapseState(treeNode: ITreeNode<IAsyncDataTreeNode<T>, any>): void {
if (!treeNode.collapsed && treeNode.element.state === AsyncDataTreeNodeState.Uninitialized) {
this.refreshNode(treeNode.element, false, ChildrenResolutionReason.Expand);
}
}
private setChildren(node: IAsyncDataTreeNode<T>, childrenElements: T[], recursive: boolean): void {
const children = childrenElements.map<ITreeElement<IAsyncDataTreeNode<T>>>(element => {
if (!this.identityProvider) {
return {
element: {
element,
parent: node,
state: AsyncDataTreeNodeState.Uninitialized
},
collapsible: !!this.dataSource.hasChildren(element),
collapsed: true
};
private _onDidChangeCollapseState({ node, deep }: ICollapseStateChangeEvent<IAsyncDataTreeNode<TInput, T>, any>): void {
if (!node.collapsed && (node.element.state === AsyncDataTreeNodeState.Uninitialized || node.element.needsRefresh)) {
if (deep) {
this.collapse(node.element.element as T);
} else {
this.refreshAndRenderNode(node.element, false, ChildrenResolutionReason.Expand)
.catch(onUnexpectedError);
}
}
}
const nodeChildren = new Map<string, IAsyncDataTreeNode<T>>();
private setChildren(node: IAsyncDataTreeNode<TInput, T>, childrenElements: T[], recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): void {
let nodeChildren: Map<string, IAsyncDataTreeNode<TInput, T>> | undefined;
for (const child of node.children!) {
if (this.identityProvider) {
nodeChildren = new Map();
for (const child of node.children) {
nodeChildren.set(child.id!, child);
}
}
const children = childrenElements.map<IAsyncDataTreeNode<TInput, T>>(element => {
if (!this.identityProvider) {
const hasChildren = !!this.dataSource.hasChildren(element);
return {
element,
parent: node,
children: [],
state: AsyncDataTreeNodeState.Uninitialized,
hasChildren,
needsRefresh: false,
disposed: false,
slow: false
};
}
const id = this.identityProvider.getId(element).toString();
const asyncDataTreeNode = nodeChildren.get(id);
const asyncDataTreeNode = nodeChildren!.get(id);
if (!asyncDataTreeNode) {
return {
element: {
element,
parent: node,
id,
children: [],
state: AsyncDataTreeNodeState.Uninitialized
},
collapsible: !!this.dataSource.hasChildren(element),
collapsed: true
const childAsyncDataTreeNode: IAsyncDataTreeNode<TInput, T> = {
element,
parent: node,
children: [],
id,
state: AsyncDataTreeNodeState.Uninitialized,
hasChildren: !!this.dataSource.hasChildren(element),
needsRefresh: false,
disposed: false,
slow: false
};
if (viewStateContext) {
if (viewStateContext.viewState.focus.indexOf(id) > -1) {
viewStateContext.focus.push(childAsyncDataTreeNode);
}
if (viewStateContext.viewState.selection.indexOf(id) > -1) {
viewStateContext.selection.push(childAsyncDataTreeNode);
}
}
return childAsyncDataTreeNode;
}
asyncDataTreeNode.element = element;
const collapsible = !!this.dataSource.hasChildren(element);
const collapsed = !collapsible || this.tree.isCollapsed(asyncDataTreeNode);
if (recursive) {
asyncDataTreeNode.state = AsyncDataTreeNodeState.Uninitialized;
if (this.tree.isCollapsed(asyncDataTreeNode)) {
asyncDataTreeNode.children!.length = 0;
return {
element: asyncDataTreeNode,
collapsible,
collapsed
};
}
if (asyncDataTreeNode.state === AsyncDataTreeNodeState.Loaded || asyncDataTreeNode.hasChildren !== !!this.dataSource.hasChildren(asyncDataTreeNode.element)) {
asyncDataTreeNode.needsRefresh = true;
}
let children: Iterator<ITreeElement<IAsyncDataTreeNode<T>>> | undefined = undefined;
if (collapsible) {
children = Iterator.map(Iterator.fromArray(asyncDataTreeNode.children!), asTreeElement);
}
return {
element: asyncDataTreeNode,
children,
collapsible,
collapsed
};
return asyncDataTreeNode;
});
// perf: if the node was and still is a leaf, avoid all these expensive no-ops
if (node.children.length === 0 && childrenElements.length === 0) {
return;
}
node.children.splice(0, node.children.length, ...children);
}
private render(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): void {
const insertedElements = new Set<T>();
const onDidCreateNode = (treeNode: ITreeNode<IAsyncDataTreeNode<T>, TFilterData>) => {
const onDidCreateNode = (treeNode: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>) => {
if (treeNode.element.element) {
insertedElements.add(treeNode.element.element);
this.nodes.set(treeNode.element.element, treeNode.element);
insertedElements.add(treeNode.element.element as T);
this.renderedNodes.set(treeNode.element.element as T, treeNode.element);
}
};
const onDidDeleteNode = (treeNode: ITreeNode<IAsyncDataTreeNode<T>, TFilterData>) => {
const onDidDeleteNode = (treeNode: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>) => {
if (treeNode.element.element) {
if (!insertedElements.has(treeNode.element.element)) {
this.nodes.delete(treeNode.element.element);
if (!insertedElements.has(treeNode.element.element as T)) {
treeNode.element.disposed = true;
this.renderedNodes.delete(treeNode.element.element as T);
}
}
};
const children = node.children.map(c => asTreeElement(c, viewStateContext));
this.tree.setChildren(node === this.root ? null : node, children, onDidCreateNode, onDidDeleteNode);
if (this.identityProvider) {
node.children!.splice(0, node.children!.length, ...children.map(c => c.element));
this._onDidRender.fire();
}
// view state
getViewState(): IAsyncDataTreeViewState {
if (!this.identityProvider) {
throw new Error('Can\'t get tree view state without an identity provider');
}
const getId = (element: T) => this.identityProvider!.getId(element).toString();
const focus = this.getFocus().map(getId);
const selection = this.getSelection().map(getId);
const expanded: string[] = [];
const root = this.tree.getNode();
const queue = [root];
while (queue.length > 0) {
const node = queue.shift()!;
if (node !== root && node.collapsible && !node.collapsed) {
expanded.push(getId(node.element!.element as T));
}
queue.push(...node.children);
}
return { focus, selection, expanded };
}
dispose(): void {

View File

@@ -0,0 +1,142 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
import { ISpliceable } from 'vs/base/common/sequence';
import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter, IDataSource } from 'vs/base/browser/ui/tree/tree';
import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
import { Iterator } from 'vs/base/common/iterator';
export interface IDataTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
sorter?: ITreeSorter<T>;
}
export interface IDataTreeViewState {
readonly focus: string[];
readonly selection: string[];
readonly collapsed: string[];
}
export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | null, TFilterData, T | null> {
protected model: ObjectTreeModel<T, TFilterData>;
private input: TInput | undefined;
private identityProvider: IIdentityProvider<T> | undefined;
constructor(
container: HTMLElement,
delegate: IListVirtualDelegate<T>,
renderers: ITreeRenderer<any /* TODO@joao */, TFilterData, any>[],
private dataSource: IDataSource<TInput, T>,
options: IDataTreeOptions<T, TFilterData> = {}
) {
super(container, delegate, renderers, options);
this.identityProvider = options.identityProvider;
}
// Model
getInput(): TInput | undefined {
return this.input;
}
setInput(input: TInput, viewState?: IDataTreeViewState): void {
if (viewState && !this.identityProvider) {
throw new Error('Can\'t restore tree view state without an identity provider');
}
this.input = input;
if (!viewState) {
this._refresh(input);
return;
}
const focus: T[] = [];
const selection: T[] = [];
const isCollapsed = (element: T) => {
const id = this.identityProvider!.getId(element).toString();
if (viewState.focus.indexOf(id) > -1) {
focus.push(element);
}
if (viewState.selection.indexOf(id) > -1) {
selection.push(element);
}
return id in viewState.collapsed;
};
this._refresh(input, isCollapsed);
this.setFocus(focus);
this.setSelection(selection);
}
updateChildren(element: TInput | T = this.input!): void {
if (typeof this.input === 'undefined') {
throw new Error('Tree input not set');
}
this._refresh(element);
}
// View
refresh(element: T): void {
this.model.refresh(element);
}
// Implementation
private _refresh(element: TInput | T, isCollapsed?: (el: T) => boolean): void {
this.model.setChildren((element === this.input ? null : element) as T, this.createIterator(element, isCollapsed));
}
private createIterator(element: TInput | T, isCollapsed?: (el: T) => boolean): Iterator<ITreeElement<T>> {
const children = Iterator.fromArray(this.dataSource.getChildren(element));
return Iterator.map<any, ITreeElement<T>>(children, element => ({
element,
children: this.createIterator(element),
collapsed: isCollapsed && isCollapsed(element)
}));
}
protected createModel(view: ISpliceable<ITreeNode<T, TFilterData>>, options: IDataTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
return new ObjectTreeModel(view, options);
}
// view state
getViewState(): IDataTreeViewState {
if (!this.identityProvider) {
throw new Error('Can\'t get tree view state without an identity provider');
}
const getId = (element: T) => this.identityProvider!.getId(element).toString();
const focus = this.getFocus().map(getId);
const selection = this.getSelection().map(getId);
const collapsed: string[] = [];
const root = this.model.getNode();
const queue = [root];
while (queue.length > 0) {
const node = queue.shift()!;
if (node !== root && node.collapsed) {
collapsed.push(getId(node.element!));
}
queue.push(...node.children);
}
return { focus, selection, collapsed };
}
}

View File

@@ -11,9 +11,7 @@ import { IndexTreeModel } from 'vs/base/browser/ui/tree/indexTreeModel';
import { ITreeElement, ITreeModel, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
export interface IIndexTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
collapseByDefault?: boolean; // defaults to false
}
export interface IIndexTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> { }
export class IndexTree<T, TFilterData = void> extends AbstractTree<T, TFilterData, number[]> {
@@ -33,6 +31,10 @@ export class IndexTree<T, TFilterData = void> extends AbstractTree<T, TFilterDat
return this.model.splice(location, deleteCount, toInsert);
}
refresh(location: number[]): void {
this.model.refresh(location);
}
protected createModel(view: ISpliceable<ITreeNode<T, TFilterData>>, options: IIndexTreeOptions<T, TFilterData>): ITreeModel<T, TFilterData, number[]> {
return new IndexTreeModel(view, this.rootElement, options);
}

View File

@@ -3,15 +3,17 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ISpliceable } from 'vs/base/common/sequence';
import { Iterator, ISequence } from 'vs/base/common/iterator';
import { Emitter, Event, EventBufferer } from 'vs/base/common/event';
import { ICollapseStateChangeEvent, ITreeElement, ITreeFilter, ITreeFilterDataResult, ITreeModel, ITreeNode, TreeVisibility, ITreeModelSpliceEvent } from 'vs/base/browser/ui/tree/tree';
import { tail2 } from 'vs/base/common/arrays';
import { ITreeFilterDataResult, TreeVisibility, ITreeFilter, ITreeModel, ITreeNode, ITreeElement } from 'vs/base/browser/ui/tree/tree';
import { Emitter, Event, EventBufferer } from 'vs/base/common/event';
import { ISequence, Iterator } from 'vs/base/common/iterator';
import { ISpliceable } from 'vs/base/common/sequence';
interface IMutableTreeNode<T, TFilterData> extends ITreeNode<T, TFilterData> {
readonly parent: IMutableTreeNode<T, TFilterData> | undefined;
readonly children: IMutableTreeNode<T, TFilterData>[];
visibleChildrenCount: number;
visibleChildIndex: number;
collapsible: boolean;
collapsed: boolean;
renderNodeCount: number;
@@ -19,10 +21,18 @@ interface IMutableTreeNode<T, TFilterData> extends ITreeNode<T, TFilterData> {
filterData: TFilterData | undefined;
}
function isFilterResult<T>(obj: any): obj is ITreeFilterDataResult<T> {
export function isFilterResult<T>(obj: any): obj is ITreeFilterDataResult<T> {
return typeof obj === 'object' && 'visibility' in obj && 'data' in obj;
}
export function getVisibleState(visibility: boolean | TreeVisibility): TreeVisibility {
switch (visibility) {
case true: return TreeVisibility.Visible;
case false: return TreeVisibility.Hidden;
default: return visibility;
}
}
function treeNodeToElement<T>(node: IMutableTreeNode<T, any>): ITreeElement<T> {
const { element, collapsed } = node;
const children = Iterator.map(Iterator.fromArray(node.children), treeNodeToElement);
@@ -30,42 +40,46 @@ function treeNodeToElement<T>(node: IMutableTreeNode<T, any>): ITreeElement<T> {
return { element, children, collapsed };
}
function getVisibleState(visibility: boolean | TreeVisibility): TreeVisibility {
switch (visibility) {
case true: return TreeVisibility.Visible;
case false: return TreeVisibility.Hidden;
default: return visibility;
}
}
export interface IIndexTreeModelOptions<T, TFilterData> {
collapseByDefault?: boolean; // defaults to false
filter?: ITreeFilter<T, TFilterData>;
readonly collapseByDefault?: boolean; // defaults to false
readonly filter?: ITreeFilter<T, TFilterData>;
readonly autoExpandSingleChildren?: boolean;
}
export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = void> implements ITreeModel<T, TFilterData, number[]> {
readonly rootRef = [];
private root: IMutableTreeNode<T, TFilterData>;
private eventBufferer = new EventBufferer();
private _onDidChangeCollapseState = new Emitter<ITreeNode<T, TFilterData>>();
readonly onDidChangeCollapseState: Event<ITreeNode<T, TFilterData>> = this.eventBufferer.wrapEvent(this._onDidChangeCollapseState.event);
private _onDidChangeCollapseState = new Emitter<ICollapseStateChangeEvent<T, TFilterData>>();
readonly onDidChangeCollapseState: Event<ICollapseStateChangeEvent<T, TFilterData>> = this.eventBufferer.wrapEvent(this._onDidChangeCollapseState.event);
private _onDidChangeRenderNodeCount = new Emitter<ITreeNode<T, TFilterData>>();
readonly onDidChangeRenderNodeCount: Event<ITreeNode<T, TFilterData>> = this.eventBufferer.wrapEvent(this._onDidChangeRenderNodeCount.event);
private collapseByDefault: boolean;
private filter?: ITreeFilter<T, TFilterData>;
private autoExpandSingleChildren: boolean;
private _onDidSplice = new Emitter<ITreeModelSpliceEvent<T, TFilterData>>();
readonly onDidSplice = this._onDidSplice.event;
constructor(private list: ISpliceable<ITreeNode<T, TFilterData>>, rootElement: T, options: IIndexTreeModelOptions<T, TFilterData> = {}) {
this.collapseByDefault = typeof options.collapseByDefault === 'undefined' ? false : options.collapseByDefault;
this.filter = options.filter;
this.autoExpandSingleChildren = typeof options.autoExpandSingleChildren === 'undefined' ? false : options.autoExpandSingleChildren;
// this.onDidChangeCollapseState(node => console.log(node.collapsed, node));
this.root = {
parent: undefined,
element: rootElement,
children: [],
depth: 0,
visibleChildrenCount: 0,
visibleChildIndex: -1,
collapsible: false,
collapsed: false,
renderNodeCount: 0,
@@ -85,22 +99,64 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
throw new Error('Invalid tree location');
}
const { parentNode, listIndex, revealed } = this.getParentNodeWithListIndex(location);
const { parentNode, listIndex, revealed, visible } = this.getParentNodeWithListIndex(location);
const treeListElementsToInsert: ITreeNode<T, TFilterData>[] = [];
const nodesToInsertIterator = Iterator.map(Iterator.from(toInsert), el => this.createTreeNode(el, parentNode, parentNode.visible ? TreeVisibility.Visible : TreeVisibility.Hidden, revealed, treeListElementsToInsert, onDidCreateNode));
const lastIndex = location[location.length - 1];
// figure out what's the visible child start index right before the
// splice point
let visibleChildStartIndex = 0;
for (let i = lastIndex; i >= 0 && i < parentNode.children.length; i--) {
const child = parentNode.children[i];
if (child.visible) {
visibleChildStartIndex = child.visibleChildIndex;
break;
}
}
const nodesToInsert: IMutableTreeNode<T, TFilterData>[] = [];
let insertedVisibleChildrenCount = 0;
let renderNodeCount = 0;
Iterator.forEach(nodesToInsertIterator, node => {
nodesToInsert.push(node);
renderNodeCount += node.renderNodeCount;
Iterator.forEach(nodesToInsertIterator, child => {
nodesToInsert.push(child);
renderNodeCount += child.renderNodeCount;
if (child.visible) {
child.visibleChildIndex = visibleChildStartIndex + insertedVisibleChildrenCount++;
}
});
const lastIndex = location[location.length - 1];
const deletedNodes = parentNode.children.splice(lastIndex, deleteCount, ...nodesToInsert);
if (revealed) {
// figure out what is the count of deleted visible children
let deletedVisibleChildrenCount = 0;
for (const child of deletedNodes) {
if (child.visible) {
deletedVisibleChildrenCount++;
}
}
// and adjust for all visible children after the splice point
if (deletedVisibleChildrenCount !== 0) {
for (let i = lastIndex + nodesToInsert.length; i < parentNode.children.length; i++) {
const child = parentNode.children[i];
if (child.visible) {
child.visibleChildIndex -= deletedVisibleChildrenCount;
}
}
}
// update parent's visible children count
parentNode.visibleChildrenCount += insertedVisibleChildrenCount - deletedVisibleChildrenCount;
if (revealed && visible) {
const visibleDeleteCount = deletedNodes.reduce((r, node) => r + node.renderNodeCount, 0);
this._updateAncestorsRenderNodeCount(parentNode, renderNodeCount - visibleDeleteCount);
@@ -116,37 +172,30 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
deletedNodes.forEach(visit);
}
return Iterator.map(Iterator.fromArray(deletedNodes), treeNodeToElement);
const result = Iterator.map(Iterator.fromArray(deletedNodes), treeNodeToElement);
this._onDidSplice.fire({ insertedNodes: nodesToInsert, deletedNodes });
return result;
}
refresh(location: number[]): void {
if (location.length === 0) {
throw new Error('Invalid tree location');
}
const { node, listIndex, revealed } = this.getTreeNodeWithListIndex(location);
if (revealed) {
this.list.splice(listIndex, 1, [node]);
}
}
getListIndex(location: number[]): number {
return this.getTreeNodeWithListIndex(location).listIndex;
const { listIndex, visible, revealed } = this.getTreeNodeWithListIndex(location);
return visible && revealed ? listIndex : -1;
}
setCollapsed(location: number[], collapsed: boolean): boolean {
const { node, listIndex, revealed } = this.getTreeNodeWithListIndex(location);
return this.eventBufferer.bufferEvents(() => this._setCollapsed(node, listIndex, revealed, collapsed));
}
toggleCollapsed(location: number[]): void {
const { node, listIndex, revealed } = this.getTreeNodeWithListIndex(location);
this.eventBufferer.bufferEvents(() => this._setCollapsed(node, listIndex, revealed));
}
collapseAll(): void {
const queue = [...this.root.children];
let listIndex = 0;
this.eventBufferer.bufferEvents(() => {
while (queue.length > 0) {
const node = queue.shift()!;
const revealed = listIndex < this.root.children.length;
this._setCollapsed(node, listIndex, revealed, true);
queue.push(...node.children);
listIndex++;
}
});
getListRenderCount(location: number[]): number {
return this.getTreeNode(location).renderNodeCount;
}
isCollapsible(location: number[]): boolean {
@@ -157,36 +206,84 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
return this.getTreeNode(location).collapsed;
}
refilter(): void {
const previousRenderNodeCount = this.root.renderNodeCount;
const toInsert = this.updateNodeAfterFilterChange(this.root);
this.list.splice(0, previousRenderNodeCount, toInsert);
}
private _setCollapsed(node: IMutableTreeNode<T, TFilterData>, listIndex: number, revealed: boolean, collapsed?: boolean | undefined): boolean {
if (!node.collapsible) {
return false;
}
setCollapsed(location: number[], collapsed?: boolean, recursive?: boolean): boolean {
const node = this.getTreeNode(location);
if (typeof collapsed === 'undefined') {
collapsed = !node.collapsed;
}
if (node.collapsed === collapsed) {
return false;
return this.eventBufferer.bufferEvents(() => this._setCollapsed(location, collapsed!, recursive));
}
private _setCollapsed(location: number[], collapsed: boolean, recursive?: boolean): boolean {
const { node, listIndex, revealed } = this.getTreeNodeWithListIndex(location);
const result = this._setListNodeCollapsed(node, listIndex, revealed, collapsed!, recursive || false);
if (this.autoExpandSingleChildren && !collapsed! && !recursive) {
let onlyVisibleChildIndex = -1;
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i];
if (child.visible) {
if (onlyVisibleChildIndex > -1) {
onlyVisibleChildIndex = -1;
break;
} else {
onlyVisibleChildIndex = i;
}
}
}
if (onlyVisibleChildIndex > -1) {
this._setCollapsed([...location, onlyVisibleChildIndex], false, false);
}
}
node.collapsed = collapsed;
return result;
}
if (revealed) {
const previousRenderNodeCount = node.renderNodeCount;
const toInsert = this.updateNodeAfterCollapseChange(node);
private _setListNodeCollapsed(node: IMutableTreeNode<T, TFilterData>, listIndex: number, revealed: boolean, collapsed: boolean, recursive: boolean): boolean {
const result = this._setNodeCollapsed(node, collapsed, recursive, false);
this.list.splice(listIndex + 1, previousRenderNodeCount - 1, toInsert.slice(1));
this._onDidChangeCollapseState.fire(node);
if (!revealed || !node.visible) {
return result;
}
return true;
const previousRenderNodeCount = node.renderNodeCount;
const toInsert = this.updateNodeAfterCollapseChange(node);
const deleteCount = previousRenderNodeCount - (listIndex === -1 ? 0 : 1);
this.list.splice(listIndex + 1, deleteCount, toInsert.slice(1));
return result;
}
private _setNodeCollapsed(node: IMutableTreeNode<T, TFilterData>, collapsed: boolean, recursive: boolean, deep: boolean): boolean {
let result = node.collapsible && node.collapsed !== collapsed;
if (node.collapsible) {
node.collapsed = collapsed;
if (result) {
this._onDidChangeCollapseState.fire({ node, deep });
}
}
if (recursive) {
for (const child of node.children) {
result = this._setNodeCollapsed(child, collapsed, true, true) || result;
}
}
return result;
}
refilter(): void {
const previousRenderNodeCount = this.root.renderNodeCount;
const toInsert = this.updateNodeAfterFilterChange(this.root);
this.list.splice(0, previousRenderNodeCount, toInsert);
}
private createTreeNode(
@@ -202,6 +299,8 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
element: treeElement.element,
children: [],
depth: parent.depth + 1,
visibleChildrenCount: 0,
visibleChildIndex: -1,
collapsible: typeof treeElement.collapsible === 'boolean' ? treeElement.collapsible : (typeof treeElement.collapsed !== 'undefined'),
collapsed: typeof treeElement.collapsed === 'undefined' ? this.collapseByDefault : treeElement.collapsed,
renderNodeCount: 1,
@@ -219,17 +318,21 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
const childRevealed = revealed && visibility !== TreeVisibility.Hidden && !node.collapsed;
const childNodes = Iterator.map(childElements, el => this.createTreeNode(el, node, visibility, childRevealed, treeListElements, onDidCreateNode));
let hasVisibleDescendants = false;
let visibleChildrenCount = 0;
let renderNodeCount = 1;
Iterator.forEach(childNodes, child => {
node.children.push(child);
hasVisibleDescendants = hasVisibleDescendants || child.visible;
renderNodeCount += child.renderNodeCount;
if (child.visible) {
child.visibleChildIndex = visibleChildrenCount++;
}
});
node.collapsible = node.collapsible || node.children.length > 0;
node.visible = visibility === TreeVisibility.Recurse ? hasVisibleDescendants : (visibility === TreeVisibility.Visible);
node.visibleChildrenCount = visibleChildrenCount;
node.visible = visibility === TreeVisibility.Recurse ? visibleChildrenCount > 0 : (visibility === TreeVisibility.Visible);
if (!node.visible) {
node.renderNodeCount = 0;
@@ -307,9 +410,19 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
let hasVisibleDescendants = false;
if (!node.collapsed || visibility! !== TreeVisibility.Hidden) {
let visibleChildIndex = 0;
for (const child of node.children) {
hasVisibleDescendants = this._updateNodeAfterFilterChange(child, visibility!, result, revealed && !node.collapsed) || hasVisibleDescendants;
if (child.visible) {
child.visibleChildIndex = visibleChildIndex++;
}
}
node.visibleChildrenCount = visibleChildIndex;
} else {
node.visibleChildrenCount = 0;
}
if (node !== this.root) {
@@ -373,8 +486,12 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
}
// expensive
private getTreeNodeWithListIndex(location: number[]): { node: IMutableTreeNode<T, TFilterData>, listIndex: number, revealed: boolean } {
const { parentNode, listIndex, revealed } = this.getParentNodeWithListIndex(location);
private getTreeNodeWithListIndex(location: number[]): { node: IMutableTreeNode<T, TFilterData>, listIndex: number, revealed: boolean, visible: boolean } {
if (location.length === 0) {
return { node: this.root, listIndex: -1, revealed: true, visible: false };
}
const { parentNode, listIndex, revealed, visible } = this.getParentNodeWithListIndex(location);
const index = location[location.length - 1];
if (index < 0 || index > parentNode.children.length) {
@@ -383,10 +500,10 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
const node = parentNode.children[index];
return { node, listIndex, revealed };
return { node, listIndex, revealed, visible: visible && node.visible };
}
private getParentNodeWithListIndex(location: number[], node: IMutableTreeNode<T, TFilterData> = this.root, listIndex: number = 0, revealed = true): { parentNode: IMutableTreeNode<T, TFilterData>; listIndex: number; revealed: boolean; } {
private getParentNodeWithListIndex(location: number[], node: IMutableTreeNode<T, TFilterData> = this.root, listIndex: number = 0, revealed = true, visible = true): { parentNode: IMutableTreeNode<T, TFilterData>; listIndex: number; revealed: boolean; visible: boolean; } {
const [index, ...rest] = location;
if (index < 0 || index > node.children.length) {
@@ -399,12 +516,13 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
}
revealed = revealed && !node.collapsed;
visible = visible && node.visible;
if (rest.length === 0) {
return { parentNode: node, listIndex, revealed };
return { parentNode: node, listIndex, revealed, visible };
}
return this.getParentNodeWithListIndex(rest, node.children[index], listIndex + 1, revealed);
return this.getParentNodeWithListIndex(rest, node.children[index], listIndex + 1, revealed, visible);
}
getNode(location: number[] = []): ITreeNode<T, TFilterData> {

View File

@@ -19,6 +19,7 @@
text-align: right;
margin-right: 6px;
flex-shrink: 0;
width: 16px;
}
.monaco-tl-contents {
@@ -28,7 +29,7 @@
.monaco-tl-twistie.collapsible {
background-size: 16px;
background-position: 100% 50%;
background-position: 3px 50%;
background-repeat: no-repeat;
background-image: url("expanded.svg");
}
@@ -56,6 +57,7 @@
.monaco-tl-twistie.loading {
background-image: url("loading.svg");
background-position: 0 center;
}
.vs-dark .monaco-tl-twistie.loading {

View File

@@ -6,12 +6,12 @@
import { Iterator, ISequence } from 'vs/base/common/iterator';
import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
import { ISpliceable } from 'vs/base/common/sequence';
import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter } from 'vs/base/browser/ui/tree/tree';
import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
export interface IObjectTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
collapseByDefault?: boolean; // defaults to false
sorter?: ITreeSorter<T>;
}
export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends AbstractTree<T | null, TFilterData, T | null> {
@@ -36,6 +36,10 @@ export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends
return this.model.setChildren(element, children, onDidCreateNode, onDidDeleteNode);
}
refresh(element: T): void {
this.model.refresh(element);
}
protected createModel(view: ISpliceable<ITreeNode<T, TFilterData>>, options: IObjectTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
return new ObjectTreeModel(view, options);
}

View File

@@ -7,24 +7,39 @@ import { ISpliceable } from 'vs/base/common/sequence';
import { Iterator, ISequence, getSequenceIterator } from 'vs/base/common/iterator';
import { IndexTreeModel, IIndexTreeModelOptions } from 'vs/base/browser/ui/tree/indexTreeModel';
import { Event } from 'vs/base/common/event';
import { ITreeModel, ITreeNode, ITreeElement } from 'vs/base/browser/ui/tree/tree';
import { ITreeModel, ITreeNode, ITreeElement, ITreeSorter, ICollapseStateChangeEvent, ITreeModelSpliceEvent } from 'vs/base/browser/ui/tree/tree';
export interface IObjectTreeModelOptions<T, TFilterData> extends IIndexTreeModelOptions<T, TFilterData> { }
export interface IObjectTreeModelOptions<T, TFilterData> extends IIndexTreeModelOptions<T, TFilterData> {
readonly sorter?: ITreeSorter<T>;
}
export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements ITreeModel<T | null, TFilterData, T | null> {
readonly rootRef = null;
private model: IndexTreeModel<T | null, TFilterData>;
private nodes = new Map<T | null, ITreeNode<T, TFilterData>>();
private sorter?: ITreeSorter<ITreeElement<T>>;
readonly onDidChangeCollapseState: Event<ITreeNode<T, TFilterData>>;
readonly onDidSplice: Event<ITreeModelSpliceEvent<T | null, TFilterData>>;
readonly onDidChangeCollapseState: Event<ICollapseStateChangeEvent<T, TFilterData>>;
readonly onDidChangeRenderNodeCount: Event<ITreeNode<T, TFilterData>>;
get size(): number { return this.nodes.size; }
constructor(list: ISpliceable<ITreeNode<T, TFilterData>>, options: IObjectTreeModelOptions<T, TFilterData> = {}) {
this.model = new IndexTreeModel(list, null, options);
this.onDidChangeCollapseState = this.model.onDidChangeCollapseState as Event<ITreeNode<T, TFilterData>>;
this.onDidSplice = this.model.onDidSplice;
this.onDidChangeCollapseState = this.model.onDidChangeCollapseState as Event<ICollapseStateChangeEvent<T, TFilterData>>;
this.onDidChangeRenderNodeCount = this.model.onDidChangeRenderNodeCount as Event<ITreeNode<T, TFilterData>>;
if (options.sorter) {
this.sorter = {
compare(a, b) {
return options.sorter!.compare(a.element, b.element);
}
};
}
}
setChildren(
@@ -65,7 +80,11 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
}
private preserveCollapseState(elements: ISequence<ITreeElement<T | null>> | undefined): ISequence<ITreeElement<T | null>> {
const iterator = elements ? getSequenceIterator(elements) : Iterator.empty<ITreeElement<T>>();
let iterator = elements ? getSequenceIterator(elements) : Iterator.empty<ITreeElement<T>>();
if (this.sorter) {
iterator = Iterator.fromArray(Iterator.collect(iterator).sort(this.sorter.compare.bind(this.sorter)));
}
return Iterator.map(iterator, treeElement => {
const node = this.nodes.get(treeElement.element);
@@ -86,6 +105,11 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
});
}
refresh(element: T): void {
const location = this.getElementLocation(element);
this.model.refresh(location);
}
getParentElement(ref: T | null = null): T | null {
const location = this.getElementLocation(ref);
return this.model.getParentElement(location);
@@ -106,18 +130,9 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
return this.model.getListIndex(location);
}
setCollapsed(element: T, collapsed: boolean): boolean {
getListRenderCount(element: T): number {
const location = this.getElementLocation(element);
return this.model.setCollapsed(location, collapsed);
}
toggleCollapsed(element: T): void {
const location = this.getElementLocation(element);
this.model.toggleCollapsed(location);
}
collapseAll(): void {
this.model.collapseAll();
return this.model.getListRenderCount(location);
}
isCollapsible(element: T): boolean {
@@ -130,6 +145,11 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
return this.model.isCollapsed(location);
}
setCollapsed(element: T, collapsed?: boolean, recursive?: boolean): boolean {
const location = this.getElementLocation(element);
return this.model.setCollapsed(location, collapsed, recursive);
}
refilter(): void {
this.model.refilter();
}

View File

@@ -5,7 +5,8 @@
import { Event } from 'vs/base/common/event';
import { Iterator } from 'vs/base/common/iterator';
import { IListRenderer } from 'vs/base/browser/ui/list/list';
import { IListRenderer, IListDragOverReaction, IListDragAndDrop, ListDragOverEffect } from 'vs/base/browser/ui/list/list';
import { IDragAndDropData } from 'vs/base/browser/dnd';
export const enum TreeVisibility {
@@ -67,6 +68,10 @@ export interface ITreeFilter<T, TFilterData = void> {
filter(element: T, parentVisibility: TreeVisibility): TreeFilterResult<TFilterData>;
}
export interface ITreeSorter<T> {
compare(element: T, otherElement: T): number;
}
export interface ITreeElement<T> {
readonly element: T;
readonly children?: Iterator<ITreeElement<T>> | ITreeElement<T>[];
@@ -79,17 +84,33 @@ export interface ITreeNode<T, TFilterData = void> {
readonly parent: ITreeNode<T, TFilterData> | undefined;
readonly children: ITreeNode<T, TFilterData>[];
readonly depth: number;
readonly visibleChildrenCount: number;
readonly visibleChildIndex: number;
readonly collapsible: boolean;
readonly collapsed: boolean;
readonly visible: boolean;
readonly filterData: TFilterData | undefined;
}
export interface ICollapseStateChangeEvent<T, TFilterData> {
node: ITreeNode<T, TFilterData>;
deep: boolean;
}
export interface ITreeModelSpliceEvent<T, TFilterData> {
insertedNodes: ITreeNode<T, TFilterData>[];
deletedNodes: ITreeNode<T, TFilterData>[];
}
export interface ITreeModel<T, TFilterData, TRef> {
readonly onDidChangeCollapseState: Event<ITreeNode<T, TFilterData>>;
readonly rootRef: TRef;
readonly onDidSplice: Event<ITreeModelSpliceEvent<T, TFilterData>>;
readonly onDidChangeCollapseState: Event<ICollapseStateChangeEvent<T, TFilterData>>;
readonly onDidChangeRenderNodeCount: Event<ITreeNode<T, TFilterData>>;
getListIndex(location: TRef): number;
getListRenderCount(location: TRef): number;
getNode(location?: TRef): ITreeNode<T, any>;
getNodeLocation(node: ITreeNode<T, any>): TRef;
getParentNodeLocation(location: TRef): TRef;
@@ -100,9 +121,7 @@ export interface ITreeModel<T, TFilterData, TRef> {
isCollapsible(location: TRef): boolean;
isCollapsed(location: TRef): boolean;
setCollapsed(location: TRef, collapsed: boolean): boolean;
toggleCollapsed(location: TRef): void;
collapseAll(): void;
setCollapsed(location: TRef, collapsed?: boolean, recursive?: boolean): boolean;
refilter(): void;
}
@@ -127,3 +146,42 @@ export interface ITreeContextMenuEvent<T> {
element: T | null;
anchor: HTMLElement | { x: number; y: number; } | undefined;
}
export interface ITreeNavigator<T> {
current(): T | null;
previous(): T | null;
parent(): T | null;
first(): T | null;
last(): T | null;
next(): T | null;
}
export interface IDataSource<TInput, T> {
getChildren(element: TInput | T): T[];
}
export interface IAsyncDataSource<TInput, T> {
hasChildren(element: TInput | T): boolean;
getChildren(element: TInput | T): T[] | Promise<T[]>;
}
export const enum TreeDragOverBubble {
Down,
Up
}
export interface ITreeDragOverReaction extends IListDragOverReaction {
bubble?: TreeDragOverBubble;
autoExpand?: boolean;
}
export const TreeDragOverReactions = {
acceptBubbleUp(): ITreeDragOverReaction { return { accept: true, bubble: TreeDragOverBubble.Up }; },
acceptBubbleDown(autoExpand = false): ITreeDragOverReaction { return { accept: true, bubble: TreeDragOverBubble.Down, autoExpand }; },
acceptCopyBubbleUp(): ITreeDragOverReaction { return { accept: true, bubble: TreeDragOverBubble.Up, effect: ListDragOverEffect.Copy }; },
acceptCopyBubbleDown(autoExpand = false): ITreeDragOverReaction { return { accept: true, bubble: TreeDragOverBubble.Down, effect: ListDragOverEffect.Copy, autoExpand }; }
};
export interface ITreeDragAndDrop<T> extends IListDragAndDrop<T> {
onDragOver(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction;
}