text: /**
   * A class which encapsulates a range of cells defining a selection in a grid.
   *
   * Note that when range start and end points are represented by an array, the
   * order is traditional `x, y` order, that is column index followed by row index.
   * @since 5.1.0
   */
  Ext.define('Ext.grid.selection.Cells', {
      extend: 'Ext.grid.selection.Selection',
  
      type: 'cells',
  
      /**
       * @property {Boolean} isCells
       * This property indicates the this selection represents selected cells.
       * @readonly
       */
      isCells: true,
  
      //-------------------------------------------------------------------------
      // Base Selection API
  
      clone: function() {
          var me = this,
              result = new me.self(me.view);
  
          if (me.startCell) {
              result.startCell = me.startCell.clone();
              result.endCell = me.endCell.clone();
          }
  
          return result;
      },
  
      /**
       * Returns `true` if the passed {@link Ext.grid.CellContext cell context} is selected.
       * @param {Ext.grid.CellContext} cellContext The cell context to test.
       * @return {Boolean} `true` if the passed {@link Ext.grid.CellContext cell context} is selected.
       */
      contains: function(cellContext) {
          var range;
  
          if (!cellContext || !cellContext.isCellContext) {
              return false;
          }
  
          if (this.startCell) {
              // get start and end rows in the range
              range = this.getRowRange();
  
              if (cellContext.rowIdx >= range[0] && cellContext.rowIdx <= range[1]) {
                  // get start and end columns in the range
                  range = this.getColumnRange();
  
                  return (cellContext.colIdx >= range[0] && cellContext.colIdx <= range[1]);
              }
          }
  
          return false;
      },
  
      eachRow: function(fn, scope) {
          var me = this,
              rowRange = me.getRowRange(),
              context = new Ext.grid.CellContext(me.view),
              rowIdx;
  
          for (rowIdx = rowRange[0]; rowIdx <= rowRange[1]; rowIdx++) {
              context.setRow(rowIdx);
  
              if (fn.call(scope || me, context.record) === false) {
                  return;
              }
          }
      },
  
      eachColumn: function(fn, scope) {
          var me = this,
              colRange = me.getColumnRange(),
              context = new Ext.grid.CellContext(me.view),
              colIdx;
  
          for (colIdx = colRange[0]; colIdx <= colRange[1]; colIdx++) {
              context.setColumn(colIdx);
  
              if (fn.call(scope || me, context.column, colIdx) === false) {
                  return;
              }
          }
      },
  
      eachCell: function(fn, scope) {
          var me = this,
              rowRange = me.getRowRange(),
              colRange = me.getColumnRange(),
              context = new Ext.grid.CellContext(me.view),
              rowIdx, colIdx;
  
          for (rowIdx = rowRange[0]; rowIdx <= rowRange[1]; rowIdx++) {
              context.setRow(rowIdx);
  
              for (colIdx = colRange[0]; colIdx <= colRange[1]; colIdx++) {
                  context.setColumn(colIdx);
  
                  if (fn.call(scope || me, context, colIdx, rowIdx) === false) {
                      return;
                  }
              }
          }
      },
  
      /**
       * @return {Number} The row index of the first row in the range or zero if no range.
       */
      getFirstRowIndex: function() {
          return this.startCell ? Math.min(this.startCell.rowIdx, this.endCell.rowIdx) : 0;
      },
  
      /**
       * @return {Number} The row index of the last row in the range or -1 if no range.
       */
      getLastRowIndex: function() {
          return this.startCell ? Math.max(this.startCell.rowIdx, this.endCell.rowIdx) : -1;
      },
  
      /**
       * @return {Number} The column index of the first column in the range or zero if no range.
       */
      getFirstColumnIndex: function() {
          return this.startCell ? Math.min(this.startCell.colIdx, this.endCell.colIdx) : 0;
      },
  
      /**
       * @return {Number} The column index of the last column in the range or -1 if no range.
       */
      getLastColumnIndex: function() {
          return this.startCell ? Math.max(this.startCell.colIdx, this.endCell.colIdx) : -1;
      },
  
      //-------------------------------------------------------------------------
  
      privates: {
          /**
           * @private
           */
          clear: function() {
              var me = this,
                  view = me.view;
  
              if (view.getVisibleColumnManager().getColumns().length) {
                  me.eachCell(function(cellContext) {
                      view.onCellDeselect(cellContext);
                  });
              }
  
              me.startCell = me.endCell = null;
          },
  
          /**
           * Used during drag/shift+downarrow range selection on start.
           * @param {Ext.grid.CellContext} startCell The start cell of the cell drag selection.
           * @param {Ext.grid.CellContext} endCell The end cell of the cell drag selection.
           * @private
           */
          setRangeStart: function(startCell, endCell) {
              // Must clone them. Users might use one instance and reconfigure it to navigate.
              this.startCell = (this.endCell = startCell.clone()).clone();
              this.view.onCellSelect(startCell);
          },
  
          /**
           * Used during drag/shift+downarrow range selection on drag.
           * @param {Ext.grid.CellContext} endCell The end cell of the cell drag selection.
           * @private
           */
          setRangeEnd: function(endCell) {
              var me = this,
                  view = me.view,
                  // rows should get all locked rows on shift key select
                  rows = view.isLockingView ? view.lockedView.all : view.all,
                  cell = new Ext.grid.CellContext(view),
                  maxColIdx = view.getVisibleColumnManager().getColumns().length - 1,
                  range, lastRange, rowStart, rowEnd, colStart, colEnd, rowIdx, colIdx;
  
              me.endCell = endCell.clone();
              range = me.getRange();
              lastRange = me.lastRange || range;
  
              rowStart = Math.max(Math.min(range[0][1], lastRange[0][1]), rows.startIndex);
              rowEnd = Math.min(Math.max(range[1][1], lastRange[1][1]), rows.endIndex);
  
              colStart = Math.min(range[0][0], lastRange[0][0]);
              colEnd = Math.min(Math.max(range[1][0], lastRange[1][0]), maxColIdx);
  
              // Loop through the union of last range and current range
              for (rowIdx = rowStart; rowIdx <= rowEnd; rowIdx++) {
                  for (colIdx = colStart; colIdx <= colEnd; colIdx++) {
                      cell.setPosition(rowIdx, colIdx);
  
                      // If we are outside the current range, deselect
                      if (rowIdx < range[0][1] || rowIdx > range[1][1] || colIdx < range[0][0] ||
                          colIdx > range[1][0]) {
                          view.onCellDeselect(cell);
                      }
                      else {
                          view.onCellSelect(cell);
                      }
                  }
              }
  
              me.lastRange = range;
          },
  
          extendRange: function(extensionVector) {
              var me = this,
                  newEndCell;
  
              if (extensionVector[extensionVector.type] < 0) {
                  newEndCell = me.endCell.clone().setPosition(
                      me.getLastRowIndex(), me.getLastColumnIndex()
                  );
  
                  me.startCell = extensionVector.start.clone();
                  me.setRangeEnd(newEndCell);
                  me.view.getNavigationModel().setPosition(extensionVector.start);
              }
              else {
                  me.startCell = me.startCell.setPosition(
                      me.getFirstRowIndex(), me.getFirstColumnIndex()
                  );
  
                  me.setRangeEnd(extensionVector.end);
                  me.view.getNavigationModel().setPosition(extensionVector.end);
              }
          },
  
          reduceRange: function(extensionVector) {
              var me = this,
                  newEndCell;
  
              if (extensionVector[extensionVector.type] < 0) {
                  newEndCell = extensionVector.end.clone();
                  me.startCell = extensionVector.start.clone();
                  me.setRangeEnd(newEndCell);
                  me.view.getNavigationModel().setPosition(extensionVector.start);
              }
          },
  
          /**
           * Returns the `[[x, y],[x,y]]` coordinates in top-left to bottom-right order
           * of the current selection.
           *
           * If no selection, returns [[0, 0],[-1, -1]] so that an incrementing iteration
           * will not execute.
           *
           * @return {Number[][]}
           * @private
           */
          getRange: function() {
              return [[this.getFirstColumnIndex(), this.getFirstRowIndex()],
                      [this.getLastColumnIndex(), this.getLastRowIndex()]];
          },
  
          /**
           * Returns the size of the selection rectangle.
           * @return {Number}
           * @private
           */
          getRangeSize: function() {
              return this.getCount();
          },
  
          /**
           * Returns the number of cells selected.
           * @return {Number} The nuimber of cells selected
           * @private
           */
          getCount: function() {
              var range = this.getRange();
  
              return (range[1][0] - range[0][0] + 1) * (range[1][1] - range[0][1] + 1);
          },
  
          /**
           * @private
           */
          selectAll: function() {
              var me = this,
                  view = me.view;
  
              me.clear();
              me.setRangeStart(new Ext.grid.CellContext(view).setPosition(0, 0));
              me.setRangeEnd(new Ext.grid.CellContext(view).setPosition(
                  view.dataSource.getCount() - 1,
                  view.getVisibleColumnManager().getColumns().length - 1
              ));
          },
  
          /**
           * @return {Boolean}
           * @private
           */
          isAllSelected: function() {
              var start = this.rangeStart,
                  end = this.rangeEnd;
  
              // All selected only if we encompass the entire store and every visible column
              if (start) {
                  if (!start.colIdx && !start.rowIdx) {
                      // eslint-disable-next-line max-len
                      return end.colIdx === end.view.getVisibleColumnManager().getColumns().length - 1 && end.rowIdx === end.view.dataSource.getCount - 1;
                  }
              }
  
              return false;
          },
  
          /**
           * @return {Number[]} The column range which encapsulates the range.
           * @private
           */
          getColumnRange: function() {
              return [this.getFirstColumnIndex(), this.getLastColumnIndex()];
          },
  
          /**
           * @private
           * Called through {@link Ext.grid.selection.SpreadsheetModel#getLastSelected}
           * by {@link Ext.panel.Table#updateBindSelection} when publishing the `selection` property.
           * It should yield the last record selected.
           */
          getLastSelected: function() {
              return this.view.dataSource.getAt(this.endCell.rowIdx);
          },
  
          /**
           * Returns the row range which encapsulates the range - the view range that needs
           * updating.
           * @return {Number[]}
           * @private
           */
          getRowRange: function() {
              return [this.getFirstRowIndex(), this.getLastRowIndex()];
          },
  
          onSelectionFinish: function() {
              var me = this;
  
              if (me.getCount()) {
                  me.view.getSelectionModel().onSelectionFinish(
                      me,
                      new Ext.grid.CellContext(me.view).setPosition(
                          me.getFirstRowIndex(), me.getFirstColumnIndex()
                      ),
                      new Ext.grid.CellContext(me.view).setPosition(
                          me.getLastRowIndex(), me.getLastColumnIndex()
                      )
                  );
              }
              else {
                  me.view.getSelectionModel().onSelectionFinish(me);
              }
          }
      }
  });
  