paneview.ts 16.3 KB
Newer Older
J
Joao Moreno 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import 'vs/css!./paneview';
M
Matt Bierner 已提交
7
import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
J
Joao Moreno 已提交
8
import { Event, Emitter } from 'vs/base/common/event';
J
Joao Moreno 已提交
9 10 11
import { domEvent } from 'vs/base/browser/event';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
S
SteVen Batten 已提交
12
import { $, append, addClass, removeClass, toggleClass, trackFocus, EventHelper, clearNode } from 'vs/base/browser/dom';
13
import { firstIndex } from 'vs/base/common/arrays';
J
Joao Moreno 已提交
14
import { Color, RGBA } from 'vs/base/common/color';
J
Joao Moreno 已提交
15
import { SplitView, IView } from './splitview';
16 17
import { isFirefox } from 'vs/base/browser/browser';
import { DataTransfers } from 'vs/base/browser/dnd';
18
import { Orientation } from 'vs/base/browser/ui/sash/sash';
19
import { localize } from 'vs/nls';
J
Joao Moreno 已提交
20

21
export interface IPaneOptions {
J
Joao Moreno 已提交
22 23
	minimumBodySize?: number;
	maximumBodySize?: number;
J
Joao Moreno 已提交
24
	expanded?: boolean;
S
SteVen Batten 已提交
25
	orientation?: Orientation;
26
	title: string;
J
Joao Moreno 已提交
27 28
}

29
export interface IPaneStyles {
J
Joao Moreno 已提交
30
	dropBackground?: Color;
31 32
	headerForeground?: Color;
	headerBackground?: Color;
B
Benjamin Pasero 已提交
33
	headerBorder?: Color;
S
SteVen Batten 已提交
34
	leftBorder?: Color;
J
Joao Moreno 已提交
35 36
}

37
/**
38
 * A Pane is a structured SplitView view.
J
Joao Moreno 已提交
39 40 41 42 43 44
 *
 * WARNING: You must call `render()` after you contruct it.
 * It can't be done automatically at the end of the ctor
 * because of the order of property initialization in TypeScript.
 * Subclasses wouldn't be able to set own properties
 * before the `render()` call, thus forbiding their use.
45
 */
46
export abstract class Pane extends Disposable implements IView {
J
Joao Moreno 已提交
47

48
	private static readonly HEADER_SIZE = 22;
J
Joao Moreno 已提交
49

50
	readonly element: HTMLElement;
J
Joao Moreno 已提交
51 52
	private header!: HTMLElement;
	private body!: HTMLElement;
53

54
	protected _expanded: boolean;
S
SteVen Batten 已提交
55
	protected _orientation: Orientation;
56

J
Joao Moreno 已提交
57
	private expandedSize: number | undefined = undefined;
J
Joao Moreno 已提交
58
	private _headerVisible = true;
J
Joao Moreno 已提交
59 60 61
	private _minimumBodySize: number;
	private _maximumBodySize: number;
	private ariaHeaderLabel: string;
62
	private styles: IPaneStyles = {};
63
	private animationTimer: number | undefined = undefined;
J
Joao Moreno 已提交
64

M
Matt Bierner 已提交
65
	private readonly _onDidChange = this._register(new Emitter<number | undefined>());
66 67
	readonly onDidChange: Event<number | undefined> = this._onDidChange.event;

68 69 70
	private readonly _onDidChangeExpansionState = this._register(new Emitter<boolean>());
	readonly onDidChangeExpansionState: Event<boolean> = this._onDidChangeExpansionState.event;

J
Joao Moreno 已提交
71
	get draggableElement(): HTMLElement {
72 73 74
		return this.header;
	}

J
Joao Moreno 已提交
75
	get dropTargetElement(): HTMLElement {
J
Joao Moreno 已提交
76
		return this.element;
J
Joao Moreno 已提交
77 78
	}

79 80 81 82 83
	private _dropBackground: Color | undefined;
	get dropBackground(): Color | undefined {
		return this._dropBackground;
	}

J
Joao Moreno 已提交
84 85 86 87 88 89
	get minimumBodySize(): number {
		return this._minimumBodySize;
	}

	set minimumBodySize(size: number) {
		this._minimumBodySize = size;
90
		this._onDidChange.fire(undefined);
J
Joao Moreno 已提交
91 92 93 94 95 96 97 98
	}

