UNPKG

@deephaven/golden-layout

Version:

A multi-screen javascript Layout manager

491 lines (465 loc) • 20.8 kB
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } import $ from 'jquery'; import AbstractContentItem from "./AbstractContentItem.js"; import { animFrame } from "../utils/index.js"; import { Splitter } from "../controls/index.js"; export default class RowOrColumn extends AbstractContentItem { constructor(isColumn, layoutManager, config, parent) { super(layoutManager, config, parent, $('<div class="lm_item lm_' + (isColumn ? 'column' : 'row') + '"></div>')); _defineProperty(this, "isRow", void 0); _defineProperty(this, "isColumn", void 0); _defineProperty(this, "childElementContainer", void 0); _defineProperty(this, "parent", void 0); _defineProperty(this, "_splitter", []); _defineProperty(this, "_splitterSize", void 0); _defineProperty(this, "_splitterGrabSize", void 0); _defineProperty(this, "_isColumn", void 0); _defineProperty(this, "_dimension", void 0); _defineProperty(this, "_splitterPosition", null); _defineProperty(this, "_splitterMinPosition", null); _defineProperty(this, "_splitterMaxPosition", null); this.parent = parent; this.isRow = !isColumn; this.isColumn = isColumn; this.childElementContainer = this.element; this._splitterSize = layoutManager.config.dimensions.borderWidth; this._splitterGrabSize = layoutManager.config.dimensions.borderGrabWidth; this._isColumn = isColumn; this._dimension = isColumn ? 'height' : 'width'; this._splitterPosition = null; this._splitterMinPosition = null; this._splitterMaxPosition = null; } /** * Add a new contentItem to the Row or Column * * @param contentItem * @param index The position of the new item within the Row or Column. * If no index is provided the item will be added to the end * @param _$suspendResize If true the items won't be resized. This will leave the item in * an inconsistent state and is only intended to be used if multiple * children need to be added in one go and resize is called afterwards */ addChild(contentItem, index, _$suspendResize) { var newItemSize, itemSize, i, splitterElement; contentItem = this.layoutManager._$normalizeContentItem(contentItem, this); if (index === undefined) { index = this.contentItems.length; } if (this.contentItems.length > 0) { splitterElement = this._createSplitter(Math.max(0, index - 1)).element; if (index > 0) { this.contentItems[index - 1].element.after(splitterElement); splitterElement.after(contentItem.element); } else { this.contentItems[0].element.before(splitterElement); splitterElement.before(contentItem.element); } } else { this.childElementContainer.append(contentItem.element); } AbstractContentItem.prototype.addChild.call(this, contentItem, index); newItemSize = 1 / this.contentItems.length * 100; if (_$suspendResize === true) { this.emitBubblingEvent('stateChanged'); return; } for (i = 0; i < this.contentItems.length; i++) { if (this.contentItems[i] === contentItem) { contentItem.config[this._dimension] = newItemSize; } else { var _this$contentItems$i$; itemSize = ((_this$contentItems$i$ = this.contentItems[i].config[this._dimension]) !== null && _this$contentItems$i$ !== void 0 ? _this$contentItems$i$ : 0) * (100 - newItemSize) / 100; this.contentItems[i].config[this._dimension] = itemSize; } } this.callDownwards('setSize'); this.emitBubblingEvent('stateChanged'); } /** * Removes a child of this element * * @param contentItem * @param keepChild If true the child will be removed, but not destroyed */ removeChild(contentItem, keepChild) { var _contentItem$config$t; var removedItemSize = (_contentItem$config$t = contentItem.config[this._dimension]) !== null && _contentItem$config$t !== void 0 ? _contentItem$config$t : 0, index = this.contentItems.indexOf(contentItem), splitterIndex = Math.max(index - 1, 0), i, childItem; if (index === -1) { throw new Error("Can't remove child. ContentItem is not child of this Row or Column"); } /** * Remove the splitter before the item or after if the item happens * to be the first in the row/column */ if (this._splitter[splitterIndex]) { this._splitter[splitterIndex]._$destroy(); this._splitter.splice(splitterIndex, 1); } /** * Allocate the space that the removed item occupied to the remaining items */ for (i = 0; i < this.contentItems.length; i++) { if (this.contentItems[i] !== contentItem) { var _this$contentItems$i$2; this.contentItems[i].config[this._dimension] = ((_this$contentItems$i$2 = this.contentItems[i].config[this._dimension]) !== null && _this$contentItems$i$2 !== void 0 ? _this$contentItems$i$2 : 0) + removedItemSize / (this.contentItems.length - 1); } } AbstractContentItem.prototype.removeChild.call(this, contentItem, keepChild); if (this.contentItems.length === 1 && this.config.isClosable === true) { var _this$parent; childItem = this.contentItems[0]; this.contentItems = []; (_this$parent = this.parent) === null || _this$parent === void 0 ? void 0 : _this$parent.replaceChild(this, childItem, true); } else { this.callDownwards('setSize'); this.emitBubblingEvent('stateChanged'); } } /** * Replaces a child of this Row or Column with another contentItem * * @param oldChild The old child to replace * @param newChild The new child to take the old child's place * @param destroyOldChild If the old child should be destroyed or not */ replaceChild(oldChild, newChild) { var destroyOldChild = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; var size = oldChild.config[this._dimension]; newChild.config[this._dimension] = size; super.replaceChild(oldChild, newChild, destroyOldChild); this.callDownwards('setSize'); this.emitBubblingEvent('stateChanged'); } /** * Called whenever the dimensions of this item or one of its parents change */ setSize() { if (this.contentItems.length > 0) { this._calculateRelativeSizes(); this._setAbsoluteSizes(); } this.emitBubblingEvent('stateChanged'); this.emit('resize'); } /** * Invoked recursively by the layout manager. AbstractContentItem.init appends * the contentItem's DOM elements to the container, RowOrColumn init adds splitters * in between them */ _$init() { if (this.isInitialised === true) return; var i; AbstractContentItem.prototype._$init.call(this); for (i = 0; i < this.contentItems.length - 1; i++) { this.contentItems[i].element.after(this._createSplitter(i).element); } } /** * Turns the relative sizes calculated by _calculateRelativeSizes into * absolute pixel values and applies them to the children's DOM elements * * Assigns additional pixels to counteract Math.floor */ _setAbsoluteSizes() { var i, sizeData = this._calculateAbsoluteSizes(); for (i = 0; i < this.contentItems.length; i++) { if (sizeData.additionalPixel - i > 0) { sizeData.itemSizes[i]++; } if (this._isColumn) { this.contentItems[i].element.width(sizeData.totalWidth); this.contentItems[i].element.height(sizeData.itemSizes[i]); } else { this.contentItems[i].element.width(sizeData.itemSizes[i]); this.contentItems[i].element.height(sizeData.totalHeight); } } } /** * Calculates the absolute sizes of all of the children of this Item. * @returns {object} - Set with absolute sizes and additional pixels. */ _calculateAbsoluteSizes() { var _this$element$width, _this$element$height; var totalSplitterSize = (this.contentItems.length - 1) * this._splitterSize; var totalWidth = (_this$element$width = this.element.width()) !== null && _this$element$width !== void 0 ? _this$element$width : 0; var totalHeight = (_this$element$height = this.element.height()) !== null && _this$element$height !== void 0 ? _this$element$height : 0; var totalAssigned = 0; var itemSizes = []; if (this._isColumn) { totalHeight -= totalSplitterSize; } else { totalWidth -= totalSplitterSize; } for (var i = 0; i < this.contentItems.length; i++) { var _this$contentItems$i$3, _this$contentItems$i$4; var itemSize = this._isColumn ? Math.floor(totalHeight * (((_this$contentItems$i$3 = this.contentItems[i].config.height) !== null && _this$contentItems$i$3 !== void 0 ? _this$contentItems$i$3 : 0) / 100)) : Math.floor(totalWidth * (((_this$contentItems$i$4 = this.contentItems[i].config.width) !== null && _this$contentItems$i$4 !== void 0 ? _this$contentItems$i$4 : 0) / 100)); totalAssigned += itemSize; itemSizes.push(itemSize); } var additionalPixel = Math.floor((this._isColumn ? totalHeight : totalWidth) - totalAssigned); return { itemSizes, additionalPixel, totalWidth, totalHeight }; } /** * Calculates the relative sizes of all children of this Item. The logic * is as follows: * * - Add up the total size of all items that have a configured size * * - If the total == 100 (check for floating point errors) * Excellent, job done * * - If the total is > 100, * set the size of items without set dimensions to 1/3 and add this to the total * set the size off all items so that the total is hundred relative to their original size * * - If the total is < 100 * If there are items without set dimensions, distribute the remainder to 100 evenly between them * If there are no items without set dimensions, increase all items sizes relative to * their original size so that they add up to 100 */ _calculateRelativeSizes() { var total = 0; var itemsWithoutSetDimension = []; var dimension = this._isColumn ? 'height' : 'width'; for (var i = 0; i < this.contentItems.length; i++) { var size = this.contentItems[i].config[dimension]; if (size != null) { total += size; } else { itemsWithoutSetDimension.push(this.contentItems[i]); } } /** * Everything adds up to hundred, all good :-) */ if (Math.round(total) === 100) { this._respectMinItemWidth(); return; } /** * Allocate the remaining size to the items without a set dimension */ if (Math.round(total) < 100 && itemsWithoutSetDimension.length > 0) { for (var _i = 0; _i < itemsWithoutSetDimension.length; _i++) { itemsWithoutSetDimension[_i].config[dimension] = (100 - total) / itemsWithoutSetDimension.length; } this._respectMinItemWidth(); return; } /** * If the total is > 100, but there are also items without a set dimension left, assing 50 * as their dimension and add it to the total * * This will be reset in the next step */ if (Math.round(total) > 100) { for (var _i2 = 0; _i2 < itemsWithoutSetDimension.length; _i2++) { itemsWithoutSetDimension[_i2].config[dimension] = 50; total += 50; } } /** * Set every items size relative to 100 relative to its size to total */ for (var _i3 = 0; _i3 < this.contentItems.length; _i3++) { var _this$contentItems$_i; this.contentItems[_i3].config[dimension] = ((_this$contentItems$_i = this.contentItems[_i3].config[dimension]) !== null && _this$contentItems$_i !== void 0 ? _this$contentItems$_i : 0) / total * 100; } this._respectMinItemWidth(); } /** * Adjusts the column widths to respect the dimensions minItemWidth if set. */ _respectMinItemWidth() { var _this$layoutManager$c; var minItemWidth = this.layoutManager.config.dimensions ? (_this$layoutManager$c = this.layoutManager.config.dimensions.minItemWidth) !== null && _this$layoutManager$c !== void 0 ? _this$layoutManager$c : 0 : 0; var entriesOverMin = []; var totalOverMin = 0; var totalUnderMin = 0; var remainingWidth = 0; var allEntries = []; var entry; if (this._isColumn || !minItemWidth || this.contentItems.length <= 1) { return; } var sizeData = this._calculateAbsoluteSizes(); /** * Figure out how much we are under the min item size total and how much room we have to use. */ for (var i = 0; i < this.contentItems.length; i++) { var contentItem = this.contentItems[i]; var itemSize = sizeData.itemSizes[i]; if (itemSize < minItemWidth) { totalUnderMin += minItemWidth - itemSize; entry = { width: minItemWidth }; } else { totalOverMin += itemSize - minItemWidth; entry = { width: itemSize }; entriesOverMin.push(entry); } allEntries.push(entry); } /** * If there is nothing under min, or there is not enough over to make up the difference, do nothing. */ if (totalUnderMin === 0 || totalUnderMin > totalOverMin) { return; } /** * Evenly reduce all columns that are over the min item width to make up the difference. */ var reducePercent = totalUnderMin / totalOverMin; remainingWidth = totalUnderMin; for (var _i4 = 0; _i4 < entriesOverMin.length; _i4++) { entry = entriesOverMin[_i4]; var reducedWidth = Math.round((entry.width - minItemWidth) * reducePercent); remainingWidth -= reducedWidth; entry.width -= reducedWidth; } /** * Take anything remaining from the last item. */ if (remainingWidth !== 0) { allEntries[allEntries.length - 1].width -= remainingWidth; } /** * Set every items size relative to 100 relative to its size to total */ for (var _i5 = 0; _i5 < this.contentItems.length; _i5++) { this.contentItems[_i5].config.width = allEntries[_i5].width / sizeData.totalWidth * 100; } } /** * Instantiates a new lm.controls.Splitter, binds events to it and adds * it to the array of splitters at the position specified as the index argument * * What it doesn't do though is append the splitter to the DOM * * @param index The position of the splitter * @returns The created splitter */ _createSplitter(index) { var splitter; splitter = new Splitter(this._isColumn, this._splitterSize, this._splitterGrabSize); splitter.on('drag', this._onSplitterDrag.bind(this, splitter), this); splitter.on('dragStop', this._onSplitterDragStop.bind(this, splitter), this); splitter.on('dragStart', this._onSplitterDragStart.bind(this, splitter), this); this._splitter.splice(index, 0, splitter); return splitter; } /** * Locates the instance of lm.controls.Splitter in the array of * registered splitters and returns a map containing the contentItem * before and after the splitters, both of which are affected if the * splitter is moved * * @param splitter * * @returns A map of contentItems that the splitter affects */ _getItemsForSplitter(splitter) { var index = this._splitter.indexOf(splitter); if (index < 0) { throw new Error('Splitter not found in RowOrColumn'); } return { before: this.contentItems[index], after: this.contentItems[index + 1] }; } /** * Gets the minimum dimensions for the given item configuration array * @param item * @private */ _getMinimumDimensions(arr) { var minWidth = 0, minHeight = 0; for (var i = 0; i < arr.length; ++i) { var _arr$i$minWidth, _arr$i$minHeight; minWidth = Math.max((_arr$i$minWidth = arr[i].minWidth) !== null && _arr$i$minWidth !== void 0 ? _arr$i$minWidth : 0, minWidth); minHeight = Math.max((_arr$i$minHeight = arr[i].minHeight) !== null && _arr$i$minHeight !== void 0 ? _arr$i$minHeight : 0, minHeight); } return { horizontal: minWidth, vertical: minHeight }; } /** * Invoked when a splitter's dragListener fires dragStart. Calculates the splitters * movement area once (so that it doesn't need calculating on every mousemove event) * * @param splitter */ _onSplitterDragStart(splitter) { var _items$before$config$, _items$after$config$c, _items$before$element, _items$after$element$; var items = this._getItemsForSplitter(splitter); var minSize = this.layoutManager.config.dimensions[this._isColumn ? 'minItemHeight' : 'minItemWidth']; var beforeMinDim = this._getMinimumDimensions((_items$before$config$ = items.before.config.content) !== null && _items$before$config$ !== void 0 ? _items$before$config$ : []); var beforeMinSize = this._isColumn ? beforeMinDim.vertical : beforeMinDim.horizontal; var afterMinDim = this._getMinimumDimensions((_items$after$config$c = items.after.config.content) !== null && _items$after$config$c !== void 0 ? _items$after$config$c : []); var afterMinSize = this._isColumn ? afterMinDim.vertical : afterMinDim.horizontal; this._splitterPosition = 0; this._splitterMinPosition = -1 * (((_items$before$element = items.before.element[this._dimension]()) !== null && _items$before$element !== void 0 ? _items$before$element : 0) - (beforeMinSize || minSize)); this._splitterMaxPosition = ((_items$after$element$ = items.after.element[this._dimension]()) !== null && _items$after$element$ !== void 0 ? _items$after$element$ : 0) - (afterMinSize || minSize); } /** * Invoked when a splitter's DragListener fires drag. Updates the splitters DOM position, * but not the sizes of the elements the splitter controls in order to minimize resize events * * @param splitter * @param offsetX Relative pixel values to the splitters original position. Can be negative * @param offsetY Relative pixel values to the splitters original position. Can be negative */ _onSplitterDrag(splitter, offsetX, offsetY) { var offset = this._isColumn ? offsetY : offsetX; if (this._splitterMaxPosition == null || this._splitterMinPosition == null) { return; } if (offset > this._splitterMinPosition && offset < this._splitterMaxPosition) { this._splitterPosition = offset; splitter.element.css(this._isColumn ? 'top' : 'left', offset); } } /** * Invoked when a splitter's DragListener fires dragStop. Resets the splitters DOM position, * and applies the new sizes to the elements before and after the splitter and their children * on the next animation frame * * @param {lm.controls.Splitter} splitter */ _onSplitterDragStop(splitter) { var _items$before$element2, _items$after$element$2, _this$_splitterPositi, _items$before$config$2, _items$after$config$t; var items = this._getItemsForSplitter(splitter); var sizeBefore = (_items$before$element2 = items.before.element[this._dimension]()) !== null && _items$before$element2 !== void 0 ? _items$before$element2 : 0; var sizeAfter = (_items$after$element$2 = items.after.element[this._dimension]()) !== null && _items$after$element$2 !== void 0 ? _items$after$element$2 : 0; var splitterPositionInRange = (((_this$_splitterPositi = this._splitterPosition) !== null && _this$_splitterPositi !== void 0 ? _this$_splitterPositi : 0) + sizeBefore) / (sizeBefore + sizeAfter); var totalRelativeSize = ((_items$before$config$2 = items.before.config[this._dimension]) !== null && _items$before$config$2 !== void 0 ? _items$before$config$2 : 0) + ((_items$after$config$t = items.after.config[this._dimension]) !== null && _items$after$config$t !== void 0 ? _items$after$config$t : 0); items.before.config[this._dimension] = splitterPositionInRange * totalRelativeSize; items.after.config[this._dimension] = (1 - splitterPositionInRange) * totalRelativeSize; splitter.element.css({ top: 0, left: 0 }); animFrame(this.callDownwards.bind(this, 'setSize', undefined, undefined, undefined)); } } //# sourceMappingURL=RowOrColumn.js.map