cellCoords.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. import { CellCoords, CellRange } from '../../3rdparty/walkontable/src/index';
  2. import { toSingleLine } from './../../helpers/templateLiteralTag';
  3. /**
  4. * The `MergedCellCoords` class represents a single merged cell.
  5. *
  6. * @class MergedCellCoords
  7. * @plugin MergeCells
  8. */
  9. class MergedCellCoords {
  10. constructor(row, column, rowspan, colspan) {
  11. /**
  12. * The index of the topmost merged cell row.
  13. *
  14. * @type {Number}
  15. */
  16. this.row = row;
  17. /**
  18. * The index of the leftmost column.
  19. *
  20. * @type {Number}
  21. */
  22. this.col = column;
  23. /**
  24. * The `rowspan` value of the merged cell.
  25. *
  26. * @type {Number}
  27. */
  28. this.rowspan = rowspan;
  29. /**
  30. * The `colspan` value of the merged cell.
  31. *
  32. * @type {Number}
  33. */
  34. this.colspan = colspan;
  35. /**
  36. * `true` only if the merged cell is bound to be removed.
  37. *
  38. * @type {Boolean}
  39. */
  40. this.removed = false;
  41. }
  42. /**
  43. * Get a warning message for when the declared merged cell data contains negative values.
  44. *
  45. * @param {Object} newMergedCell Object containg information about the merged cells that was about to be added.
  46. * @return {String}
  47. */
  48. static NEGATIVE_VALUES_WARNING(newMergedCell) {
  49. return toSingleLine`The merged cell declared with {row: ${newMergedCell.row}, col: ${newMergedCell.col}, rowspan:
  50. ${newMergedCell.rowspan}, colspan: ${newMergedCell.colspan}} contains negative values, which is not supported. It
  51. will not be added to the collection.`;
  52. }
  53. /**
  54. * Get a warning message for when the declared merged cell data contains values exceeding the table limits.
  55. *
  56. * @param {Object} newMergedCell Object containg information about the merged cells that was about to be added.
  57. * @return {String}
  58. */
  59. static IS_OUT_OF_BOUNDS_WARNING(newMergedCell) {
  60. return toSingleLine`The merged cell declared at [${newMergedCell.row}, ${newMergedCell.col}] is positioned (or positioned partially)
  61. outside of the table range. It was not added to the table, please fix your setup.`;
  62. }
  63. /**
  64. * Get a warning message for when the declared merged cell data represents a single cell.
  65. *
  66. * @param {Object} newMergedCell Object containg information about the merged cells that was about to be added.
  67. * @return {String}
  68. */
  69. static IS_SINGLE_CELL(newMergedCell) {
  70. return toSingleLine`The merged cell declared at [${newMergedCell.row}, ${newMergedCell.col}] has both "rowspan"
  71. and "colspan" declared as "1", which makes it a single cell. It cannot be added to the collection.`;
  72. }
  73. /**
  74. * Get a warning message for when the declared merged cell data contains "colspan" or "rowspan", that equals 0.
  75. *
  76. * @param {Object} newMergedCell Object containg information about the merged cells that was about to be added.
  77. * @return {String}
  78. */
  79. static ZERO_SPAN_WARNING(newMergedCell) {
  80. return toSingleLine`The merged cell declared at [${newMergedCell.row}, ${newMergedCell.col}] has "rowspan" or "colspan" declared as
  81. "0", which is not supported. It cannot be added to the collection.`;
  82. }
  83. /**
  84. * Check whether the values provided for a merged cell contain any negative values.
  85. *
  86. * @param {Object} mergedCellInfo Object containing the `row`, `col`, `rowspan` and `colspan` properties.
  87. * @return {Boolean}
  88. */
  89. static containsNegativeValues(mergedCellInfo) {
  90. return mergedCellInfo.row < 0 || mergedCellInfo.col < 0 || mergedCellInfo.rowspan < 0 || mergedCellInfo.colspan < 0;
  91. }
  92. /**
  93. * Check whether the provided merged cell information object represents a single cell.
  94. *
  95. * @private
  96. * @param {Object} mergedCellInfo An object with `row`, `col`, `rowspan` and `colspan` properties.
  97. * @return {Boolean}
  98. */
  99. static isSingleCell(mergedCellInfo) {
  100. return mergedCellInfo.colspan === 1 && mergedCellInfo.rowspan === 1;
  101. }
  102. /**
  103. * Check whether the provided merged cell information object contains a rowspan or colspan of 0.
  104. *
  105. * @private
  106. * @param {Object} mergedCellInfo An object with `row`, `col`, `rowspan` and `colspan` properties.
  107. * @return {Boolean}
  108. */
  109. static containsZeroSpan(mergedCellInfo) {
  110. return mergedCellInfo.colspan === 0 || mergedCellInfo.rowspan === 0;
  111. }
  112. /**
  113. * Check whether the provided merged cell object is to be declared out of bounds of the table.
  114. *
  115. * @param {Object} mergeCell Object containing the `row`, `col`, `rowspan` and `colspan` properties.
  116. * @param {Number} rowCount Number of rows in the table.
  117. * @param {Number} columnCount Number of rows in the table.
  118. * @return {Boolean}
  119. */
  120. static isOutOfBounds(mergeCell, rowCount, columnCount) {
  121. return mergeCell.row < 0 ||
  122. mergeCell.col < 0 ||
  123. mergeCell.row >= rowCount ||
  124. mergeCell.row + mergeCell.rowspan - 1 >= rowCount ||
  125. mergeCell.col >= columnCount ||
  126. mergeCell.col + mergeCell.colspan - 1 >= columnCount;
  127. }
  128. /**
  129. * Sanitize (prevent from going outside the boundaries) the merged cell.
  130. *
  131. * @param hotInstance
  132. */
  133. normalize(hotInstance) {
  134. const totalRows = hotInstance.countRows();
  135. const totalColumns = hotInstance.countCols();
  136. if (this.row < 0) {
  137. this.row = 0;
  138. } else if (this.row > totalRows - 1) {
  139. this.row = totalRows - 1;
  140. }
  141. if (this.col < 0) {
  142. this.col = 0;
  143. } else if (this.col > totalColumns - 1) {
  144. this.col = totalColumns - 1;
  145. }
  146. if (this.row + this.rowspan > totalRows - 1) {
  147. this.rowspan = totalRows - this.row;
  148. }
  149. if (this.col + this.colspan > totalColumns - 1) {
  150. this.colspan = totalColumns - this.col;
  151. }
  152. }
  153. /**
  154. * Returns `true` if the provided coordinates are inside the merged cell.
  155. *
  156. * @param {Number} row The row index.
  157. * @param {Number} column The column index.
  158. * @return {Boolean}
  159. */
  160. includes(row, column) {
  161. return this.row <= row && this.col <= column && this.row + this.rowspan - 1 >= row && this.col + this.colspan - 1 >= column;
  162. }
  163. /**
  164. * Returns `true` if the provided `column` property is within the column span of the merged cell.
  165. *
  166. * @param {Number} column The column index.
  167. * @return {Boolean}
  168. */
  169. includesHorizontally(column) {
  170. return this.col <= column && this.col + this.colspan - 1 >= column;
  171. }
  172. /**
  173. * Returns `true` if the provided `row` property is within the row span of the merged cell.
  174. *
  175. * @param {Number} row Row index.
  176. * @return {Boolean}
  177. */
  178. includesVertically(row) {
  179. return this.row <= row && this.row + this.rowspan - 1 >= row;
  180. }
  181. /**
  182. * Shift (and possibly resize, if needed) the merged cell.
  183. *
  184. * @param {Array} shiftVector 2-element array containing the information on the shifting in the `x` and `y` axis.
  185. * @param {Number} indexOfChange Index of the preceding change.
  186. * @returns {Boolean} Returns `false` if the whole merged cell was removed.
  187. */
  188. shift(shiftVector, indexOfChange) {
  189. const shiftValue = shiftVector[0] || shiftVector[1];
  190. const shiftedIndex = indexOfChange + Math.abs(shiftVector[0] || shiftVector[1]) - 1;
  191. const span = shiftVector[0] ? 'colspan' : 'rowspan';
  192. const index = shiftVector[0] ? 'col' : 'row';
  193. const changeStart = Math.min(indexOfChange, shiftedIndex);
  194. const changeEnd = Math.max(indexOfChange, shiftedIndex);
  195. const mergeStart = this[index];
  196. const mergeEnd = this[index] + this[span] - 1;
  197. if (mergeStart >= indexOfChange) {
  198. this[index] += shiftValue;
  199. }
  200. // adding rows/columns
  201. if (shiftValue > 0) {
  202. if (indexOfChange <= mergeEnd && indexOfChange > mergeStart) {
  203. this[span] += shiftValue;
  204. }
  205. // removing rows/columns
  206. } else if (shiftValue < 0) {
  207. // removing the whole merge
  208. if (changeStart <= mergeStart && changeEnd >= mergeEnd) {
  209. this.removed = true;
  210. return false;
  211. // removing the merge partially, including the beginning
  212. } else if (mergeStart >= changeStart && mergeStart <= changeEnd) {
  213. const removedOffset = changeEnd - mergeStart + 1;
  214. const preRemovedOffset = Math.abs(shiftValue) - removedOffset;
  215. this[index] -= preRemovedOffset + shiftValue;
  216. this[span] -= removedOffset;
  217. // removing the middle part of the merge
  218. } else if (mergeStart <= changeStart && mergeEnd >= changeEnd) {
  219. this[span] += shiftValue;
  220. // removing the end part of the merge
  221. } else if (mergeStart <= changeStart && mergeEnd >= changeStart && mergeEnd < changeEnd) {
  222. const removedPart = mergeEnd - changeStart + 1;
  223. this[span] -= removedPart;
  224. }
  225. }
  226. return true;
  227. }
  228. /**
  229. * Check if the second provided merged cell is "farther" in the provided direction.
  230. *
  231. * @param {MergedCellCoords} mergedCell The merged cell to check.
  232. * @param {String} direction Drag direction.
  233. * @return {Boolean|null} `true` if the second provided merged cell is "farther".
  234. */
  235. isFarther(mergedCell, direction) {
  236. if (!mergedCell) {
  237. return true;
  238. }
  239. if (direction === 'down') {
  240. return mergedCell.row + mergedCell.rowspan - 1 < this.row + this.rowspan - 1;
  241. } else if (direction === 'up') {
  242. return mergedCell.row > this.row;
  243. } else if (direction === 'right') {
  244. return mergedCell.col + mergedCell.colspan - 1 < this.col + this.colspan - 1;
  245. } else if (direction === 'left') {
  246. return mergedCell.col > this.col;
  247. }
  248. return null;
  249. }
  250. /**
  251. * Get the bottom row index of the merged cell.
  252. *
  253. * @returns {Number}
  254. */
  255. getLastRow() {
  256. return this.row + this.rowspan - 1;
  257. }
  258. /**
  259. * Get the rightmost column index of the merged cell.
  260. *
  261. * @returns {Number}
  262. */
  263. getLastColumn() {
  264. return this.col + this.colspan - 1;
  265. }
  266. /**
  267. * Get the range coordinates of the merged cell.
  268. *
  269. * @return {CellRange}
  270. */
  271. getRange() {
  272. return new CellRange(new CellCoords(this.row, this.col), new CellCoords(this.row, this.col), new CellCoords(this.getLastRow(), this.getLastColumn()));
  273. }
  274. }
  275. export default MergedCellCoords;