	get maximumBodySize(): number {
		return this._maximumBodySize;
	}

	set maximumBodySize(size: number) {
		this._maximumBodySize = size;
99
		this._onDidChange.fire(undefined);
J
Joao Moreno 已提交
100 101
	}

102
	private get headerSize(): number {
103
		return this.headerVisible ? Pane.HEADER_SIZE : 0;
104 105
	}

J
Joao Moreno 已提交
106
	get minimumSize(): number {
107
		const headerSize = this.headerSize;
108
		const expanded = !this.headerVisible || this.isExpanded();
109
		const minimumBodySize = expanded ? this.minimumBodySize : this._orientation === Orientation.HORIZONTAL ? 50 : 0;
J
Joao Moreno 已提交
110 111

		return headerSize + minimumBodySize;
J
Joao Moreno 已提交
112 113 114
	}

	get maximumSize(): number {
115
		const headerSize = this.headerSize;
116
		const expanded = !this.headerVisible || this.isExpanded();
117
		const maximumBodySize = expanded ? this.maximumBodySize : this._orientation === Orientation.HORIZONTAL ? 50 : 0;
J
Joao Moreno 已提交
118 119

		return headerSize + maximumBodySize;
J
Joao Moreno 已提交
120 121
	}

S
SteVen Batten 已提交
122
	orthogonalSize: number = 0;
123

124
	constructor(options: IPaneOptions) {
M
Matt Bierner 已提交
125
		super();
J
Joao Moreno 已提交
126
		this._expanded = typeof options.expanded === 'undefined' ? true : !!options.expanded;
S
Sandeep Somavarapu 已提交
127
		this._orientation = typeof options.orientation === 'undefined' ? Orientation.VERTICAL : options.orientation;
128
		this.ariaHeaderLabel = localize('viewSection', "{0} Section", options.title);
J
Joao Moreno 已提交
129
		this._minimumBodySize = typeof options.minimumBodySize === 'number' ? options.minimumBodySize : 120;
J
Joao Moreno 已提交
130
		this._maximumBodySize = typeof options.maximumBodySize === 'number' ? options.maximumBodySize : Number.POSITIVE_INFINITY;
J
Joao Moreno 已提交
131

132
		this.element = $('.pane');
J
Joao Moreno 已提交
133 134
	}

135
	isExpanded(): boolean {
J
Joao Moreno 已提交
136 137 138
		return this._expanded;
	}

139
	setExpanded(expanded: boolean): boolean {
J
Joao Moreno 已提交
140
		if (this._expanded === !!expanded) {
141
			return false;
J
Joao Moreno 已提交
142 143 144
		}

		this._expanded = !!expanded;
145
		this.updateHeader();
146

J
Joao Moreno 已提交
147
		if (expanded) {
J
Joao Moreno 已提交
148 149 150
			if (typeof this.animationTimer === 'number') {
				clearTimeout(this.animationTimer);
			}
J
Joao Moreno 已提交
151 152 153 154 155 156 157
			append(this.element, this.body);
		} else {
			this.animationTimer = window.setTimeout(() => {
				this.body.remove();
			}, 200);
		}

158
		this._onDidChangeExpansionState.fire(expanded);
J
Joao Moreno 已提交
159
		this._onDidChange.fire(expanded ? this.expandedSize : undefined);
160
		return true;
J
Joao Moreno 已提交
161 162 163 164 165 166 167 168 169 170 171 172
	}

	get headerVisible(): boolean {
		return this._headerVisible;
	}

	set headerVisible(visible: boolean) {
		if (this._headerVisible === !!visible) {
			return;
		}

		this._headerVisible = !!visible;
173
		this.updateHeader();
174
		this._onDidChange.fire(undefined);
J
Joao Moreno 已提交
175 176
	}

S
SteVen Batten 已提交
177 178 179 180 181 182 183 184 185 186 187 188
	get orientation(): Orientation {
		return this._orientation;
	}

