text: Ext.define('Ext.grid.selection.SelectionExtender', {
      extend: 'Ext.dd.DragTracker',
      
      maskBox: {},
      
      constructor: function(config) {
          var me = this;
  
          // We can only initialize properly if there are elements to work with
          if (config.view.rendered) {
              me.initSelectionExtender(config);
          } else {
              me.view = config.view;
              config.view.on({
                  render: me.initSelectionExtender,
                  args: [config],
                  scope: me
              });
          }
      },
  
      initSelectionExtender: function(config) {
          var me = this,
              displayMode = Ext.dom.Element.DISPLAY;
  
          me.el = config.view.el;
  
          me.handle = config.view.ownerGrid.body.createChild({
              cls: Ext.baseCSSPrefix + 'ssm-extender-drag-handle',
              style: 'display:none'
          }).setVisibilityMode(displayMode);
          me.handle.on({
              contextmenu: function(e) {
                  e.stopEvent();
              }
          });
          
          me.mask = me.el.createChild({
              cls: Ext.baseCSSPrefix + 'ssm-extender-mask',
              style: 'display:none'
          }).setVisibilityMode(displayMode);
  
          me.superclass.constructor.call(me, config);
  
          // Mask and andle must survive being orphaned
          me.mask.skipGarbageCollection = me.handle.skipGarbageCollection = true;
  
          me.viewListeners = me.view.on({
              scroll: me.onViewScroll,
              scope: me,
              destroyable: true
          });
          me.gridListeners = me.view.ownerGrid.on({
              columnResize: me.alignHandle,
              scope: me,
              destroyable: true
          });
  
          me.extendX = !!(me.axes & 1);
          me.extendY = !!(me.axes & 2);
      },
  
      setHandle: function(firstPos, lastPos) {
          var me = this;
  
          if (!me.view.rendered) {
              me.view.on({
                  render: me.initSelectionExtender,
                  args: [firstPos, lastPos],
                  scope: me
              });
              return;
          }
  
          me.firstPos = firstPos;
          me.lastPos = lastPos;
  
          if (firstPos && lastPos) {
              if (me.curPos) {
                  me.curPos.setPosition(lastPos);
              } else {
                  me.curPos = lastPos.clone();
              }
  
              // If we've done a "select all rows" and there is buffered rendering, then
              // the cells might not be rendered, so we can't activate the replicator.
              if (firstPos && lastPos) {
                  // Align centre of handle with bottom-right corner of last cell if possible.
                  me.alignHandle();
              }
          } else {
              me.disable();
          }
      },
  
      alignHandle: function() {
          var me = this,
              lastCell = me.lastPos && me.lastPos.getCell();
  
          // Cell corresponding to the position might not be rendered.
          // This will be called upon scroll
          if (lastCell) {
              me.enable();
              me.handle.alignTo(lastCell, 'c-br');
          } else {
              me.disable();
          }
      },
  
      enable: function() {
          this.handle.show();
          this.callParent();
      },
  
      disable: function() {
          this.handle.hide();
          this.mask.hide();
          this.callParent();
      },
  
      onDrag: function(e) {
          // pointer-events-none is not supported on IE10m.
          // So if shrinking the extension zone, the mousemove target may be the mask.
          // We have to retarget on the cell *below* that.
          if (e.target === this.mask.dom) {
              this.mask.hide();
              e.target = document.elementFromPoint.apply(document, e.getXY());
              this.mask.show();
          }
  
          var me = this,
              view = me.view,
              viewTop = view.el.getY(),
              viewLeft = view.el.getX(),
              overCell = e.getTarget(me.view.getCellSelector()),
              scrollTask = me.scrollTask || (me.scrollTask = Ext.util.TaskManager.newTask({
                  run: me.doAutoScroll,
                  scope: me,
                  interval: 10
              })),
              scrollBy = me.scrollBy || (me.scrollBy = []);
  
          // Dragged outside the view; stop scrolling.
          if (!me.el.contains(e.target)) {
              scrollBy[0] = scrollBy[1] = 0;
              return scrollTask.stop();
          }
  
          // Neart bottom of view
          if (me.lastXY[1] > viewTop + view.el.getHeight(true) - 15) {
              if (me.extendY) {
                  scrollBy[1] = 3;
                  scrollTask.start();
              }
          }
          
          // Near top of view
          else if (me.lastXY[1] < viewTop + 10) {
              if (me.extendY) {
                  scrollBy[1] = -3;
                  scrollTask.start();
              }
          }
  
          // Near right edge of view
          else if (me.lastXY[0] > viewLeft + view.el.getWidth(true) - 15) {
              if (me.extendX) {
                  scrollBy[0] = 3;
                  scrollTask.start();
              }
          }
          
          // Near left edge of view
          else if (me.lastXY[0] < viewLeft + 10) {
              if (me.extendX) {
                  scrollBy[0] = -3;
                  scrollTask.start();
              }
          }
          
          // Not near an edge, cancel autoscrolling
          else {
              scrollBy[0] = scrollBy[1] = 0;
              scrollTask.stop();
          }
  
          if (overCell && overCell !== me.lastOverCell) {
              me.lastOverCell = overCell;
              me.syncMaskOnCell(overCell);
          }
      },
  
      doAutoScroll: function() {
          var me = this,
              view = me.view,
              scrollOverCell;
  
          // Bump the view in whatever direction was decided in the onDrag method.
          view.scrollBy.apply(view, me.scrollBy);
  
          // Mouseover does not fire on autoscroll so see where the mouse is over on each scroll
          scrollOverCell = document.elementFromPoint.apply(document, me.lastXY);
          if (scrollOverCell) {
              scrollOverCell = Ext.fly(scrollOverCell).up(view.cellSelector);
              if (scrollOverCell && scrollOverCell !== me.lastOverCell) {
                  me.lastOverCell = scrollOverCell;
                  me.syncMaskOnCell(scrollOverCell);
              }
          }
      },
  
      onEnd: function(e) {
          var me = this;
  
          if (me.scrollTask) {
              me.scrollTask.stop();
          }
          if (me.extensionDescriptor) {
              me.disable();
              me.view.getSelectionModel().extendSelection(me.extensionDescriptor);
          }
      },
      
      onViewScroll: function() {
          var me = this;
  
          // If being dragged
          if (me.active && me.lastOverCell) {
              me.syncMaskOnCell(me.lastOverCell);
          }
  
          // We have been applied to a selection block
          if (me.firstPos) {
  
              // Align centre of handle with bottom-right corner of last cell if possible.
              me.alignHandle();
          }
      },
  
      syncMaskOnCell: function(overCell) {
          var me = this,
              view = me.view,
              rows = view.all,
              curPos = me.curPos,
              maskBox = me.maskBox,
              selRegion,
              firstPos = me.firstPos.clone(),
              lastPos = me.lastPos.clone(),
              extensionStart = me.firstPos.clone(),
              extensionEnd = me.lastPos.clone();
  
          // Constrain cell positions to be within rendered range.
          firstPos.setRow(Math.min(Math.max(firstPos.rowIdx, rows.startIndex), rows.endIndex));
          lastPos.setRow( Math.min(Math.max(lastPos.rowIdx,  rows.startIndex), rows.endIndex));
  
          me.selectionRegion = selRegion = firstPos.getCell().getRegion().union(lastPos.getCell().getRegion());
  
          curPos.setPosition(view.getRecord(overCell), view.getHeaderByCell(overCell));
  
          // The above calls require the cell to be a DOM reference
          overCell = Ext.fly(overCell);
  
          // Reset border to default, which is the overall border setting from SASS
          // We disable the border which is contiguous to the selection.
          me.mask.dom.style.borderTopWidth = me.mask.dom.style.borderRightWidth = me.mask.dom.style.borderBottomWidth = me.mask.dom.style.borderLeftWidth = '';
  
          // Dragged above the selection
          if (curPos.rowIdx < me.firstPos.rowIdx && me.extendY) {
              me.extensionDescriptor = {
                  type: 'rows',
                  start: extensionStart.setRow(curPos.rowIdx),
                  end: extensionEnd.setRow(me.firstPos.rowIdx - 1),
                  rows: curPos.rowIdx - me.firstPos.rowIdx,
                  mousePosition: me.lastXY
              };
              me.mask.dom.style.borderBottomWidth = '0';
              maskBox.x = selRegion.x;
              maskBox.y = overCell.getY();
              maskBox.width = selRegion.right - selRegion.left;
              maskBox.height = selRegion.top - overCell.getY();
          }
  
          // Dragged below selection
          else if (curPos.rowIdx > me.lastPos.rowIdx && me.extendY) {
              me.extensionDescriptor = {
                  type: 'rows',
                  start: extensionStart.setRow(me.lastPos.rowIdx + 1),
                  end: extensionEnd.setRow(curPos.rowIdx),
                  rows: curPos.rowIdx - me.lastPos.rowIdx,
                  mousePosition: me.lastXY
              };
              me.mask.dom.style.borderTopWidth = '0';
              maskBox.x = selRegion.x;
              maskBox.y = selRegion.bottom;
              maskBox.width = selRegion.right - selRegion.left;
              maskBox.height = overCell.getRegion().bottom - selRegion.bottom;
          }
  
          // row position is within selected row range
          else {
  
              // Dragged to left of selection
              if (curPos.colIdx < me.firstPos.colIdx && me.extendX) {
                  me.extensionDescriptor = {
                      type: 'columns',
                      start: extensionStart.setColumn(curPos.colIdx),
                      end: extensionEnd.setColumn(me.firstPos.colIdx - 1),
                      columns: curPos.colIdx - me.firstPos.colIdx,
                      mousePosition: me.lastXY
                  };
                  me.mask.dom.style.borderRightWidth = '0';
                  maskBox.x = overCell.getX();
                  maskBox.y = selRegion.top;
                  maskBox.width = selRegion.left - overCell.getX();
                  maskBox.height = selRegion.bottom - selRegion.top;
              }
  
              // Dragged to right of selection
              else if (curPos.colIdx > me.lastPos.colIdx && me.extendX) {
                  me.extensionDescriptor = {
                      type: 'columns',
                      start: extensionStart.setColumn(me.lastPos.colIdx + 1),
                      end: extensionEnd.setColumn(curPos.colIdx),
                      columns: curPos.colIdx - me.lastPos.colIdx,
                      mousePosition: me.lastXY
                  };
                  me.mask.dom.style.borderLeftWidth = '0';
                  maskBox.x = selRegion.right;
                  maskBox.y = selRegion.top;
                  maskBox.width = overCell.getRegion().right - selRegion.right;
                  maskBox.height = selRegion.bottom - selRegion.top;
              } else {
                  me.extensionDescriptor = null;
              }
          }
  
          if (view.ownerGrid.hasListeners.selectionextenderdrag) {
              view.ownerGrid.fireEvent('selectionextenderdrag', view.ownerGrid, view.getSelectionModel().getSelected(), me.extensionDescriptor);
          }
          if (me.extensionDescriptor) {
              me.mask.show();
              me.mask.setBox(maskBox);
          } else {
              me.mask.hide();
          }
      },
  
      destroy: function() {
          var me = this;
          Ext.destroy(me.gridListeners, me.viewListeners, me.mask, me.handle);
          me.callParent();
      }
  });