UNPKG

handsontable

Version:

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

411 lines (402 loc) • 15.4 kB
import "core-js/modules/es.error.cause.js"; import "core-js/modules/esnext.iterator.constructor.js"; import "core-js/modules/esnext.iterator.every.js"; 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 { extend } from "../../../helpers/object.mjs"; import { arrayEach } from "../../../helpers/array.mjs"; /** * Class responsible for all of the Autofill-related operations on merged cells. * * @private * @class AutofillCalculations */ class AutofillCalculations { constructor(plugin) { /** * Reference to the Merge Cells plugin. * * @type {MergeCells} */ _defineProperty(this, "plugin", void 0); /** * Reference to the MergedCellsCollection class instance. * * @type {MergedCellsCollection} */ _defineProperty(this, "mergedCellsCollection", void 0); /** * Cache of the currently processed autofill data. * * @private * @type {object} */ _defineProperty(this, "currentFillData", null); this.plugin = plugin; this.mergedCellsCollection = this.plugin.mergedCellsCollection; } /** * Get the direction of the autofill process. * * @param {Array} baseArea The selection area. * @param {Array} fullArea The final area (base + drag). * @returns {string} `up`, `down`, `left` or `right`. */ getDirection(baseArea, fullArea) { let direction = null; if (fullArea[0] === baseArea[0] && fullArea[1] === baseArea[1] && fullArea[3] === baseArea[3]) { direction = 'down'; } else if (fullArea[2] === baseArea[2] && fullArea[1] === baseArea[1] && fullArea[3] === baseArea[3]) { direction = 'up'; } else if (fullArea[1] === baseArea[1] && fullArea[2] === baseArea[2]) { direction = 'right'; } else { direction = 'left'; } return direction; } /** * Snap the drag area to the farthest merged cell, so it won't clip any of the merged cells. * * @param {Array} baseArea The base selected area. * @param {Array} fullArea The drag area. * @param {string} dragDirection The autofill drag direction. * @param {Array} foundMergedCells MergeCellCoords found in the base selection area. * @returns {Array} The new drag area. */ snapDragArea(baseArea, fullArea, dragDirection, foundMergedCells) { const newDragArea = fullArea.slice(0); const fillSize = this.getAutofillSize(baseArea, fullArea, dragDirection); const [baseAreaStartRow, baseAreaStartColumn, baseAreaEndRow, baseAreaEndColumn] = baseArea; const verticalDirection = ['up', 'down'].indexOf(dragDirection) > -1; const fullCycle = verticalDirection ? baseAreaEndRow - baseAreaStartRow + 1 : baseAreaEndColumn - baseAreaStartColumn + 1; const fulls = Math.floor(fillSize / fullCycle) * fullCycle; const partials = fillSize - fulls; const farthestCollection = this.getFarthestCollection(baseArea, fullArea, dragDirection, foundMergedCells); if (farthestCollection) { if (dragDirection === 'down') { const fill = farthestCollection.row + farthestCollection.rowspan - baseAreaStartRow - partials; const newLimit = newDragArea[2] + fill; if (newLimit >= this.plugin.hot.countRows()) { newDragArea[2] -= partials; } else { newDragArea[2] += partials ? fill : 0; } } else if (dragDirection === 'right') { const fill = farthestCollection.col + farthestCollection.colspan - baseAreaStartColumn - partials; const newLimit = newDragArea[3] + fill; if (newLimit >= this.plugin.hot.countCols()) { newDragArea[3] -= partials; } else { newDragArea[3] += partials ? fill : 0; } } else if (dragDirection === 'up') { const fill = baseAreaEndRow - partials - farthestCollection.row + 1; const newLimit = newDragArea[0] + fill; if (newLimit < 0) { newDragArea[0] += partials; } else { newDragArea[0] -= partials ? fill : 0; } } else if (dragDirection === 'left') { const fill = baseAreaEndColumn - partials - farthestCollection.col + 1; const newLimit = newDragArea[1] + fill; if (newLimit < 0) { newDragArea[1] += partials; } else { newDragArea[1] -= partials ? fill : 0; } } } this.updateCurrentFillCache({ baseArea, dragDirection, foundMergedCells, fillSize, dragArea: newDragArea, cycleLength: fullCycle }); return newDragArea; } /** * Update the current fill cache with the provided object. * * @private * @param {object} updateObject The current filled object cache. */ updateCurrentFillCache(updateObject) { if (!this.currentFillData) { this.currentFillData = {}; } extend(this.currentFillData, updateObject); } /** * Get the "length" of the drag area. * * @private * @param {Array} baseArea The base selection area. * @param {Array} fullArea The drag area (containing the base area). * @param {string} direction The drag direction. * @returns {number|null} The "length" (height or width, depending on the direction) of the drag. */ getAutofillSize(baseArea, fullArea, direction) { const [baseAreaStartRow, baseAreaStartColumn, baseAreaEndRow, baseAreaEndColumn] = baseArea; const [dragAreaStartRow, dragAreaStartColumn, dragAreaEndRow, dragAreaEndColumn] = fullArea; switch (direction) { case 'up': return baseAreaStartRow - dragAreaStartRow; case 'down': return dragAreaEndRow - baseAreaEndRow; case 'left': return baseAreaStartColumn - dragAreaStartColumn; case 'right': return dragAreaEndColumn - baseAreaEndColumn; default: return null; } } /** * Trim the default drag area (containing the selection area) to the drag-only area. * * @private * @param {Array} baseArea The base selection area. * @param {Array} fullArea The base selection area extended by the drag area. * @param {string} direction Drag direction. * @returns {Array|null} Array representing the drag area coordinates. */ getDragArea(baseArea, fullArea, direction) { const [baseAreaStartRow, baseAreaStartColumn, baseAreaEndRow, baseAreaEndColumn] = baseArea; const [fullAreaStartRow, fullAreaStartColumn, fullAreaEndRow, fullAreaEndColumn] = fullArea; switch (direction) { case 'up': return [fullAreaStartRow, fullAreaStartColumn, baseAreaStartRow - 1, baseAreaEndColumn]; case 'down': { const mergedCell = this.mergedCellsCollection.get(fullAreaEndRow, baseAreaEndColumn); const rowShift = mergedCell ? mergedCell.rowspan - 1 : 0; return [baseAreaEndRow + 1, baseAreaStartColumn, fullAreaEndRow + rowShift, baseAreaEndColumn]; } case 'left': return [fullAreaStartRow, fullAreaStartColumn, baseAreaEndRow, baseAreaStartColumn - 1]; case 'right': { const mergedCell = this.mergedCellsCollection.get(fullAreaEndRow, baseAreaEndColumn); const columnShift = mergedCell ? mergedCell.colspan - 1 : 0; return [baseAreaStartRow, baseAreaEndColumn + columnShift, fullAreaEndRow, fullAreaEndColumn]; } default: return null; } } /** * Get the to-be-farthest merged cell in the newly filled area. * * @private * @param {Array} baseArea The base selection area. * @param {Array} fullArea The drag area (containing the base area). * @param {string} direction The drag direction. * @param {Array} mergedCellArray Array of the merged cells found in the base area. * @returns {MergedCellCoords|null} */ getFarthestCollection(baseArea, fullArea, direction, mergedCellArray) { const [baseAreaStartRow, baseAreaStartColumn, baseAreaEndRow, baseAreaEndColumn] = baseArea; const verticalDirection = ['up', 'down'].indexOf(direction) > -1; const baseEnd = verticalDirection ? baseAreaEndRow : baseAreaEndColumn; const baseStart = verticalDirection ? baseAreaStartRow : baseAreaStartColumn; const fillSize = this.getAutofillSize(baseArea, fullArea, direction); const fullCycle = verticalDirection ? baseAreaEndRow - baseAreaStartRow + 1 : baseAreaEndColumn - baseAreaStartColumn + 1; const fulls = Math.floor(fillSize / fullCycle) * fullCycle; const partials = fillSize - fulls; let inclusionFunctionName = null; let farthestCollection = null; let endOfDragRecreationIndex = null; switch (direction) { case 'up': inclusionFunctionName = 'includesVertically'; endOfDragRecreationIndex = baseEnd - partials + 1; break; case 'left': inclusionFunctionName = 'includesHorizontally'; endOfDragRecreationIndex = baseEnd - partials + 1; break; case 'down': inclusionFunctionName = 'includesVertically'; endOfDragRecreationIndex = baseStart + partials - 1; break; case 'right': inclusionFunctionName = 'includesHorizontally'; endOfDragRecreationIndex = baseStart + partials - 1; break; default: } arrayEach(mergedCellArray, currentCollection => { if (currentCollection[inclusionFunctionName](endOfDragRecreationIndex) && currentCollection.isFarther(farthestCollection, direction)) { farthestCollection = currentCollection; } }); return farthestCollection; } /** * Recreate the merged cells after the autofill process. * * @param {Array} changes Changes made. */ recreateAfterDataPopulation(changes) { if (!this.currentFillData) { return; } const fillRange = this.getRangeFromChanges(changes); const foundMergedCells = this.currentFillData.foundMergedCells; const dragDirection = this.currentFillData.dragDirection; const inBounds = (current, offset) => { switch (dragDirection) { case 'up': return current.row - offset >= fillRange.from.row; case 'down': return current.row + current.rowspan - 1 + offset <= fillRange.to.row; case 'left': return current.col - offset >= fillRange.from.column; case 'right': return current.col + current.colspan - 1 + offset <= fillRange.to.column; default: return null; } }; let fillOffset = 0; let current = null; let multiplier = 1; do { for (let j = 0; j < foundMergedCells.length; j += 1) { current = foundMergedCells[j]; fillOffset = multiplier * this.currentFillData.cycleLength; if (inBounds(current, fillOffset)) { switch (dragDirection) { case 'up': this.plugin.mergedCellsCollection.add({ row: current.row - fillOffset, rowspan: current.rowspan, col: current.col, colspan: current.colspan }, true); break; case 'down': this.plugin.mergedCellsCollection.add({ row: current.row + fillOffset, rowspan: current.rowspan, col: current.col, colspan: current.colspan }, true); break; case 'left': this.plugin.mergedCellsCollection.add({ row: current.row, rowspan: current.rowspan, col: current.col - fillOffset, colspan: current.colspan }, true); break; case 'right': this.plugin.mergedCellsCollection.add({ row: current.row, rowspan: current.rowspan, col: current.col + fillOffset, colspan: current.colspan }, true); break; default: } } if (j === foundMergedCells.length - 1) { multiplier += 1; } } } while (inBounds(current, fillOffset)); this.currentFillData = null; this.plugin.hot.render(); if (foundMergedCells.length > 0) { this.plugin.ifChromeForceRepaint(); } } /** * Get the drag range from the changes made. * * @private * @param {Array} changes The changes made. * @returns {object} Object with `from` and `to` properties, both containing `row` and `column` keys. */ getRangeFromChanges(changes) { const rows = { min: null, max: null }; const columns = { min: null, max: null }; arrayEach(changes, change => { const rowIndex = change[0]; const columnIndex = this.plugin.hot.propToCol(change[1]); if (rows.min === null || rowIndex < rows.min) { rows.min = rowIndex; } if (rows.max === null || rowIndex > rows.max) { rows.max = rowIndex; } if (columns.min === null || columnIndex < columns.min) { columns.min = columnIndex; } if (columns.max === null || columnIndex > columns.max) { columns.max = columnIndex; } }); return { from: { row: rows.min, column: columns.min }, to: { row: rows.max, column: columns.max } }; } /** * Check if the drag area contains any merged cells. * * @param {Array} baseArea The base selection area. * @param {Array} fullArea The base area extended by the drag area. * @param {string} direction Drag direction. * @returns {boolean} */ dragAreaOverlapsCollections(baseArea, fullArea, direction) { const dragArea = this.getDragArea(baseArea, fullArea, direction); const [dragAreaStartRow, dragAreaStartColumn, dragAreaEndRow, dragAreaEndColumn] = dragArea; const topLeft = this.plugin.hot._createCellCoords(dragAreaStartRow, dragAreaStartColumn); const bottomRight = this.plugin.hot._createCellCoords(dragAreaEndRow, dragAreaEndColumn); const dragRange = this.plugin.hot._createCellRange(topLeft, topLeft, bottomRight); const mergedCellsWithPartials = this.mergedCellsCollection.getWithinRange(dragRange, true); if (mergedCellsWithPartials.length === 0) { return false; } const mergedCellsWithoutPartials = this.mergedCellsCollection.getWithinRange(dragRange, false); if (mergedCellsWithoutPartials.length === 0) { return true; } if (direction === 'up' || direction === 'down') { return !mergedCellsWithoutPartials.every(_ref => { let { colspan } = _ref; return colspan === dragRange.getWidth(); }); } return !mergedCellsWithoutPartials.every(_ref2 => { let { rowspan } = _ref2; return rowspan === dragRange.getHeight(); }); } } export default AutofillCalculations;