	set orientation(orientation: Orientation) {
		if (this._orientation === orientation) {
			return;
		}

		this._orientation = orientation;
	}

189
	render(): void {
190
		this.header = $('.pane-header');
J
Joao Moreno 已提交
191
		append(this.element, this.header);
192 193 194 195
		this.header.setAttribute('tabindex', '0');
		this.header.setAttribute('role', 'toolbar');
		this.header.setAttribute('aria-label', this.ariaHeaderLabel);
		this.renderHeader(this.header);
J
Joao Moreno 已提交
196 197

		const focusTracker = trackFocus(this.header);
M
Matt Bierner 已提交
198 199 200
		this._register(focusTracker);
		this._register(focusTracker.onDidFocus(() => addClass(this.header, 'focused'), null));
		this._register(focusTracker.onDidBlur(() => removeClass(this.header, 'focused'), null));
J
Joao Moreno 已提交
201

202
		this.updateHeader();
J
Joao Moreno 已提交
203 204


S
SteVen Batten 已提交
205 206
		const onHeaderKeyDown = Event.chain(domEvent(this.header, 'keydown'))
			.map(e => new StandardKeyboardEvent(e));
J
Joao Moreno 已提交
207

S
SteVen Batten 已提交
208 209
		this._register(onHeaderKeyDown.filter(e => e.keyCode === KeyCode.Enter || e.keyCode === KeyCode.Space)
			.event(() => this.setExpanded(!this.isExpanded()), null));
J
Joao Moreno 已提交
210

S
SteVen Batten 已提交
211 212
		this._register(onHeaderKeyDown.filter(e => e.keyCode === KeyCode.LeftArrow)
			.event(() => this.setExpanded(false), null));
J
Joao Moreno 已提交
213

S
SteVen Batten 已提交
214 215
		this._register(onHeaderKeyDown.filter(e => e.keyCode === KeyCode.RightArrow)
			.event(() => this.setExpanded(true), null));
216

S
SteVen Batten 已提交
217
		this._register(domEvent(this.header, 'click')
218 219 220 221 222
			(e => {
				if (!e.defaultPrevented) {
					this.setExpanded(!this.isExpanded());
				}
			}, null));
223

224
		this.body = append(this.element, $('.pane-body'));
J
Joao Moreno 已提交
225
		this.renderBody(this.body);
S
Sandeep Somavarapu 已提交
226 227 228 229

		if (!this.isExpanded()) {
			this.body.remove();
		}
J
Joao Moreno 已提交
230 231
	}

S
SteVen Batten 已提交
232
	layout(size: number): void {
233
		const headerSize = this.headerVisible ? Pane.HEADER_SIZE : 0;
234

S
SteVen Batten 已提交
235 236 237
		const width = this._orientation === Orientation.VERTICAL ? this.orthogonalSize : size;
		const height = this._orientation === Orientation.VERTICAL ? size - headerSize : this.orthogonalSize - headerSize;

238
		if (this.isExpanded()) {
S
SteVen Batten 已提交
239 240
			this.layoutBody(height, width);
			this.expandedSize = size;
241
		}
J
Joao Moreno 已提交
242 243
	}

244
	style(styles: IPaneStyles): void {
J
Joao Moreno 已提交
245 246
		this.styles = styles;

S
SteVen Batten 已提交
247 248
		this.element.style.borderLeft = this.styles.leftBorder && this.orientation === Orientation.HORIZONTAL ? `1px solid ${this.styles.leftBorder}` : '';

J
Joao Moreno 已提交
249 250 251 252 253
		if (!this.header) {
			return;
		}

		this.updateHeader();
J
Joao Moreno 已提交
254 255
	}

256 257
	protected updateHeader(): void {
		const expanded = !this.headerVisible || this.isExpanded();
J
Joao Moreno 已提交
258

259 260
		this.header.style.height = `${this.headerSize}px`;
		this.header.style.lineHeight = `${this.headerSize}px`;
J
Joao Moreno 已提交
261 262 263
		toggleClass(this.header, 'hidden', !this.headerVisible);
		toggleClass(this.header, 'expanded', expanded);
		this.header.setAttribute('aria-expanded', String(expanded));
J
Joao Moreno 已提交
264

M
Matt Bierner 已提交
265
		this.header.style.color = this.styles.headerForeground ? this.styles.headerForeground.toString() : '';
M
Matt Bierner 已提交
266
		this.header.style.backgroundColor = this.styles.headerBackground ? this.styles.headerBackground.toString() : '';
S
SteVen Batten 已提交
267
		this.header.style.borderTop = this.styles.headerBorder && this.orientation === Orientation.VERTICAL ? `1px solid ${this.styles.headerBorder}` : '';
J
Joao Moreno 已提交
268
		this._dropBackground = this.styles.dropBackground;
J
Joao Moreno 已提交
269 270
	}

271
	protected abstract renderHeader(container: HTMLElement): void;
J
Joao Moreno 已提交
272
	protected abstract renderBody(container: HTMLElement): void;
273
	protected abstract layoutBody(height: number, width: number): void;
J
Joao Moreno 已提交
274 275
}

