UNPKG

handsontable

Version:

Handsontable is a JavaScript Spreadsheet Component available for React, Angular and Vue.

1,307 lines (1,102 loc) 49.5 kB
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } import "core-js/modules/es.function.name.js"; import "core-js/modules/es.array.from.js"; import "core-js/modules/es.string.iterator.js"; import "core-js/modules/es.array.includes.js"; import "core-js/modules/es.object.to-string.js"; import "core-js/modules/web.dom-collections.for-each.js"; import "core-js/modules/es.symbol.js"; import "core-js/modules/es.symbol.description.js"; import "core-js/modules/es.symbol.iterator.js"; import "core-js/modules/es.array.iterator.js"; import "core-js/modules/web.dom-collections.iterator.js"; import "core-js/modules/es.array.slice.js"; import "core-js/modules/es.regexp.exec.js"; 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); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } import { hasClass, index, offset, removeClass, removeTextNodes, overlayContainsElement, closest, outerHeight, outerWidth, innerHeight, isVisible as _isVisible } from "../../../helpers/dom/element.mjs"; import { isFunction } from "../../../helpers/function.mjs"; import ColumnFilter from "./filter/column.mjs"; import RowFilter from "./filter/row.mjs"; import { Renderer } from "./renderer/index.mjs"; import ColumnUtils from "./utils/column.mjs"; import RowUtils from "./utils/row.mjs"; import { CLONE_TOP, CLONE_BOTTOM, CLONE_INLINE_START, CLONE_TOP_INLINE_START_CORNER, CLONE_BOTTOM_INLINE_START_CORNER } from "./overlay/index.mjs"; /** * @todo These mixes are never added to the class Table, however their members are used here. * @todo Continue: Potentially it works only, because some of these mixes are added to every inherited class. * @todo Refactoring, move code from `if(this.isMaster)` into MasterTable, and others like that. * @mixes stickyColumnsStart * @mixes stickyRowsBottom * @mixes stickyRowsTop * @mixes calculatedRows * @mixes calculatedColumns * @abstract */ var Table = /*#__PURE__*/function () { /** * The walkontable settings. * * @protected * @type {Settings} */ /** * Indicates if the table has height bigger than 0px. * * @type {boolean} */ /** * Indicates if the table has width bigger than 0px. * * @type {boolean} */ /** * Indicates if the table is visible. By visible, it means that the holder * element has CSS 'display' property different than 'none'. * * @type {boolean} */ /** * * @abstract * @param {TableDao} dataAccessObject The data access object. * @param {FacadeGetter} facadeGetter Function which return proper facade. * @param {DomBindings} domBindings Bindings into DOM. * @param {Settings} wtSettings The Walkontable settings. * @param {'master'|CLONE_TYPES_ENUM} name Overlay name. */ function Table(dataAccessObject, facadeGetter, domBindings, wtSettings, name) { var _this = this; _classCallCheck(this, Table); _defineProperty(this, "wtSettings", null); _defineProperty(this, "domBindings", void 0); _defineProperty(this, "TBODY", null); _defineProperty(this, "THEAD", null); _defineProperty(this, "COLGROUP", null); _defineProperty(this, "hasTableHeight", true); _defineProperty(this, "hasTableWidth", true); _defineProperty(this, "isTableVisible", false); _defineProperty(this, "tableOffset", 0); _defineProperty(this, "holderOffset", 0); this.domBindings = domBindings; /** * Indicates if this instance is of type `MasterTable` (i.e. It is NOT an overlay). * * @type {boolean} */ this.isMaster = name === 'master'; this.name = name; this.dataAccessObject = dataAccessObject; this.facadeGetter = facadeGetter; this.wtSettings = wtSettings; // legacy support this.instance = this.dataAccessObject.wot; // TODO refactoring: it might be removed here, and provides legacy support through facade. this.wot = this.dataAccessObject.wot; this.TABLE = domBindings.rootTable; removeTextNodes(this.TABLE); // TODO refactoring, to recognize the legitimacy of moving them into domBidings this.spreader = this.createSpreader(this.TABLE); this.hider = this.createHider(this.spreader); this.holder = this.createHolder(this.hider); this.wtRootElement = this.holder.parentNode; if (this.isMaster) { this.alignOverlaysWithTrimmingContainer(); // todo wow, It calls method from child class (MasterTable). } this.fixTableDomTree(); this.rowFilter = null; // TODO refactoring, eliminate all (re)creations of this object, then updates state when needed. this.columnFilter = null; // TODO refactoring, eliminate all (re)creations of this object, then updates state when needed. this.correctHeaderWidth = false; var origRowHeaderWidth = this.wtSettings.getSettingPure('rowHeaderWidth'); // Fix for jumping row headers (https://github.com/handsontable/handsontable/issues/3850) this.wtSettings.update('rowHeaderWidth', function () { return _this._modifyRowHeaderWidth(origRowHeaderWidth); }); this.rowUtils = new RowUtils(this.dataAccessObject, this.wtSettings); // TODO refactoring, It can be passed through IOC. this.columnUtils = new ColumnUtils(this.dataAccessObject, this.wtSettings); // TODO refactoring, It can be passed through IOC. this.tableRenderer = new Renderer({ // TODO refactoring, It can be passed through IOC. TABLE: this.TABLE, THEAD: this.THEAD, COLGROUP: this.COLGROUP, TBODY: this.TBODY, rowUtils: this.rowUtils, columnUtils: this.columnUtils, cellRenderer: this.wtSettings.getSettingPure('cellRenderer') }); } /** * Returns a boolean that is true if this Table represents a specific overlay, identified by the overlay name. * For MasterTable, it returns false. * * @param {string} overlayTypeName The overlay type. * @returns {boolean} */ _createClass(Table, [{ key: "is", value: function is(overlayTypeName) { // todo refactoring: eliminate all protected and private usages return this.name === overlayTypeName; } /** * */ }, { key: "fixTableDomTree", value: function fixTableDomTree() { var rootDocument = this.domBindings.rootDocument; this.TBODY = this.TABLE.querySelector('tbody'); if (!this.TBODY) { this.TBODY = rootDocument.createElement('tbody'); this.TABLE.appendChild(this.TBODY); } this.THEAD = this.TABLE.querySelector('thead'); if (!this.THEAD) { this.THEAD = rootDocument.createElement('thead'); this.TABLE.insertBefore(this.THEAD, this.TBODY); } this.COLGROUP = this.TABLE.querySelector('colgroup'); if (!this.COLGROUP) { this.COLGROUP = rootDocument.createElement('colgroup'); this.TABLE.insertBefore(this.COLGROUP, this.THEAD); } } /** * @param {HTMLTableElement} table An element to process. * @returns {HTMLElement} */ }, { key: "createSpreader", value: function createSpreader(table) { var parent = table.parentNode; var spreader; if (!parent || parent.nodeType !== Node.ELEMENT_NODE || !hasClass(parent, 'wtHolder')) { spreader = this.domBindings.rootDocument.createElement('div'); spreader.className = 'wtSpreader'; if (parent) { // if TABLE is detached (e.g. in Jasmine test), it has no parentNode so we cannot attach holder to it parent.insertBefore(spreader, table); } spreader.appendChild(table); } spreader.style.position = 'relative'; return spreader; } /** * @param {HTMLElement} spreader An element to the hider element is injected. * @returns {HTMLElement} */ }, { key: "createHider", value: function createHider(spreader) { var parent = spreader.parentNode; var hider; if (!parent || parent.nodeType !== Node.ELEMENT_NODE || !hasClass(parent, 'wtHolder')) { hider = this.domBindings.rootDocument.createElement('div'); hider.className = 'wtHider'; if (parent) { // if TABLE is detached (e.g. in Jasmine test), it has no parentNode so we cannot attach holder to it parent.insertBefore(hider, spreader); } hider.appendChild(spreader); } return hider; } /** * * @param {HTMLElement} hider An element to the holder element is injected. * @returns {HTMLElement} */ }, { key: "createHolder", value: function createHolder(hider) { var parent = hider.parentNode; var holder; if (!parent || parent.nodeType !== Node.ELEMENT_NODE || !hasClass(parent, 'wtHolder')) { holder = this.domBindings.rootDocument.createElement('div'); holder.style.position = 'relative'; holder.className = 'wtHolder'; if (parent) { // if TABLE is detached (e.g. in Jasmine test), it has no parentNode so we cannot attach holder to it parent.insertBefore(holder, hider); } if (this.isMaster) { holder.parentNode.className += 'ht_master handsontable'; holder.parentNode.setAttribute('dir', this.wtSettings.getSettingPure('rtlMode') ? 'rtl' : 'ltr'); } holder.appendChild(hider); } return holder; } /** * Redraws the table. * * @param {boolean} [fastDraw=false] If TRUE, will try to avoid full redraw and only update the border positions. * If FALSE or UNDEFINED, will perform a full redraw. * @returns {Table} */ }, { key: "draw", value: function draw() { var fastDraw = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; var wtSettings = this.wtSettings; var _this$dataAccessObjec = this.dataAccessObject, wtOverlays = _this$dataAccessObjec.wtOverlays, wtViewport = _this$dataAccessObjec.wtViewport; var totalRows = wtSettings.getSetting('totalRows'); var totalColumns = wtSettings.getSetting('totalColumns'); var rowHeaders = wtSettings.getSetting('rowHeaders'); var rowHeadersCount = rowHeaders.length; var columnHeaders = wtSettings.getSetting('columnHeaders'); var columnHeadersCount = columnHeaders.length; var syncScroll = false; var runFastDraw = fastDraw; if (this.isMaster) { this.holderOffset = offset(this.holder); runFastDraw = wtViewport.createRenderCalculators(runFastDraw); if (rowHeadersCount && !wtSettings.getSetting('fixedColumnsStart')) { var leftScrollPos = wtOverlays.inlineStartOverlay.getScrollPosition(); var previousState = this.correctHeaderWidth; this.correctHeaderWidth = leftScrollPos !== 0; if (previousState !== this.correctHeaderWidth) { runFastDraw = false; } } } if (this.isMaster) { syncScroll = wtOverlays.updateStateOfRendering(); } if (runFastDraw) { if (this.isMaster) { // in case we only scrolled without redraw, update visible rows information in oldRowsCalculator wtViewport.createVisibleCalculators(); } if (wtOverlays) { wtOverlays.refresh(true); } } else { if (this.isMaster) { this.tableOffset = offset(this.TABLE); } else { this.tableOffset = this.dataAccessObject.parentTableOffset; } var startRow = totalRows > 0 ? this.getFirstRenderedRow() : 0; var startColumn = totalColumns > 0 ? this.getFirstRenderedColumn() : 0; this.rowFilter = new RowFilter(startRow, totalRows, columnHeadersCount); this.columnFilter = new ColumnFilter(startColumn, totalColumns, rowHeadersCount); var performRedraw = true; // Only master table rendering can be skipped if (this.isMaster) { this.alignOverlaysWithTrimmingContainer(); // todo It calls method from child class (MasterTable). var skipRender = {}; this.wtSettings.getSetting('beforeDraw', true, skipRender); performRedraw = skipRender.skipRender !== true; } if (performRedraw) { this.tableRenderer.setHeaderContentRenderers(rowHeaders, columnHeaders); if (this.is(CLONE_BOTTOM) || this.is(CLONE_BOTTOM_INLINE_START_CORNER)) { // do NOT render headers on the bottom or bottom-left corner overlay this.tableRenderer.setHeaderContentRenderers(rowHeaders, []); } this.resetOversizedRows(); this.tableRenderer.setViewportSize(this.getRenderedRowsCount(), this.getRenderedColumnsCount()).setFilters(this.rowFilter, this.columnFilter).render(); var workspaceWidth; if (this.isMaster) { workspaceWidth = this.dataAccessObject.workspaceWidth; this.dataAccessObject.wtViewport.containerWidth = null; this.markOversizedColumnHeaders(); } this.adjustColumnHeaderHeights(); if (this.isMaster || this.is(CLONE_BOTTOM)) { this.markOversizedRows(); } if (this.isMaster) { this.dataAccessObject.wtViewport.createVisibleCalculators(); this.dataAccessObject.wtOverlays.refresh(false); this.dataAccessObject.wtOverlays.applyToDOM(); var hiderWidth = outerWidth(this.hider); var tableWidth = outerWidth(this.TABLE); if (hiderWidth !== 0 && tableWidth !== hiderWidth) { // Recalculate the column widths, if width changes made in the overlays removed the scrollbar, thus changing the viewport width. this.columnUtils.calculateWidths(); this.tableRenderer.renderer.colGroup.render(); } if (workspaceWidth !== this.dataAccessObject.wtViewport.getWorkspaceWidth()) { // workspace width changed though to shown/hidden vertical scrollbar. Let's reapply stretching this.dataAccessObject.wtViewport.containerWidth = null; this.columnUtils.calculateWidths(); this.tableRenderer.renderer.colGroup.render(); } this.wtSettings.getSetting('onDraw', true); } else if (this.is(CLONE_BOTTOM)) { this.dataAccessObject.cloneSource.wtOverlays.adjustElementsSize(); } } } if (this.isMaster) { var positionChanged = wtOverlays.topOverlay.resetFixedPosition(); if (wtOverlays.bottomOverlay.clone) { positionChanged = wtOverlays.bottomOverlay.resetFixedPosition() || positionChanged; } positionChanged = wtOverlays.inlineStartOverlay.resetFixedPosition() || positionChanged; if (wtOverlays.topInlineStartCornerOverlay) { wtOverlays.topInlineStartCornerOverlay.resetFixedPosition(); } if (wtOverlays.bottomInlineStartCornerOverlay && wtOverlays.bottomInlineStartCornerOverlay.clone) { wtOverlays.bottomInlineStartCornerOverlay.resetFixedPosition(); } if (positionChanged) { // It refreshes the cells borders caused by a 1px shift (introduced by overlays which add or // remove `innerBorderTop` and `innerBorderInlineStart` CSS classes to the DOM element. This happens // when there is a switch between rendering from 0 to N rows/columns and vice versa). wtOverlays.refreshAll(); wtOverlays.adjustElementsSize(); } } this.refreshSelections(runFastDraw); if (syncScroll) { wtOverlays.syncScrollWithMaster(); } this.dataAccessObject.drawn = true; return this; } /** * @param {number} col The visual column index. */ }, { key: "markIfOversizedColumnHeader", value: function markIfOversizedColumnHeader(col) { var sourceColIndex = this.columnFilter.renderedToSource(col); var level = this.wtSettings.getSetting('columnHeaders').length; var defaultRowHeight = this.wtSettings.getSetting('defaultRowHeight'); var previousColHeaderHeight; var currentHeader; var currentHeaderHeight; var columnHeaderHeightSetting = this.wtSettings.getSetting('columnHeaderHeight') || []; while (level) { level -= 1; previousColHeaderHeight = this.getColumnHeaderHeight(level); currentHeader = this.getColumnHeader(sourceColIndex, level); if (!currentHeader) { /* eslint-disable no-continue */ continue; } currentHeaderHeight = innerHeight(currentHeader); if (!previousColHeaderHeight && defaultRowHeight < currentHeaderHeight || previousColHeaderHeight < currentHeaderHeight) { this.dataAccessObject.wtViewport.oversizedColumnHeaders[level] = currentHeaderHeight; } if (Array.isArray(columnHeaderHeightSetting)) { if (columnHeaderHeightSetting[level] !== null && columnHeaderHeightSetting[level] !== void 0) { this.dataAccessObject.wtViewport.oversizedColumnHeaders[level] = columnHeaderHeightSetting[level]; } } else if (!isNaN(columnHeaderHeightSetting)) { this.dataAccessObject.wtViewport.oversizedColumnHeaders[level] = columnHeaderHeightSetting; } if (this.dataAccessObject.wtViewport.oversizedColumnHeaders[level] < (columnHeaderHeightSetting[level] || columnHeaderHeightSetting)) { this.dataAccessObject.wtViewport.oversizedColumnHeaders[level] = columnHeaderHeightSetting[level] || columnHeaderHeightSetting; // eslint-disable-line max-len } } } /** * */ }, { key: "adjustColumnHeaderHeights", value: function adjustColumnHeaderHeights() { var wtSettings = this.wtSettings; var children = this.THEAD.childNodes; var oversizedColumnHeaders = this.dataAccessObject.wtViewport.oversizedColumnHeaders; var columnHeaders = wtSettings.getSetting('columnHeaders'); for (var i = 0, len = columnHeaders.length; i < len; i++) { if (oversizedColumnHeaders[i]) { if (!children[i] || children[i].childNodes.length === 0) { return; } children[i].childNodes[0].style.height = "".concat(oversizedColumnHeaders[i], "px"); } } } /** * Resets cache of row heights. The cache should be cached for each render cycle in a case * when new cell values have content which increases/decreases cell height. */ }, { key: "resetOversizedRows", value: function resetOversizedRows() { var wtSettings = this.wtSettings; var wtViewport = this.dataAccessObject.wtViewport; if (!this.isMaster && !this.is(CLONE_BOTTOM)) { return; } if (!wtSettings.getSetting('externalRowCalculator')) { var rowsToRender = this.getRenderedRowsCount(); // Reset the oversized row cache for rendered rows for (var visibleRowIndex = 0; visibleRowIndex < rowsToRender; visibleRowIndex++) { var sourceRow = this.rowFilter.renderedToSource(visibleRowIndex); if (wtViewport.oversizedRows && wtViewport.oversizedRows[sourceRow]) { wtViewport.oversizedRows[sourceRow] = void 0; } } } } /** * @param {string} className The CSS class name to remove from the table cells. */ }, { key: "removeClassFromCells", value: function removeClassFromCells(className) { var nodes = this.TABLE.querySelectorAll(".".concat(className)); for (var i = 0, len = nodes.length; i < len; i++) { removeClass(nodes[i], className); } } /** * Refresh the table selection by re-rendering Selection instances connected with that instance. * * @param {boolean} fastDraw If fast drawing is enabled than additionally className clearing is applied. */ }, { key: "refreshSelections", value: function refreshSelections(fastDraw) { var wtSettings = this.wtSettings; var selections = this.dataAccessObject.selections; if (!selections) { return; } var highlights = Array.from(selections); var len = highlights.length; if (fastDraw) { var classesToRemove = []; for (var i = 0; i < len; i++) { var _highlights$i$setting = highlights[i].settings, highlightHeaderClassName = _highlights$i$setting.highlightHeaderClassName, highlightRowClassName = _highlights$i$setting.highlightRowClassName, highlightColumnClassName = _highlights$i$setting.highlightColumnClassName; var classNames = highlights[i].classNames; var classNamesLength = classNames.length; for (var j = 0; j < classNamesLength; j++) { if (!classesToRemove.includes(classNames[j])) { classesToRemove.push(classNames[j]); } } if (highlightHeaderClassName && !classesToRemove.includes(highlightHeaderClassName)) { classesToRemove.push(highlightHeaderClassName); } if (highlightRowClassName && !classesToRemove.includes(highlightRowClassName)) { classesToRemove.push(highlightRowClassName); } if (highlightColumnClassName && !classesToRemove.includes(highlightColumnClassName)) { classesToRemove.push(highlightColumnClassName); } } var additionalClassesToRemove = wtSettings.getSetting('onBeforeRemoveCellClassNames'); if (Array.isArray(additionalClassesToRemove)) { for (var _i = 0; _i < additionalClassesToRemove.length; _i++) { classesToRemove.push(additionalClassesToRemove[_i]); } } var classesToRemoveLength = classesToRemove.length; for (var _i2 = 0; _i2 < classesToRemoveLength; _i2++) { // there was no rerender, so we need to remove classNames by ourselves this.removeClassFromCells(classesToRemove[_i2]); } } for (var _i3 = 0; _i3 < len; _i3++) { highlights[_i3].draw(this.facadeGetter(), fastDraw); } } /** * Get cell element at coords. * Negative coords.row or coords.col are used to retrieve header cells. If there are multiple header levels, the * negative value corresponds to the distance from the working area. For example, when there are 3 levels of column * headers, coords.col=-1 corresponds to the most inner header element, while coords.col=-3 corresponds to the * outmost header element. * * In case an element for the coords is not rendered, the method returns an error code. * To produce the error code, the input parameters are validated in the order in which they * are given. Thus, if both the row and the column coords are out of the rendered bounds, * the method returns the error code for the row. * * @param {CellCoords} coords The cell coordinates. * @returns {HTMLElement|number} HTMLElement on success or Number one of the exit codes on error: * -1 row before viewport * -2 row after viewport * -3 column before viewport * -4 column after viewport. */ }, { key: "getCell", value: function getCell(coords) { var row = coords.row; var column = coords.col; var hookResult = this.wtSettings.getSetting('onModifyGetCellCoords', row, column); if (hookResult && Array.isArray(hookResult)) { var _hookResult = _slicedToArray(hookResult, 2); row = _hookResult[0]; column = _hookResult[1]; } if (this.isRowBeforeRenderedRows(row)) { // row before rendered rows return -1; } else if (this.isRowAfterRenderedRows(row)) { // row after rendered rows return -2; } else if (this.isColumnBeforeRenderedColumns(column)) { // column before rendered columns return -3; } else if (this.isColumnAfterRenderedColumns(column)) { // column after rendered columns return -4; } var TR; if (row < 0) { TR = this.THEAD.childNodes[this.rowFilter.sourceRowToVisibleColHeadedRow(row)]; } else { TR = this.TBODY.childNodes[this.rowFilter.sourceToRendered(row)]; } if (!TR && row >= 0) { throw new Error('TR was expected to be rendered but is not'); } var TD = TR.childNodes[this.columnFilter.sourceColumnToVisibleRowHeadedColumn(column)]; if (!TD && column >= 0) { throw new Error('TD or TH was expected to be rendered but is not'); } return TD; } /** * GetColumnHeader. * * @param {number} col Column index. * @param {number} [level=0] Header level (0 = most distant to the table). * @returns {object} HTMLElement on success or undefined on error. */ }, { key: "getColumnHeader", value: function getColumnHeader(col) { var level = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; var TR = this.THEAD.childNodes[level]; return TR === null || TR === void 0 ? void 0 : TR.childNodes[this.columnFilter.sourceColumnToVisibleRowHeadedColumn(col)]; } /** * Gets all columns headers (TH elements) from the table. * * @param {number} column A source column index. * @returns {HTMLTableCellElement[]} */ }, { key: "getColumnHeaders", value: function getColumnHeaders(column) { var THs = []; var visibleColumn = this.columnFilter.sourceColumnToVisibleRowHeadedColumn(column); this.THEAD.childNodes.forEach(function (TR) { var TH = TR.childNodes[visibleColumn]; if (TH) { THs.push(TH); } }); return THs; } /** * GetRowHeader. * * @param {number} row Row index. * @param {number} [level=0] Header level (0 = most distant to the table). * @returns {HTMLElement} HTMLElement on success or Number one of the exit codes on error: `null table doesn't have row headers`. */ }, { key: "getRowHeader", value: function getRowHeader(row) { var level = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; if (this.columnFilter.sourceColumnToVisibleRowHeadedColumn(0) === 0) { return; } var rowHeadersCount = this.wtSettings.getSetting('rowHeaders').length; if (level >= rowHeadersCount) { return; } var TR = this.TBODY.childNodes[this.rowFilter.sourceToRendered(row)]; return TR === null || TR === void 0 ? void 0 : TR.childNodes[level]; } /** * Gets all rows headers (TH elements) from the table. * * @param {number} row A source row index. * @returns {HTMLTableCellElement[]} */ }, { key: "getRowHeaders", value: function getRowHeaders(row) { if (this.columnFilter.sourceColumnToVisibleRowHeadedColumn(0) === 0) { return []; } var THs = []; var rowHeadersCount = this.wtSettings.getSetting('rowHeaders').length; for (var renderedRowIndex = 0; renderedRowIndex < rowHeadersCount; renderedRowIndex++) { var TR = this.TBODY.childNodes[this.rowFilter.sourceToRendered(row)]; var TH = TR === null || TR === void 0 ? void 0 : TR.childNodes[renderedRowIndex]; if (TH) { THs.push(TH); } } return THs; } /** * Returns cell coords object for a given TD (or a child element of a TD element). * * @param {HTMLTableCellElement} TD A cell DOM element (or a child of one). * @returns {CellCoords|null} The coordinates of the provided TD element (or the closest TD element) or null, if the provided element is not applicable. */ }, { key: "getCoords", value: function getCoords(TD) { var cellElement = TD; if (cellElement.nodeName !== 'TD' && cellElement.nodeName !== 'TH') { cellElement = closest(cellElement, ['TD', 'TH']); } if (cellElement === null) { return null; } var TR = cellElement.parentNode; var CONTAINER = TR.parentNode; var row = index(TR); var col = cellElement.cellIndex; if (overlayContainsElement(CLONE_TOP_INLINE_START_CORNER, cellElement, this.wtRootElement) || overlayContainsElement(CLONE_TOP, cellElement, this.wtRootElement)) { if (CONTAINER.nodeName === 'THEAD') { row -= CONTAINER.childNodes.length; } } else if (overlayContainsElement(CLONE_BOTTOM_INLINE_START_CORNER, cellElement, this.wtRootElement) || overlayContainsElement(CLONE_BOTTOM, cellElement, this.wtRootElement)) { var totalRows = this.wtSettings.getSetting('totalRows'); row = totalRows - CONTAINER.childNodes.length + row; } else if (CONTAINER === this.THEAD) { row = this.rowFilter.visibleColHeadedRowToSourceRow(row); } else { row = this.rowFilter.renderedToSource(row); } if (overlayContainsElement(CLONE_TOP_INLINE_START_CORNER, cellElement, this.wtRootElement) || overlayContainsElement(CLONE_INLINE_START, cellElement, this.wtRootElement) || overlayContainsElement(CLONE_BOTTOM_INLINE_START_CORNER, cellElement, this.wtRootElement)) { col = this.columnFilter.offsettedTH(col); } else { col = this.columnFilter.visibleRowHeadedColumnToSourceColumn(col); } return this.wot.createCellCoords(row, col); } /** * Check if any of the rendered rows is higher than expected, and if so, cache them. */ }, { key: "markOversizedRows", value: function markOversizedRows() { if (this.wtSettings.getSetting('externalRowCalculator')) { return; } var rowCount = this.TBODY.childNodes.length; var expectedTableHeight = rowCount * this.wtSettings.getSetting('defaultRowHeight'); var actualTableHeight = innerHeight(this.TBODY) - 1; var previousRowHeight; var rowInnerHeight; var sourceRowIndex; var currentTr; var rowHeader; if (expectedTableHeight === actualTableHeight && !this.wtSettings.getSetting('fixedRowsBottom')) { // If the actual table height equals rowCount * default single row height, no row is oversized -> no need to iterate over them return; } while (rowCount) { rowCount -= 1; sourceRowIndex = this.rowFilter.renderedToSource(rowCount); previousRowHeight = this.getRowHeight(sourceRowIndex); currentTr = this.getTrForRow(sourceRowIndex); rowHeader = currentTr.querySelector('th'); if (rowHeader) { rowInnerHeight = innerHeight(rowHeader); } else { rowInnerHeight = innerHeight(currentTr) - 1; } if (!previousRowHeight && this.wtSettings.getSetting('defaultRowHeight') < rowInnerHeight || previousRowHeight < rowInnerHeight) { rowInnerHeight += 1; this.dataAccessObject.wtViewport.oversizedRows[sourceRowIndex] = rowInnerHeight; } } } /** * @param {number} row The visual row index. * @returns {HTMLTableElement} */ }, { key: "getTrForRow", value: function getTrForRow(row) { return this.TBODY.childNodes[this.rowFilter.sourceToRendered(row)]; } /** * Checks if the column index (negative value from -1 to N) is rendered. * * @param {number} column The column index (negative value from -1 to N). * @returns {boolean} */ }, { key: "isColumnHeaderRendered", value: function isColumnHeaderRendered(column) { if (column >= 0) { return false; } var rowHeaders = this.wtSettings.getSetting('rowHeaders'); var rowHeadersCount = rowHeaders.length; return Math.abs(column) <= rowHeadersCount; } /** * Checks if the row index (negative value from -1 to N) is rendered. * * @param {number} row The row index (negative value from -1 to N). * @returns {boolean} */ }, { key: "isRowHeaderRendered", value: function isRowHeaderRendered(row) { if (row >= 0) { return false; } var columnHeaders = this.wtSettings.getSetting('columnHeaders'); var columnHeadersCount = columnHeaders.length; return Math.abs(row) <= columnHeadersCount; } /* eslint-disable jsdoc/require-description-complete-sentence */ /** * Check if the given row index is lower than the index of the first row that * is currently rendered and return TRUE in that case, or FALSE otherwise. * * Negative row index is used to check the columns' headers. * * Headers * +--------------+ │ * -3 │ │ │ │ │ * +--------------+ │ * -2 │ │ │ │ │ TRUE * +--------------+ │ * -1 │ │ │ │ │ * Cells +==================+ │ * 0 ┇ ┇ ┇ ┇ <--- For fixedRowsTop: 1 │ * +--------------+ the master overlay do ---+ first rendered row (index 1) * 1 │ A2 │ B2 │ C2 │ not render the first row. │ * +--------------+ │ FALSE * 2 │ A3 │ B3 │ C3 │ │ * +--------------+ ---+ last rendered row * │ * │ FALSE * * @param {number} row The visual row index. * @memberof Table# * @function isRowBeforeRenderedRows * @returns {boolean} */ /* eslint-enable jsdoc/require-description-complete-sentence */ }, { key: "isRowBeforeRenderedRows", value: function isRowBeforeRenderedRows(row) { var first = this.getFirstRenderedRow(); // Check the headers only in case when the first rendered row is -1 or 0. // This is an indication that the overlay is placed on the most top position. if (row < 0 && first <= 0) { return !this.isRowHeaderRendered(row); } return row < first; } /* eslint-disable jsdoc/require-description-complete-sentence */ /** * Check if the given column index is greater than the index of the last column that * is currently rendered and return TRUE in that case, or FALSE otherwise. * * The negative row index is used to check the columns' headers. However, * keep in mind that for negative indexes, the method always returns FALSE as * it is not possible to render headers partially. The "after" index can not be * lower than -1. * * Headers * +--------------+ │ * -3 │ │ │ │ │ * +--------------+ │ * -2 │ │ │ │ │ FALSE * +--------------+ │ * -1 │ │ │ │ │ * Cells +==================+ │ * 0 ┇ ┇ ┇ ┇ <--- For fixedRowsTop: 1 │ * +--------------+ the master overlay do ---+ first rendered row (index 1) * 1 │ A2 │ B2 │ C2 │ not render the first rows │ * +--------------+ │ FALSE * 2 │ A3 │ B3 │ C3 │ │ * +--------------+ ---+ last rendered row * │ * │ TRUE * * @param {number} row The visual row index. * @memberof Table# * @function isRowAfterRenderedRows * @returns {boolean} */ /* eslint-enable jsdoc/require-description-complete-sentence */ }, { key: "isRowAfterRenderedRows", value: function isRowAfterRenderedRows(row) { return row > this.getLastRenderedRow(); } /* eslint-disable jsdoc/require-description-complete-sentence */ /** * Check if the given column index is lower than the index of the first column that * is currently rendered and return TRUE in that case, or FALSE otherwise. * * Negative column index is used to check the rows' headers. * * For fixedColumnsStart: 1 the master overlay * do not render this first columns. * Headers -3 -2 -1 | * +----+----+----║┄ ┄ +------+------+ * │ │ │ ║ │ B1 │ C1 │ * +--------------║┄ ┄ --------------│ * │ │ │ ║ │ B2 │ C2 │ * +--------------║┄ ┄ --------------│ * │ │ │ ║ │ B3 │ C3 │ * +----+----+----║┄ ┄ +------+------+ * ╷ ╷ * -------------------------+-------------+----------------> * TRUE first FALSE last FALSE * rendered rendered * column column * * @param {number} column The visual column index. * @memberof Table# * @function isColumnBeforeRenderedColumns * @returns {boolean} */ /* eslint-enable jsdoc/require-description-complete-sentence */ }, { key: "isColumnBeforeRenderedColumns", value: function isColumnBeforeRenderedColumns(column) { var first = this.getFirstRenderedColumn(); // Check the headers only in case when the first rendered column is -1 or 0. // This is an indication that the overlay is placed on the most left position. if (column < 0 && first <= 0) { return !this.isColumnHeaderRendered(column); } return column < first; } /* eslint-disable jsdoc/require-description-complete-sentence */ /** * Check if the given column index is greater than the index of the last column that * is currently rendered and return TRUE in that case, or FALSE otherwise. * * The negative column index is used to check the rows' headers. However, * keep in mind that for negative indexes, the method always returns FALSE as * it is not possible to render headers partially. The "after" index can not be * lower than -1. * * For fixedColumnsStart: 1 the master overlay * do not render this first columns. * Headers -3 -2 -1 | * +----+----+----║┄ ┄ +------+------+ * │ │ │ ║ │ B1 │ C1 │ * +--------------║┄ ┄ --------------│ * │ │ │ ║ │ B2 │ C2 │ * +--------------║┄ ┄ --------------│ * │ │ │ ║ │ B3 │ C3 │ * +----+----+----║┄ ┄ +------+------+ * ╷ ╷ * -------------------------+-------------+----------------> * FALSE first FALSE last TRUE * rendered rendered * column column * * @param {number} column The visual column index. * @memberof Table# * @function isColumnAfterRenderedColumns * @returns {boolean} */ /* eslint-enable jsdoc/require-description-complete-sentence */ }, { key: "isColumnAfterRenderedColumns", value: function isColumnAfterRenderedColumns(column) { return this.columnFilter && column > this.getLastRenderedColumn(); } }, { key: "isColumnAfterViewport", value: function isColumnAfterViewport(column) { return this.columnFilter && column > this.getLastVisibleColumn(); } }, { key: "isRowAfterViewport", value: function isRowAfterViewport(row) { return this.rowFilter && row > this.getLastVisibleRow(); } }, { key: "isColumnBeforeViewport", value: function isColumnBeforeViewport(column) { return this.columnFilter && this.columnFilter.sourceToRendered(column) < 0 && column >= 0; } }, { key: "isLastRowFullyVisible", value: function isLastRowFullyVisible() { return this.getLastVisibleRow() === this.getLastRenderedRow(); } }, { key: "isLastColumnFullyVisible", value: function isLastColumnFullyVisible() { return this.getLastVisibleColumn() === this.getLastRenderedColumn(); } }, { key: "allRowsInViewport", value: function allRowsInViewport() { return this.wtSettings.getSetting('totalRows') === this.getVisibleRowsCount(); } }, { key: "allColumnsInViewport", value: function allColumnsInViewport() { return this.wtSettings.getSetting('totalColumns') === this.getVisibleColumnsCount(); } /** * Checks if any of the row's cells content exceeds its initial height, and if so, returns the oversized height. * * @param {number} sourceRow The physical row index. * @returns {number} */ }, { key: "getRowHeight", value: function getRowHeight(sourceRow) { return this.rowUtils.getHeight(sourceRow); } /** * @param {number} level The column level. * @returns {number} */ }, { key: "getColumnHeaderHeight", value: function getColumnHeaderHeight(level) { return this.columnUtils.getHeaderHeight(level); } /** * @param {number} sourceColumn The physical column index. * @returns {number} */ }, { key: "getColumnWidth", value: function getColumnWidth(sourceColumn) { return this.columnUtils.getWidth(sourceColumn); } /** * @param {number} sourceColumn The physical column index. * @returns {number} */ }, { key: "getStretchedColumnWidth", value: function getStretchedColumnWidth(sourceColumn) { return this.columnUtils.getStretchedColumnWidth(sourceColumn); } /** * Checks if the table has defined size. It returns `true` when the table has width and height * set bigger than `0px`. * * @returns {boolean} */ }, { key: "hasDefinedSize", value: function hasDefinedSize() { return this.hasTableHeight && this.hasTableWidth; } /** * Gets table's width. The returned width is the width of the rendered cells that fit in the * current viewport. The value may change depends on the viewport position (scroll position). * * @returns {number} */ }, { key: "getWidth", value: function getWidth() { return outerWidth(this.TABLE); } /** * Gets table's height. The returned height is the height of the rendered cells that fit in the * current viewport. The value may change depends on the viewport position (scroll position). * * @returns {number} */ }, { key: "getHeight", value: function getHeight() { return outerHeight(this.TABLE); } /** * Gets table's total width. The returned width is the width of all rendered cells (including headers) * that can be displayed in the table. * * @returns {number} */ }, { key: "getTotalWidth", value: function getTotalWidth() { var width = outerWidth(this.hider); // when the overlay's table does not have any cells the hider returns 0, get then width from the table element return width !== 0 ? width : this.getWidth(); } /** * Gets table's total height. The returned height is the height of all rendered cells (including headers) * that can be displayed in the table. * * @returns {number} */ }, { key: "getTotalHeight", value: function getTotalHeight() { var height = outerHeight(this.hider); // when the overlay's table does not have any cells the hider returns 0, get then height from the table element return height !== 0 ? height : this.getHeight(); } /** * Checks if the table is visible. It returns `true` when the holder element (or its parents) * has CSS 'display' property different than 'none'. * * @returns {boolean} */ }, { key: "isVisible", value: function isVisible() { return _isVisible(this.TABLE); } /** * Modify row header widths provided by user in class contructor. * * @private * @param {Function} rowHeaderWidthFactory The function which can provide default width values for rows.. * @returns {number} */ }, { key: "_modifyRowHeaderWidth", value: function _modifyRowHeaderWidth(rowHeaderWidthFactory) { var widths = isFunction(rowHeaderWidthFactory) ? rowHeaderWidthFactory() : null; if (Array.isArray(widths)) { widths = _toConsumableArray(widths); widths[widths.length - 1] = this._correctRowHeaderWidth(widths[widths.length - 1]); } else { widths = this._correctRowHeaderWidth(widths); } return widths; } /** * Correct row header width if necessary. * * @private * @param {number} width The width to process. * @returns {number} */ }, { key: "_correctRowHeaderWidth", value: function _correctRowHeaderWidth(width) { var rowHeaderWidth = width; if (typeof width !== 'number') { rowHeaderWidth = this.wtSettings.getSetting('defaultColumnWidth'); } if (this.correctHeaderWidth) { rowHeaderWidth += 1; } return rowHeaderWidth; } }]); return Table; }(); export default Table;