public-api-BoO5eSq-.mjs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. /**
  2. * Class for determining, from a list of tiles, the (row, col) position of each of those tiles
  3. * in the grid. This is necessary (rather than just rendering the tiles in normal document flow)
  4. * because the tiles can have a rowspan.
  5. *
  6. * The positioning algorithm greedily places each tile as soon as it encounters a gap in the grid
  7. * large enough to accommodate it so that the tiles still render in the same order in which they
  8. * are given.
  9. *
  10. * The basis of the algorithm is the use of an array to track the already placed tiles. Each
  11. * element of the array corresponds to a column, and the value indicates how many cells in that
  12. * column are already occupied; zero indicates an empty cell. Moving "down" to the next row
  13. * decrements each value in the tracking array (indicating that the column is one cell closer to
  14. * being free).
  15. *
  16. * @docs-private
  17. */
  18. class TileCoordinator {
  19. /** Tracking array (see class description). */
  20. tracker;
  21. /** Index at which the search for the next gap will start. */
  22. columnIndex = 0;
  23. /** The current row index. */
  24. rowIndex = 0;
  25. /** Gets the total number of rows occupied by tiles */
  26. get rowCount() {
  27. return this.rowIndex + 1;
  28. }
  29. /**
  30. * Gets the total span of rows occupied by tiles.
  31. * Ex: A list with 1 row that contains a tile with rowspan 2 will have a total rowspan of 2.
  32. */
  33. get rowspan() {
  34. const lastRowMax = Math.max(...this.tracker);
  35. // if any of the tiles has a rowspan that pushes it beyond the total row count,
  36. // add the difference to the rowcount
  37. return lastRowMax > 1 ? this.rowCount + lastRowMax - 1 : this.rowCount;
  38. }
  39. /** The computed (row, col) position of each tile (the output). */
  40. positions;
  41. /**
  42. * Updates the tile positions.
  43. * @param numColumns Amount of columns in the grid.
  44. * @param tiles Tiles to be positioned.
  45. */
  46. update(numColumns, tiles) {
  47. this.columnIndex = 0;
  48. this.rowIndex = 0;
  49. this.tracker = new Array(numColumns);
  50. this.tracker.fill(0, 0, this.tracker.length);
  51. this.positions = tiles.map(tile => this._trackTile(tile));
  52. }
  53. /** Calculates the row and col position of a tile. */
  54. _trackTile(tile) {
  55. // Find a gap large enough for this tile.
  56. const gapStartIndex = this._findMatchingGap(tile.colspan);
  57. // Place tile in the resulting gap.
  58. this._markTilePosition(gapStartIndex, tile);
  59. // The next time we look for a gap, the search will start at columnIndex, which should be
  60. // immediately after the tile that has just been placed.
  61. this.columnIndex = gapStartIndex + tile.colspan;
  62. return new TilePosition(this.rowIndex, gapStartIndex);
  63. }
  64. /** Finds the next available space large enough to fit the tile. */
  65. _findMatchingGap(tileCols) {
  66. if (tileCols > this.tracker.length && (typeof ngDevMode === 'undefined' || ngDevMode)) {
  67. throw Error(`mat-grid-list: tile with colspan ${tileCols} is wider than ` +
  68. `grid with cols="${this.tracker.length}".`);
  69. }
  70. // Start index is inclusive, end index is exclusive.
  71. let gapStartIndex = -1;
  72. let gapEndIndex = -1;
  73. // Look for a gap large enough to fit the given tile. Empty spaces are marked with a zero.
  74. do {
  75. // If we've reached the end of the row, go to the next row.
  76. if (this.columnIndex + tileCols > this.tracker.length) {
  77. this._nextRow();
  78. gapStartIndex = this.tracker.indexOf(0, this.columnIndex);
  79. gapEndIndex = this._findGapEndIndex(gapStartIndex);
  80. continue;
  81. }
  82. gapStartIndex = this.tracker.indexOf(0, this.columnIndex);
  83. // If there are no more empty spaces in this row at all, move on to the next row.
  84. if (gapStartIndex == -1) {
  85. this._nextRow();
  86. gapStartIndex = this.tracker.indexOf(0, this.columnIndex);
  87. gapEndIndex = this._findGapEndIndex(gapStartIndex);
  88. continue;
  89. }
  90. gapEndIndex = this._findGapEndIndex(gapStartIndex);
  91. // If a gap large enough isn't found, we want to start looking immediately after the current
  92. // gap on the next iteration.
  93. this.columnIndex = gapStartIndex + 1;
  94. // Continue iterating until we find a gap wide enough for this tile. Since gapEndIndex is
  95. // exclusive, gapEndIndex is 0 means we didn't find a gap and should continue.
  96. } while (gapEndIndex - gapStartIndex < tileCols || gapEndIndex == 0);
  97. // If we still didn't manage to find a gap, ensure that the index is
  98. // at least zero so the tile doesn't get pulled out of the grid.
  99. return Math.max(gapStartIndex, 0);
  100. }
  101. /** Move "down" to the next row. */
  102. _nextRow() {
  103. this.columnIndex = 0;
  104. this.rowIndex++;
  105. // Decrement all spaces by one to reflect moving down one row.
  106. for (let i = 0; i < this.tracker.length; i++) {
  107. this.tracker[i] = Math.max(0, this.tracker[i] - 1);
  108. }
  109. }
  110. /**
  111. * Finds the end index (exclusive) of a gap given the index from which to start looking.
  112. * The gap ends when a non-zero value is found.
  113. */
  114. _findGapEndIndex(gapStartIndex) {
  115. for (let i = gapStartIndex + 1; i < this.tracker.length; i++) {
  116. if (this.tracker[i] != 0) {
  117. return i;
  118. }
  119. }
  120. // The gap ends with the end of the row.
  121. return this.tracker.length;
  122. }
  123. /** Update the tile tracker to account for the given tile in the given space. */
  124. _markTilePosition(start, tile) {
  125. for (let i = 0; i < tile.colspan; i++) {
  126. this.tracker[start + i] = tile.rowspan;
  127. }
  128. }
  129. }
  130. /**
  131. * Simple data structure for tile position (row, col).
  132. * @docs-private
  133. */
  134. class TilePosition {
  135. row;
  136. col;
  137. constructor(row, col) {
  138. this.row = row;
  139. this.col = col;
  140. }
  141. }
  142. // Privately exported for the grid-list harness.
  143. const ɵTileCoordinator = TileCoordinator;
  144. export { TileCoordinator as T, ɵTileCoordinator as ɵ };
  145. //# sourceMappingURL=public-api-BoO5eSq-.mjs.map