J
Joao Moreno 已提交
276
interface IDndContext {
277
	draggable: PaneDraggable | null;
J
Joao Moreno 已提交
278 279
}

280
class PaneDraggable extends Disposable {
J
Joao Moreno 已提交
281

282
	private static readonly DefaultDragOverBackgroundColor = new Color(new RGBA(128, 128, 128, 0.5));
J
Joao Moreno 已提交
283

284
	private dragOverCounter = 0; // see https://github.com/Microsoft/vscode/issues/14470
J
Joao Moreno 已提交
285

286
	private _onDidDrop = this._register(new Emitter<{ from: Pane, to: Pane }>());
J
Joao Moreno 已提交
287 288
	readonly onDidDrop = this._onDidDrop.event;

289
	constructor(private pane: Pane, private dnd: IPaneDndController, private context: IDndContext) {
290 291
		super();

292 293 294 295 296 297
		pane.draggableElement.draggable = true;
		this._register(domEvent(pane.draggableElement, 'dragstart')(this.onDragStart, this));
		this._register(domEvent(pane.dropTargetElement, 'dragenter')(this.onDragEnter, this));
		this._register(domEvent(pane.dropTargetElement, 'dragleave')(this.onDragLeave, this));
		this._register(domEvent(pane.dropTargetElement, 'dragend')(this.onDragEnd, this));
		this._register(domEvent(pane.dropTargetElement, 'drop')(this.onDrop, this));
J
Joao Moreno 已提交
298 299 300
	}

	private onDragStart(e: DragEvent): void {
301
		if (!this.dnd.canDrag(this.pane) || !e.dataTransfer) {
J
Joao Moreno 已提交
302 303 304 305 306
			e.preventDefault();
			e.stopPropagation();
			return;
		}

J
Joao Moreno 已提交
307 308
		e.dataTransfer.effectAllowed = 'move';

309 310
		if (isFirefox) {
			// Firefox: requires to set a text data transfer to get going
311
			e.dataTransfer?.setData(DataTransfers.TEXT, this.pane.draggableElement.textContent || '');
312 313
		}

314
		const dragImage = append(document.body, $('.monaco-drag-image', {}, this.pane.draggableElement.textContent || ''));
J
Joao Moreno 已提交
315 316 317 318 319 320 321 322 323 324 325
		e.dataTransfer.setDragImage(dragImage, -10, -10);
		setTimeout(() => document.body.removeChild(dragImage), 0);

		this.context.draggable = this;
	}

	private onDragEnter(e: DragEvent): void {
		if (!this.context.draggable || this.context.draggable === this) {
			return;
		}

326
		if (!this.dnd.canDrop(this.context.draggable.pane, this.pane)) {
J
Joao Moreno 已提交
327 328 329
			return;
		}

J
Joao Moreno 已提交
330
		this.dragOverCounter++;
J
Joao Moreno 已提交
331
		this.render();
J
Joao Moreno 已提交
332 333 334 335 336 337 338
	}

	private onDragLeave(e: DragEvent): void {
		if (!this.context.draggable || this.context.draggable === this) {
			return;
		}

339
		if (!this.dnd.canDrop(this.context.draggable.pane, this.pane)) {
J
Joao Moreno 已提交
340 341 342
			return;
		}

J
Joao Moreno 已提交
343 344 345
		this.dragOverCounter--;

		if (this.dragOverCounter === 0) {
J
Joao Moreno 已提交
346
			this.render();
J
Joao Moreno 已提交
347 348 349 350 351 352 353 354 355
		}
	}

	private onDragEnd(e: DragEvent): void {
		if (!this.context.draggable) {
			return;
		}

		this.dragOverCounter = 0;
J
Joao Moreno 已提交
356
		this.render();
J
Joao Moreno 已提交
357 358 359 360 361 362 363 364
		this.context.draggable = null;
	}

