Merge from vscode 8e0f348413f4f616c23a88ae30030efa85811973 (#6381)
* Merge from vscode 8e0f348413f4f616c23a88ae30030efa85811973 * disable strict null check
@@ -4,14 +4,14 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/tree';
|
||||
import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IListOptions, List, IListStyles, mightProducePrintableCharacter, MouseController } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListEvent, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IKeyboardNavigationLabelProvider, IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { append, $, toggleClass, getDomNodePagePosition, removeClass, addClass, hasClass } from 'vs/base/browser/dom';
|
||||
import { append, $, toggleClass, getDomNodePagePosition, removeClass, addClass, hasClass, hasParentWithClass, createStyleSheet, clearNode } from 'vs/base/browser/dom';
|
||||
import { Event, Relay, Emitter, EventBufferer } from 'vs/base/common/event';
|
||||
import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { ITreeModel, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeFilter, ITreeNavigator, ICollapseStateChangeEvent, ITreeDragAndDrop, TreeDragOverBubble, TreeVisibility, TreeFilterResult, ITreeModelSpliceEvent } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ITreeModel, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeFilter, ITreeNavigator, ICollapseStateChangeEvent, ITreeDragAndDrop, TreeDragOverBubble, TreeVisibility, TreeFilterResult, ITreeModelSpliceEvent, TreeMouseEventTarget } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ISpliceable } from 'vs/base/common/sequence';
|
||||
import { IDragAndDropData, StaticDND, DragAndDropData } from 'vs/base/browser/dnd';
|
||||
import { range, equals, distinctES6 } from 'vs/base/common/arrays';
|
||||
@@ -25,6 +25,7 @@ import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { values } from 'vs/base/common/map';
|
||||
import { clamp } from 'vs/base/common/numbers';
|
||||
import { ScrollEvent } from 'vs/base/common/scrollable';
|
||||
import { SetMap } from 'vs/base/common/collections';
|
||||
|
||||
function asTreeDragAndDropData<T, TFilterData>(data: IDragAndDropData): IDragAndDropData {
|
||||
if (data instanceof ElementsDragAndDropData) {
|
||||
@@ -152,7 +153,7 @@ function asListOptions<T, TFilterData, TRef>(modelProvider: () => ITreeModel<T,
|
||||
}
|
||||
},
|
||||
enableKeyboardNavigation: options.simpleKeyboardNavigation,
|
||||
ariaSetProvider: {
|
||||
ariaProvider: {
|
||||
getSetSize(node) {
|
||||
return node.parent!.visibleChildrenCount;
|
||||
},
|
||||
@@ -188,12 +189,48 @@ export class ComposedTreeDelegate<T, N extends { element: T }> implements IListV
|
||||
|
||||
interface ITreeListTemplateData<T> {
|
||||
readonly container: HTMLElement;
|
||||
readonly indent: HTMLElement;
|
||||
readonly twistie: HTMLElement;
|
||||
indentGuidesDisposable: IDisposable;
|
||||
readonly templateData: T;
|
||||
}
|
||||
|
||||
export enum RenderIndentGuides {
|
||||
None = 'none',
|
||||
OnHover = 'onHover',
|
||||
Always = 'always'
|
||||
}
|
||||
|
||||
interface ITreeRendererOptions {
|
||||
readonly indent?: number;
|
||||
readonly renderIndentGuides?: RenderIndentGuides;
|
||||
}
|
||||
|
||||
interface IRenderData<TTemplateData> {
|
||||
templateData: ITreeListTemplateData<TTemplateData>;
|
||||
height: number;
|
||||
}
|
||||
|
||||
interface Collection<T> {
|
||||
readonly elements: T[];
|
||||
readonly onDidChange: Event<T[]>;
|
||||
}
|
||||
|
||||
class EventCollection<T> implements Collection<T> {
|
||||
|
||||
private disposables = new DisposableStore();
|
||||
|
||||
get elements(): T[] {
|
||||
return this._elements;
|
||||
}
|
||||
|
||||
constructor(readonly onDidChange: Event<T[]>, private _elements: T[] = []) {
|
||||
onDidChange(e => this._elements = e, null, this.disposables);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.disposables.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class TreeRenderer<T, TFilterData, TTemplateData> implements IListRenderer<ITreeNode<T, TFilterData>, ITreeListTemplateData<TTemplateData>> {
|
||||
@@ -202,13 +239,20 @@ class TreeRenderer<T, TFilterData, TTemplateData> implements IListRenderer<ITree
|
||||
|
||||
readonly templateId: string;
|
||||
private renderedElements = new Map<T, ITreeNode<T, TFilterData>>();
|
||||
private renderedNodes = new Map<ITreeNode<T, TFilterData>, ITreeListTemplateData<TTemplateData>>();
|
||||
private renderedNodes = new Map<ITreeNode<T, TFilterData>, IRenderData<TTemplateData>>();
|
||||
private indent: number = TreeRenderer.DefaultIndent;
|
||||
|
||||
private _renderIndentGuides: RenderIndentGuides = RenderIndentGuides.None;
|
||||
private renderedIndentGuides = new SetMap<ITreeNode<T, TFilterData>, HTMLDivElement>();
|
||||
private activeParentNodes = new Set<ITreeNode<T, TFilterData>>();
|
||||
private indentGuidesDisposable: IDisposable = Disposable.None;
|
||||
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
private renderer: ITreeRenderer<T, TFilterData, TTemplateData>,
|
||||
onDidChangeCollapseState: Event<ICollapseStateChangeEvent<T, TFilterData>>,
|
||||
private activeNodes: Collection<ITreeNode<T, TFilterData>>,
|
||||
options: ITreeRendererOptions = {}
|
||||
) {
|
||||
this.templateId = renderer.templateId;
|
||||
@@ -226,34 +270,57 @@ class TreeRenderer<T, TFilterData, TTemplateData> implements IListRenderer<ITree
|
||||
this.indent = clamp(options.indent, 0, 40);
|
||||
}
|
||||
|
||||
this.renderedNodes.forEach((templateData, node) => {
|
||||
templateData.twistie.style.marginLeft = `${node.depth * this.indent}px`;
|
||||
});
|
||||
if (typeof options.renderIndentGuides !== 'undefined') {
|
||||
const renderIndentGuides = options.renderIndentGuides;
|
||||
|
||||
if (renderIndentGuides !== this._renderIndentGuides) {
|
||||
this._renderIndentGuides = renderIndentGuides;
|
||||
|
||||
if (renderIndentGuides) {
|
||||
const disposables = new DisposableStore();
|
||||
this.activeNodes.onDidChange(this._onDidChangeActiveNodes, this, disposables);
|
||||
this.indentGuidesDisposable = disposables;
|
||||
|
||||
this._onDidChangeActiveNodes(this.activeNodes.elements);
|
||||
} else {
|
||||
this.indentGuidesDisposable.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): ITreeListTemplateData<TTemplateData> {
|
||||
const el = append(container, $('.monaco-tl-row'));
|
||||
const indent = append(el, $('.monaco-tl-indent'));
|
||||
const twistie = append(el, $('.monaco-tl-twistie'));
|
||||
const contents = append(el, $('.monaco-tl-contents'));
|
||||
const templateData = this.renderer.renderTemplate(contents);
|
||||
|
||||
return { container, twistie, templateData };
|
||||
return { container, indent, twistie, indentGuidesDisposable: Disposable.None, templateData };
|
||||
}
|
||||
|
||||
renderElement(node: ITreeNode<T, TFilterData>, index: number, templateData: ITreeListTemplateData<TTemplateData>, height: number | undefined): void {
|
||||
if (typeof height === 'number') {
|
||||
this.renderedNodes.set(node, templateData);
|
||||
this.renderedNodes.set(node, { templateData, height });
|
||||
this.renderedElements.set(node.element, node);
|
||||
}
|
||||
|
||||
const indent = TreeRenderer.DefaultIndent + (node.depth - 1) * this.indent;
|
||||
templateData.twistie.style.marginLeft = `${indent}px`;
|
||||
this.update(node, templateData);
|
||||
templateData.indent.style.width = `${indent + this.indent - 16}px`;
|
||||
|
||||
this.renderTwistie(node, templateData);
|
||||
|
||||
if (typeof height === 'number') {
|
||||
this.renderIndentGuides(node, templateData);
|
||||
}
|
||||
|
||||
this.renderer.renderElement(node, index, templateData.templateData, height);
|
||||
}
|
||||
|
||||
disposeElement(node: ITreeNode<T, TFilterData>, index: number, templateData: ITreeListTemplateData<TTemplateData>, height: number | undefined): void {
|
||||
templateData.indentGuidesDisposable.dispose();
|
||||
|
||||
if (this.renderer.disposeElement) {
|
||||
this.renderer.disposeElement(node, index, templateData.templateData, height);
|
||||
}
|
||||
@@ -279,16 +346,17 @@ class TreeRenderer<T, TFilterData, TTemplateData> implements IListRenderer<ITree
|
||||
}
|
||||
|
||||
private onDidChangeNodeTwistieState(node: ITreeNode<T, TFilterData>): void {
|
||||
const templateData = this.renderedNodes.get(node);
|
||||
const data = this.renderedNodes.get(node);
|
||||
|
||||
if (!templateData) {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.update(node, templateData);
|
||||
this.renderTwistie(node, data.templateData);
|
||||
this.renderIndentGuides(node, data.templateData);
|
||||
}
|
||||
|
||||
private update(node: ITreeNode<T, TFilterData>, templateData: ITreeListTemplateData<TTemplateData>) {
|
||||
private renderTwistie(node: ITreeNode<T, TFilterData>, templateData: ITreeListTemplateData<TTemplateData>) {
|
||||
if (this.renderer.renderTwistie) {
|
||||
this.renderer.renderTwistie(node.element, templateData.twistie);
|
||||
}
|
||||
@@ -303,9 +371,72 @@ class TreeRenderer<T, TFilterData, TTemplateData> implements IListRenderer<ITree
|
||||
}
|
||||
}
|
||||
|
||||
private renderIndentGuides(target: ITreeNode<T, TFilterData>, templateData: ITreeListTemplateData<TTemplateData>): void {
|
||||
clearNode(templateData.indent);
|
||||
templateData.indentGuidesDisposable.dispose();
|
||||
|
||||
if (this._renderIndentGuides === RenderIndentGuides.None) {
|
||||
return;
|
||||
}
|
||||
|
||||
const disposableStore = new DisposableStore();
|
||||
let node = target;
|
||||
|
||||
while (node.parent && node.parent.parent) {
|
||||
const parent = node.parent;
|
||||
const guide = $<HTMLDivElement>('.indent-guide', { style: `width: ${this.indent}px` });
|
||||
|
||||
if (this.activeParentNodes.has(parent)) {
|
||||
addClass(guide, 'active');
|
||||
}
|
||||
|
||||
if (templateData.indent.childElementCount === 0) {
|
||||
templateData.indent.appendChild(guide);
|
||||
} else {
|
||||
templateData.indent.insertBefore(guide, templateData.indent.firstElementChild);
|
||||
}
|
||||
|
||||
this.renderedIndentGuides.add(parent, guide);
|
||||
disposableStore.add(toDisposable(() => this.renderedIndentGuides.delete(parent, guide)));
|
||||
|
||||
node = parent;
|
||||
}
|
||||
|
||||
templateData.indentGuidesDisposable = disposableStore;
|
||||
}
|
||||
|
||||
private _onDidChangeActiveNodes(nodes: ITreeNode<T, TFilterData>[]): void {
|
||||
if (this._renderIndentGuides === RenderIndentGuides.None) {
|
||||
return;
|
||||
}
|
||||
|
||||
const set = new Set<ITreeNode<T, TFilterData>>();
|
||||
|
||||
nodes.forEach(node => {
|
||||
if (node.parent) {
|
||||
set.add(node.parent);
|
||||
}
|
||||
});
|
||||
|
||||
this.activeParentNodes.forEach(node => {
|
||||
if (!set.has(node)) {
|
||||
this.renderedIndentGuides.forEach(node, line => removeClass(line, 'active'));
|
||||
}
|
||||
});
|
||||
|
||||
set.forEach(node => {
|
||||
if (!this.activeParentNodes.has(node)) {
|
||||
this.renderedIndentGuides.forEach(node, line => addClass(line, 'active'));
|
||||
}
|
||||
});
|
||||
|
||||
this.activeParentNodes = set;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.renderedNodes.clear();
|
||||
this.renderedElements.clear();
|
||||
this.indentGuidesDisposable.dispose();
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
@@ -722,9 +853,18 @@ function asTreeEvent<T>(event: IListEvent<ITreeNode<T, any>>): ITreeEvent<T> {
|
||||
}
|
||||
|
||||
function asTreeMouseEvent<T>(event: IListMouseEvent<ITreeNode<T, any>>): ITreeMouseEvent<T> {
|
||||
let target: TreeMouseEventTarget = TreeMouseEventTarget.Unknown;
|
||||
|
||||
if (hasParentWithClass(event.browserEvent.target as HTMLElement, 'monaco-tl-twistie', 'monaco-tl-row')) {
|
||||
target = TreeMouseEventTarget.Twistie;
|
||||
} else if (hasParentWithClass(event.browserEvent.target as HTMLElement, 'monaco-tl-contents', 'monaco-tl-row')) {
|
||||
target = TreeMouseEventTarget.Element;
|
||||
}
|
||||
|
||||
return {
|
||||
browserEvent: event.browserEvent,
|
||||
element: event.element ? event.element.element : null
|
||||
element: event.element ? event.element.element : null,
|
||||
target
|
||||
};
|
||||
}
|
||||
|
||||
@@ -811,6 +951,10 @@ class Trait<T> {
|
||||
return [...this.elements];
|
||||
}
|
||||
|
||||
getNodes(): readonly ITreeNode<T, any>[] {
|
||||
return this.nodes;
|
||||
}
|
||||
|
||||
has(node: ITreeNode<T, any>): boolean {
|
||||
return this.nodeSet.has(node);
|
||||
}
|
||||
@@ -999,6 +1143,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
||||
private eventBufferer = new EventBufferer();
|
||||
private typeFilterController?: TypeFilterController<T, TFilterData>;
|
||||
private focusNavigationFilter: ((node: ITreeNode<T, TFilterData>) => boolean) | undefined;
|
||||
private styleElement: HTMLStyleElement;
|
||||
protected disposables: IDisposable[] = [];
|
||||
|
||||
get onDidScroll(): Event<ScrollEvent> { return this.view.onDidScroll; }
|
||||
@@ -1027,7 +1172,6 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
||||
get filterOnType(): boolean { return !!this._options.filterOnType; }
|
||||
get onDidChangeTypeFilterPattern(): Event<string> { return this.typeFilterController ? this.typeFilterController.onDidChangePattern : Event.None; }
|
||||
|
||||
// Options TODO@joao expose options only, not Optional<>
|
||||
get openOnSingleClick(): boolean { return typeof this._options.openOnSingleClick === 'undefined' ? true : this._options.openOnSingleClick; }
|
||||
get expandOnlyOnTwistieClick(): boolean | ((e: T) => boolean) { return typeof this._options.expandOnlyOnTwistieClick === 'undefined' ? false : this._options.expandOnlyOnTwistieClick; }
|
||||
|
||||
@@ -1045,7 +1189,11 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
||||
const treeDelegate = new ComposedTreeDelegate<T, ITreeNode<T, TFilterData>>(delegate);
|
||||
|
||||
const onDidChangeCollapseStateRelay = new Relay<ICollapseStateChangeEvent<T, TFilterData>>();
|
||||
this.renderers = renderers.map(r => new TreeRenderer<T, TFilterData, any>(r, onDidChangeCollapseStateRelay.event, _options));
|
||||
const onDidChangeActiveNodes = new Relay<ITreeNode<T, TFilterData>[]>();
|
||||
const activeNodes = new EventCollection(onDidChangeActiveNodes.event);
|
||||
this.disposables.push(activeNodes);
|
||||
|
||||
this.renderers = renderers.map(r => new TreeRenderer<T, TFilterData, any>(r, onDidChangeCollapseStateRelay.event, activeNodes, _options));
|
||||
this.disposables.push(...this.renderers);
|
||||
|
||||
let filter: TypeFilter<T> | undefined;
|
||||
@@ -1068,6 +1216,8 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
||||
this.selection.onDidModelSplice(e);
|
||||
}, null, this.disposables);
|
||||
|
||||
onDidChangeActiveNodes.input = Event.map(Event.any<any>(this.focus.onDidChange, this.selection.onDidChange, this.model.onDidSplice), () => [...this.focus.getNodes(), ...this.selection.getNodes()]);
|
||||
|
||||
if (_options.keyboardSupport !== false) {
|
||||
const onKeyDown = Event.chain(this.view.onKeyDown)
|
||||
.filter(e => !isInputElement(e.target as HTMLElement))
|
||||
@@ -1083,6 +1233,9 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
||||
this.focusNavigationFilter = node => this.typeFilterController!.shouldAllowFocus(node);
|
||||
this.disposables.push(this.typeFilterController!);
|
||||
}
|
||||
|
||||
this.styleElement = createStyleSheet(this.view.getHTMLElement());
|
||||
toggleClass(this.getHTMLElement(), 'always', this._options.renderIndentGuides === RenderIndentGuides.Always);
|
||||
}
|
||||
|
||||
updateOptions(optionsUpdate: IAbstractTreeOptionsUpdate = {}): void {
|
||||
@@ -1102,6 +1255,8 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
||||
}
|
||||
|
||||
this._onDidUpdateOptions.fire(this._options);
|
||||
|
||||
toggleClass(this.getHTMLElement(), 'always', this._options.renderIndentGuides === RenderIndentGuides.Always);
|
||||
}
|
||||
|
||||
get options(): IAbstractTreeOptions<T, TFilterData> {
|
||||
@@ -1183,6 +1338,19 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
||||
}
|
||||
|
||||
style(styles: IListStyles): void {
|
||||
const suffix = `.${this.view.domId}`;
|
||||
const content: string[] = [];
|
||||
|
||||
if (styles.treeIndentGuidesStroke) {
|
||||
content.push(`.monaco-list${suffix}:hover .monaco-tl-indent > .indent-guide, .monaco-list${suffix}.always .monaco-tl-indent > .indent-guide { border-color: ${styles.treeIndentGuidesStroke.transparent(0.4)}; }`);
|
||||
content.push(`.monaco-list${suffix} .monaco-tl-indent > .indent-guide.active { border-color: ${styles.treeIndentGuidesStroke}; }`);
|
||||
}
|
||||
|
||||
const newStyles = content.join('\n');
|
||||
if (newStyles !== this.styleElement.innerHTML) {
|
||||
this.styleElement.innerHTML = newStyles;
|
||||
}
|
||||
|
||||
this.view.style(styles);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ interface IAsyncDataTreeNode<TInput, T> {
|
||||
hasChildren: boolean;
|
||||
stale: boolean;
|
||||
slow: boolean;
|
||||
collapsedByDefault: boolean | undefined;
|
||||
}
|
||||
|
||||
interface IAsyncDataTreeNodeRequiredProps<TInput, T> extends Partial<IAsyncDataTreeNode<TInput, T>> {
|
||||
@@ -42,7 +43,8 @@ function createAsyncDataTreeNode<TInput, T>(props: IAsyncDataTreeNodeRequiredPro
|
||||
children: [],
|
||||
loading: false,
|
||||
stale: true,
|
||||
slow: false
|
||||
slow: false,
|
||||
collapsedByDefault: undefined
|
||||
};
|
||||
}
|
||||
|
||||
@@ -133,7 +135,8 @@ function asTreeEvent<TInput, T>(e: ITreeEvent<IAsyncDataTreeNode<TInput, T>>): I
|
||||
function asTreeMouseEvent<TInput, T>(e: ITreeMouseEvent<IAsyncDataTreeNode<TInput, T>>): ITreeMouseEvent<T> {
|
||||
return {
|
||||
browserEvent: e.browserEvent,
|
||||
element: e.element && e.element.element as T
|
||||
element: e.element && e.element.element as T,
|
||||
target: e.target
|
||||
};
|
||||
}
|
||||
|
||||
@@ -224,6 +227,7 @@ function asObjectTreeOptions<TInput, T, TFilterData>(options?: IAsyncDataTreeOpt
|
||||
}
|
||||
},
|
||||
keyboardNavigationLabelProvider: options.keyboardNavigationLabelProvider && {
|
||||
...options.keyboardNavigationLabelProvider,
|
||||
getKeyboardNavigationLabel(e) {
|
||||
return options.keyboardNavigationLabelProvider!.getKeyboardNavigationLabel(e.element as T);
|
||||
}
|
||||
@@ -234,17 +238,21 @@ function asObjectTreeOptions<TInput, T, TFilterData>(options?: IAsyncDataTreeOpt
|
||||
e => (options.expandOnlyOnTwistieClick as ((e: T) => boolean))(e.element as T)
|
||||
)
|
||||
),
|
||||
ariaSetProvider: undefined
|
||||
ariaProvider: undefined
|
||||
};
|
||||
}
|
||||
|
||||
function asTreeElement<TInput, T>(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): ITreeElement<IAsyncDataTreeNode<TInput, T>> {
|
||||
let collapsed: boolean | undefined;
|
||||
|
||||
if (viewStateContext && viewStateContext.viewState.expanded && node.id) {
|
||||
collapsed = viewStateContext.viewState.expanded.indexOf(node.id) === -1;
|
||||
if (viewStateContext && viewStateContext.viewState.expanded && node.id && viewStateContext.viewState.expanded.indexOf(node.id) > -1) {
|
||||
collapsed = false;
|
||||
} else {
|
||||
collapsed = node.collapsedByDefault;
|
||||
}
|
||||
|
||||
node.collapsedByDefault = undefined;
|
||||
|
||||
return {
|
||||
element: node,
|
||||
children: node.hasChildren ? Iterator.map(Iterator.fromArray(node.children), child => asTreeElement(child, viewStateContext)) : [],
|
||||
@@ -255,10 +263,11 @@ function asTreeElement<TInput, T>(node: IAsyncDataTreeNode<TInput, T>, viewState
|
||||
|
||||
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 interface IAsyncDataTreeOptions<T, TFilterData = void> extends IAsyncDataTreeOptionsUpdate, Pick<IAbstractTreeOptions<T, TFilterData>, Exclude<keyof IAbstractTreeOptions<T, TFilterData>, 'collapseByDefault'>> {
|
||||
readonly collapseByDefault?: { (e: T): boolean; };
|
||||
readonly identityProvider?: IIdentityProvider<T>;
|
||||
readonly sorter?: ITreeSorter<T>;
|
||||
readonly autoExpandSingleChildren?: boolean;
|
||||
}
|
||||
|
||||
export interface IAsyncDataTreeViewState {
|
||||
@@ -285,6 +294,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
private readonly root: IAsyncDataTreeNode<TInput, T>;
|
||||
private readonly nodes = new Map<null | T, IAsyncDataTreeNode<TInput, T>>();
|
||||
private readonly sorter?: ITreeSorter<T>;
|
||||
private readonly collapseByDefault?: { (e: T): boolean; };
|
||||
|
||||
private readonly subTreeRefreshPromises = new Map<IAsyncDataTreeNode<TInput, T>, Promise<void>>();
|
||||
private readonly refreshPromises = new Map<IAsyncDataTreeNode<TInput, T>, CancelablePromise<T[]>>();
|
||||
@@ -310,10 +320,20 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
get onDidFocus(): Event<void> { return this.tree.onDidFocus; }
|
||||
get onDidBlur(): Event<void> { return this.tree.onDidBlur; }
|
||||
|
||||
get onDidChangeCollapseState(): Event<ICollapseStateChangeEvent<IAsyncDataTreeNode<TInput, T>, TFilterData>> { return this.tree.onDidChangeCollapseState; }
|
||||
|
||||
get onDidUpdateOptions(): Event<IAsyncDataTreeOptionsUpdate> { return this.tree.onDidUpdateOptions; }
|
||||
|
||||
get filterOnType(): boolean { return this.tree.filterOnType; }
|
||||
get openOnSingleClick(): boolean { return this.tree.openOnSingleClick; }
|
||||
get expandOnlyOnTwistieClick(): boolean | ((e: T) => boolean) {
|
||||
if (typeof this.tree.expandOnlyOnTwistieClick === 'boolean') {
|
||||
return this.tree.expandOnlyOnTwistieClick;
|
||||
}
|
||||
|
||||
const fn = this.tree.expandOnlyOnTwistieClick;
|
||||
return element => fn(this.nodes.get((element === this.root.element ? null : element) as T) || null);
|
||||
}
|
||||
|
||||
get onDidDispose(): Event<void> { return this.tree.onDidDispose; }
|
||||
|
||||
@@ -327,6 +347,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
this.identityProvider = options.identityProvider;
|
||||
this.autoExpandSingleChildren = typeof options.autoExpandSingleChildren === 'undefined' ? false : options.autoExpandSingleChildren;
|
||||
this.sorter = options.sorter;
|
||||
this.collapseByDefault = options.collapseByDefault;
|
||||
|
||||
const objectTreeDelegate = new ComposedTreeDelegate<TInput | T, IAsyncDataTreeNode<TInput, T>>(delegate);
|
||||
const objectTreeRenderers = renderers.map(r => new DataTreeRenderer(r, this._onDidChangeNodeSlowState.event));
|
||||
@@ -762,12 +783,17 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
const childrenToRefresh: IAsyncDataTreeNode<TInput, T>[] = [];
|
||||
|
||||
const children = childrenElements.map<IAsyncDataTreeNode<TInput, T>>(element => {
|
||||
const hasChildren = !!this.dataSource.hasChildren(element);
|
||||
|
||||
if (!this.identityProvider) {
|
||||
return createAsyncDataTreeNode({
|
||||
element,
|
||||
parent: node,
|
||||
hasChildren: !!this.dataSource.hasChildren(element)
|
||||
});
|
||||
const asyncDataTreeNode = createAsyncDataTreeNode({ element, parent: node, hasChildren });
|
||||
|
||||
if (hasChildren && this.collapseByDefault && !this.collapseByDefault(element)) {
|
||||
asyncDataTreeNode.collapsedByDefault = false;
|
||||
childrenToRefresh.push(asyncDataTreeNode);
|
||||
}
|
||||
|
||||
return asyncDataTreeNode;
|
||||
}
|
||||
|
||||
const id = this.identityProvider.getId(element).toString();
|
||||
@@ -781,7 +807,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
this.nodes.set(element, asyncDataTreeNode);
|
||||
|
||||
asyncDataTreeNode.element = element;
|
||||
asyncDataTreeNode.hasChildren = !!this.dataSource.hasChildren(element);
|
||||
asyncDataTreeNode.hasChildren = hasChildren;
|
||||
|
||||
if (recursive) {
|
||||
if (childNode.collapsed) {
|
||||
@@ -789,17 +815,15 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
} else {
|
||||
childrenToRefresh.push(asyncDataTreeNode);
|
||||
}
|
||||
} else if (hasChildren && this.collapseByDefault && !this.collapseByDefault(element)) {
|
||||
asyncDataTreeNode.collapsedByDefault = false;
|
||||
childrenToRefresh.push(asyncDataTreeNode);
|
||||
}
|
||||
|
||||
return asyncDataTreeNode;
|
||||
}
|
||||
|
||||
const childAsyncDataTreeNode = createAsyncDataTreeNode({
|
||||
element,
|
||||
parent: node,
|
||||
id,
|
||||
hasChildren: !!this.dataSource.hasChildren(element)
|
||||
});
|
||||
const childAsyncDataTreeNode = createAsyncDataTreeNode({ element, parent: node, id, hasChildren });
|
||||
|
||||
if (viewStateContext && viewStateContext.viewState.focus && viewStateContext.viewState.focus.indexOf(id) > -1) {
|
||||
viewStateContext.focus.push(childAsyncDataTreeNode);
|
||||
@@ -811,6 +835,9 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
|
||||
if (viewStateContext && viewStateContext.viewState.expanded && viewStateContext.viewState.expanded.indexOf(id) > -1) {
|
||||
childrenToRefresh.push(childAsyncDataTreeNode);
|
||||
} else if (hasChildren && this.collapseByDefault && !this.collapseByDefault(element)) {
|
||||
childAsyncDataTreeNode.collapsedByDefault = false;
|
||||
childrenToRefresh.push(childAsyncDataTreeNode);
|
||||
}
|
||||
|
||||
return childAsyncDataTreeNode;
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface IDataTreeViewState {
|
||||
readonly focus: string[];
|
||||
readonly selection: string[];
|
||||
readonly expanded: string[];
|
||||
readonly scrollTop: number;
|
||||
}
|
||||
|
||||
export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | null, TFilterData, T | null> {
|
||||
@@ -80,6 +81,10 @@ export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | nu
|
||||
this._refresh(input, isCollapsed, onDidCreateNode);
|
||||
this.setFocus(focus);
|
||||
this.setSelection(selection);
|
||||
|
||||
if (viewState && typeof viewState.scrollTop === 'number') {
|
||||
this.scrollTop = viewState.scrollTop;
|
||||
}
|
||||
}
|
||||
|
||||
updateChildren(element: TInput | T = this.input!): void {
|
||||
@@ -193,6 +198,6 @@ export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | nu
|
||||
queue.push(...node.children);
|
||||
}
|
||||
|
||||
return { focus, selection, expanded };
|
||||
return { focus, selection, expanded, scrollTop: this.scrollTop };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#E8E8E8" d="M6 4v8l4-4-4-4zm1 2.414L8.586 8 7 9.586V6.414z"/></svg>
|
||||
|
Before Width: | Height: | Size: 139 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#fff" d="M6 4v8l4-4-4-4zm1 2.414l1.586 1.586-1.586 1.586v-3.172z"/></svg>
|
||||
|
Before Width: | Height: | Size: 148 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#646465" d="M6 4v8l4-4-4-4zm1 2.414L8.586 8 7 9.586V6.414z"/></svg>
|
||||
|
Before Width: | Height: | Size: 139 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#E8E8E8" d="M11 10H5.344L11 4.414V10z"/></svg>
|
||||
|
Before Width: | Height: | Size: 118 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#fff" d="M11 10.07h-5.656l5.656-5.656v5.656z"/></svg>
|
||||
|
Before Width: | Height: | Size: 128 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#646465" d="M11 10H5.344L11 4.414V10z"/></svg>
|
||||
|
Before Width: | Height: | Size: 118 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.0719 7.99999L5.71461 12.3573L6.33333 12.976L11 8.30935V7.69064L6.33333 3.02397L5.71461 3.64269L10.0719 7.99999Z" fill="#C5C5C5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 286 B |
3
src/vs/base/browser/ui/tree/media/tree-collapsed-hc.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.0719 7.99999L5.71461 12.3573L6.33333 12.976L11 8.30935V7.69063L6.33333 3.02396L5.71461 3.64268L10.0719 7.99999Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 284 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.0719 7.99999L5.71461 12.3573L6.33333 12.976L11 8.30935V7.69063L6.33333 3.02396L5.71461 3.64268L10.0719 7.99999Z" fill="#424242"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 286 B |
3
src/vs/base/browser/ui/tree/media/tree-expanded-dark.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.97603 10.0719L12.3333 5.71461L12.9521 6.33333L8.28539 11L7.66667 11L3 6.33333L3.61872 5.71461L7.97603 10.0719Z" fill="#C5C5C5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 284 B |
3
src/vs/base/browser/ui/tree/media/tree-expanded-hc.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.97603 10.0719L12.3333 5.71461L12.9521 6.33333L8.28539 11L7.66667 11L3 6.33333L3.61872 5.71461L7.97603 10.0719Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 282 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.97603 10.0719L12.3333 5.71461L12.9521 6.33333L8.28539 11L7.66667 11L3 6.33333L3.61872 5.71461L7.97603 10.0719Z" fill="#424242"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 284 B |
@@ -7,6 +7,30 @@
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.monaco-tl-indent {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 18px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.hide-arrows .monaco-tl-indent {
|
||||
left: 12px;
|
||||
}
|
||||
|
||||
.monaco-tl-indent > .indent-guide {
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
border-left: 1px solid transparent;
|
||||
}
|
||||
|
||||
.monaco-tl-indent > .indent-guide {
|
||||
transition: border-color 0.1s linear;
|
||||
}
|
||||
|
||||
.monaco-tl-twistie,
|
||||
@@ -31,28 +55,28 @@
|
||||
background-size: 16px;
|
||||
background-position: 3px 50%;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url("expanded.svg");
|
||||
background-image: url("tree-expanded-light.svg");
|
||||
}
|
||||
|
||||
.monaco-tl-twistie.collapsible.collapsed:not(.loading) {
|
||||
display: inline-block;
|
||||
background-image: url("collapsed.svg");
|
||||
background-image: url("tree-collapsed-light.svg");
|
||||
}
|
||||
|
||||
.vs-dark .monaco-tl-twistie.collapsible:not(.loading) {
|
||||
background-image: url("expanded-dark.svg");
|
||||
background-image: url("tree-expanded-dark.svg");
|
||||
}
|
||||
|
||||
.vs-dark .monaco-tl-twistie.collapsible.collapsed:not(.loading) {
|
||||
background-image: url("collapsed-dark.svg");
|
||||
background-image: url("tree-collapsed-dark.svg");
|
||||
}
|
||||
|
||||
.hc-black .monaco-tl-twistie.collapsible:not(.loading) {
|
||||
background-image: url("expanded-hc.svg");
|
||||
background-image: url("tree-expanded-hc.svg");
|
||||
}
|
||||
|
||||
.hc-black .monaco-tl-twistie.collapsible.collapsed:not(.loading) {
|
||||
background-image: url("collapsed-hc.svg");
|
||||
background-image: url("tree-collapsed-hc.svg");
|
||||
}
|
||||
|
||||
.monaco-tl-twistie.loading {
|
||||
@@ -66,4 +90,4 @@
|
||||
|
||||
.hc-black .monaco-tl-twistie.loading {
|
||||
background-image: url("loading-hc.svg");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
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, ITreeSorter } from 'vs/base/browser/ui/tree/tree';
|
||||
import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter, ICollapseStateChangeEvent } 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';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
export interface IObjectTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
|
||||
sorter?: ITreeSorter<T>;
|
||||
@@ -18,6 +19,8 @@ export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends
|
||||
|
||||
protected model: ObjectTreeModel<T, TFilterData>;
|
||||
|
||||
get onDidChangeCollapseState(): Event<ICollapseStateChangeEvent<T, TFilterData>> { return this.model.onDidChangeCollapseState; }
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
delegate: IListVirtualDelegate<T>,
|
||||
|
||||
@@ -8,9 +8,11 @@ import { Iterator, ISequence, getSequenceIterator } from 'vs/base/common/iterato
|
||||
import { IndexTreeModel, IIndexTreeModelOptions } from 'vs/base/browser/ui/tree/indexTreeModel';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { ITreeModel, ITreeNode, ITreeElement, ITreeSorter, ICollapseStateChangeEvent, ITreeModelSpliceEvent } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
|
||||
export interface IObjectTreeModelOptions<T, TFilterData> extends IIndexTreeModelOptions<T, TFilterData> {
|
||||
readonly sorter?: ITreeSorter<T>;
|
||||
readonly identityProvider?: IIdentityProvider<T>;
|
||||
}
|
||||
|
||||
export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> implements ITreeModel<T | null, TFilterData, T | null> {
|
||||
@@ -19,6 +21,8 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
|
||||
private model: IndexTreeModel<T | null, TFilterData>;
|
||||
private nodes = new Map<T | null, ITreeNode<T, TFilterData>>();
|
||||
private readonly nodesByIdentity = new Map<string, ITreeNode<T, TFilterData>>();
|
||||
private readonly identityProvider?: IIdentityProvider<T>;
|
||||
private sorter?: ITreeSorter<{ element: T; }>;
|
||||
|
||||
readonly onDidSplice: Event<ITreeModelSpliceEvent<T | null, TFilterData>>;
|
||||
@@ -40,6 +44,8 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.identityProvider = options.identityProvider;
|
||||
}
|
||||
|
||||
setChildren(
|
||||
@@ -59,11 +65,18 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
onDidDeleteNode?: (node: ITreeNode<T, TFilterData>) => void
|
||||
): Iterator<ITreeElement<T | null>> {
|
||||
const insertedElements = new Set<T | null>();
|
||||
const insertedElementIds = new Set<string>();
|
||||
|
||||
const _onDidCreateNode = (node: ITreeNode<T, TFilterData>) => {
|
||||
insertedElements.add(node.element);
|
||||
this.nodes.set(node.element, node);
|
||||
|
||||
if (this.identityProvider) {
|
||||
const id = this.identityProvider.getId(node.element).toString();
|
||||
insertedElementIds.add(id);
|
||||
this.nodesByIdentity.set(id, node);
|
||||
}
|
||||
|
||||
if (onDidCreateNode) {
|
||||
onDidCreateNode(node);
|
||||
}
|
||||
@@ -74,18 +87,27 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
this.nodes.delete(node.element);
|
||||
}
|
||||
|
||||
if (this.identityProvider) {
|
||||
const id = this.identityProvider.getId(node.element).toString();
|
||||
if (!insertedElementIds.has(id)) {
|
||||
this.nodesByIdentity.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
if (onDidDeleteNode) {
|
||||
onDidDeleteNode(node);
|
||||
}
|
||||
};
|
||||
|
||||
return this.model.splice(
|
||||
const result = this.model.splice(
|
||||
[...location, 0],
|
||||
Number.MAX_VALUE,
|
||||
children,
|
||||
_onDidCreateNode,
|
||||
_onDidDeleteNode
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private preserveCollapseState(elements: ISequence<ITreeElement<T>> | undefined): ISequence<ITreeElement<T>> {
|
||||
@@ -96,7 +118,12 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
|
||||
}
|
||||
|
||||
return Iterator.map(iterator, treeElement => {
|
||||
const node = this.nodes.get(treeElement.element);
|
||||
let node = this.nodes.get(treeElement.element);
|
||||
|
||||
if (!node && this.identityProvider) {
|
||||
const id = this.identityProvider.getId(treeElement.element).toString();
|
||||
node = this.nodesByIdentity.get(id);
|
||||
}
|
||||
|
||||
if (!node) {
|
||||
return {
|
||||
|
||||
@@ -137,9 +137,16 @@ export interface ITreeEvent<T> {
|
||||
browserEvent?: UIEvent;
|
||||
}
|
||||
|
||||
export enum TreeMouseEventTarget {
|
||||
Unknown,
|
||||
Twistie,
|
||||
Element
|
||||
}
|
||||
|
||||
export interface ITreeMouseEvent<T> {
|
||||
browserEvent: MouseEvent;
|
||||
element: T | null;
|
||||
target: TreeMouseEventTarget;
|
||||
}
|
||||
|
||||
export interface ITreeContextMenuEvent<T> {
|
||||
|
||||
25
src/vs/base/browser/ui/tree/treeDefaults.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree';
|
||||
|
||||
export class CollapseAllAction<TInput, T, TFilterData = void> extends Action {
|
||||
|
||||
constructor(private viewer: AsyncDataTree<TInput, T, TFilterData>, enabled: boolean) {
|
||||
super('vs.tree.collapse', nls.localize('collapse all', "Collapse All"), 'monaco-tree-action collapse-all', enabled);
|
||||
}
|
||||
|
||||
public run(context?: any): Promise<any> {
|
||||
this.viewer.collapseAll();
|
||||
this.viewer.setSelection([]);
|
||||
this.viewer.setFocus([]);
|
||||
this.viewer.domFocus();
|
||||
this.viewer.focusFirst();
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||