Merge VS Code 1.31.1 (#4283)
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
};
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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!();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
1
src/vs/base/browser/ui/list/media/close-dark.svg
Normal 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 |
33
src/vs/base/browser/ui/list/media/close-hc.svg
Normal 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 |
1
src/vs/base/browser/ui/list/media/close.svg
Normal 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 |
5
src/vs/base/browser/ui/list/media/filter-dark.svg
Normal 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 |
5
src/vs/base/browser/ui/list/media/filter-hc.svg
Normal 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 |
5
src/vs/base/browser/ui/list/media/filter.svg
Normal 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 |
5
src/vs/base/browser/ui/list/media/no-filter-dark.svg
Normal 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 |
5
src/vs/base/browser/ui/list/media/no-filter-hc.svg
Normal 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 |
5
src/vs/base/browser/ui/list/media/no-filter.svg
Normal 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 |
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 = /(?:&){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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
}
|
||||
@@ -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" }
|
||||
|
||||
@@ -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'} /* ⚡ */
|
||||
@@ -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'} /* ⚡ */
|
||||
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 131 KiB |
@@ -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'} /* ⚡ */
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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/ */
|
||||
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)));
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
142
src/vs/base/browser/ui/tree/dataTree.ts
Normal 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 };
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||