	private onDrop(e: DragEvent): void {
		if (!this.context.draggable) {
			return;
		}

365 366
		EventHelper.stop(e);

J
Joao Moreno 已提交
367
		this.dragOverCounter = 0;
J
Joao Moreno 已提交
368
		this.render();
J
Joao Moreno 已提交
369

370 371
		if (this.dnd.canDrop(this.context.draggable.pane, this.pane) && this.context.draggable !== this) {
			this._onDidDrop.fire({ from: this.context.draggable.pane, to: this.pane });
J
Joao Moreno 已提交
372 373 374 375 376
		}

		this.context.draggable = null;
	}

J
Joao Moreno 已提交
377
	private render(): void {
378
		let backgroundColor: string | null = null;
J
Joao Moreno 已提交
379 380

		if (this.dragOverCounter > 0) {
381
			backgroundColor = (this.pane.dropBackground || PaneDraggable.DefaultDragOverBackgroundColor).toString();
J
Joao Moreno 已提交
382 383
		}

384
		this.pane.dropTargetElement.style.backgroundColor = backgroundColor || '';
J
Joao Moreno 已提交
385 386 387
	}
}

388 389 390
export interface IPaneDndController {
	canDrag(pane: Pane): boolean;
	canDrop(pane: Pane, overPane: Pane): boolean;
J
Joao Moreno 已提交
391 392
}

393
export class DefaultPaneDndController implements IPaneDndController {
J
Joao Moreno 已提交
394

395
	canDrag(pane: Pane): boolean {
J
Joao Moreno 已提交
396 397 398
		return true;
	}

399
	canDrop(pane: Pane, overPane: Pane): boolean {
J
Joao Moreno 已提交
400 401 402 403
		return true;
	}
}

404 405
export interface IPaneViewOptions {
	dnd?: IPaneDndController;
406
	orientation?: Orientation;
407 408
}

409 410
interface IPaneItem {
	pane: Pane;
411 412 413
	disposable: IDisposable;
}

