123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637 |
- import Hooks from './../../pluginHooks';
- import { registerPlugin } from './../../plugins';
- import { stopImmediatePropagation } from './../../helpers/dom/event';
- import { CellCoords, CellRange, Table } from './../../3rdparty/walkontable/src';
- function CellInfoCollection() {
- var collection = [];
- collection.getInfo = function (row, col) {
- for (var i = 0, ilen = this.length; i < ilen; i++) {
- if (this[i].row <= row && this[i].row + this[i].rowspan - 1 >= row && this[i].col <= col && this[i].col + this[i].colspan - 1 >= col) {
- return this[i];
- }
- }
- };
- collection.setInfo = function (info) {
- for (var i = 0, ilen = this.length; i < ilen; i++) {
- if (this[i].row === info.row && this[i].col === info.col) {
- this[i] = info;
- return;
- }
- }
- this.push(info);
- };
- collection.removeInfo = function (row, col) {
- for (var i = 0, ilen = this.length; i < ilen; i++) {
- if (this[i].row === row && this[i].col === col) {
- this.splice(i, 1);
- break;
- }
- }
- };
- return collection;
- }
- /**
- * Plugin used to merge cells in Handsontable.
- *
- * @private
- * @plugin MergeCells
- * @class MergeCells
- */
- function MergeCells(mergeCellsSetting) {
- this.mergedCellInfoCollection = new CellInfoCollection();
- if (Array.isArray(mergeCellsSetting)) {
- for (var i = 0, ilen = mergeCellsSetting.length; i < ilen; i++) {
- this.mergedCellInfoCollection.setInfo(mergeCellsSetting[i]);
- }
- }
- }
- /**
- * @param cellRange (CellRange)
- */
- MergeCells.prototype.canMergeRange = function (cellRange) {
- // is more than one cell selected
- return !cellRange.isSingle();
- };
- MergeCells.prototype.mergeRange = function (cellRange) {
- if (!this.canMergeRange(cellRange)) {
- return;
- }
- // normalize top left corner
- var topLeft = cellRange.getTopLeftCorner();
- var bottomRight = cellRange.getBottomRightCorner();
- var mergeParent = {};
- mergeParent.row = topLeft.row;
- mergeParent.col = topLeft.col;
- // TD has rowspan == 1 by default. rowspan == 2 means spread over 2 cells
- mergeParent.rowspan = bottomRight.row - topLeft.row + 1;
- mergeParent.colspan = bottomRight.col - topLeft.col + 1;
- this.mergedCellInfoCollection.setInfo(mergeParent);
- };
- MergeCells.prototype.mergeOrUnmergeSelection = function (cellRange) {
- var info = this.mergedCellInfoCollection.getInfo(cellRange.from.row, cellRange.from.col);
- if (info) {
- // unmerge
- this.unmergeSelection(cellRange.from);
- } else {
- // merge
- this.mergeSelection(cellRange);
- }
- };
- MergeCells.prototype.mergeSelection = function (cellRange) {
- this.mergeRange(cellRange);
- };
- MergeCells.prototype.unmergeSelection = function (cellRange) {
- var info = this.mergedCellInfoCollection.getInfo(cellRange.row, cellRange.col);
- this.mergedCellInfoCollection.removeInfo(info.row, info.col);
- };
- MergeCells.prototype.applySpanProperties = function (TD, row, col) {
- var info = this.mergedCellInfoCollection.getInfo(row, col);
- if (info) {
- if (info.row === row && info.col === col) {
- TD.setAttribute('rowspan', info.rowspan);
- TD.setAttribute('colspan', info.colspan);
- } else {
- TD.removeAttribute('rowspan');
- TD.removeAttribute('colspan');
- TD.style.display = 'none';
- }
- } else {
- TD.removeAttribute('rowspan');
- TD.removeAttribute('colspan');
- }
- };
- MergeCells.prototype.modifyTransform = function (hook, currentSelectedRange, delta) {
- var sameRowspan = function sameRowspan(merged, coords) {
- if (coords.row >= merged.row && coords.row <= merged.row + merged.rowspan - 1) {
- return true;
- }
- return false;
- },
- sameColspan = function sameColspan(merged, coords) {
- if (coords.col >= merged.col && coords.col <= merged.col + merged.colspan - 1) {
- return true;
- }
- return false;
- },
- getNextPosition = function getNextPosition(newDelta) {
- return new CellCoords(currentSelectedRange.to.row + newDelta.row, currentSelectedRange.to.col + newDelta.col);
- };
- var newDelta = {
- row: delta.row,
- col: delta.col
- };
- if (hook == 'modifyTransformStart') {
- /* eslint-disable block-scoped-var */
- var nextPosition;
- if (!this.lastDesiredCoords) {
- this.lastDesiredCoords = new CellCoords(null, null);
- }
- var currentPosition = new CellCoords(currentSelectedRange.highlight.row, currentSelectedRange.highlight.col),
- // if current position's parent is a merged range, returns it
- mergedParent = this.mergedCellInfoCollection.getInfo(currentPosition.row, currentPosition.col),
- currentRangeContainsMerge; // if current range contains a merged range
- for (var i = 0, mergesLength = this.mergedCellInfoCollection.length; i < mergesLength; i++) {
- var range = this.mergedCellInfoCollection[i];
- range = new CellCoords(range.row + range.rowspan - 1, range.col + range.colspan - 1);
- if (currentSelectedRange.includes(range)) {
- currentRangeContainsMerge = true;
- break;
- }
- }
- if (mergedParent) {
- // only merge selected
- var mergeTopLeft = new CellCoords(mergedParent.row, mergedParent.col);
- var mergeBottomRight = new CellCoords(mergedParent.row + mergedParent.rowspan - 1, mergedParent.col + mergedParent.colspan - 1);
- var mergeRange = new CellRange(mergeTopLeft, mergeTopLeft, mergeBottomRight);
- if (!mergeRange.includes(this.lastDesiredCoords)) {
- this.lastDesiredCoords = new CellCoords(null, null); // reset outdated version of lastDesiredCoords
- }
- newDelta.row = this.lastDesiredCoords.row ? this.lastDesiredCoords.row - currentPosition.row : newDelta.row;
- newDelta.col = this.lastDesiredCoords.col ? this.lastDesiredCoords.col - currentPosition.col : newDelta.col;
- if (delta.row > 0) {
- // moving down
- newDelta.row = mergedParent.row + mergedParent.rowspan - 1 - currentPosition.row + delta.row;
- } else if (delta.row < 0) {
- // moving up
- newDelta.row = currentPosition.row - mergedParent.row + delta.row;
- }
- if (delta.col > 0) {
- // moving right
- newDelta.col = mergedParent.col + mergedParent.colspan - 1 - currentPosition.col + delta.col;
- } else if (delta.col < 0) {
- // moving left
- newDelta.col = currentPosition.col - mergedParent.col + delta.col;
- }
- }
- nextPosition = new CellCoords(currentSelectedRange.highlight.row + newDelta.row, currentSelectedRange.highlight.col + newDelta.col);
- var nextParentIsMerged = this.mergedCellInfoCollection.getInfo(nextPosition.row, nextPosition.col);
- if (nextParentIsMerged) {
- // skipping the invisible cells in the merge range
- this.lastDesiredCoords = nextPosition;
- newDelta = {
- row: nextParentIsMerged.row - currentPosition.row,
- col: nextParentIsMerged.col - currentPosition.col
- };
- }
- } else if (hook == 'modifyTransformEnd') {
- for (var _i = 0, _mergesLength = this.mergedCellInfoCollection.length; _i < _mergesLength; _i++) {
- var currentMerge = this.mergedCellInfoCollection[_i];
- var _mergeTopLeft = new CellCoords(currentMerge.row, currentMerge.col);
- var _mergeBottomRight = new CellCoords(currentMerge.row + currentMerge.rowspan - 1, currentMerge.col + currentMerge.colspan - 1);
- var mergedRange = new CellRange(_mergeTopLeft, _mergeTopLeft, _mergeBottomRight);
- var sharedBorders = currentSelectedRange.getBordersSharedWith(mergedRange);
- if (mergedRange.isEqual(currentSelectedRange)) {
- // only the merged range is selected
- currentSelectedRange.setDirection('NW-SE');
- } else if (sharedBorders.length > 0) {
- var mergeHighlighted = currentSelectedRange.highlight.isEqual(mergedRange.from);
- if (sharedBorders.indexOf('top') > -1) {
- // if range shares a border with the merged section, change range direction accordingly
- if (currentSelectedRange.to.isSouthEastOf(mergedRange.from) && mergeHighlighted) {
- currentSelectedRange.setDirection('NW-SE');
- } else if (currentSelectedRange.to.isSouthWestOf(mergedRange.from) && mergeHighlighted) {
- currentSelectedRange.setDirection('NE-SW');
- }
- } else if (sharedBorders.indexOf('bottom') > -1) {
- if (currentSelectedRange.to.isNorthEastOf(mergedRange.from) && mergeHighlighted) {
- currentSelectedRange.setDirection('SW-NE');
- } else if (currentSelectedRange.to.isNorthWestOf(mergedRange.from) && mergeHighlighted) {
- currentSelectedRange.setDirection('SE-NW');
- }
- }
- }
- nextPosition = getNextPosition(newDelta);
- var withinRowspan = sameRowspan(currentMerge, nextPosition),
- withinColspan = sameColspan(currentMerge, nextPosition);
- if (currentSelectedRange.includesRange(mergedRange) && (mergedRange.includes(nextPosition) || withinRowspan || withinColspan)) {
- // if next step overlaps a merged range, jump past it
- if (withinRowspan) {
- if (newDelta.row < 0) {
- newDelta.row -= currentMerge.rowspan - 1;
- } else if (newDelta.row > 0) {
- newDelta.row += currentMerge.rowspan - 1;
- }
- }
- if (withinColspan) {
- if (newDelta.col < 0) {
- newDelta.col -= currentMerge.colspan - 1;
- } else if (newDelta.col > 0) {
- newDelta.col += currentMerge.colspan - 1;
- }
- }
- }
- }
- }
- if (newDelta.row !== 0) {
- delta.row = newDelta.row;
- }
- if (newDelta.col !== 0) {
- delta.col = newDelta.col;
- }
- };
- MergeCells.prototype.shiftCollection = function (direction, index, count) {
- var shiftVector = [0, 0];
- switch (direction) {
- case 'right':
- shiftVector[0] += 1;
- break;
- case 'left':
- shiftVector[0] -= 1;
- break;
- case 'down':
- shiftVector[1] += 1;
- break;
- case 'up':
- shiftVector[1] -= 1;
- break;
- default:
- break;
- }
- for (var i = 0; i < this.mergedCellInfoCollection.length; i++) {
- var currentMerge = this.mergedCellInfoCollection[i];
- if (direction === 'right' || direction === 'left') {
- if (index <= currentMerge.col) {
- currentMerge.col += shiftVector[0];
- }
- } else if (index <= currentMerge.row) {
- currentMerge.row += shiftVector[1];
- }
- }
- };
- var beforeInit = function beforeInit() {
- var instance = this;
- var mergeCellsSetting = instance.getSettings().mergeCells;
- if (mergeCellsSetting) {
- if (!instance.mergeCells) {
- instance.mergeCells = new MergeCells(mergeCellsSetting);
- }
- }
- };
- var afterInit = function afterInit() {
- var instance = this;
- if (instance.mergeCells) {
- /**
- * Monkey patch Table.prototype.getCell to return TD for merged cell parent if asked for TD of a cell that is
- * invisible due to the merge. This is not the cleanest solution but there is a test case for it (merged cells scroll) so feel free to refactor it!
- */
- instance.view.wt.wtTable.getCell = function (coords) {
- if (instance.getSettings().mergeCells) {
- var mergeParent = instance.mergeCells.mergedCellInfoCollection.getInfo(coords.row, coords.col);
- if (mergeParent) {
- coords = mergeParent;
- }
- }
- return Table.prototype.getCell.call(this, coords);
- };
- }
- };
- var afterUpdateSettings = function afterUpdateSettings() {
- var instance = this;
- var mergeCellsSetting = instance.getSettings().mergeCells;
- if (mergeCellsSetting) {
- if (instance.mergeCells) {
- instance.mergeCells.mergedCellInfoCollection = new CellInfoCollection();
- if (Array.isArray(mergeCellsSetting)) {
- for (var i = 0, ilen = mergeCellsSetting.length; i < ilen; i++) {
- instance.mergeCells.mergedCellInfoCollection.setInfo(mergeCellsSetting[i]);
- }
- }
- } else {
- instance.mergeCells = new MergeCells(mergeCellsSetting);
- }
- } else if (instance.mergeCells) {
- // it doesn't actually turn off the plugin, just resets the settings. Need to refactor.
- instance.mergeCells.mergedCellInfoCollection = new CellInfoCollection();
- }
- };
- var onBeforeKeyDown = function onBeforeKeyDown(event) {
- if (!this.mergeCells) {
- return;
- }
- var ctrlDown = (event.ctrlKey || event.metaKey) && !event.altKey;
- if (ctrlDown) {
- if (event.keyCode === 77) {
- // CTRL + M
- this.mergeCells.mergeOrUnmergeSelection(this.getSelectedRange());
- this.render();
- stopImmediatePropagation(event);
- }
- }
- };
- var addMergeActionsToContextMenu = function addMergeActionsToContextMenu(defaultOptions) {
- if (!this.getSettings().mergeCells) {
- return;
- }
- defaultOptions.items.push({ name: '---------' });
- defaultOptions.items.push({
- key: 'mergeCells',
- name: function name() {
- var sel = this.getSelected();
- var info = this.mergeCells.mergedCellInfoCollection.getInfo(sel[0], sel[1]);
- if (info) {
- return 'Unmerge cells';
- }
- return 'Merge cells';
- },
- callback: function callback() {
- this.mergeCells.mergeOrUnmergeSelection(this.getSelectedRange());
- this.render();
- },
- disabled: function disabled() {
- return this.selection.selectedHeader.corner;
- }
- });
- };
- var afterRenderer = function afterRenderer(TD, row, col, prop, value, cellProperties) {
- if (this.mergeCells) {
- this.mergeCells.applySpanProperties(TD, row, col);
- }
- };
- var modifyTransformFactory = function modifyTransformFactory(hook) {
- return function (delta) {
- var mergeCellsSetting = this.getSettings().mergeCells;
- if (mergeCellsSetting) {
- var currentSelectedRange = this.getSelectedRange();
- this.mergeCells.modifyTransform(hook, currentSelectedRange, delta);
- if (hook === 'modifyTransformEnd') {
- // sanitize "from" (core.js will sanitize to)
- var totalRows = this.countRows();
- var totalCols = this.countCols();
- if (currentSelectedRange.from.row < 0) {
- currentSelectedRange.from.row = 0;
- } else if (currentSelectedRange.from.row > 0 && currentSelectedRange.from.row >= totalRows) {
- currentSelectedRange.from.row = currentSelectedRange.from - 1;
- }
- if (currentSelectedRange.from.col < 0) {
- currentSelectedRange.from.col = 0;
- } else if (currentSelectedRange.from.col > 0 && currentSelectedRange.from.col >= totalCols) {
- currentSelectedRange.from.col = totalCols - 1;
- }
- }
- }
- };
- };
- /**
- * While selecting cells with keyboard or mouse, make sure that rectangular area is expanded to the extent of the merged cell
- * @param coords
- */
- var beforeSetRangeEnd = function beforeSetRangeEnd(coords) {
- this.lastDesiredCoords = null; // unset lastDesiredCoords when selection is changed with mouse
- var mergeCellsSetting = this.getSettings().mergeCells;
- if (mergeCellsSetting) {
- var selRange = this.getSelectedRange();
- selRange.highlight = new CellCoords(selRange.highlight.row, selRange.highlight.col); // clone in case we will modify its reference
- selRange.to = coords;
- var rangeExpanded = false;
- do {
- rangeExpanded = false;
- for (var i = 0, ilen = this.mergeCells.mergedCellInfoCollection.length; i < ilen; i++) {
- var cellInfo = this.mergeCells.mergedCellInfoCollection[i];
- var mergedCellTopLeft = new CellCoords(cellInfo.row, cellInfo.col);
- var mergedCellBottomRight = new CellCoords(cellInfo.row + cellInfo.rowspan - 1, cellInfo.col + cellInfo.colspan - 1);
- var mergedCellRange = new CellRange(mergedCellTopLeft, mergedCellTopLeft, mergedCellBottomRight);
- if (selRange.expandByRange(mergedCellRange)) {
- coords.row = selRange.to.row;
- coords.col = selRange.to.col;
- rangeExpanded = true;
- }
- }
- } while (rangeExpanded);
- }
- };
- /**
- * Returns correct coordinates for merged start / end cells in selection for area borders
- * @param corners
- * @param className
- */
- var beforeDrawAreaBorders = function beforeDrawAreaBorders(corners, className) {
- if (className && className == 'area') {
- var mergeCellsSetting = this.getSettings().mergeCells;
- if (mergeCellsSetting) {
- var selRange = this.getSelectedRange();
- var startRange = new CellRange(selRange.from, selRange.from, selRange.from);
- var stopRange = new CellRange(selRange.to, selRange.to, selRange.to);
- for (var i = 0, ilen = this.mergeCells.mergedCellInfoCollection.length; i < ilen; i++) {
- var cellInfo = this.mergeCells.mergedCellInfoCollection[i];
- var mergedCellTopLeft = new CellCoords(cellInfo.row, cellInfo.col);
- var mergedCellBottomRight = new CellCoords(cellInfo.row + cellInfo.rowspan - 1, cellInfo.col + cellInfo.colspan - 1);
- var mergedCellRange = new CellRange(mergedCellTopLeft, mergedCellTopLeft, mergedCellBottomRight);
- if (startRange.expandByRange(mergedCellRange)) {
- corners[0] = startRange.from.row;
- corners[1] = startRange.from.col;
- }
- if (stopRange.expandByRange(mergedCellRange)) {
- corners[2] = stopRange.from.row;
- corners[3] = stopRange.from.col;
- }
- }
- }
- }
- };
- var afterGetCellMeta = function afterGetCellMeta(row, col, cellProperties) {
- var mergeCellsSetting = this.getSettings().mergeCells;
- if (mergeCellsSetting) {
- var mergeParent = this.mergeCells.mergedCellInfoCollection.getInfo(row, col);
- if (mergeParent && (mergeParent.row != row || mergeParent.col != col)) {
- cellProperties.copyable = false;
- }
- }
- };
- var afterViewportRowCalculatorOverride = function afterViewportRowCalculatorOverride(calc) {
- var mergeCellsSetting = this.getSettings().mergeCells;
- if (mergeCellsSetting) {
- var colCount = this.countCols();
- var mergeParent;
- for (var c = 0; c < colCount; c++) {
- mergeParent = this.mergeCells.mergedCellInfoCollection.getInfo(calc.startRow, c);
- if (mergeParent) {
- if (mergeParent.row < calc.startRow) {
- calc.startRow = mergeParent.row;
- return afterViewportRowCalculatorOverride.call(this, calc); // recursively search upwards
- }
- }
- mergeParent = this.mergeCells.mergedCellInfoCollection.getInfo(calc.endRow, c);
- if (mergeParent) {
- var mergeEnd = mergeParent.row + mergeParent.rowspan - 1;
- if (mergeEnd > calc.endRow) {
- calc.endRow = mergeEnd;
- return afterViewportRowCalculatorOverride.call(this, calc); // recursively search upwards
- }
- }
- }
- }
- };
- var afterViewportColumnCalculatorOverride = function afterViewportColumnCalculatorOverride(calc) {
- var mergeCellsSetting = this.getSettings().mergeCells;
- if (mergeCellsSetting) {
- var rowCount = this.countRows();
- var mergeParent;
- for (var r = 0; r < rowCount; r++) {
- mergeParent = this.mergeCells.mergedCellInfoCollection.getInfo(r, calc.startColumn);
- if (mergeParent) {
- if (mergeParent.col < calc.startColumn) {
- calc.startColumn = mergeParent.col;
- return afterViewportColumnCalculatorOverride.call(this, calc); // recursively search upwards
- }
- }
- mergeParent = this.mergeCells.mergedCellInfoCollection.getInfo(r, calc.endColumn);
- if (mergeParent) {
- var mergeEnd = mergeParent.col + mergeParent.colspan - 1;
- if (mergeEnd > calc.endColumn) {
- calc.endColumn = mergeEnd;
- return afterViewportColumnCalculatorOverride.call(this, calc); // recursively search upwards
- }
- }
- }
- }
- };
- var isMultipleSelection = function isMultipleSelection(isMultiple) {
- if (isMultiple && this.mergeCells) {
- var mergedCells = this.mergeCells.mergedCellInfoCollection,
- selectionRange = this.getSelectedRange();
- for (var group in mergedCells) {
- if (selectionRange.highlight.row == mergedCells[group].row && selectionRange.highlight.col == mergedCells[group].col && selectionRange.to.row == mergedCells[group].row + mergedCells[group].rowspan - 1 && selectionRange.to.col == mergedCells[group].col + mergedCells[group].colspan - 1) {
- return false;
- }
- }
- }
- return isMultiple;
- };
- function modifyAutofillRange(select, drag) {
- var mergeCellsSetting = this.getSettings().mergeCells;
- if (!mergeCellsSetting || this.selection.isMultiple()) {
- return;
- }
- var info = this.mergeCells.mergedCellInfoCollection.getInfo(select[0], select[1]);
- if (info) {
- select[0] = info.row;
- select[1] = info.col;
- select[2] = info.row + info.rowspan - 1;
- select[3] = info.col + info.colspan - 1;
- }
- }
- function onAfterCreateCol(col, count) {
- if (this.mergeCells) {
- this.mergeCells.shiftCollection('right', col, count);
- }
- }
- function onAfterRemoveCol(col, count) {
- if (this.mergeCells) {
- this.mergeCells.shiftCollection('left', col, count);
- }
- }
- function onAfterCreateRow(row, count) {
- if (this.mergeCells) {
- this.mergeCells.shiftCollection('down', row, count);
- }
- }
- function onAfterRemoveRow(row, count) {
- if (this.mergeCells) {
- this.mergeCells.shiftCollection('up', row, count);
- }
- }
- var hook = Hooks.getSingleton();
- hook.add('beforeInit', beforeInit);
- hook.add('afterInit', afterInit);
- hook.add('afterUpdateSettings', afterUpdateSettings);
- hook.add('beforeKeyDown', onBeforeKeyDown);
- hook.add('modifyTransformStart', modifyTransformFactory('modifyTransformStart'));
- hook.add('modifyTransformEnd', modifyTransformFactory('modifyTransformEnd'));
- hook.add('beforeSetRangeEnd', beforeSetRangeEnd);
- hook.add('beforeDrawBorders', beforeDrawAreaBorders);
- hook.add('afterIsMultipleSelection', isMultipleSelection);
- hook.add('afterRenderer', afterRenderer);
- hook.add('afterContextMenuDefaultOptions', addMergeActionsToContextMenu);
- hook.add('afterGetCellMeta', afterGetCellMeta);
- hook.add('afterViewportRowCalculatorOverride', afterViewportRowCalculatorOverride);
- hook.add('afterViewportColumnCalculatorOverride', afterViewportColumnCalculatorOverride);
- hook.add('modifyAutofillRange', modifyAutofillRange);
- hook.add('afterCreateCol', onAfterCreateCol);
- hook.add('afterRemoveCol', onAfterRemoveCol);
- hook.add('afterCreateRow', onAfterCreateRow);
- hook.add('afterRemoveRow', onAfterRemoveRow);
- export default MergeCells;
|