/*--------------------------------------------------------------------------------------------- * 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);