| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007 |
- /*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
- return c > 3 && r && Object.defineProperty(target, key, r), r;
- };
- import { isFirefox } from '../../browser.js';
- import { DataTransfers, StaticDND } from '../../dnd.js';
- import { $, addDisposableListener, animate, getContentHeight, getContentWidth, getTopLeftOffset, scheduleAtNextAnimationFrame } from '../../dom.js';
- import { DomEmitter } from '../../event.js';
- import { EventType as TouchEventType, Gesture } from '../../touch.js';
- import { SmoothScrollableElement } from '../scrollbar/scrollableElement.js';
- import { distinct, equals } from '../../../common/arrays.js';
- import { Delayer, disposableTimeout } from '../../../common/async.js';
- import { memoize } from '../../../common/decorators.js';
- import { Emitter, Event } from '../../../common/event.js';
- import { Disposable, DisposableStore, dispose, toDisposable } from '../../../common/lifecycle.js';
- import { Range } from '../../../common/range.js';
- import { Scrollable } from '../../../common/scrollable.js';
- import { RangeMap, shift } from './rangeMap.js';
- import { RowCache } from './rowCache.js';
- const DefaultOptions = {
- useShadows: true,
- verticalScrollMode: 1 /* ScrollbarVisibility.Auto */,
- setRowLineHeight: true,
- setRowHeight: true,
- supportDynamicHeights: false,
- dnd: {
- getDragElements(e) { return [e]; },
- getDragURI() { return null; },
- onDragStart() { },
- onDragOver() { return false; },
- drop() { }
- },
- horizontalScrolling: false,
- transformOptimization: true,
- alwaysConsumeMouseWheel: true,
- };
- export class ElementsDragAndDropData {
- constructor(elements) {
- this.elements = elements;
- }
- update() { }
- getData() {
- return this.elements;
- }
- }
- export class ExternalElementsDragAndDropData {
- constructor(elements) {
- this.elements = elements;
- }
- update() { }
- getData() {
- return this.elements;
- }
- }
- export class NativeDragAndDropData {
- constructor() {
- this.types = [];
- this.files = [];
- }
- update(dataTransfer) {
- 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() {
- return {
- types: this.types,
- files: this.files
- };
- }
- }
- function equalsDragFeedback(f1, f2) {
- if (Array.isArray(f1) && Array.isArray(f2)) {
- return equals(f1, f2);
- }
- return f1 === f2;
- }
- class ListViewAccessibilityProvider {
- constructor(accessibilityProvider) {
- if (accessibilityProvider === null || accessibilityProvider === void 0 ? void 0 : accessibilityProvider.getSetSize) {
- this.getSetSize = accessibilityProvider.getSetSize.bind(accessibilityProvider);
- }
- else {
- this.getSetSize = (e, i, l) => l;
- }
- if (accessibilityProvider === null || accessibilityProvider === void 0 ? void 0 : accessibilityProvider.getPosInSet) {
- this.getPosInSet = accessibilityProvider.getPosInSet.bind(accessibilityProvider);
- }
- else {
- this.getPosInSet = (e, i) => i + 1;
- }
- if (accessibilityProvider === null || accessibilityProvider === void 0 ? void 0 : accessibilityProvider.getRole) {
- this.getRole = accessibilityProvider.getRole.bind(accessibilityProvider);
- }
- else {
- this.getRole = _ => 'listitem';
- }
- if (accessibilityProvider === null || accessibilityProvider === void 0 ? void 0 : accessibilityProvider.isChecked) {
- this.isChecked = accessibilityProvider.isChecked.bind(accessibilityProvider);
- }
- else {
- this.isChecked = _ => undefined;
- }
- }
- }
- /**
- * The {@link ListView} is a virtual scrolling engine.
- *
- * Given that it only renders elements within its viewport, it can hold large
- * collections of elements and stay very performant. The performance bottleneck
- * usually lies within the user's rendering code for each element.
- *
- * @remarks It is a low-level widget, not meant to be used directly. Refer to the
- * List widget instead.
- */
- export class ListView {
- constructor(container, virtualDelegate, renderers, options = DefaultOptions) {
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
- this.virtualDelegate = virtualDelegate;
- this.domId = `list_id_${++ListView.InstanceCount}`;
- this.renderers = new Map();
- this.renderWidth = 0;
- this._scrollHeight = 0;
- this.scrollableElementUpdateDisposable = null;
- this.scrollableElementWidthDelayer = new Delayer(50);
- this.splicing = false;
- this.dragOverAnimationStopDisposable = Disposable.None;
- this.dragOverMouseY = 0;
- this.canDrop = false;
- this.currentDragFeedbackDisposable = Disposable.None;
- this.onDragLeaveTimeout = Disposable.None;
- this.disposables = new DisposableStore();
- this._onDidChangeContentHeight = new Emitter();
- this._horizontalScrolling = false;
- 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();
- for (const renderer of renderers) {
- this.renderers.set(renderer.templateId, renderer);
- }
- this.cache = this.disposables.add(new RowCache(this.renderers));
- this.lastRenderTop = 0;
- this.lastRenderHeight = 0;
- this.domNode = document.createElement('div');
- this.domNode.className = 'monaco-list';
- this.domNode.classList.add(this.domId);
- this.domNode.tabIndex = 0;
- this.domNode.classList.toggle('mouse-support', typeof options.mouseSupport === 'boolean' ? options.mouseSupport : true);
- this._horizontalScrolling = (_a = options.horizontalScrolling) !== null && _a !== void 0 ? _a : DefaultOptions.horizontalScrolling;
- this.domNode.classList.toggle('horizontal-scrolling', this._horizontalScrolling);
- this.additionalScrollHeight = typeof options.additionalScrollHeight === 'undefined' ? 0 : options.additionalScrollHeight;
- this.accessibilityProvider = new ListViewAccessibilityProvider(options.accessibilityProvider);
- this.rowsContainer = document.createElement('div');
- this.rowsContainer.className = 'monaco-list-rows';
- const transformOptimization = (_b = options.transformOptimization) !== null && _b !== void 0 ? _b : DefaultOptions.transformOptimization;
- if (transformOptimization) {
- this.rowsContainer.style.transform = 'translate3d(0px, 0px, 0px)';
- }
- this.disposables.add(Gesture.addTarget(this.rowsContainer));
- this.scrollable = new Scrollable({
- forceIntegerValues: true,
- smoothScrollDuration: ((_c = options.smoothScrolling) !== null && _c !== void 0 ? _c : false) ? 125 : 0,
- scheduleAtNextAnimationFrame: cb => scheduleAtNextAnimationFrame(cb)
- });
- this.scrollableElement = this.disposables.add(new SmoothScrollableElement(this.rowsContainer, {
- alwaysConsumeMouseWheel: (_d = options.alwaysConsumeMouseWheel) !== null && _d !== void 0 ? _d : DefaultOptions.alwaysConsumeMouseWheel,
- horizontal: 1 /* ScrollbarVisibility.Auto */,
- vertical: (_e = options.verticalScrollMode) !== null && _e !== void 0 ? _e : DefaultOptions.verticalScrollMode,
- useShadows: (_f = options.useShadows) !== null && _f !== void 0 ? _f : DefaultOptions.useShadows,
- mouseWheelScrollSensitivity: options.mouseWheelScrollSensitivity,
- fastScrollSensitivity: options.fastScrollSensitivity
- }, this.scrollable));
- this.domNode.appendChild(this.scrollableElement.getDomNode());
- container.appendChild(this.domNode);
- this.scrollableElement.onScroll(this.onScroll, this, this.disposables);
- this.disposables.add(addDisposableListener(this.rowsContainer, TouchEventType.Change, e => this.onTouchChange(e)));
- // Prevent the monaco-scrollable-element from scrolling
- // https://github.com/microsoft/vscode/issues/44181
- this.disposables.add(addDisposableListener(this.scrollableElement.getDomNode(), 'scroll', e => e.target.scrollTop = 0));
- this.disposables.add(addDisposableListener(this.domNode, 'dragover', e => this.onDragOver(this.toDragEvent(e))));
- this.disposables.add(addDisposableListener(this.domNode, 'drop', e => this.onDrop(this.toDragEvent(e))));
- this.disposables.add(addDisposableListener(this.domNode, 'dragleave', e => this.onDragLeave(this.toDragEvent(e))));
- this.disposables.add(addDisposableListener(this.domNode, 'dragend', e => this.onDragEnd(e)));
- this.setRowLineHeight = (_g = options.setRowLineHeight) !== null && _g !== void 0 ? _g : DefaultOptions.setRowLineHeight;
- this.setRowHeight = (_h = options.setRowHeight) !== null && _h !== void 0 ? _h : DefaultOptions.setRowHeight;
- this.supportDynamicHeights = (_j = options.supportDynamicHeights) !== null && _j !== void 0 ? _j : DefaultOptions.supportDynamicHeights;
- this.dnd = (_k = options.dnd) !== null && _k !== void 0 ? _k : DefaultOptions.dnd;
- this.layout();
- }
- get contentHeight() { return this.rangeMap.size; }
- get horizontalScrolling() { return this._horizontalScrolling; }
- set horizontalScrolling(value) {
- if (value === this._horizontalScrolling) {
- return;
- }
- if (value && this.supportDynamicHeights) {
- throw new Error('Horizontal scrolling and dynamic heights not supported simultaneously');
- }
- this._horizontalScrolling = value;
- this.domNode.classList.toggle('horizontal-scrolling', this._horizontalScrolling);
- if (this._horizontalScrolling) {
- for (const item of this.items) {
- this.measureItemWidth(item);
- }
- this.updateScrollWidth();
- this.scrollableElement.setScrollDimensions({ width: getContentWidth(this.domNode) });
- this.rowsContainer.style.width = `${Math.max(this.scrollWidth || 0, this.renderWidth)}px`;
- }
- else {
- this.scrollableElementWidthDelayer.cancel();
- this.scrollableElement.setScrollDimensions({ width: this.renderWidth, scrollWidth: this.renderWidth });
- this.rowsContainer.style.width = '';
- }
- }
- updateOptions(options) {
- if (options.additionalScrollHeight !== undefined) {
- this.additionalScrollHeight = options.additionalScrollHeight;
- this.scrollableElement.setScrollDimensions({ scrollHeight: this.scrollHeight });
- }
- if (options.smoothScrolling !== undefined) {
- this.scrollable.setSmoothScrollDuration(options.smoothScrolling ? 125 : 0);
- }
- if (options.horizontalScrolling !== undefined) {
- this.horizontalScrolling = options.horizontalScrolling;
- }
- if (options.mouseWheelScrollSensitivity !== undefined) {
- this.scrollableElement.updateOptions({ mouseWheelScrollSensitivity: options.mouseWheelScrollSensitivity });
- }
- if (options.fastScrollSensitivity !== undefined) {
- this.scrollableElement.updateOptions({ fastScrollSensitivity: options.fastScrollSensitivity });
- }
- }
- splice(start, deleteCount, elements = []) {
- if (this.splicing) {
- throw new Error('Can\'t run recursive splices.');
- }
- this.splicing = true;
- try {
- return this._splice(start, deleteCount, elements);
- }
- finally {
- this.splicing = false;
- this._onDidChangeContentHeight.fire(this.contentHeight);
- }
- }
- _splice(start, deleteCount, elements = []) {
- const previousRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight);
- const deleteRange = { start, end: start + deleteCount };
- const removeRange = Range.intersect(previousRenderRange, deleteRange);
- // try to reuse rows, avoid removing them from DOM
- const rowsToDispose = new Map();
- for (let i = removeRange.end - 1; i >= removeRange.start; i--) {
- const item = this.items[i];
- item.dragStartDisposable.dispose();
- item.checkedDisposable.dispose();
- if (item.row) {
- let rows = rowsToDispose.get(item.templateId);
- if (!rows) {
- rows = [];
- rowsToDispose.set(item.templateId, rows);
- }
- const renderer = this.renderers.get(item.templateId);
- if (renderer && renderer.disposeElement) {
- renderer.disposeElement(item.element, i, item.row.templateData, item.size);
- }
- rows.push(item.row);
- }
- item.row = null;
- }
- const previousRestRange = { start: start + deleteCount, end: this.items.length };
- const previousRenderedRestRange = Range.intersect(previousRestRange, previousRenderRange);
- const previousUnrenderedRestRanges = Range.relativeComplement(previousRestRange, previousRenderRange);
- const inserted = elements.map(element => ({
- id: String(this.itemId++),
- element,
- templateId: this.virtualDelegate.getTemplateId(element),
- size: this.virtualDelegate.getHeight(element),
- width: undefined,
- hasDynamicHeight: !!this.virtualDelegate.hasDynamicHeight && this.virtualDelegate.hasDynamicHeight(element),
- lastDynamicHeightWidth: undefined,
- row: null,
- uri: undefined,
- dropTarget: false,
- dragStartDisposable: Disposable.None,
- checkedDisposable: Disposable.None
- }));
- let deleted;
- // TODO@joao: improve this optimization to catch even more cases
- if (start === 0 && deleteCount >= this.items.length) {
- this.rangeMap = new RangeMap();
- this.rangeMap.splice(0, 0, inserted);
- deleted = this.items;
- this.items = inserted;
- }
- else {
- this.rangeMap.splice(start, deleteCount, inserted);
- deleted = this.items.splice(start, deleteCount, ...inserted);
- }
- const delta = elements.length - deleteCount;
- const renderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight);
- const renderedRestRange = shift(previousRenderedRestRange, delta);
- const updateRange = Range.intersect(renderRange, renderedRestRange);
- for (let i = updateRange.start; i < updateRange.end; i++) {
- this.updateItemInDOM(this.items[i], i);
- }
- const removeRanges = Range.relativeComplement(renderedRestRange, renderRange);
- for (const range of removeRanges) {
- for (let i = range.start; i < range.end; i++) {
- this.removeItemFromDOM(i);
- }
- }
- const unrenderedRestRanges = previousUnrenderedRestRanges.map(r => shift(r, delta));
- const elementsRange = { start, end: start + elements.length };
- const insertRanges = [elementsRange, ...unrenderedRestRanges].map(r => Range.intersect(renderRange, r));
- const beforeElement = this.getNextToLastElement(insertRanges);
- for (const range of insertRanges) {
- for (let i = range.start; i < range.end; i++) {
- const item = this.items[i];
- const rows = rowsToDispose.get(item.templateId);
- const row = rows === null || rows === void 0 ? void 0 : rows.pop();
- this.insertItemInDOM(i, beforeElement, row);
- }
- }
- for (const rows of rowsToDispose.values()) {
- for (const row of rows) {
- this.cache.release(row);
- }
- }
- this.eventuallyUpdateScrollDimensions();
- if (this.supportDynamicHeights) {
- this._rerender(this.scrollTop, this.renderHeight);
- }
- return deleted.map(i => i.element);
- }
- eventuallyUpdateScrollDimensions() {
- this._scrollHeight = this.contentHeight;
- this.rowsContainer.style.height = `${this._scrollHeight}px`;
- if (!this.scrollableElementUpdateDisposable) {
- this.scrollableElementUpdateDisposable = scheduleAtNextAnimationFrame(() => {
- this.scrollableElement.setScrollDimensions({ scrollHeight: this.scrollHeight });
- this.updateScrollWidth();
- this.scrollableElementUpdateDisposable = null;
- });
- }
- }
- eventuallyUpdateScrollWidth() {
- if (!this.horizontalScrolling) {
- this.scrollableElementWidthDelayer.cancel();
- return;
- }
- this.scrollableElementWidthDelayer.trigger(() => this.updateScrollWidth());
- }
- updateScrollWidth() {
- if (!this.horizontalScrolling) {
- return;
- }
- 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 === 0 ? 0 : (scrollWidth + 10) });
- }
- rerender() {
- if (!this.supportDynamicHeights) {
- return;
- }
- for (const item of this.items) {
- item.lastDynamicHeightWidth = undefined;
- }
- this._rerender(this.lastRenderTop, this.lastRenderHeight);
- }
- get length() {
- return this.items.length;
- }
- get renderHeight() {
- const scrollDimensions = this.scrollableElement.getScrollDimensions();
- return scrollDimensions.height;
- }
- element(index) {
- return this.items[index].element;
- }
- domElement(index) {
- const row = this.items[index].row;
- return row && row.domNode;
- }
- elementHeight(index) {
- return this.items[index].size;
- }
- elementTop(index) {
- return this.rangeMap.positionAt(index);
- }
- indexAt(position) {
- return this.rangeMap.indexAt(position);
- }
- indexAfter(position) {
- return this.rangeMap.indexAfter(position);
- }
- layout(height, width) {
- const scrollDimensions = {
- height: typeof height === 'number' ? height : getContentHeight(this.domNode)
- };
- if (this.scrollableElementUpdateDisposable) {
- this.scrollableElementUpdateDisposable.dispose();
- this.scrollableElementUpdateDisposable = null;
- scrollDimensions.scrollHeight = this.scrollHeight;
- }
- this.scrollableElement.setScrollDimensions(scrollDimensions);
- if (typeof width !== 'undefined') {
- this.renderWidth = width;
- if (this.supportDynamicHeights) {
- this._rerender(this.scrollTop, this.renderHeight);
- }
- }
- if (this.horizontalScrolling) {
- this.scrollableElement.setScrollDimensions({
- width: typeof width === 'number' ? width : getContentWidth(this.domNode)
- });
- }
- }
- // Render
- render(previousRenderRange, renderTop, renderHeight, renderLeft, scrollWidth, updateItemsInDOM = false) {
- const renderRange = this.getRenderRange(renderTop, renderHeight);
- const rangesToInsert = Range.relativeComplement(renderRange, previousRenderRange);
- const rangesToRemove = Range.relativeComplement(previousRenderRange, renderRange);
- const beforeElement = this.getNextToLastElement(rangesToInsert);
- if (updateItemsInDOM) {
- const rangesToUpdate = Range.intersect(previousRenderRange, renderRange);
- for (let i = rangesToUpdate.start; i < rangesToUpdate.end; i++) {
- this.updateItemInDOM(this.items[i], i);
- }
- }
- for (const range of rangesToInsert) {
- for (let i = range.start; i < range.end; i++) {
- this.insertItemInDOM(i, beforeElement);
- }
- }
- for (const range of rangesToRemove) {
- for (let i = range.start; i < range.end; i++) {
- this.removeItemFromDOM(i);
- }
- }
- if (renderLeft !== undefined) {
- this.rowsContainer.style.left = `-${renderLeft}px`;
- }
- this.rowsContainer.style.top = `-${renderTop}px`;
- if (this.horizontalScrolling && scrollWidth !== undefined) {
- this.rowsContainer.style.width = `${Math.max(scrollWidth, this.renderWidth)}px`;
- }
- this.lastRenderTop = renderTop;
- this.lastRenderHeight = renderHeight;
- }
- // DOM operations
- insertItemInDOM(index, beforeElement, row) {
- const item = this.items[index];
- if (!item.row) {
- item.row = row !== null && row !== void 0 ? row : this.cache.alloc(item.templateId);
- }
- const role = this.accessibilityProvider.getRole(item.element) || 'listitem';
- item.row.domNode.setAttribute('role', role);
- const checked = this.accessibilityProvider.isChecked(item.element);
- if (typeof checked === 'boolean') {
- item.row.domNode.setAttribute('aria-checked', String(!!checked));
- }
- else if (checked) {
- const update = (checked) => item.row.domNode.setAttribute('aria-checked', String(!!checked));
- update(checked.value);
- item.checkedDisposable = checked.onDidChange(update);
- }
- if (!item.row.domNode.parentElement) {
- if (beforeElement) {
- this.rowsContainer.insertBefore(item.row.domNode, beforeElement);
- }
- else {
- this.rowsContainer.appendChild(item.row.domNode);
- }
- }
- this.updateItemInDOM(item, index);
- const renderer = this.renderers.get(item.templateId);
- if (!renderer) {
- throw new Error(`No renderer found for template id ${item.templateId}`);
- }
- renderer === null || renderer === void 0 ? void 0 : renderer.renderElement(item.element, index, item.row.templateData, item.size);
- const uri = this.dnd.getDragURI(item.element);
- item.dragStartDisposable.dispose();
- item.row.domNode.draggable = !!uri;
- if (uri) {
- item.dragStartDisposable = addDisposableListener(item.row.domNode, 'dragstart', event => this.onDragStart(item.element, uri, event));
- }
- if (this.horizontalScrolling) {
- this.measureItemWidth(item);
- this.eventuallyUpdateScrollWidth();
- }
- }
- measureItemWidth(item) {
- if (!item.row || !item.row.domNode) {
- return;
- }
- item.row.domNode.style.width = isFirefox ? '-moz-fit-content' : 'fit-content';
- item.width = 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 = '';
- }
- updateItemInDOM(item, index) {
- item.row.domNode.style.top = `${this.elementTop(index)}px`;
- if (this.setRowHeight) {
- item.row.domNode.style.height = `${item.size}px`;
- }
- if (this.setRowLineHeight) {
- item.row.domNode.style.lineHeight = `${item.size}px`;
- }
- item.row.domNode.setAttribute('data-index', `${index}`);
- item.row.domNode.setAttribute('data-last-element', index === this.length - 1 ? 'true' : 'false');
- item.row.domNode.setAttribute('data-parity', index % 2 === 0 ? 'even' : 'odd');
- item.row.domNode.setAttribute('aria-setsize', String(this.accessibilityProvider.getSetSize(item.element, index, this.length)));
- item.row.domNode.setAttribute('aria-posinset', String(this.accessibilityProvider.getPosInSet(item.element, index)));
- item.row.domNode.setAttribute('id', this.getElementDomId(index));
- item.row.domNode.classList.toggle('drop-target', item.dropTarget);
- }
- removeItemFromDOM(index) {
- const item = this.items[index];
- item.dragStartDisposable.dispose();
- item.checkedDisposable.dispose();
- if (item.row) {
- const renderer = this.renderers.get(item.templateId);
- if (renderer && renderer.disposeElement) {
- renderer.disposeElement(item.element, index, item.row.templateData, item.size);
- }
- this.cache.release(item.row);
- item.row = null;
- }
- if (this.horizontalScrolling) {
- this.eventuallyUpdateScrollWidth();
- }
- }
- getScrollTop() {
- const scrollPosition = this.scrollableElement.getScrollPosition();
- return scrollPosition.scrollTop;
- }
- setScrollTop(scrollTop, reuseAnimation) {
- if (this.scrollableElementUpdateDisposable) {
- this.scrollableElementUpdateDisposable.dispose();
- this.scrollableElementUpdateDisposable = null;
- this.scrollableElement.setScrollDimensions({ scrollHeight: this.scrollHeight });
- }
- this.scrollableElement.setScrollPosition({ scrollTop, reuseAnimation });
- }
- get scrollTop() {
- return this.getScrollTop();
- }
- set scrollTop(scrollTop) {
- this.setScrollTop(scrollTop);
- }
- get scrollHeight() {
- return this._scrollHeight + (this.horizontalScrolling ? 10 : 0) + this.additionalScrollHeight;
- }
- // Events
- get onMouseClick() { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'click')).event, e => this.toMouseEvent(e), this.disposables); }
- get onMouseDblClick() { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'dblclick')).event, e => this.toMouseEvent(e), this.disposables); }
- get onMouseMiddleClick() { return Event.filter(Event.map(this.disposables.add(new DomEmitter(this.domNode, 'auxclick')).event, e => this.toMouseEvent(e), this.disposables), e => e.browserEvent.button === 1, this.disposables); }
- get onMouseDown() { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'mousedown')).event, e => this.toMouseEvent(e), this.disposables); }
- get onMouseOver() { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'mouseover')).event, e => this.toMouseEvent(e), this.disposables); }
- get onContextMenu() { return Event.any(Event.map(this.disposables.add(new DomEmitter(this.domNode, 'contextmenu')).event, e => this.toMouseEvent(e), this.disposables), Event.map(this.disposables.add(new DomEmitter(this.domNode, TouchEventType.Contextmenu)).event, e => this.toGestureEvent(e), this.disposables)); }
- get onTouchStart() { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'touchstart')).event, e => this.toTouchEvent(e), this.disposables); }
- get onTap() { return Event.map(this.disposables.add(new DomEmitter(this.rowsContainer, TouchEventType.Tap)).event, e => this.toGestureEvent(e), this.disposables); }
- toMouseEvent(browserEvent) {
- 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 };
- }
- toTouchEvent(browserEvent) {
- 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 };
- }
- toGestureEvent(browserEvent) {
- const index = this.getItemIndexFromEventTarget(browserEvent.initialTarget || null);
- const item = typeof index === 'undefined' ? undefined : this.items[index];
- const element = item && item.element;
- return { browserEvent, index, element };
- }
- toDragEvent(browserEvent) {
- 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 };
- }
- onScroll(e) {
- try {
- const previousRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight);
- this.render(previousRenderRange, e.scrollTop, e.height, e.scrollLeft, e.scrollWidth);
- if (this.supportDynamicHeights) {
- this._rerender(e.scrollTop, e.height, e.inSmoothScrolling);
- }
- }
- catch (err) {
- console.error('Got bad scroll event:', e);
- throw err;
- }
- }
- onTouchChange(event) {
- event.preventDefault();
- event.stopPropagation();
- this.scrollTop -= event.translationY;
- }
- // DND
- onDragStart(element, uri, event) {
- var _a, _b;
- if (!event.dataTransfer) {
- return;
- }
- const elements = this.dnd.getDragElements(element);
- event.dataTransfer.effectAllowed = 'copyMove';
- event.dataTransfer.setData(DataTransfers.TEXT, uri);
- if (event.dataTransfer.setDragImage) {
- let label;
- if (this.dnd.getDragLabel) {
- label = this.dnd.getDragLabel(elements, event);
- }
- if (typeof label === 'undefined') {
- label = String(elements.length);
- }
- const dragImage = $('.monaco-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);
- (_b = (_a = this.dnd).onDragStart) === null || _b === void 0 ? void 0 : _b.call(_a, this.currentDragData, event);
- }
- onDragOver(event) {
- var _a;
- event.browserEvent.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome)
- this.onDragLeaveTimeout.dispose();
- if (StaticDND.CurrentDragAndDropData && StaticDND.CurrentDragAndDropData.getData() === 'vscode-ui') {
- return false;
- }
- 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 NativeDragAndDropData();
- }
- }
- 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 === 0 /* ListDragOverEffect.Copy */) ? 'copy' : 'move';
- let feedback;
- 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((a, b) => a - b);
- feedback = feedback[0] === -1 ? [-1] : feedback;
- if (equalsDragFeedback(this.currentDragFeedback, feedback)) {
- return true;
- }
- this.currentDragFeedback = feedback;
- this.currentDragFeedbackDisposable.dispose();
- if (feedback[0] === -1) { // entire list feedback
- this.domNode.classList.add('drop-target');
- this.rowsContainer.classList.add('drop-target');
- this.currentDragFeedbackDisposable = toDisposable(() => {
- this.domNode.classList.remove('drop-target');
- this.rowsContainer.classList.remove('drop-target');
- });
- }
- else {
- for (const index of feedback) {
- const item = this.items[index];
- item.dropTarget = true;
- (_a = item.row) === null || _a === void 0 ? void 0 : _a.domNode.classList.add('drop-target');
- }
- this.currentDragFeedbackDisposable = toDisposable(() => {
- var _a;
- for (const index of feedback) {
- const item = this.items[index];
- item.dropTarget = false;
- (_a = item.row) === null || _a === void 0 ? void 0 : _a.domNode.classList.remove('drop-target');
- }
- });
- }
- return true;
- }
- onDragLeave(event) {
- var _a, _b;
- this.onDragLeaveTimeout.dispose();
- this.onDragLeaveTimeout = disposableTimeout(() => this.clearDragOverFeedback(), 100);
- if (this.currentDragData) {
- (_b = (_a = this.dnd).onDragLeave) === null || _b === void 0 ? void 0 : _b.call(_a, this.currentDragData, event.element, event.index, event.browserEvent);
- }
- }
- onDrop(event) {
- 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);
- }
- onDragEnd(event) {
- var _a, _b;
- this.canDrop = false;
- this.teardownDragAndDropScrollTopAnimation();
- this.clearDragOverFeedback();
- this.currentDragData = undefined;
- StaticDND.CurrentDragAndDropData = undefined;
- (_b = (_a = this.dnd).onDragEnd) === null || _b === void 0 ? void 0 : _b.call(_a, event);
- }
- clearDragOverFeedback() {
- this.currentDragFeedback = undefined;
- this.currentDragFeedbackDisposable.dispose();
- this.currentDragFeedbackDisposable = Disposable.None;
- }
- // DND scroll top animation
- setupDragAndDropScrollTopAnimation(event) {
- if (!this.dragOverAnimationDisposable) {
- const viewTop = getTopLeftOffset(this.domNode).top;
- this.dragOverAnimationDisposable = 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;
- }
- animateDragAndDropScrollTop(viewTop) {
- 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)));
- }
- }
- teardownDragAndDropScrollTopAnimation() {
- this.dragOverAnimationStopDisposable.dispose();
- if (this.dragOverAnimationDisposable) {
- this.dragOverAnimationDisposable.dispose();
- this.dragOverAnimationDisposable = undefined;
- }
- }
- // Util
- getItemIndexFromEventTarget(target) {
- const scrollableElement = this.scrollableElement.getDomNode();
- let element = target;
- while (element instanceof HTMLElement && element !== this.rowsContainer && scrollableElement.contains(element)) {
- const rawIndex = element.getAttribute('data-index');
- if (rawIndex) {
- const index = Number(rawIndex);
- if (!isNaN(index)) {
- return index;
- }
- }
- element = element.parentElement;
- }
- return undefined;
- }
- getRenderRange(renderTop, renderHeight) {
- return {
- start: this.rangeMap.indexAt(renderTop),
- end: this.rangeMap.indexAfter(renderTop + renderHeight - 1)
- };
- }
- /**
- * Given a stable rendered state, checks every rendered element whether it needs
- * to be probed for dynamic height. Adjusts scroll height and top if necessary.
- */
- _rerender(renderTop, renderHeight, inSmoothScrolling) {
- const previousRenderRange = this.getRenderRange(renderTop, renderHeight);
- // Let's remember the second element's position, this helps in scrolling up
- // and preserving a linear upwards scroll movement
- let anchorElementIndex;
- let anchorElementTopDelta;
- 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;
- while (true) {
- const renderRange = this.getRenderRange(renderTop, renderHeight);
- let didChange = false;
- for (let i = renderRange.start; i < renderRange.end; i++) {
- const diff = this.probeDynamicHeight(i);
- if (diff !== 0) {
- this.rangeMap.splice(i, 1, [this.items[i]]);
- }
- heightDiff += diff;
- didChange = didChange || diff !== 0;
- }
- if (!didChange) {
- if (heightDiff !== 0) {
- this.eventuallyUpdateScrollDimensions();
- }
- const unrenderRanges = Range.relativeComplement(previousRenderRange, renderRange);
- for (const range of unrenderRanges) {
- for (let i = range.start; i < range.end; i++) {
- if (this.items[i].row) {
- this.removeItemFromDOM(i);
- }
- }
- }
- 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 anchorElementIndex === 'number') {
- // To compute a destination scroll top, we need to take into account the current smooth scrolling
- // animation, and then reuse it with a new target (to avoid prolonging the scroll)
- // See https://github.com/microsoft/vscode/issues/104144
- // See https://github.com/microsoft/vscode/pull/104284
- // See https://github.com/microsoft/vscode/issues/107704
- const deltaScrollTop = this.scrollable.getFutureScrollPosition().scrollTop - renderTop;
- const newScrollTop = this.elementTop(anchorElementIndex) - anchorElementTopDelta + deltaScrollTop;
- this.setScrollTop(newScrollTop, inSmoothScrolling);
- }
- this._onDidChangeContentHeight.fire(this.contentHeight);
- return;
- }
- }
- }
- probeDynamicHeight(index) {
- var _a, _b, _c;
- const item = this.items[index];
- if (!!this.virtualDelegate.getDynamicHeight) {
- const newSize = this.virtualDelegate.getDynamicHeight(item.element);
- if (newSize !== null) {
- const size = item.size;
- item.size = newSize;
- item.lastDynamicHeightWidth = this.renderWidth;
- return newSize - size;
- }
- }
- if (!item.hasDynamicHeight || item.lastDynamicHeightWidth === this.renderWidth) {
- return 0;
- }
- if (!!this.virtualDelegate.hasDynamicHeight && !this.virtualDelegate.hasDynamicHeight(item.element)) {
- return 0;
- }
- const size = item.size;
- if (!this.setRowHeight && item.row) {
- const newSize = item.row.domNode.offsetHeight;
- item.size = newSize;
- item.lastDynamicHeightWidth = this.renderWidth;
- return newSize - size;
- }
- const row = this.cache.alloc(item.templateId);
- row.domNode.style.height = '';
- this.rowsContainer.appendChild(row.domNode);
- const renderer = this.renderers.get(item.templateId);
- if (renderer) {
- renderer.renderElement(item.element, index, row.templateData, undefined);
- (_a = renderer.disposeElement) === null || _a === void 0 ? void 0 : _a.call(renderer, item.element, index, row.templateData, undefined);
- }
- item.size = row.domNode.offsetHeight;
- (_c = (_b = this.virtualDelegate).setDynamicHeight) === null || _c === void 0 ? void 0 : _c.call(_b, item.element, item.size);
- item.lastDynamicHeightWidth = this.renderWidth;
- this.rowsContainer.removeChild(row.domNode);
- this.cache.release(row);
- return item.size - size;
- }
- getNextToLastElement(ranges) {
- const lastRange = ranges[ranges.length - 1];
- if (!lastRange) {
- return null;
- }
- const nextToLastItem = this.items[lastRange.end];
- if (!nextToLastItem) {
- return null;
- }
- if (!nextToLastItem.row) {
- return null;
- }
- return nextToLastItem.row.domNode;
- }
- getElementDomId(index) {
- return `${this.domId}_${index}`;
- }
- // Dispose
- dispose() {
- var _a;
- if (this.items) {
- for (const item of this.items) {
- if (item.row) {
- const renderer = this.renderers.get(item.row.templateId);
- if (renderer) {
- (_a = renderer.disposeElement) === null || _a === void 0 ? void 0 : _a.call(renderer, item.element, -1, item.row.templateData, undefined);
- renderer.disposeTemplate(item.row.templateData);
- }
- }
- }
- this.items = [];
- }
- if (this.domNode && this.domNode.parentNode) {
- this.domNode.parentNode.removeChild(this.domNode);
- }
- dispose(this.disposables);
- }
- }
- ListView.InstanceCount = 0;
- __decorate([
- memoize
- ], ListView.prototype, "onMouseClick", null);
- __decorate([
- memoize
- ], ListView.prototype, "onMouseDblClick", null);
- __decorate([
- memoize
- ], ListView.prototype, "onMouseMiddleClick", null);
- __decorate([
- memoize
- ], ListView.prototype, "onMouseDown", null);
- __decorate([
- memoize
- ], ListView.prototype, "onMouseOver", null);
- __decorate([
- memoize
- ], ListView.prototype, "onContextMenu", null);
- __decorate([
- memoize
- ], ListView.prototype, "onTouchStart", null);
- __decorate([
- memoize
- ], ListView.prototype, "onTap", null);
|