UNPKG

monaco-editor

Version:
493 lines (492 loc) • 22.3 kB
/*--------------------------------------------------------------------------------------------- * 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 { getOrDefault } from '../../../common/objects.js'; import { dispose } from '../../../common/lifecycle.js'; import { Gesture, EventType as TouchEventType } from '../../touch.js'; import * as DOM from '../../dom.js'; import { mapEvent, filterEvent } from '../../../common/event.js'; import { domEvent } from '../../event.js'; import { ScrollableElement } from '../scrollbar/scrollableElement.js'; import { RangeMap, shift } from './rangeMap.js'; import { RowCache } from './rowCache.js'; import { isWindows } from '../../../common/platform.js'; import * as browser from '../../browser.js'; import { memoize } from '../../../common/decorators.js'; import { DragMouseEvent } from '../../mouseEvent.js'; import { Range } from '../../../common/range.js'; function canUseTranslate3d() { if (browser.isFirefox) { return false; } if (browser.getZoomLevel() !== 0) { return false; } return true; } var DefaultOptions = { useShadows: true, verticalScrollMode: 1 /* Auto */, setRowLineHeight: true }; var ListView = /** @class */ (function () { function ListView(container, virtualDelegate, renderers, options) { if (options === void 0) { options = DefaultOptions; } this.virtualDelegate = virtualDelegate; this.renderers = new Map(); this.didRequestScrollableElementUpdate = false; this.splicing = false; this.items = []; this.itemId = 0; this.rangeMap = new RangeMap(); for (var _i = 0, renderers_1 = renderers; _i < renderers_1.length; _i++) { var renderer = renderers_1[_i]; this.renderers.set(renderer.templateId, renderer); } this.cache = new RowCache(this.renderers); this.lastRenderTop = 0; this.lastRenderHeight = 0; this._domNode = document.createElement('div'); this._domNode.className = 'monaco-list'; this.rowsContainer = document.createElement('div'); this.rowsContainer.className = 'monaco-list-rows'; Gesture.addTarget(this.rowsContainer); this.scrollableElement = new ScrollableElement(this.rowsContainer, { alwaysConsumeMouseWheel: true, horizontal: 2 /* Hidden */, vertical: getOrDefault(options, function (o) { return o.verticalScrollMode; }, DefaultOptions.verticalScrollMode), useShadows: getOrDefault(options, function (o) { return o.useShadows; }, DefaultOptions.useShadows) }); this._domNode.appendChild(this.scrollableElement.getDomNode()); container.appendChild(this._domNode); this.disposables = [this.rangeMap, this.gesture, this.scrollableElement, this.cache]; this.scrollableElement.onScroll(this.onScroll, this, this.disposables); domEvent(this.rowsContainer, TouchEventType.Change)(this.onTouchChange, this, this.disposables); // Prevent the monaco-scrollable-element from scrolling // https://github.com/Microsoft/vscode/issues/44181 domEvent(this.scrollableElement.getDomNode(), 'scroll')(function (e) { return e.target.scrollTop = 0; }, null, this.disposables); var onDragOver = mapEvent(domEvent(this.rowsContainer, 'dragover'), function (e) { return new DragMouseEvent(e); }); onDragOver(this.onDragOver, this, this.disposables); this.setRowLineHeight = getOrDefault(options, function (o) { return o.setRowLineHeight; }, DefaultOptions.setRowLineHeight); this.layout(); } Object.defineProperty(ListView.prototype, "domNode", { get: function () { return this._domNode; }, enumerable: true, configurable: true }); ListView.prototype.splice = function (start, deleteCount, elements) { if (elements === void 0) { 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; } }; ListView.prototype._splice = function (start, deleteCount, elements) { var _this = this; if (elements === void 0) { elements = []; } var _a; var previousRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight); var deleteRange = { start: start, end: start + deleteCount }; var removeRange = Range.intersect(previousRenderRange, deleteRange); for (var i = removeRange.start; i < removeRange.end; i++) { this.removeItemFromDOM(i); } var previousRestRange = { start: start + deleteCount, end: this.items.length }; var previousRenderedRestRange = Range.intersect(previousRestRange, previousRenderRange); var previousUnrenderedRestRanges = Range.relativeComplement(previousRestRange, previousRenderRange); var inserted = elements.map(function (element) { return ({ id: String(_this.itemId++), element: element, size: _this.virtualDelegate.getHeight(element), templateId: _this.virtualDelegate.getTemplateId(element), row: null }); }); var 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); this.items = inserted; deleted = []; } else { this.rangeMap.splice(start, deleteCount, inserted); deleted = (_a = this.items).splice.apply(_a, [start, deleteCount].concat(inserted)); } var delta = elements.length - deleteCount; var renderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight); var renderedRestRange = shift(previousRenderedRestRange, delta); var updateRange = Range.intersect(renderRange, renderedRestRange); for (var i = updateRange.start; i < updateRange.end; i++) { this.updateItemInDOM(this.items[i], i); } var removeRanges = Range.relativeComplement(renderedRestRange, renderRange); for (var r = 0; r < removeRanges.length; r++) { var removeRange_1 = removeRanges[r]; for (var i = removeRange_1.start; i < removeRange_1.end; i++) { this.removeItemFromDOM(i); } } var unrenderedRestRanges = previousUnrenderedRestRanges.map(function (r) { return shift(r, delta); }); var elementsRange = { start: start, end: start + elements.length }; var insertRanges = [elementsRange].concat(unrenderedRestRanges).map(function (r) { return Range.intersect(renderRange, r); }); var beforeElement = this.getNextToLastElement(insertRanges); for (var r = 0; r < insertRanges.length; r++) { var insertRange = insertRanges[r]; for (var i = insertRange.start; i < insertRange.end; i++) { this.insertItemInDOM(i, beforeElement); } } this.scrollHeight = this.getContentHeight(); this.rowsContainer.style.height = this.scrollHeight + "px"; if (!this.didRequestScrollableElementUpdate) { DOM.scheduleAtNextAnimationFrame(function () { _this.scrollableElement.setScrollDimensions({ scrollHeight: _this.scrollHeight }); _this.didRequestScrollableElementUpdate = false; }); this.didRequestScrollableElementUpdate = true; } return deleted.map(function (i) { return i.element; }); }; Object.defineProperty(ListView.prototype, "length", { get: function () { return this.items.length; }, enumerable: true, configurable: true }); Object.defineProperty(ListView.prototype, "renderHeight", { get: function () { var scrollDimensions = this.scrollableElement.getScrollDimensions(); return scrollDimensions.height; }, enumerable: true, configurable: true }); ListView.prototype.element = function (index) { return this.items[index].element; }; ListView.prototype.domElement = function (index) { var row = this.items[index].row; return row && row.domNode; }; ListView.prototype.elementHeight = function (index) { return this.items[index].size; }; ListView.prototype.elementTop = function (index) { return this.rangeMap.positionAt(index); }; ListView.prototype.indexAt = function (position) { return this.rangeMap.indexAt(position); }; ListView.prototype.indexAfter = function (position) { return this.rangeMap.indexAfter(position); }; ListView.prototype.layout = function (height) { this.scrollableElement.setScrollDimensions({ height: height || DOM.getContentHeight(this._domNode) }); }; // Render ListView.prototype.render = function (renderTop, renderHeight) { var previousRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight); var renderRange = this.getRenderRange(renderTop, renderHeight); var rangesToInsert = Range.relativeComplement(renderRange, previousRenderRange); var rangesToRemove = Range.relativeComplement(previousRenderRange, renderRange); var beforeElement = this.getNextToLastElement(rangesToInsert); for (var _i = 0, rangesToInsert_1 = rangesToInsert; _i < rangesToInsert_1.length; _i++) { var range = rangesToInsert_1[_i]; for (var i = range.start; i < range.end; i++) { this.insertItemInDOM(i, beforeElement); } } for (var _a = 0, rangesToRemove_1 = rangesToRemove; _a < rangesToRemove_1.length; _a++) { var range = rangesToRemove_1[_a]; for (var i = range.start; i < range.end; i++) { this.removeItemFromDOM(i); } } if (canUseTranslate3d() && !isWindows /* Windows: translate3d breaks subpixel-antialias (ClearType) unless a background is defined */) { var transform = "translate3d(0px, -" + renderTop + "px, 0px)"; this.rowsContainer.style.transform = transform; this.rowsContainer.style.webkitTransform = transform; } else { this.rowsContainer.style.top = "-" + renderTop + "px"; } this.lastRenderTop = renderTop; this.lastRenderHeight = renderHeight; }; // DOM operations ListView.prototype.insertItemInDOM = function (index, beforeElement) { var item = this.items[index]; if (!item.row) { item.row = this.cache.alloc(item.templateId); } if (!item.row.domNode.parentElement) { if (beforeElement) { this.rowsContainer.insertBefore(item.row.domNode, beforeElement); } else { this.rowsContainer.appendChild(item.row.domNode); } } item.row.domNode.style.height = item.size + "px"; if (this.setRowLineHeight) { item.row.domNode.style.lineHeight = item.size + "px"; } this.updateItemInDOM(item, index); var renderer = this.renderers.get(item.templateId); renderer.renderElement(item.element, index, item.row.templateData); }; ListView.prototype.updateItemInDOM = function (item, index) { item.row.domNode.style.top = this.elementTop(index) + "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('aria-setsize', "" + this.length); item.row.domNode.setAttribute('aria-posinset', "" + (index + 1)); }; ListView.prototype.removeItemFromDOM = function (index) { var item = this.items[index]; var renderer = this.renderers.get(item.templateId); if (renderer.disposeElement) { renderer.disposeElement(item.element, index, item.row.templateData); } this.cache.release(item.row); item.row = null; }; ListView.prototype.getContentHeight = function () { return this.rangeMap.size; }; ListView.prototype.getScrollTop = function () { var scrollPosition = this.scrollableElement.getScrollPosition(); return scrollPosition.scrollTop; }; ListView.prototype.setScrollTop = function (scrollTop) { this.scrollableElement.setScrollPosition({ scrollTop: scrollTop }); }; Object.defineProperty(ListView.prototype, "scrollTop", { get: function () { return this.getScrollTop(); }, set: function (scrollTop) { this.setScrollTop(scrollTop); }, enumerable: true, configurable: true }); Object.defineProperty(ListView.prototype, "onMouseClick", { // Events get: function () { var _this = this; return filterEvent(mapEvent(domEvent(this.domNode, 'click'), function (e) { return _this.toMouseEvent(e); }), function (e) { return e.index >= 0; }); }, enumerable: true, configurable: true }); Object.defineProperty(ListView.prototype, "onMouseDblClick", { get: function () { var _this = this; return filterEvent(mapEvent(domEvent(this.domNode, 'dblclick'), function (e) { return _this.toMouseEvent(e); }), function (e) { return e.index >= 0; }); }, enumerable: true, configurable: true }); Object.defineProperty(ListView.prototype, "onMouseDown", { get: function () { var _this = this; return filterEvent(mapEvent(domEvent(this.domNode, 'mousedown'), function (e) { return _this.toMouseEvent(e); }), function (e) { return e.index >= 0; }); }, enumerable: true, configurable: true }); Object.defineProperty(ListView.prototype, "onContextMenu", { get: function () { var _this = this; return filterEvent(mapEvent(domEvent(this.domNode, 'contextmenu'), function (e) { return _this.toMouseEvent(e); }), function (e) { return e.index >= 0; }); }, enumerable: true, configurable: true }); Object.defineProperty(ListView.prototype, "onTouchStart", { get: function () { var _this = this; return filterEvent(mapEvent(domEvent(this.domNode, 'touchstart'), function (e) { return _this.toTouchEvent(e); }), function (e) { return e.index >= 0; }); }, enumerable: true, configurable: true }); Object.defineProperty(ListView.prototype, "onTap", { get: function () { var _this = this; return filterEvent(mapEvent(domEvent(this.rowsContainer, TouchEventType.Tap), function (e) { return _this.toGestureEvent(e); }), function (e) { return e.index >= 0; }); }, enumerable: true, configurable: true }); ListView.prototype.toMouseEvent = function (browserEvent) { var index = this.getItemIndexFromEventTarget(browserEvent.target); var item = index < 0 ? undefined : this.items[index]; var element = item && item.element; return { browserEvent: browserEvent, index: index, element: element }; }; ListView.prototype.toTouchEvent = function (browserEvent) { var index = this.getItemIndexFromEventTarget(browserEvent.target); var item = index < 0 ? undefined : this.items[index]; var element = item && item.element; return { browserEvent: browserEvent, index: index, element: element }; }; ListView.prototype.toGestureEvent = function (browserEvent) { var index = this.getItemIndexFromEventTarget(browserEvent.initialTarget); var item = index < 0 ? undefined : this.items[index]; var element = item && item.element; return { browserEvent: browserEvent, index: index, element: element }; }; ListView.prototype.onScroll = function (e) { try { this.render(e.scrollTop, e.height); } catch (err) { console.log('Got bad scroll event:', e); throw err; } }; ListView.prototype.onTouchChange = function (event) { event.preventDefault(); event.stopPropagation(); this.scrollTop -= event.translationY; }; ListView.prototype.onDragOver = function (event) { this.setupDragAndDropScrollInterval(); this.dragAndDropMouseY = event.posy; }; ListView.prototype.setupDragAndDropScrollInterval = function () { var _this = this; var viewTop = DOM.getTopLeftOffset(this._domNode).top; if (!this.dragAndDropScrollInterval) { this.dragAndDropScrollInterval = window.setInterval(function () { if (_this.dragAndDropMouseY === undefined) { return; } var diff = _this.dragAndDropMouseY - viewTop; var scrollDiff = 0; var upperLimit = _this.renderHeight - 35; if (diff < 35) { scrollDiff = Math.max(-14, 0.2 * (diff - 35)); } else if (diff > upperLimit) { scrollDiff = Math.min(14, 0.2 * (diff - upperLimit)); } _this.scrollTop += scrollDiff; }, 10); this.cancelDragAndDropScrollTimeout(); this.dragAndDropScrollTimeout = window.setTimeout(function () { _this.cancelDragAndDropScrollInterval(); _this.dragAndDropScrollTimeout = null; }, 1000); } }; ListView.prototype.cancelDragAndDropScrollInterval = function () { if (this.dragAndDropScrollInterval) { window.clearInterval(this.dragAndDropScrollInterval); this.dragAndDropScrollInterval = null; } this.cancelDragAndDropScrollTimeout(); }; ListView.prototype.cancelDragAndDropScrollTimeout = function () { if (this.dragAndDropScrollTimeout) { window.clearTimeout(this.dragAndDropScrollTimeout); this.dragAndDropScrollTimeout = null; } }; // Util ListView.prototype.getItemIndexFromEventTarget = function (target) { while (target instanceof HTMLElement && target !== this.rowsContainer) { var element = target; var rawIndex = element.getAttribute('data-index'); if (rawIndex) { var index = Number(rawIndex); if (!isNaN(index)) { return index; } } target = element.parentElement; } return -1; }; ListView.prototype.getRenderRange = function (renderTop, renderHeight) { return { start: this.rangeMap.indexAt(renderTop), end: this.rangeMap.indexAfter(renderTop + renderHeight - 1) }; }; ListView.prototype.getNextToLastElement = function (ranges) { var lastRange = ranges[ranges.length - 1]; if (!lastRange) { return null; } var nextToLastItem = this.items[lastRange.end]; if (!nextToLastItem) { return null; } if (!nextToLastItem.row) { return null; } return nextToLastItem.row.domNode; }; // Dispose ListView.prototype.dispose = function () { if (this.items) { for (var _i = 0, _a = this.items; _i < _a.length; _i++) { var item = _a[_i]; if (item.row) { var renderer = this.renderers.get(item.row.templateId); renderer.disposeTemplate(item.row.templateData); item.row = null; } } this.items = null; } if (this._domNode && this._domNode.parentElement) { this._domNode.parentNode.removeChild(this._domNode); this._domNode = null; } this.disposables = dispose(this.disposables); }; __decorate([ memoize ], ListView.prototype, "onMouseClick", null); __decorate([ memoize ], ListView.prototype, "onMouseDblClick", null); __decorate([ memoize ], ListView.prototype, "onMouseDown", null); __decorate([ memoize ], ListView.prototype, "onContextMenu", null); __decorate([ memoize ], ListView.prototype, "onTouchStart", null); __decorate([ memoize ], ListView.prototype, "onTap", null); return ListView; }()); export { ListView };