UNPKG

handsontable

Version:

Handsontable is a JavaScript Data Grid available for React, Angular and Vue.

448 lines (438 loc) • 22.2 kB
"use strict"; exports.__esModule = true; require("core-js/modules/es.error.cause.js"); var _object = require("../../helpers/object"); var _localHooks = _interopRequireDefault(require("../../mixins/localHooks")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); } 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); } function _classPrivateFieldInitSpec(e, t, a) { _checkPrivateRedeclaration(e, t), t.set(e, a); } function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); } function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); } function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; } function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); } /** * The BaseTransformation class implements algorithms for transforming coordinates based on current settings * passed to the Handsontable. The class performs the calculations based on the renderable indexes. * * Transformation is always applied relative to the currently active selection layer. * * The class operates on a table size defined by the renderable indexes. If the `navigableHeaders` * option is enabled, the table size is increased by the number of row and/or column headers. * Because the headers are treated as cells as part of the table size (indexes always go from 0 to N), * the algorithm can be written as simply as possible (without new if's that distinguish the headers * logic). * * @class BaseTransformation * @private */ var _range = /*#__PURE__*/new WeakMap(); var _activeLayerIndex = /*#__PURE__*/new WeakMap(); var _offset = /*#__PURE__*/new WeakMap(); var _BaseTransformation_brand = /*#__PURE__*/new WeakSet(); class BaseTransformation { constructor(range, tableApi) { /** * Chooses the next selection layer as active one. */ _classPrivateMethodInitSpec(this, _BaseTransformation_brand); /** * Instance of the SelectionRange, holder for visual coordinates applied to the table. * * @type {SelectionRange} */ _classPrivateFieldInitSpec(this, _range, void 0); /** * Index of the currently active selection layer. * * @type {number} */ _classPrivateFieldInitSpec(this, _activeLayerIndex, 0); /** * Increases the table size by applying the offsets. The option is used by the `navigableHeaders` * option. * * @type {{ x: number, y: number }} */ _classPrivateFieldInitSpec(this, _offset, { x: 0, y: 0 }); /** * An object containing the table API methods and settings. * * @type {object} */ _defineProperty(this, "tableApi", void 0); _classPrivateFieldSet(_range, this, range); this.tableApi = tableApi; } /** * Sets the currently active selection layer index. * * @param {number} layerIndex The layer index to set as active. */ setActiveLayerIndex(layerIndex) { _classPrivateFieldSet(_activeLayerIndex, this, layerIndex); } /** * Gets the currently active selection layer range. * * @returns {CellRange} */ getCurrentSelection() { return _classPrivateFieldGet(_range, this).peekByIndex(_classPrivateFieldGet(_activeLayerIndex, this)); } /** * Selects cell relative to the current cell (if possible). * * @param {number} rowDelta Rows number to move, value can be passed as negative number. * @param {number} colDelta Columns number to move, value can be passed as negative number. * @param {boolean} [createMissingRecords=false] If `true` the new rows/columns will be created if necessary. Otherwise, row/column will * be created according to `minSpareRows/minSpareCols` settings of Handsontable. * @returns {{selectionLayer: number, visualCoords: CellCoords}} Visual coordinates with selection layer after transformation. */ transformStart(rowDelta, colDelta) { let createMissingRecords = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; _classPrivateFieldSet(_offset, this, this.calculateOffset()); const delta = this.tableApi.createCellCoords(rowDelta, colDelta); let visualCoords = this.getCurrentSelection().highlight; const highlightRenderableCoords = this.tableApi.visualToRenderableCoords(visualCoords); let rowTransformDir = 0; let colTransformDir = 0; this.runLocalHooks('beforeTransformStart', delta); if (highlightRenderableCoords.row !== null && highlightRenderableCoords.col !== null) { let { width, height } = _assertClassBrand(_BaseTransformation_brand, this, _getTableSize).call(this); const { row, col } = _assertClassBrand(_BaseTransformation_brand, this, _visualToZeroBasedCoords).call(this, visualCoords); const fixedRowsBottom = this.tableApi.fixedRowsBottom(); const minSpareRows = this.tableApi.minSpareRows(); const minSpareCols = this.tableApi.minSpareCols(); const autoWrapRow = this.tableApi.autoWrapRow(); const autoWrapCol = this.tableApi.autoWrapCol(); const zeroBasedCoords = this.tableApi.createCellCoords(row + delta.row, col + delta.col); if (zeroBasedCoords.row >= height) { const isActionInterrupted = (0, _object.createObjectPropListener)(createMissingRecords && minSpareRows > 0 && fixedRowsBottom === 0); const nextColumn = zeroBasedCoords.col + 1; const isColumnOutOfRange = nextColumn >= width; const newCoords = this.tableApi.createCellCoords(zeroBasedCoords.row - height, isColumnOutOfRange ? nextColumn - width : nextColumn); this.runLocalHooks('beforeColumnWrap', isActionInterrupted, _assertClassBrand(_BaseTransformation_brand, this, _zeroBasedToVisualCoords).call(this, newCoords), isColumnOutOfRange); if (isActionInterrupted.value) { this.runLocalHooks('insertRowRequire', this.countRenderableRows()); } else if (autoWrapCol) { if (this.shouldSwitchSelectionLayer() && isColumnOutOfRange && _classPrivateFieldGet(_range, this).size() > 1) { _assertClassBrand(_BaseTransformation_brand, this, _chooseNextSelectionLayer).call(this); const nextLayerCoords = _assertClassBrand(_BaseTransformation_brand, this, _findNonHiddenZeroBasedCoordsInSelection).call(this, 'forward'); if (nextLayerCoords !== null) { newCoords.assign(nextLayerCoords); } } zeroBasedCoords.assign(newCoords); } } else if (zeroBasedCoords.row < 0) { const isActionInterrupted = (0, _object.createObjectPropListener)(autoWrapCol); const previousColumn = zeroBasedCoords.col - 1; const isColumnOutOfRange = previousColumn < 0; const newCoords = this.tableApi.createCellCoords(height + zeroBasedCoords.row, isColumnOutOfRange ? width + previousColumn : previousColumn); this.runLocalHooks('beforeColumnWrap', isActionInterrupted, _assertClassBrand(_BaseTransformation_brand, this, _zeroBasedToVisualCoords).call(this, newCoords), isColumnOutOfRange); if (autoWrapCol) { if (this.shouldSwitchSelectionLayer() && isColumnOutOfRange && _classPrivateFieldGet(_range, this).size() > 1) { _assertClassBrand(_BaseTransformation_brand, this, _choosePreviousSelectionLayer).call(this); const nextLayerCoords = _assertClassBrand(_BaseTransformation_brand, this, _findNonHiddenZeroBasedCoordsInSelection).call(this, 'backward'); if (nextLayerCoords !== null) { newCoords.assign(nextLayerCoords); } } zeroBasedCoords.assign(newCoords); } } // the range size may be changed after the column wrap, so we need to recalculate it for row wrap ({ width, height } = _assertClassBrand(_BaseTransformation_brand, this, _getTableSize).call(this)); if (zeroBasedCoords.col >= width) { const isActionInterrupted = (0, _object.createObjectPropListener)(createMissingRecords && minSpareCols > 0); const nextRow = zeroBasedCoords.row + 1; const isRowOutOfRange = nextRow >= height; const newCoords = this.tableApi.createCellCoords(isRowOutOfRange ? nextRow - height : nextRow, zeroBasedCoords.col - width); this.runLocalHooks('beforeRowWrap', isActionInterrupted, _assertClassBrand(_BaseTransformation_brand, this, _zeroBasedToVisualCoords).call(this, newCoords), isRowOutOfRange); if (isActionInterrupted.value) { this.runLocalHooks('insertColRequire', this.countRenderableColumns()); } else if (autoWrapRow) { if (this.shouldSwitchSelectionLayer() && isRowOutOfRange && _classPrivateFieldGet(_range, this).size() > 1) { _assertClassBrand(_BaseTransformation_brand, this, _chooseNextSelectionLayer).call(this); const nextLayerCoords = _assertClassBrand(_BaseTransformation_brand, this, _findNonHiddenZeroBasedCoordsInSelection).call(this, 'forward'); if (nextLayerCoords !== null) { newCoords.assign(nextLayerCoords); } } zeroBasedCoords.assign(newCoords); } } else if (zeroBasedCoords.col < 0) { const isActionInterrupted = (0, _object.createObjectPropListener)(autoWrapRow); const previousRow = zeroBasedCoords.row - 1; const isRowOutOfRange = previousRow < 0; const newCoords = this.tableApi.createCellCoords(isRowOutOfRange ? height + previousRow : previousRow, width + zeroBasedCoords.col); this.runLocalHooks('beforeRowWrap', isActionInterrupted, _assertClassBrand(_BaseTransformation_brand, this, _zeroBasedToVisualCoords).call(this, newCoords), isRowOutOfRange); if (autoWrapRow) { if (this.shouldSwitchSelectionLayer() && isRowOutOfRange && _classPrivateFieldGet(_range, this).size() > 1) { _assertClassBrand(_BaseTransformation_brand, this, _choosePreviousSelectionLayer).call(this); const nextLayerCoords = _assertClassBrand(_BaseTransformation_brand, this, _findNonHiddenZeroBasedCoordsInSelection).call(this, 'backward'); if (nextLayerCoords !== null) { newCoords.assign(nextLayerCoords); } } zeroBasedCoords.assign(newCoords); } } const { rowDir, colDir } = _assertClassBrand(_BaseTransformation_brand, this, _clampCoords).call(this, zeroBasedCoords); rowTransformDir = rowDir; colTransformDir = colDir; visualCoords = _assertClassBrand(_BaseTransformation_brand, this, _zeroBasedToVisualCoords).call(this, zeroBasedCoords); } this.runLocalHooks('afterTransformStart', visualCoords, rowTransformDir, colTransformDir); return { selectionLayer: _classPrivateFieldGet(_activeLayerIndex, this), visualCoords }; } /** * Sets selection end cell relative to the current selection end cell (if possible). * * @param {number} rowDelta Rows number to move, value can be passed as negative number. * @param {number} colDelta Columns number to move, value can be passed as negative number. * @returns {{selectionLayer: number, visualCoords: CellCoords}} Visual coordinates with selection layer after transformation. */ transformEnd(rowDelta, colDelta) { _classPrivateFieldSet(_offset, this, this.calculateOffset()); const delta = this.tableApi.createCellCoords(rowDelta, colDelta); const cellRange = this.getCurrentSelection(); const highlightRenderableCoords = this.tableApi.visualToRenderableCoords(cellRange.highlight); const toRow = _assertClassBrand(_BaseTransformation_brand, this, _findFirstNonHiddenZeroBasedRow).call(this, cellRange.to.row, cellRange.from.row); const toColumn = _assertClassBrand(_BaseTransformation_brand, this, _findFirstNonHiddenZeroBasedColumn).call(this, cellRange.to.col, cellRange.from.col); const visualCoords = cellRange.to.clone(); let rowTransformDir = 0; let colTransformDir = 0; this.runLocalHooks('beforeTransformEnd', delta); if (highlightRenderableCoords.row !== null && highlightRenderableCoords.col !== null && toRow !== null && toColumn !== null) { const { row: highlightRow, col: highlightColumn } = _assertClassBrand(_BaseTransformation_brand, this, _visualToZeroBasedCoords).call(this, cellRange.highlight); const coords = this.tableApi.createCellCoords(toRow + delta.row, toColumn + delta.col); const topStartCorner = cellRange.getTopStartCorner(); const topEndCorner = cellRange.getTopEndCorner(); const bottomEndCorner = cellRange.getBottomEndCorner(); if (delta.col < 0 && toColumn >= highlightColumn && coords.col < highlightColumn) { const columnRestDelta = coords.col - highlightColumn; coords.col = _assertClassBrand(_BaseTransformation_brand, this, _findFirstNonHiddenZeroBasedColumn).call(this, topStartCorner.col, topEndCorner.col) + columnRestDelta; } else if (delta.col > 0 && toColumn <= highlightColumn && coords.col > highlightColumn) { const endColumnIndex = _assertClassBrand(_BaseTransformation_brand, this, _findFirstNonHiddenZeroBasedColumn).call(this, topEndCorner.col, topStartCorner.col); const columnRestDelta = Math.max(coords.col - endColumnIndex, 1); coords.col = endColumnIndex + columnRestDelta; } if (delta.row < 0 && toRow >= highlightRow && coords.row < highlightRow) { const rowRestDelta = coords.row - highlightRow; coords.row = _assertClassBrand(_BaseTransformation_brand, this, _findFirstNonHiddenZeroBasedRow).call(this, topStartCorner.row, bottomEndCorner.row) + rowRestDelta; } else if (delta.row > 0 && toRow <= highlightRow && coords.row > highlightRow) { const bottomRowIndex = _assertClassBrand(_BaseTransformation_brand, this, _findFirstNonHiddenZeroBasedRow).call(this, bottomEndCorner.row, topStartCorner.row); const rowRestDelta = Math.max(coords.row - bottomRowIndex, 1); coords.row = bottomRowIndex + rowRestDelta; } const { rowDir, colDir } = _assertClassBrand(_BaseTransformation_brand, this, _clampCoords).call(this, coords); rowTransformDir = rowDir; colTransformDir = colDir; const newVisualCoords = _assertClassBrand(_BaseTransformation_brand, this, _zeroBasedToVisualCoords).call(this, coords); if (delta.row === 0 && delta.col !== 0) { visualCoords.col = newVisualCoords.col; } else if (delta.row !== 0 && delta.col === 0) { visualCoords.row = newVisualCoords.row; } else { visualCoords.row = newVisualCoords.row; visualCoords.col = newVisualCoords.col; } } this.runLocalHooks('afterTransformEnd', visualCoords, rowTransformDir, colTransformDir); return { selectionLayer: _classPrivateFieldGet(_activeLayerIndex, this), visualCoords }; } /** * Abstract method that child classes must implement to calculate offset coordinates based on * the current processed selection layer. */ calculateOffset() { throw new Error('`calculateOffset` is not implemented'); } /** * Abstract method that child classes must implement to provide the count of renderable rows * based on their specific transformation logic. */ countRenderableRows() { throw new Error('`countRenderableRows` is not implemented'); } /** * Abstract method that child classes must implement to provide the count of renderable columns * based on their specific transformation logic. */ countRenderableColumns() { throw new Error('`countRenderableColumns` is not implemented'); } /** * Determines whether selection layer switching should occur during transformation. * Child classes can override this method to control the behavior. */ shouldSwitchSelectionLayer() { throw new Error('`shouldSwitchSelectionLayer` is not implemented'); } } exports.BaseTransformation = BaseTransformation; function _chooseNextSelectionLayer() { const layerIndex = _classPrivateFieldGet(_activeLayerIndex, this) + 1; this.setActiveLayerIndex(layerIndex >= _classPrivateFieldGet(_range, this).size() ? 0 : layerIndex); _classPrivateFieldSet(_offset, this, this.calculateOffset()); } /** * Chooses the previous selection layer as active one. */ function _choosePreviousSelectionLayer() { const layerIndex = _classPrivateFieldGet(_activeLayerIndex, this) - 1; this.setActiveLayerIndex(layerIndex < 0 ? _classPrivateFieldGet(_range, this).size() - 1 : layerIndex); _classPrivateFieldSet(_offset, this, this.calculateOffset()); } /** * Clamps the coords to make sure they points to the cell (or header) in the table range. * * @param {CellCoords} zeroBasedCoords The coords object to clamp. * @returns {{rowDir: 1|0|-1, colDir: 1|0|-1}} */ function _clampCoords(zeroBasedCoords) { const { width, height } = _assertClassBrand(_BaseTransformation_brand, this, _getTableSize).call(this); let rowDir = 0; let colDir = 0; if (zeroBasedCoords.row < 0) { rowDir = -1; zeroBasedCoords.row = 0; } else if (zeroBasedCoords.row > 0 && zeroBasedCoords.row >= height) { rowDir = 1; zeroBasedCoords.row = height - 1; } if (zeroBasedCoords.col < 0) { colDir = -1; zeroBasedCoords.col = 0; } else if (zeroBasedCoords.col > 0 && zeroBasedCoords.col >= width) { colDir = 1; zeroBasedCoords.col = width - 1; } return { rowDir, colDir }; } /** * Gets the table size in number of rows with headers as "height" and number of columns with * headers as "width". * * @returns {{width: number, height: number}} */ function _getTableSize() { return { width: _classPrivateFieldGet(_offset, this).x + this.countRenderableColumns(), height: _classPrivateFieldGet(_offset, this).y + this.countRenderableRows() }; } /** * Finds non-hidden zero-based coordinates in the current selection range. * * @param {'forward' | 'backward'} direction The direction to search within the selection. * @returns {CellCoords | null} Zero-based coordinates or null if not found. */ function _findNonHiddenZeroBasedCoordsInSelection(direction) { if (!['forward', 'backward'].includes(direction)) { return null; } const topStartCoords = this.getCurrentSelection().getTopStartCorner(); const bottomEndCoords = this.getCurrentSelection().getBottomEndCorner(); const [visualRowFrom, visualRowTo, visualColumnFrom, visualColumnTo] = direction === 'forward' ? [topStartCoords.row, bottomEndCoords.row, topStartCoords.col, bottomEndCoords.col] : [bottomEndCoords.row, topStartCoords.row, bottomEndCoords.col, topStartCoords.col]; const zeroBasedRow = _assertClassBrand(_BaseTransformation_brand, this, _findFirstNonHiddenZeroBasedRow).call(this, visualRowFrom, visualRowTo); const zeroBasedCol = _assertClassBrand(_BaseTransformation_brand, this, _findFirstNonHiddenZeroBasedColumn).call(this, visualColumnFrom, visualColumnTo); if (zeroBasedRow === null || zeroBasedCol === null) { return null; } return this.tableApi.createCellCoords(zeroBasedRow, zeroBasedCol); } /** * Finds the first non-hidden zero-based row in the table range. * * @param {number} visualRowFrom The visual row from which the search should start. * @param {number} visualRowTo The visual row to which the search should end. * @returns {number | null} */ function _findFirstNonHiddenZeroBasedRow(visualRowFrom, visualRowTo) { const row = this.tableApi.findFirstNonHiddenRenderableRow(visualRowFrom, visualRowTo); if (row === null) { return null; } return _classPrivateFieldGet(_offset, this).y + row; } /** * Finds the first non-hidden zero-based column in the table range. * * @param {number} visualColumnFrom The visual column from which the search should start. * @param {number} visualColumnTo The visual column to which the search should end. * @returns {number | null} */ function _findFirstNonHiddenZeroBasedColumn(visualColumnFrom, visualColumnTo) { const column = this.tableApi.findFirstNonHiddenRenderableColumn(visualColumnFrom, visualColumnTo); if (column === null) { return null; } return _classPrivateFieldGet(_offset, this).x + column; } /** * Translates the visual coordinates to zero-based ones. * * @param {CellCoords} visualCoords The visual coords to process. * @returns {CellCoords} */ function _visualToZeroBasedCoords(visualCoords) { const { row, col } = this.tableApi.visualToRenderableCoords(visualCoords); if (row === null || col === null) { throw new Error('Renderable coords are not visible.'); } return this.tableApi.createCellCoords(_classPrivateFieldGet(_offset, this).y + row, _classPrivateFieldGet(_offset, this).x + col); } /** * Translates the zero-based coordinates to visual ones. * * @param {CellCoords} zeroBasedCoords The coordinates to process. * @returns {CellCoords} */ function _zeroBasedToVisualCoords(zeroBasedCoords) { const coords = zeroBasedCoords.clone(); coords.col = zeroBasedCoords.col - _classPrivateFieldGet(_offset, this).x; coords.row = zeroBasedCoords.row - _classPrivateFieldGet(_offset, this).y; return this.tableApi.renderableToVisualCoords(coords); } (0, _object.mixin)(BaseTransformation, _localHooks.default);