414
export class PaneView extends Disposable {
J
Joao Moreno 已提交
415

416
	private dnd: IPaneDndController | undefined;
417
	private dndContext: IDndContext = { draggable: null };
418
	private el: HTMLElement;
419
	private paneItems: IPaneItem[] = [];
S
SteVen Batten 已提交
420
	private orthogonalSize: number = 0;
S
SteVen Batten 已提交
421
	private size: number = 0;
J
Joao Moreno 已提交
422
	private splitview: SplitView;
423
	private animationTimer: number | undefined = undefined;
J
Joao Moreno 已提交
424

425 426
	private _onDidDrop = this._register(new Emitter<{ from: Pane, to: Pane }>());
	readonly onDidDrop: Event<{ from: Pane, to: Pane }> = this._onDidDrop.event;
J
Joao Moreno 已提交
427

S
SteVen Batten 已提交
428
	orientation: Orientation;
J
Joao Moreno 已提交
429
	readonly onDidSashChange: Event<number>;
J
Joao Moreno 已提交
430

431
	constructor(container: HTMLElement, options: IPaneViewOptions = {}) {
432 433
		super();

J
Joao Moreno 已提交
434
		this.dnd = options.dnd;
435
		this.orientation = options.orientation ?? Orientation.VERTICAL;
436
		this.el = append(container, $('.monaco-pane-view'));
437
		this.splitview = this._register(new SplitView(this.el, { orientation: this.orientation }));
J
Joao Moreno 已提交
438
		this.onDidSashChange = this.splitview.onDidSashChange;
J
Joao Moreno 已提交
439 440
	}

441
	addPane(pane: Pane, size: number, index = this.splitview.length): void {
442
		const disposables = new DisposableStore();
443
		pane.onDidChangeExpansionState(this.setupAnimation, this, disposables);
J
Joao Moreno 已提交
444

445 446
		const paneItem = { pane: pane, disposable: disposables };
		this.paneItems.splice(index, 0, paneItem);
S
SteVen Batten 已提交
447
		pane.orientation = this.orientation;
S
SteVen Batten 已提交
448
		pane.orthogonalSize = this.orthogonalSize;
449
		this.splitview.addView(pane, size, index);
J
Joao Moreno 已提交
450

J
Joao Moreno 已提交
451
		if (this.dnd) {
452
			const draggable = new PaneDraggable(pane, this.dnd, this.dndContext);
453 454
			disposables.add(draggable);
			disposables.add(draggable.onDidDrop(this._onDidDrop.fire, this._onDidDrop));
J
Joao Moreno 已提交
455
		}
J
Joao Moreno 已提交
456 457
	}

458 459
	removePane(pane: Pane): void {
		const index = firstIndex(this.paneItems, item => item.pane === pane);
J
Joao Moreno 已提交
460

461 462 463 464 465
		if (index === -1) {
			return;
		}

		this.splitview.removeView(index);
466 467
		const paneItem = this.paneItems.splice(index, 1)[0];
		paneItem.disposable.dispose();
J
Joao Moreno 已提交
468 469
	}

470 471 472
	movePane(from: Pane, to: Pane): void {
		const fromIndex = firstIndex(this.paneItems, item => item.pane === from);
		const toIndex = firstIndex(this.paneItems, item => item.pane === to);
J
Joao Moreno 已提交
473 474 475 476 477

		if (fromIndex === -1 || toIndex === -1) {
			return;
		}

478 479
		const [paneItem] = this.paneItems.splice(fromIndex, 1);
		this.paneItems.splice(toIndex, 0, paneItem);
J
Joao Moreno 已提交
480

J
Joao Moreno 已提交
481 482 483
		this.splitview.moveView(fromIndex, toIndex);
	}

484 485
	resizePane(pane: Pane, size: number): void {
		const index = firstIndex(this.paneItems, item => item.pane === pane);
J
Joao Moreno 已提交
486 487 488 489 490 491 492 493

		if (index === -1) {
			return;
		}

		this.splitview.resizeView(index, size);
	}

494 495
	getPaneSize(pane: Pane): number {
		const index = firstIndex(this.paneItems, item => item.pane === pane);
J
Joao Moreno 已提交
496 497 498 499 500 501 502 503

		if (index === -1) {
			return -1;
		}

		return this.splitview.getViewSize(index);
	}

504
	layout(height: number, width: number): void {
S
SteVen Batten 已提交
505
		this.orthogonalSize = this.orientation === Orientation.VERTICAL ? width : height;
S
SteVen Batten 已提交
506
		this.size = this.orientation === Orientation.HORIZONTAL ? width : height;
507

508
		for (const paneItem of this.paneItems) {
S
SteVen Batten 已提交
509
			paneItem.pane.orthogonalSize = this.orthogonalSize;
510 511
		}

S
SteVen Batten 已提交
512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538
		this.splitview.layout(this.size);
	}

	flipOrientation(height: number, width: number): void {
		this.orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL;
		const paneSizes = this.paneItems.map(pane => this.getPaneSize(pane.pane));

		this.splitview.dispose();
		clearNode(this.el);

		this.splitview = this._register(new SplitView(this.el, { orientation: this.orientation }));

		const newOrthogonalSize = this.orientation === Orientation.VERTICAL ? width : height;
		const newSize = this.orientation === Orientation.HORIZONTAL ? width : height;

		this.paneItems.forEach((pane, index) => {
			pane.pane.orthogonalSize = newOrthogonalSize;
			pane.pane.orientation = this.orientation;

			const viewSize = this.size === 0 ? 0 : (newSize * paneSizes[index]) / this.size;
			this.splitview.addView(pane.pane, viewSize, index);
		});

		this.size = newSize;
		this.orthogonalSize = newOrthogonalSize;

		this.splitview.layout(this.size);
539 540 541 542 543 544 545 546
	}

	private setupAnimation(): void {
		if (typeof this.animationTimer === 'number') {
			window.clearTimeout(this.animationTimer);
		}

		addClass(this.el, 'animated');
J
Joao Moreno 已提交
547

548
		this.animationTimer = window.setTimeout(() => {
549
			this.animationTimer = undefined;
550 551
			removeClass(this.el, 'animated');
		}, 200);
J
Joao Moreno 已提交
552 553 554
	}

	dispose(): void {
555 556
		super.dispose();

557
		this.paneItems.forEach(i => i.disposable.dispose());
J
Joao Moreno 已提交
558 559
	}
}