"use strict"; require("core-js/modules/es.symbol"); require("core-js/modules/es.symbol.description"); require("core-js/modules/es.symbol.iterator"); require("core-js/modules/es.array.index-of"); require("core-js/modules/es.array.iterator"); require("core-js/modules/es.array.reverse"); require("core-js/modules/es.array.slice"); require("core-js/modules/es.object.freeze"); require("core-js/modules/es.object.get-own-property-descriptor"); require("core-js/modules/es.object.get-prototype-of"); require("core-js/modules/es.object.set-prototype-of"); require("core-js/modules/es.object.to-string"); require("core-js/modules/es.reflect.get"); require("core-js/modules/es.string.iterator"); require("core-js/modules/web.dom-collections.iterator"); exports.__esModule = true; exports.default = void 0; var _element = require("../../helpers/dom/element"); var _number = require("../../helpers/number"); var _array = require("../../helpers/array"); var _object = require("../../helpers/object"); var _templateLiteralTag = require("../../helpers/templateLiteralTag"); var _console = require("../../helpers/console"); var _plugins = require("../../plugins"); var _base = _interopRequireDefault(require("../_base")); var _src = require("../../3rdparty/walkontable/src"); var _ghostTable = _interopRequireDefault(require("./utils/ghostTable")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _templateObject2() { var data = _taggedTemplateLiteral(["Your Nested Headers plugin setup contains overlapping headers. This kind of configuration\n is currently not supported and might result in glitches."]); _templateObject2 = function _templateObject2() { return data; }; return data; } function _templateObject() { var data = _taggedTemplateLiteral(["You have declared a Nested Header overlapping the Fixed Columns section - it may lead to visual\n glitches. To prevent that kind of problems, split the nested headers between the fixed and non-fixed columns."]); _templateObject = function _templateObject() { return data; }; return data; } function _taggedTemplateLiteral(strings, raw) { if (!raw) { raw = strings.slice(0); } return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _get(target, property, receiver) { if (typeof Reflect !== "undefined" && Reflect.get) { _get = Reflect.get; } else { _get = function _get(target, property, receiver) { var base = _superPropBase(target, property); if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property); if (desc.get) { return desc.get.call(receiver); } return desc.value; }; } return _get(target, property, receiver || target); } function _superPropBase(object, property) { while (!Object.prototype.hasOwnProperty.call(object, property)) { object = _getPrototypeOf(object); if (object === null) break; } return object; } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } /** * @plugin NestedHeaders * @description * The plugin allows to create a nested header structure, using the HTML's colspan attribute. * * To make any header wider (covering multiple table columns), it's corresponding configuration array element should be * provided as an object with `label` and `colspan` properties. The `label` property defines the header's label, * while the `colspan` property defines a number of columns that the header should cover. * * __Note__ that the plugin supports a *nested* structure, which means, any header cannot be wider than it's "parent". In * other words, headers cannot overlap each other. * @example * * ```js * const container = document.getElementById('example'); * const hot = new Handsontable(container, { * date: getData(), * nestedHeaders: [ * ['A', {label: 'B', colspan: 8}, 'C'], * ['D', {label: 'E', colspan: 4}, {label: 'F', colspan: 4}, 'G'], * ['H', {label: 'I', colspan: 2}, {label: 'J', colspan: 2}, {label: 'K', colspan: 2}, {label: 'L', colspan: 2}, 'M'], * ['N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W'] * ], * ``` */ var NestedHeaders = /*#__PURE__*/ function (_BasePlugin) { _inherits(NestedHeaders, _BasePlugin); function NestedHeaders(hotInstance) { var _this2; _classCallCheck(this, NestedHeaders); _this2 = _possibleConstructorReturn(this, _getPrototypeOf(NestedHeaders).call(this, hotInstance)); /** * Nasted headers cached settings. * * @private * @type {Object} */ _this2.settings = []; /** * Cached number of column header levels. * * @private * @type {Number} */ _this2.columnHeaderLevelCount = 0; /** * Array of nested headers' colspans. * * @private * @type {Array} */ _this2.colspanArray = []; /** * Custom helper for getting widths of the nested headers. * @TODO This should be changed after refactor handsontable/utils/ghostTable. * * @private * @type {GhostTable} */ _this2.ghostTable = new _ghostTable.default(_assertThisInitialized(_this2)); return _this2; } /** * Check if plugin is enabled * * @returns {Boolean} */ _createClass(NestedHeaders, [{ key: "isEnabled", value: function isEnabled() { return !!this.hot.getSettings().nestedHeaders; } /** * Enables the plugin functionality for this Handsontable instance. */ }, { key: "enablePlugin", value: function enablePlugin() { var _this3 = this; if (this.enabled) { return; } this.settings = this.hot.getSettings().nestedHeaders; this.addHook('afterGetColumnHeaderRenderers', function (array) { return _this3.onAfterGetColumnHeaderRenderers(array); }); this.addHook('afterInit', function () { return _this3.onAfterInit(); }); this.addHook('afterOnCellMouseDown', function (event, coords) { return _this3.onAfterOnCellMouseDown(event, coords); }); this.addHook('beforeOnCellMouseOver', function (event, coords, TD, blockCalculations) { return _this3.onBeforeOnCellMouseOver(event, coords, TD, blockCalculations); }); this.addHook('afterViewportColumnCalculatorOverride', function (calc) { return _this3.onAfterViewportColumnCalculatorOverride(calc); }); this.addHook('modifyColWidth', function (width, column) { return _this3.onModifyColWidth(width, column); }); this.setupColspanArray(); this.checkForFixedColumnsCollision(); this.columnHeaderLevelCount = this.hot.view ? this.hot.view.wt.getSetting('columnHeaders').length : 0; _get(_getPrototypeOf(NestedHeaders.prototype), "enablePlugin", this).call(this); } /** * Disables the plugin functionality for this Handsontable instance. */ }, { key: "disablePlugin", value: function disablePlugin() { this.clearColspans(); this.settings = []; this.columnHeaderLevelCount = 0; this.colspanArray = []; this.ghostTable.clear(); _get(_getPrototypeOf(NestedHeaders.prototype), "disablePlugin", this).call(this); } /** * Updates the plugin state. This method is executed when {@link Core#updateSettings} is invoked. */ }, { key: "updatePlugin", value: function updatePlugin() { this.disablePlugin(); this.enablePlugin(); _get(_getPrototypeOf(NestedHeaders.prototype), "updatePlugin", this).call(this); this.ghostTable.buildWidthsMapper(); } /** * Clear the colspans remaining after plugin usage. * * @private */ }, { key: "clearColspans", value: function clearColspans() { if (!this.hot.view) { return; } var headerLevels = this.hot.view.wt.getSetting('columnHeaders').length; var mainHeaders = this.hot.view.wt.wtTable.THEAD; var topHeaders = this.hot.view.wt.wtOverlays.topOverlay.clone.wtTable.THEAD; var topLeftCornerHeaders = this.hot.view.wt.wtOverlays.topLeftCornerOverlay ? this.hot.view.wt.wtOverlays.topLeftCornerOverlay.clone.wtTable.THEAD : null; for (var i = 0; i < headerLevels; i++) { var masterLevel = mainHeaders.childNodes[i]; if (!masterLevel) { break; } var topLevel = topHeaders.childNodes[i]; var topLeftCornerLevel = topLeftCornerHeaders ? topLeftCornerHeaders.childNodes[i] : null; for (var j = 0, masterNodes = masterLevel.childNodes.length; j < masterNodes; j++) { masterLevel.childNodes[j].removeAttribute('colspan'); if (topLevel && topLevel.childNodes[j]) { topLevel.childNodes[j].removeAttribute('colspan'); } if (topLeftCornerHeaders && topLeftCornerLevel && topLeftCornerLevel.childNodes[j]) { topLeftCornerLevel.childNodes[j].removeAttribute('colspan'); } } } } /** * Check if the nested headers overlap the fixed columns overlay, if so - display a warning. * * @private */ }, { key: "checkForFixedColumnsCollision", value: function checkForFixedColumnsCollision() { var _this4 = this; var fixedColumnsLeft = this.hot.getSettings().fixedColumnsLeft; (0, _array.arrayEach)(this.colspanArray, function (value, i) { if (_this4.getNestedParent(i, fixedColumnsLeft) !== fixedColumnsLeft) { (0, _console.warn)((0, _templateLiteralTag.toSingleLine)(_templateObject())); } }); } /** * Check if the configuration contains overlapping headers. * * @private */ }, { key: "checkForOverlappingHeaders", value: function checkForOverlappingHeaders() { var _this5 = this; (0, _array.arrayEach)(this.colspanArray, function (level, i) { (0, _array.arrayEach)(_this5.colspanArray[i], function (header, j) { if (header.colspan > 1) { var row = _this5.levelToRowCoords(i); var childHeaders = _this5.getChildHeaders(row, j); if (childHeaders.length > 0) { var childColspanSum = 0; (0, _array.arrayEach)(childHeaders, function (col) { childColspanSum += _this5.getColspan(row + 1, col); }); if (childColspanSum > header.colspan) { (0, _console.warn)((0, _templateLiteralTag.toSingleLine)(_templateObject2())); } return false; } } }); }); } /** * Create an internal array containing information of the headers with a colspan attribute. * * @private */ }, { key: "setupColspanArray", value: function setupColspanArray() { var _this6 = this; function checkIfExists(array, index) { if (!array[index]) { array[index] = []; } } (0, _object.objectEach)(this.settings, function (levelValues, level) { (0, _object.objectEach)(levelValues, function (val, col, levelValue) { checkIfExists(_this6.colspanArray, level); if (levelValue[col].colspan === void 0) { _this6.colspanArray[level].push({ label: levelValue[col] || '', colspan: 1, hidden: false }); } else { var colspan = levelValue[col].colspan || 1; _this6.colspanArray[level].push({ label: levelValue[col].label || '', colspan: colspan, hidden: false }); _this6.fillColspanArrayWithDummies(colspan, level); } }); }); } /** * Fill the "colspan array" with default data for the dummy hidden headers. * * @private * @param {Number} colspan The colspan value. * @param {Number} level Header level. */ }, { key: "fillColspanArrayWithDummies", value: function fillColspanArrayWithDummies(colspan, level) { var _this7 = this; (0, _number.rangeEach)(0, colspan - 2, function () { _this7.colspanArray[level].push({ label: '', colspan: 1, hidden: true }); }); } /** * Generates the appropriate header renderer for a header row. * * @private * @param {Number} headerRow The header row. * @returns {Function} * * @fires Hooks#afterGetColHeader */ }, { key: "headerRendererFactory", value: function headerRendererFactory(headerRow) { var _this = this; return function (index, TH) { var rootDocument = _this.hot.rootDocument; TH.removeAttribute('colspan'); (0, _element.removeClass)(TH, 'hiddenHeader'); // header row is the index of header row counting from the top (=> positive values) if (_this.colspanArray[headerRow][index] && _this.colspanArray[headerRow][index].colspan) { var colspan = _this.colspanArray[headerRow][index].colspan; var fixedColumnsLeft = _this.hot.getSettings().fixedColumnsLeft || 0; var _this$hot$view$wt$wtO = _this.hot.view.wt.wtOverlays, leftOverlay = _this$hot$view$wt$wtO.leftOverlay, topLeftCornerOverlay = _this$hot$view$wt$wtO.topLeftCornerOverlay; var isInTopLeftCornerOverlay = topLeftCornerOverlay ? topLeftCornerOverlay.clone.wtTable.THEAD.contains(TH) : false; var isInLeftOverlay = leftOverlay ? leftOverlay.clone.wtTable.THEAD.contains(TH) : false; if (colspan > 1) { TH.setAttribute('colspan', isInTopLeftCornerOverlay || isInLeftOverlay ? Math.min(colspan, fixedColumnsLeft - index) : colspan); } if (isInTopLeftCornerOverlay || isInLeftOverlay && index === fixedColumnsLeft - 1) { (0, _element.addClass)(TH, 'overlayEdge'); } } if (_this.colspanArray[headerRow][index] && _this.colspanArray[headerRow][index].hidden) { (0, _element.addClass)(TH, 'hiddenHeader'); } (0, _element.empty)(TH); var divEl = rootDocument.createElement('DIV'); (0, _element.addClass)(divEl, 'relative'); var spanEl = rootDocument.createElement('SPAN'); (0, _element.addClass)(spanEl, 'colHeader'); (0, _element.fastInnerHTML)(spanEl, _this.colspanArray[headerRow][index] ? _this.colspanArray[headerRow][index].label || '' : ''); divEl.appendChild(spanEl); TH.appendChild(divEl); _this.hot.runHooks('afterGetColHeader', index, TH); }; } /** * Returns the colspan for the provided coordinates. * * @private * @param {Number} row Row index. * @param {Number} column Column index. * @returns {Number} */ }, { key: "getColspan", value: function getColspan(row, column) { var header = this.colspanArray[this.rowCoordsToLevel(row)][column]; return header ? header.colspan : 1; } /** * Translates the level value (header row index from the top) to the row value (negative index). * * @private * @param {Number} level Header level. * @returns {Number} */ }, { key: "levelToRowCoords", value: function levelToRowCoords(level) { return level - this.columnHeaderLevelCount; } /** * Translates the row value (negative index) to the level value (header row index from the top). * * @private * @param {Number} row Row index. * @returns {Number} */ }, { key: "rowCoordsToLevel", value: function rowCoordsToLevel(row) { return row + this.columnHeaderLevelCount; } /** * Returns the column index of the "parent" nested header. * * @private * @param {Number} level Header level. * @param {Number} column Column index. * @returns {*} */ }, { key: "getNestedParent", value: function getNestedParent(level, column) { if (level < 0) { return false; } var colspan = this.colspanArray[level][column] ? this.colspanArray[level][column].colspan : 1; var hidden = this.colspanArray[level][column] ? this.colspanArray[level][column].hidden : false; if (colspan > 1 || colspan === 1 && hidden === false) { return column; } var parentCol = column - 1; do { if (this.colspanArray[level][parentCol].colspan > 1) { break; } parentCol -= 1; } while (column >= 0); return parentCol; } /** * Returns (physical) indexes of headers below the header with provided coordinates. * * @private * @param {Number} row Row index. * @param {Number} column Column index. * @returns {Number[]} */ }, { key: "getChildHeaders", value: function getChildHeaders(row, column) { var level = this.rowCoordsToLevel(row); var childColspanLevel = this.colspanArray[level + 1]; var nestedParentCol = this.getNestedParent(level, column); var colspan = this.colspanArray[level][column].colspan; var childHeaderRange = []; if (!childColspanLevel) { return childHeaderRange; } (0, _number.rangeEach)(nestedParentCol, nestedParentCol + colspan - 1, function (i) { if (childColspanLevel[i] && childColspanLevel[i].colspan > 1) { colspan -= childColspanLevel[i].colspan - 1; } if (childColspanLevel[i] && !childColspanLevel[i].hidden && childHeaderRange.indexOf(i) === -1) { childHeaderRange.push(i); } }); return childHeaderRange; } /** * Fill the remaining colspanArray entries for the undeclared column headers. * * @private */ }, { key: "fillTheRemainingColspans", value: function fillTheRemainingColspans() { var _this8 = this; (0, _object.objectEach)(this.settings, function (levelValue, level) { (0, _number.rangeEach)(_this8.colspanArray[level].length - 1, _this8.hot.countCols() - 1, function (col) { _this8.colspanArray[level].push({ label: levelValue[col] || '', colspan: 1, hidden: false }); }, true); }); } /** * Updates headers highlight in nested structure. * * @private */ }, { key: "updateHeadersHighlight", value: function updateHeadersHighlight() { var _this9 = this; var selection = this.hot.getSelectedLast(); if (selection === void 0) { return; } var wtOverlays = this.hot.view.wt.wtOverlays; var selectionByHeader = this.hot.selection.isSelectedByColumnHeader(); var from = Math.min(selection[1], selection[3]); var to = Math.max(selection[1], selection[3]); var levelLimit = selectionByHeader ? -1 : this.columnHeaderLevelCount - 1; var changes = []; var classNameModifier = function classNameModifier(className) { return function (TH, modifier) { return function () { return modifier(TH, className); }; }; }; var highlightHeader = classNameModifier('ht__highlight'); var activeHeader = classNameModifier('ht__active_highlight'); (0, _number.rangeEach)(from, to, function (column) { var _loop = function _loop(level) { var visibleColumnIndex = _this9.getNestedParent(level, column); var topTH = wtOverlays.topOverlay ? wtOverlays.topOverlay.clone.wtTable.getColumnHeader(visibleColumnIndex, level) : void 0; var topLeftTH = wtOverlays.topLeftCornerOverlay ? wtOverlays.topLeftCornerOverlay.clone.wtTable.getColumnHeader(visibleColumnIndex, level) : void 0; var listTH = [topTH, topLeftTH]; var colspanLen = _this9.getColspan(level - _this9.columnHeaderLevelCount, visibleColumnIndex); var isInSelection = visibleColumnIndex >= from && visibleColumnIndex + colspanLen - 1 <= to; (0, _array.arrayEach)(listTH, function (TH) { if (TH === void 0) { return false; } if (!selectionByHeader && level < levelLimit || selectionByHeader && !isInSelection) { changes.push(highlightHeader(TH, _element.removeClass)); if (selectionByHeader) { changes.push(activeHeader(TH, _element.removeClass)); } } else { changes.push(highlightHeader(TH, _element.addClass)); if (selectionByHeader) { changes.push(activeHeader(TH, _element.addClass)); } } }); }; for (var level = _this9.columnHeaderLevelCount - 1; level > -1; level--) { _loop(level); } }); (0, _array.arrayEach)(changes, function (fn) { return void fn(); }); changes.length = 0; } /** * Make the renderer render the first nested column in its entirety. * * @private * @param {Object} calc Viewport column calculator. */ }, { key: "onAfterViewportColumnCalculatorOverride", value: function onAfterViewportColumnCalculatorOverride(calc) { var _this10 = this; var newStartColumn = calc.startColumn; (0, _number.rangeEach)(0, Math.max(this.columnHeaderLevelCount - 1, 0), function (l) { var startColumnNestedParent = _this10.getNestedParent(l, calc.startColumn); if (startColumnNestedParent < calc.startColumn) { newStartColumn = Math.min(newStartColumn, startColumnNestedParent); } }); calc.startColumn = newStartColumn; } /** * Select all nested headers of clicked cell. * * @private * @param {MouseEvent} event Mouse event. * @param {Object} coords Clicked cell coords. */ }, { key: "onAfterOnCellMouseDown", value: function onAfterOnCellMouseDown(event, coords) { if (coords.row < 0) { var colspan = this.getColspan(coords.row, coords.col); var lastColIndex = coords.col + colspan - 1; if (colspan > 1) { var lastRowIndex = this.hot.countRows() - 1; this.hot.selection.setRangeEnd(new _src.CellCoords(lastRowIndex, lastColIndex)); } } } /** * Make the header-selection properly select the nested headers. * * @private * @param {MouseEvent} event Mouse event. * @param {Object} coords Clicked cell coords. * @param {HTMLElement} TD */ }, { key: "onBeforeOnCellMouseOver", value: function onBeforeOnCellMouseOver(event, coords, TD, blockCalculations) { if (coords.row >= 0 || coords.col < 0 || !this.hot.view.isMouseDown()) { return; } var _this$hot$getSelected = this.hot.getSelectedRangeLast(), from = _this$hot$getSelected.from, to = _this$hot$getSelected.to; var colspan = this.getColspan(coords.row, coords.col); var lastColIndex = coords.col + colspan - 1; var changeDirection = false; if (from.col <= to.col) { if (coords.col < from.col && lastColIndex === to.col || coords.col < from.col && lastColIndex < from.col || coords.col < from.col && lastColIndex >= from.col && lastColIndex < to.col) { changeDirection = true; } } else if (coords.col < to.col && lastColIndex > from.col || coords.col > from.col || coords.col <= to.col && lastColIndex > from.col || coords.col > to.col && lastColIndex > from.col) { changeDirection = true; } if (changeDirection) { var _ref = [to.col, from.col]; from.col = _ref[0]; to.col = _ref[1]; } if (colspan > 1) { var _this$hot; blockCalculations.column = true; blockCalculations.cell = true; var columnRange = []; if (from.col === to.col) { if (lastColIndex <= from.col && coords.col < from.col) { columnRange.push(to.col, coords.col); } else { columnRange.push(coords.col < from.col ? coords.col : from.col, lastColIndex > to.col ? lastColIndex : to.col); } } if (from.col < to.col) { columnRange.push(coords.col < from.col ? coords.col : from.col, lastColIndex); } if (from.col > to.col) { columnRange.push(from.col, coords.col); } (_this$hot = this.hot).selectColumns.apply(_this$hot, columnRange); } } /** * Cache column header count. * * @private */ }, { key: "onAfterInit", value: function onAfterInit() { this.columnHeaderLevelCount = this.hot.view.wt.getSetting('columnHeaders').length; this.fillTheRemainingColspans(); this.checkForOverlappingHeaders(); this.ghostTable.buildWidthsMapper(); } /** * `afterGetColumnHeader` hook callback - prepares the header structure. * * @private * @param {Array} renderersArray Array of renderers. */ }, { key: "onAfterGetColumnHeaderRenderers", value: function onAfterGetColumnHeaderRenderers(renderersArray) { if (renderersArray) { renderersArray.length = 0; for (var headersCount = this.colspanArray.length, i = headersCount - 1; i >= 0; i--) { renderersArray.push(this.headerRendererFactory(i)); } renderersArray.reverse(); } this.updateHeadersHighlight(); } /** * `modifyColWidth` hook callback - returns width from cache, when is greater than incoming from hook. * * @private * @param width Width from hook. * @param column Visual index of an column. * @returns {Number} */ }, { key: "onModifyColWidth", value: function onModifyColWidth(width, column) { var cachedWidth = this.ghostTable.widthsCache[column]; return width > cachedWidth ? width : cachedWidth; } /** * Destroys the plugin instance. */ }, { key: "destroy", value: function destroy() { this.settings = null; this.columnHeaderLevelCount = null; this.colspanArray = null; _get(_getPrototypeOf(NestedHeaders.prototype), "destroy", this).call(this); } }]); return NestedHeaders; }(_base.default); (0, _plugins.registerPlugin)('nestedHeaders', NestedHeaders); var _default = NestedHeaders; exports.default = _default;