text: // Copyright 2006 The Closure Library Authors. All Rights Reserved.
  //
  // Licensed under the Apache License, Version 2.0 (the "License");
  // you may not use this file except in compliance with the License.
  // You may obtain a copy of the License at
  //
  //      http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing, software
  // distributed under the License is distributed on an "AS-IS" BASIS,
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // See the License for the specific language governing permissions and
  // limitations under the License.
  
  /**
   * @fileoverview Abstract Base Class for Drag and Drop.
   *
   * Provides functionality for implementing drag and drop classes. Also provides
   * support classes and events.
   */
  
  goog.provide('goog.fx.AbstractDragDrop');
  goog.provide('goog.fx.AbstractDragDrop.EventType');
  goog.provide('goog.fx.DragDropEvent');
  goog.provide('goog.fx.DragDropItem');
  
  goog.require('goog.array');
  goog.require('goog.asserts');
  goog.require('goog.dom');
  goog.require('goog.dom.classlist');
  goog.require('goog.events');
  goog.require('goog.events.Event');
  goog.require('goog.events.EventHandler');
  goog.require('goog.events.EventTarget');
  goog.require('goog.events.EventType');
  goog.require('goog.fx.Dragger');
  goog.require('goog.math.Box');
  goog.require('goog.math.Coordinate');
  goog.require('goog.style');
  
  
  
  /**
   * Abstract class that provides reusable functionality for implementing drag
   * and drop functionality.
   *
   * This class also allows clients to define their own subtargeting function
   * so that drop areas can have finer granularity than a single element. This is
   * accomplished by using a client provided function to map from element and
   * coordinates to a subregion id.
   *
   * This class can also be made aware of scrollable containers that contain
   * drop targets by calling addScrollableContainer. This will cause dnd to
   * take changing scroll positions into account while a drag is occurring.
   *
   * @extends {goog.events.EventTarget}
   * @constructor
   * @struct
   */
  goog.fx.AbstractDragDrop = function() {
    goog.fx.AbstractDragDrop.base(this, 'constructor');
  
    /**
     * List of items that makes up the drag source or drop target.
     * @protected {Array<goog.fx.DragDropItem>}
     * @suppress {underscore|visibility}
     */
    this.items_ = [];
  
    /**
     * List of associated drop targets.
     * @private {Array<goog.fx.AbstractDragDrop>}
     */
    this.targets_ = [];
  
    /**
     * Scrollable containers to account for during drag
     * @private {Array<goog.fx.ScrollableContainer_>}
     */
    this.scrollableContainers_ = [];
  
    /**
     * Flag indicating if it's a drag source, set by addTarget.
     * @private {boolean}
     */
    this.isSource_ = false;
  
    /**
     * Flag indicating if it's a drop target, set when added as target to another
     * DragDrop object.
     * @private {boolean}
     */
    this.isTarget_ = false;
  
    /**
     * Subtargeting function accepting args:
     * (goog.fx.DragDropItem, goog.math.Box, number, number)
     * @private {?Function}
     */
    this.subtargetFunction_;
  
    /**
     * Last active subtarget.
     * @private {?Object}
     */
    this.activeSubtarget_;
  
    /**
     * Class name to add to source elements being dragged. Set by setDragClass.
     * @private {?string}
     */
    this.dragClass_;
  
    /**
     * Class name to add to source elements. Set by setSourceClass.
     * @private {?string}
     */
    this.sourceClass_;
  
    /**
     * Class name to add to target elements. Set by setTargetClass.
     * @private {?string}
     */
    this.targetClass_;
  
    /**
     * The SCROLL event target used to make drag element follow scrolling.
     * @private {?EventTarget}
     */
    this.scrollTarget_;
  
    /**
     * Dummy target, {@see maybeCreateDummyTargetForPosition_}.
     * @private {?goog.fx.ActiveDropTarget_}
     */
    this.dummyTarget_;
  
    /**
     * Whether the object has been initialized.
     * @private {boolean}
     */
    this.initialized_ = false;
  
    /** @private {?Element} */
    this.dragEl_;
  
    /** @private {?Array<!goog.fx.ActiveDropTarget_>} */
    this.targetList_;
  
    /** @private {?goog.math.Box} */
    this.targetBox_;
  
    /** @private {?goog.fx.ActiveDropTarget_} */
    this.activeTarget_;
  
    /** @private {?goog.fx.DragDropItem} */
    this.dragItem_;
  
    /** @private {?goog.fx.Dragger} */
    this.dragger_;
  };
  goog.inherits(goog.fx.AbstractDragDrop, goog.events.EventTarget);
  
  
  /**
   * Minimum size (in pixels) for a dummy target. If the box for the target is
   * less than the specified size it's not created.
   * @type {number}
   * @private
   */
  goog.fx.AbstractDragDrop.DUMMY_TARGET_MIN_SIZE_ = 10;
  
  
  /**
   * Constants for event names
   * @const
   */
  goog.fx.AbstractDragDrop.EventType = {
    DRAGOVER: 'dragover',
    DRAGOUT: 'dragout',
    DRAG: 'drag',
    DROP: 'drop',
    DRAGSTART: 'dragstart',
    DRAGEND: 'dragend'
  };
  
  
  /**
   * Constant for distance threshold, in pixels, an element has to be moved to
   * initiate a drag operation.
   * @type {number}
   */
  goog.fx.AbstractDragDrop.initDragDistanceThreshold = 5;
  
  
  /**
   * Set class to add to source elements being dragged.
   *
   * @param {string} className Class to be added.  Must be a single, valid
   *     classname.
   */
  goog.fx.AbstractDragDrop.prototype.setDragClass = function(className) {
    this.dragClass_ = className;
  };
  
  
  /**
   * Set class to add to source elements.
   *
   * @param {string} className Class to be added.  Must be a single, valid
   *     classname.
   */
  goog.fx.AbstractDragDrop.prototype.setSourceClass = function(className) {
    this.sourceClass_ = className;
  };
  
  
  /**
   * Set class to add to target elements.
   *
   * @param {string} className Class to be added.  Must be a single, valid
   *     classname.
   */
  goog.fx.AbstractDragDrop.prototype.setTargetClass = function(className) {
    this.targetClass_ = className;
  };
  
  
  /**
   * Whether the control has been initialized.
   *
   * @return {boolean} True if it's been initialized.
   */
  goog.fx.AbstractDragDrop.prototype.isInitialized = function() {
    return this.initialized_;
  };
  
  
  /**
   * Add item to drag object.
   *
   * @param {Element|string} element Dom Node, or string representation of node
   *     id, to be used as drag source/drop target.
   * @throws Error Thrown if called on instance of abstract class
   */
  goog.fx.AbstractDragDrop.prototype.addItem = goog.abstractMethod;
  
  
  /**
   * Associate drop target with drag element.
   *
   * @param {goog.fx.AbstractDragDrop} target Target to add.
   */
  goog.fx.AbstractDragDrop.prototype.addTarget = function(target) {
    this.targets_.push(target);
    target.isTarget_ = true;
    this.isSource_ = true;
  };
  
  
  /**
   * Removes the specified target from the list of drop targets.
   *
   * @param {!goog.fx.AbstractDragDrop} target Target to remove.
   */
  goog.fx.AbstractDragDrop.prototype.removeTarget = function(target) {
    goog.array.remove(this.targets_, target);
    if (this.activeTarget_ && this.activeTarget_.target_ == target) {
      this.activeTarget_ = null;
    }
    this.recalculateDragTargets();
  };
  
  
  /**
   * Sets the SCROLL event target to make drag element follow scrolling.
   *
   * @param {EventTarget} scrollTarget The element that dispatches SCROLL events.
   */
  goog.fx.AbstractDragDrop.prototype.setScrollTarget = function(scrollTarget) {
    this.scrollTarget_ = scrollTarget;
  };
  
  
  /**
   * Initialize drag and drop functionality for sources/targets already added.
   * Sources/targets added after init has been called will initialize themselves
   * one by one.
   */
  goog.fx.AbstractDragDrop.prototype.init = function() {
    if (this.initialized_) {
      return;
    }
    for (var item, i = 0; item = this.items_[i]; i++) {
      this.initItem(item);
    }
  
    this.initialized_ = true;
  };
  
  
  /**
   * Initializes a single item.
   *
   * @param {goog.fx.DragDropItem} item Item to initialize.
   * @protected
   */
  goog.fx.AbstractDragDrop.prototype.initItem = function(item) {
    if (this.isSource_) {
      goog.events.listen(
          item.element, goog.events.EventType.MOUSEDOWN, item.mouseDown_, false,
          item);
      if (this.sourceClass_) {
        goog.dom.classlist.add(
            goog.asserts.assert(item.element), this.sourceClass_);
      }
    }
  
    if (this.isTarget_ && this.targetClass_) {
      goog.dom.classlist.add(
          goog.asserts.assert(item.element), this.targetClass_);
    }
  };
  
  
  /**
   * Called when removing an item. Removes event listeners and classes.
   *
   * @param {goog.fx.DragDropItem} item Item to dispose.
   * @protected
   */
  goog.fx.AbstractDragDrop.prototype.disposeItem = function(item) {
    if (this.isSource_) {
      goog.events.unlisten(
          item.element, goog.events.EventType.MOUSEDOWN, item.mouseDown_, false,
          item);
      if (this.sourceClass_) {
        goog.dom.classlist.remove(
            goog.asserts.assert(item.element), this.sourceClass_);
      }
    }
    if (this.isTarget_ && this.targetClass_) {
      goog.dom.classlist.remove(
          goog.asserts.assert(item.element), this.targetClass_);
    }
    item.dispose();
  };
  
  
  /**
   * Removes all items.
   */
  goog.fx.AbstractDragDrop.prototype.removeItems = function() {
    for (var item, i = 0; item = this.items_[i]; i++) {
      this.disposeItem(item);
    }
    this.items_.length = 0;
  };
  
  
  /**
   * Starts a drag event for an item if the mouse button stays pressed and the
   * cursor moves a few pixels. Allows dragging of items without first having to
   * register them with addItem.
   *
   * @param {goog.events.BrowserEvent} event Mouse down event.
   * @param {goog.fx.DragDropItem} item Item that's being dragged.
   */
  goog.fx.AbstractDragDrop.prototype.maybeStartDrag = function(event, item) {
    item.maybeStartDrag_(event, item.element);
  };
  
  
  /**
   * Event handler that's used to start drag.
   *
   * @param {goog.events.BrowserEvent} event Mouse move event.
   * @param {goog.fx.DragDropItem} item Item that's being dragged.
   */
  goog.fx.AbstractDragDrop.prototype.startDrag = function(event, item) {
  
    // Prevent a new drag operation from being started if another one is already
    // in progress (could happen if the mouse was released outside of the
    // document).
    if (this.dragItem_) {
      return;
    }
  
    this.dragItem_ = item;
  
    // Dispatch DRAGSTART event
    var dragStartEvent = new goog.fx.DragDropEvent(
        goog.fx.AbstractDragDrop.EventType.DRAGSTART, this, this.dragItem_,
        undefined,  // opt_target
        undefined,  // opt_targetItem
        undefined,  // opt_targetElement
        undefined,  // opt_clientX
        undefined,  // opt_clientY
        undefined,  // opt_x
        undefined,  // opt_y
        undefined,  // opt_subtarget
        event);
    if (this.dispatchEvent(dragStartEvent) == false) {
      this.dragItem_ = null;
      return;
    }
  
    // Get the source element and create a drag element for it.
    var el = item.getCurrentDragElement();
    this.dragEl_ = this.createDragElement(el);
    var doc = goog.dom.getOwnerDocument(el);
    doc.body.appendChild(this.dragEl_);
  
    this.dragger_ = this.createDraggerFor(el, this.dragEl_, event);
    this.dragger_.setScrollTarget(this.scrollTarget_);
  
    goog.events.listen(
        this.dragger_, goog.fx.Dragger.EventType.DRAG, this.moveDrag_, false,
        this);
  
    goog.events.listen(
        this.dragger_, goog.fx.Dragger.EventType.END, this.endDrag, false, this);
  
    // IE may issue a 'selectstart' event when dragging over an iframe even when
    // default mousemove behavior is suppressed. If the default selectstart
    // behavior is not suppressed, elements dragged over will show as selected.
    goog.events.listen(
        doc.body, goog.events.EventType.SELECTSTART, this.suppressSelect_);
  
    this.recalculateDragTargets();
    this.recalculateScrollableContainers();
    this.activeTarget_ = null;
    this.initScrollableContainerListeners_();
    this.dragger_.startDrag(event);
  
    event.preventDefault();
  };
  
  
  /**
   * Recalculates the geometry of this source's drag targets.  Call this
   * if the position or visibility of a drag target has changed during
   * a drag, or if targets are added or removed.
   *
   * TODO(user): this is an expensive operation;  more efficient APIs
   * may be necessary.
   */
  goog.fx.AbstractDragDrop.prototype.recalculateDragTargets = function() {
    this.targetList_ = [];
    for (var target, i = 0; target = this.targets_[i]; i++) {
      for (var itm, j = 0; itm = target.items_[j]; j++) {
        this.addDragTarget_(target, itm);
      }
    }
    if (!this.targetBox_) {
      this.targetBox_ = new goog.math.Box(0, 0, 0, 0);
    }
  };
  
  
  /**
   * Recalculates the current scroll positions of scrollable containers and
   * allocates targets. Call this if the position of a container changed or if
   * targets are added or removed.
   */
  goog.fx.AbstractDragDrop.prototype.recalculateScrollableContainers =
      function() {
    var container, i, j, target;
    for (i = 0; container = this.scrollableContainers_[i]; i++) {
      container.containedTargets_ = [];
      container.savedScrollLeft_ = container.element_.scrollLeft;
      container.savedScrollTop_ = container.element_.scrollTop;
      var pos = goog.style.getPageOffset(container.element_);
      var size = goog.style.getSize(container.element_);
      container.box_ = new goog.math.Box(
          pos.y, pos.x + size.width, pos.y + size.height, pos.x);
    }
  
    for (i = 0; target = this.targetList_[i]; i++) {
      for (j = 0; container = this.scrollableContainers_[j]; j++) {
        if (goog.dom.contains(container.element_, target.element_)) {
          container.containedTargets_.push(target);
          target.scrollableContainer_ = container;
        }
      }
    }
  };
  
  
  /**
   * Creates the Dragger for the drag element.
   * @param {Element} sourceEl Drag source element.
   * @param {Element} el the element created by createDragElement().
   * @param {goog.events.BrowserEvent} event Mouse down event for start of drag.
   * @return {!goog.fx.Dragger} The new Dragger.
   * @protected
   */
  goog.fx.AbstractDragDrop.prototype.createDraggerFor = function(
      sourceEl, el, event) {
    // Position the drag element.
    var pos = this.getDragElementPosition(sourceEl, el, event);
    el.style.position = 'absolute';
    el.style.left = pos.x + 'px';
    el.style.top = pos.y + 'px';
    return new goog.fx.Dragger(el);
  };
  
  
  /**
   * Event handler that's used to stop drag. Fires a drop event if over a valid
   * target.
   *
   * @param {goog.fx.DragEvent} event Drag event.
   */
  goog.fx.AbstractDragDrop.prototype.endDrag = function(event) {
    var activeTarget = event.dragCanceled ? null : this.activeTarget_;
    if (activeTarget && activeTarget.target_) {
      var clientX = event.clientX;
      var clientY = event.clientY;
      var scroll = this.getScrollPos();
      var x = clientX + scroll.x;
      var y = clientY + scroll.y;
  
      var subtarget;
      // If a subtargeting function is enabled get the current subtarget
      if (this.subtargetFunction_) {
        subtarget =
            this.subtargetFunction_(activeTarget.item_, activeTarget.box_, x, y);
      }
  
      var dragEvent = new goog.fx.DragDropEvent(
          goog.fx.AbstractDragDrop.EventType.DRAG, this, this.dragItem_,
          activeTarget.target_, activeTarget.item_, activeTarget.element_,
          clientX, clientY, x, y);
      this.dispatchEvent(dragEvent);
  
      var dropEvent = new goog.fx.DragDropEvent(
          goog.fx.AbstractDragDrop.EventType.DROP, this, this.dragItem_,
          activeTarget.target_, activeTarget.item_, activeTarget.element_,
          clientX, clientY, x, y, subtarget, event.browserEvent);
      activeTarget.target_.dispatchEvent(dropEvent);
    }
  
    var dragEndEvent = new goog.fx.DragDropEvent(
        goog.fx.AbstractDragDrop.EventType.DRAGEND, this, this.dragItem_,
        activeTarget ? activeTarget.target_ : undefined,
        activeTarget ? activeTarget.item_ : undefined,
        activeTarget ? activeTarget.element_ : undefined);
    this.dispatchEvent(dragEndEvent);
  
    goog.events.unlisten(
        this.dragger_, goog.fx.Dragger.EventType.DRAG, this.moveDrag_, false,
        this);
    goog.events.unlisten(
        this.dragger_, goog.fx.Dragger.EventType.END, this.endDrag, false, this);
    var doc = goog.dom.getOwnerDocument(this.dragItem_.getCurrentDragElement());
    goog.events.unlisten(
        doc.body, goog.events.EventType.SELECTSTART, this.suppressSelect_);
  
  
    this.afterEndDrag(this.activeTarget_ ? this.activeTarget_.item_ : null);
  };
  
  
  /**
   * Called after a drag operation has finished.
   *
   * @param {goog.fx.DragDropItem=} opt_dropTarget Target for successful drop.
   * @protected
   */
  goog.fx.AbstractDragDrop.prototype.afterEndDrag = function(opt_dropTarget) {
    this.disposeDrag();
  };
  
  
  /**
   * Called once a drag operation has finished. Removes event listeners and
   * elements.
   *
   * @protected
   */
  goog.fx.AbstractDragDrop.prototype.disposeDrag = function() {
    this.disposeScrollableContainerListeners_();
    this.dragger_.dispose();
  
    goog.dom.removeNode(this.dragEl_);
    delete this.dragItem_;
    delete this.dragEl_;
    delete this.dragger_;
    delete this.targetList_;
    delete this.activeTarget_;
  };
  
  
  /**
   * Event handler for drag events. Determines the active drop target, if any, and
   * fires dragover and dragout events appropriately.
   *
   * @param {goog.fx.DragEvent} event Drag event.
   * @private
   */
  goog.fx.AbstractDragDrop.prototype.moveDrag_ = function(event) {
    var position = this.getEventPosition(event);
    var x = position.x;
    var y = position.y;
  
    var activeTarget = this.activeTarget_;
  
    this.dispatchEvent(
        new goog.fx.DragDropEvent(
            goog.fx.AbstractDragDrop.EventType.DRAG, this, this.dragItem_,
            activeTarget ? activeTarget.target_ : undefined,
            activeTarget ? activeTarget.item_ : undefined,
            activeTarget ? activeTarget.element_ : undefined, event.clientX,
            event.clientY, x, y));
  
    // Check if we're still inside the bounds of the active target, if not fire
    // a dragout event and proceed to find a new target.
    var subtarget;
    if (activeTarget) {
      // If a subtargeting function is enabled get the current subtarget
      if (this.subtargetFunction_ && activeTarget.target_) {
        subtarget =
            this.subtargetFunction_(activeTarget.item_, activeTarget.box_, x, y);
      }
  
      if (activeTarget.box_.contains(position) &&
          subtarget == this.activeSubtarget_) {
        return;
      }
  
      if (activeTarget.target_) {
        var sourceDragOutEvent = new goog.fx.DragDropEvent(
            goog.fx.AbstractDragDrop.EventType.DRAGOUT, this, this.dragItem_,
            activeTarget.target_, activeTarget.item_, activeTarget.element_);
        this.dispatchEvent(sourceDragOutEvent);
  
        // The event should be dispatched the by target DragDrop so that the
        // target DragDrop can manage these events without having to know what
        // sources this is a target for.
        var targetDragOutEvent = new goog.fx.DragDropEvent(
            goog.fx.AbstractDragDrop.EventType.DRAGOUT, this, this.dragItem_,
            activeTarget.target_, activeTarget.item_, activeTarget.element_,
            undefined, undefined, undefined, undefined, this.activeSubtarget_);
        activeTarget.target_.dispatchEvent(targetDragOutEvent);
      }
      this.activeSubtarget_ = subtarget;
      this.activeTarget_ = null;
    }
  
    // Check if inside target box
    if (this.targetBox_.contains(position)) {
      // Search for target and fire a dragover event if found
      activeTarget = this.activeTarget_ = this.getTargetFromPosition_(position);
      if (activeTarget && activeTarget.target_) {
        // If a subtargeting function is enabled get the current subtarget
        if (this.subtargetFunction_) {
          subtarget = this.subtargetFunction_(
              activeTarget.item_, activeTarget.box_, x, y);
        }
        var sourceDragOverEvent = new goog.fx.DragDropEvent(
            goog.fx.AbstractDragDrop.EventType.DRAGOVER, this, this.dragItem_,
            activeTarget.target_, activeTarget.item_, activeTarget.element_);
        sourceDragOverEvent.subtarget = subtarget;
        this.dispatchEvent(sourceDragOverEvent);
  
        // The event should be dispatched by the target DragDrop so that the
        // target DragDrop can manage these events without having to know what
        // sources this is a target for.
        var targetDragOverEvent = new goog.fx.DragDropEvent(
            goog.fx.AbstractDragDrop.EventType.DRAGOVER, this, this.dragItem_,
            activeTarget.target_, activeTarget.item_, activeTarget.element_,
            event.clientX, event.clientY, undefined, undefined, subtarget);
        activeTarget.target_.dispatchEvent(targetDragOverEvent);
  
      } else if (!activeTarget) {
        // If no target was found create a dummy one so we won't have to iterate
        // over all possible targets for every move event.
        this.activeTarget_ = this.maybeCreateDummyTargetForPosition_(x, y);
      }
    }
  };
  
  
  /**
   * Event handler for suppressing selectstart events. Selecting should be
   * disabled while dragging.
   *
   * @param {goog.events.Event} event The selectstart event to suppress.
   * @return {boolean} Whether to perform default behavior.
   * @private
   */
  goog.fx.AbstractDragDrop.prototype.suppressSelect_ = function(event) {
    return false;
  };
  
  
  /**
   * Sets up listeners for the scrollable containers that keep track of their
   * scroll positions.
   * @private
   */
  goog.fx.AbstractDragDrop.prototype.initScrollableContainerListeners_ =
      function() {
    var container, i;
    for (i = 0; container = this.scrollableContainers_[i]; i++) {
      goog.events.listen(
          container.element_, goog.events.EventType.SCROLL,
          this.containerScrollHandler_, false, this);
    }
  };
  
  
  /**
   * Cleans up the scrollable container listeners.
   * @private
   */
  goog.fx.AbstractDragDrop.prototype.disposeScrollableContainerListeners_ =
      function() {
    for (var i = 0, container; container = this.scrollableContainers_[i]; i++) {
      goog.events.unlisten(
          container.element_, 'scroll', this.containerScrollHandler_, false,
          this);
      container.containedTargets_ = [];
    }
  };
  
  
  /**
   * Makes drag and drop aware of a target container that could scroll mid drag.
   * @param {Element} element The scroll container.
   */
  goog.fx.AbstractDragDrop.prototype.addScrollableContainer = function(element) {
    this.scrollableContainers_.push(new goog.fx.ScrollableContainer_(element));
  };
  
  
  /**
   * Removes all scrollable containers.
   */
  goog.fx.AbstractDragDrop.prototype.removeAllScrollableContainers = function() {
    this.disposeScrollableContainerListeners_();
    this.scrollableContainers_ = [];
  };
  
  
  /**
   * Event handler for containers scrolling.
   * @param {goog.events.BrowserEvent} e The event.
   * @suppress {visibility} TODO(user): update dependent projects.
   * @private
   */
  goog.fx.AbstractDragDrop.prototype.containerScrollHandler_ = function(e) {
    for (var i = 0, container; container = this.scrollableContainers_[i]; i++) {
      if (e.target == container.element_) {
        var deltaTop = container.savedScrollTop_ - container.element_.scrollTop;
        var deltaLeft =
            container.savedScrollLeft_ - container.element_.scrollLeft;
        container.savedScrollTop_ = container.element_.scrollTop;
        container.savedScrollLeft_ = container.element_.scrollLeft;
  
        // When the container scrolls, it's possible that one of the targets will
        // move to the region contained by the dummy target. Since we don't know
        // which sides (if any) of the dummy target are defined by targets
        // contained by this container, we are conservative and just shrink it.
        if (this.dummyTarget_ && this.activeTarget_ == this.dummyTarget_) {
          if (deltaTop > 0) {
            this.dummyTarget_.box_.top += deltaTop;
          } else {
            this.dummyTarget_.box_.bottom += deltaTop;
          }
          if (deltaLeft > 0) {
            this.dummyTarget_.box_.left += deltaLeft;
          } else {
            this.dummyTarget_.box_.right += deltaLeft;
          }
        }
        for (var j = 0, target; target = container.containedTargets_[j]; j++) {
          var box = target.box_;
          box.top += deltaTop;
          box.left += deltaLeft;
          box.bottom += deltaTop;
          box.right += deltaLeft;
  
          this.calculateTargetBox_(box);
        }
      }
    }
    this.dragger_.onScroll_(e);
  };
  
  
  /**
   * Set a function that provides subtargets. A subtargeting function
   * returns an arbitrary identifier for each subtarget of an element.
   * DnD code will generate additional drag over / out events when
   * switching from subtarget to subtarget. This is useful for instance
   * if you are interested if you are on the top half or the bottom half
   * of the element.
   * The provided function will be given the DragDropItem, box, x, y
   * box is the current window coordinates occupied by element
   * x, y is the mouse position in window coordinates
   *
   * @param {Function} f The new subtarget function.
   */
  goog.fx.AbstractDragDrop.prototype.setSubtargetFunction = function(f) {
    this.subtargetFunction_ = f;
  };
  
  
  /**
   * Creates an element for the item being dragged.
   *
   * @param {Element} sourceEl Drag source element.
   * @return {Element} The new drag element.
   */
  goog.fx.AbstractDragDrop.prototype.createDragElement = function(sourceEl) {
    var dragEl = this.createDragElementInternal(sourceEl);
    goog.asserts.assert(dragEl);
    if (this.dragClass_) {
      goog.dom.classlist.add(dragEl, this.dragClass_);
    }
  
    return dragEl;
  };
  
  
  /**
   * Returns the position for the drag element.
   *
   * @param {Element} el Drag source element.
   * @param {Element} dragEl The dragged element created by createDragElement().
   * @param {goog.events.BrowserEvent} event Mouse down event for start of drag.
   * @return {!goog.math.Coordinate} The position for the drag element.
   */
  goog.fx.AbstractDragDrop.prototype.getDragElementPosition = function(
      el, dragEl, event) {
    var pos = goog.style.getPageOffset(el);
  
    // Subtract margin from drag element position twice, once to adjust the
    // position given by the original node and once for the drag node.
    var marginBox = goog.style.getMarginBox(el);
    pos.x -= (marginBox.left || 0) * 2;
    pos.y -= (marginBox.top || 0) * 2;
  
    return pos;
  };
  
  
  /**
   * Returns the dragger object.
   *
   * @return {goog.fx.Dragger} The dragger object used by this drag and drop
   *     instance.
   */
  goog.fx.AbstractDragDrop.prototype.getDragger = function() {
    return this.dragger_;
  };
  
  
  /**
   * Creates copy of node being dragged.
   *
   * @param {Element} sourceEl Element to copy.
   * @return {!Element} The clone of `sourceEl`.
   * @deprecated Use goog.fx.Dragger.cloneNode().
   * @private
   */
  goog.fx.AbstractDragDrop.prototype.cloneNode_ = function(sourceEl) {
    return goog.fx.Dragger.cloneNode(sourceEl);
  };
  
  
  /**
   * Generates an element to follow the cursor during dragging, given a drag
   * source element.  The default behavior is simply to clone the source element,
   * but this may be overridden in subclasses.  This method is called by
   * `createDragElement()` before the drag class is added.
   *
   * @param {Element} sourceEl Drag source element.
   * @return {!Element} The new drag element.
   * @protected
   * @suppress {deprecated}
   */
  goog.fx.AbstractDragDrop.prototype.createDragElementInternal = function(
      sourceEl) {
    return this.cloneNode_(sourceEl);
  };
  
  
  /**
   * Add possible drop target for current drag operation.
   *
   * @param {goog.fx.AbstractDragDrop} target Drag handler.
   * @param {goog.fx.DragDropItem} item Item that's being dragged.
   * @private
   */
  goog.fx.AbstractDragDrop.prototype.addDragTarget_ = function(target, item) {
  
    // Get all the draggable elements and add each one.
    var draggableElements = item.getDraggableElements();
    for (var i = 0; i < draggableElements.length; i++) {
      var draggableElement = draggableElements[i];
  
      // Determine target position and dimension
      var box = this.getElementBox(item, draggableElement);
  
      this.targetList_.push(
          new goog.fx.ActiveDropTarget_(box, target, item, draggableElement));
  
      this.calculateTargetBox_(box);
    }
  };
  
  
  /**
   * Calculates the position and dimension of a draggable element.
   *
   * @param {goog.fx.DragDropItem} item Item that's being dragged.
   * @param {Element} element The element to calculate the box.
   *
   * @return {!goog.math.Box} Box describing the position and dimension
   *     of element.
   * @protected
   */
  goog.fx.AbstractDragDrop.prototype.getElementBox = function(item, element) {
    var pos = goog.style.getPageOffset(element);
    var size = goog.style.getSize(element);
    return new goog.math.Box(
        pos.y, pos.x + size.width, pos.y + size.height, pos.x);
  };
  
  
  /**
   * Calculate the outer bounds (the region all targets are inside).
   *
   * @param {goog.math.Box} box Box describing the position and dimension
   *     of a drag target.
   * @private
   */
  goog.fx.AbstractDragDrop.prototype.calculateTargetBox_ = function(box) {
    if (this.targetList_.length == 1) {
      this.targetBox_ =
          new goog.math.Box(box.top, box.right, box.bottom, box.left);
    } else {
      var tb = this.targetBox_;
      tb.left = Math.min(box.left, tb.left);
      tb.right = Math.max(box.right, tb.right);
      tb.top = Math.min(box.top, tb.top);
      tb.bottom = Math.max(box.bottom, tb.bottom);
    }
  };
  
  
  /**
   * Creates a dummy target for the given cursor position. The assumption is to
   * create as big dummy target box as possible, the only constraints are:
   * - The dummy target box cannot overlap any of real target boxes.
   * - The dummy target has to contain a point with current mouse coordinates.
   *
   * NOTE: For performance reasons the box construction algorithm is kept simple
   * and it is not optimal (see example below). Currently it is O(n) in regard to
   * the number of real drop target boxes, but its result depends on the order
   * of those boxes being processed (the order in which they're added to the
   * targetList_ collection).
   *
   * The algorithm.
   * a) Assumptions
   * - Mouse pointer is in the bounding box of real target boxes.
   * - None of the boxes have negative coordinate values.
   * - Mouse pointer is not contained by any of "real target" boxes.
   * - For targets inside a scrollable container, the box used is the
   *   intersection of the scrollable container's box and the target's box.
   *   This is because the part of the target that extends outside the scrollable
   *   container should not be used in the clipping calculations.
   *
   * b) Outline
   * - Initialize the fake target to the bounding box of real targets.
   * - For each real target box - clip the fake target box so it does not contain
   *   that target box, but does contain the mouse pointer.
   *   -- Project the real target box, mouse pointer and fake target box onto
   *      both axes and calculate the clipping coordinates.
   *   -- Only one coordinate is used to clip the fake target box to keep the
   *      fake target as big as possible.
   *   -- If the projection of the real target box contains the mouse pointer,
   *      clipping for a given axis is not possible.
   *   -- If both clippings are possible, the clipping more distant from the
   *      mouse pointer is selected to keep bigger fake target area.
   * - Save the created fake target only if it has a big enough area.
   *
   *
   * c) Example
   * <pre>
   *        Input:           Algorithm created box:        Maximum box:
   * +---------------------+ +---------------------+ +---------------------+
   * | B1      |        B2 | | B1               B2 | | B1               B2 |
   * |         |           | |   +-------------+   | |+-------------------+|
   * |---------x-----------| |   |             |   | ||                   ||
   * |         |           | |   |             |   | ||                   ||
   * |         |           | |   |             |   | ||                   ||
   * |         |           | |   |             |   | ||                   ||
   * |         |           | |   |             |   | ||                   ||
   * |         |           | |   +-------------+   | |+-------------------+|
   * | B4      |        B3 | | B4               B3 | | B4               B3 |
   * +---------------------+ +---------------------+ +---------------------+
   * </pre>
   *
   * @param {number} x Cursor position on the x-axis.
   * @param {number} y Cursor position on the y-axis.
   * @return {goog.fx.ActiveDropTarget_} Dummy drop target.
   * @private
   */
  goog.fx.AbstractDragDrop.prototype.maybeCreateDummyTargetForPosition_ =
      function(x, y) {
    if (!this.dummyTarget_) {
      this.dummyTarget_ = new goog.fx.ActiveDropTarget_(this.targetBox_.clone());
    }
    var fakeTargetBox = this.dummyTarget_.box_;
  
    // Initialize the fake target box to the bounding box of DnD targets.
    fakeTargetBox.top = this.targetBox_.top;
    fakeTargetBox.right = this.targetBox_.right;
    fakeTargetBox.bottom = this.targetBox_.bottom;
    fakeTargetBox.left = this.targetBox_.left;
  
    // Clip the fake target based on mouse position and DnD target boxes.
    for (var i = 0, target; target = this.targetList_[i]; i++) {
      var box = target.box_;
  
      if (target.scrollableContainer_) {
        // If the target has a scrollable container, use the intersection of that
        // container's box and the target's box.
        var scrollBox = target.scrollableContainer_.box_;
  
        box = new goog.math.Box(
            Math.max(box.top, scrollBox.top),
            Math.min(box.right, scrollBox.right),
            Math.min(box.bottom, scrollBox.bottom),
            Math.max(box.left, scrollBox.left));
      }
  
      // Calculate clipping coordinates for horizontal and vertical axis.
      // The clipping coordinate is calculated by projecting fake target box,
      // the mouse pointer and DnD target box onto an axis and checking how
      // box projections overlap and if the projected DnD target box contains
      // mouse pointer. The clipping coordinate cannot be computed and is set to
      // a negative value if the projected DnD target contains the mouse pointer.
  
      var horizontalClip = null;  // Assume mouse is above or below the DnD box.
      if (x >= box.right) {       // Mouse is to the right of the DnD box.
        // Clip the fake box only if the DnD box overlaps it.
        horizontalClip =
            box.right > fakeTargetBox.left ? box.right : fakeTargetBox.left;
      } else if (x < box.left) {  // Mouse is to the left of the DnD box.
        // Clip the fake box only if the DnD box overlaps it.
        horizontalClip =
            box.left < fakeTargetBox.right ? box.left : fakeTargetBox.right;
      }
      var verticalClip = null;
      if (y >= box.bottom) {
        verticalClip =
            box.bottom > fakeTargetBox.top ? box.bottom : fakeTargetBox.top;
      } else if (y < box.top) {
        verticalClip =
            box.top < fakeTargetBox.bottom ? box.top : fakeTargetBox.bottom;
      }
  
      // If both clippings are possible, choose one that gives us larger distance
      // to mouse pointer (mark the shorter clipping as impossible, by setting it
      // to null).
      if (horizontalClip !== null && verticalClip !== null) {
        if (Math.abs(horizontalClip - x) > Math.abs(verticalClip - y)) {
          verticalClip = null;
        } else {
          horizontalClip = null;
        }
      }
  
      // Clip none or one of fake target box sides (at most one clipping
      // coordinate can be active).
      if (horizontalClip !== null) {
        if (horizontalClip <= x) {
          fakeTargetBox.left = horizontalClip;
        } else {
          fakeTargetBox.right = horizontalClip;
        }
      } else if (verticalClip !== null) {
        if (verticalClip <= y) {
          fakeTargetBox.top = verticalClip;
        } else {
          fakeTargetBox.bottom = verticalClip;
        }
      }
    }
  
    // Only return the new fake target if it is big enough.
    return (fakeTargetBox.right - fakeTargetBox.left) *
                (fakeTargetBox.bottom - fakeTargetBox.top) >=
            goog.fx.AbstractDragDrop.DUMMY_TARGET_MIN_SIZE_ ?
        this.dummyTarget_ :
        null;
  };
  
  
  /**
   * Returns the target for a given cursor position.
   *
   * @param {goog.math.Coordinate} position Cursor position.
   * @return {goog.fx.ActiveDropTarget_} Target for position or null if no target
   *     was defined for the given position.
   * @private
   */
  goog.fx.AbstractDragDrop.prototype.getTargetFromPosition_ = function(position) {
    for (var target, i = 0; target = this.targetList_[i]; i++) {
      if (target.box_.contains(position)) {
        if (target.scrollableContainer_) {
          // If we have a scrollable container we will need to make sure
          // we account for clipping of the scroll area
          var box = target.scrollableContainer_.box_;
          if (box.contains(position)) {
            return target;
          }
        } else {
          return target;
        }
      }
    }
  
    return null;
  };
  
  
  /**
   * Checks whatever a given point is inside a given box.
   *
   * @param {number} x Cursor position on the x-axis.
   * @param {number} y Cursor position on the y-axis.
   * @param {goog.math.Box} box Box to check position against.
   * @return {boolean} Whether the given point is inside `box`.
   * @protected
   * @deprecated Use goog.math.Box.contains.
   */
  goog.fx.AbstractDragDrop.prototype.isInside = function(x, y, box) {
    return x >= box.left && x < box.right && y >= box.top && y < box.bottom;
  };
  
  
  /**
   * Gets the scroll distance as a coordinate object, using
   * the window of the current drag element's dom.
   * @return {!goog.math.Coordinate} Object with scroll offsets 'x' and 'y'.
   * @protected
   */
  goog.fx.AbstractDragDrop.prototype.getScrollPos = function() {
    return goog.dom.getDomHelper(this.dragEl_).getDocumentScroll();
  };
  
  
  /**
   * Get the position of a drag event.
   * @param {goog.fx.DragEvent} event Drag event.
   * @return {!goog.math.Coordinate} Position of the event.
   * @protected
   */
  goog.fx.AbstractDragDrop.prototype.getEventPosition = function(event) {
    var scroll = this.getScrollPos();
    return new goog.math.Coordinate(
        event.clientX + scroll.x, event.clientY + scroll.y);
  };
  
  
  /**
   * @override
   * @protected
   */
  goog.fx.AbstractDragDrop.prototype.disposeInternal = function() {
    goog.fx.AbstractDragDrop.base(this, 'disposeInternal');
    this.removeItems();
  };
  
  
  
  /**
   * Object representing a drag and drop event.
   *
   * @param {string} type Event type.
   * @param {goog.fx.AbstractDragDrop} source Source drag drop object.
   * @param {goog.fx.DragDropItem} sourceItem Source item.
   * @param {goog.fx.AbstractDragDrop=} opt_target Target drag drop object.
   * @param {goog.fx.DragDropItem=} opt_targetItem Target item.
   * @param {Element=} opt_targetElement Target element.
   * @param {number=} opt_clientX X-Position relative to the screen.
   * @param {number=} opt_clientY Y-Position relative to the screen.
   * @param {number=} opt_x X-Position relative to the viewport.
   * @param {number=} opt_y Y-Position relative to the viewport.
   * @param {Object=} opt_subtarget The currently active subtarget.
   * @param {goog.events.BrowserEvent=} opt_browserEvent The browser event
   *     that caused this dragdrop event.
   * @extends {goog.events.Event}
   * @constructor
   * @struct
   */
  goog.fx.DragDropEvent = function(
      type, source, sourceItem, opt_target, opt_targetItem, opt_targetElement,
      opt_clientX, opt_clientY, opt_x, opt_y, opt_subtarget, opt_browserEvent) {
    // TODO(user): Get rid of all the optional parameters and have the caller set
    // the fields directly instead.
    goog.fx.DragDropEvent.base(this, 'constructor', type);
  
    /**
     * Reference to the source goog.fx.AbstractDragDrop object.
     * @type {goog.fx.AbstractDragDrop}
     */
    this.dragSource = source;
  
    /**
     * Reference to the source goog.fx.DragDropItem object.
     * @type {goog.fx.DragDropItem}
     */
    this.dragSourceItem = sourceItem;
  
    /**
     * Reference to the target goog.fx.AbstractDragDrop object.
     * @type {goog.fx.AbstractDragDrop|undefined}
     */
    this.dropTarget = opt_target;
  
    /**
     * Reference to the target goog.fx.DragDropItem object.
     * @type {goog.fx.DragDropItem|undefined}
     */
    this.dropTargetItem = opt_targetItem;
  
    /**
     * The actual element of the drop target that is the target for this event.
     * @type {Element|undefined}
     */
    this.dropTargetElement = opt_targetElement;
  
    /**
     * X-Position relative to the screen.
     * @type {number|undefined}
     */
    this.clientX = opt_clientX;
  
    /**
     * Y-Position relative to the screen.
     * @type {number|undefined}
     */
    this.clientY = opt_clientY;
  
    /**
     * X-Position relative to the viewport.
     * @type {number|undefined}
     */
    this.viewportX = opt_x;
  
    /**
     * Y-Position relative to the viewport.
     * @type {number|undefined}
     */
    this.viewportY = opt_y;
  
    /**
     * The subtarget that is currently active if a subtargeting function
     * is supplied.
     * @type {Object|undefined}
     */
    this.subtarget = opt_subtarget;
  
    /**
     * The browser event that caused this dragdrop event.
     * @const
     */
    this.browserEvent = opt_browserEvent;
  };
  goog.inherits(goog.fx.DragDropEvent, goog.events.Event);
  
  
  
  /**
   * Class representing a source or target element for drag and drop operations.
   *
   * @param {Element|string} element Dom Node, or string representation of node
   *     id, to be used as drag source/drop target.
   * @param {Object=} opt_data Data associated with the source/target.
   * @throws Error If no element argument is provided or if the type is invalid
   * @extends {goog.events.EventTarget}
   * @constructor
   * @struct
   */
  goog.fx.DragDropItem = function(element, opt_data) {
    goog.fx.DragDropItem.base(this, 'constructor');
  
    /**
     * Reference to drag source/target element
     * @type {Element}
     */
    this.element = goog.dom.getElement(element);
  
    /**
     * Data associated with element.
     * @type {Object|undefined}
     */
    this.data = opt_data;
  
    /**
     * Drag object the item belongs to.
     * @type {goog.fx.AbstractDragDrop?}
     * @private
     */
    this.parent_ = null;
  
    /**
     * Event handler for listeners on events that can initiate a drag.
     * @type {!goog.events.EventHandler<!goog.fx.DragDropItem>}
     * @private
     */
    this.eventHandler_ = new goog.events.EventHandler(this);
    this.registerDisposable(this.eventHandler_);
  
    /**
     * The current element being dragged. This is needed because a DragDropItem
     * can have multiple elements that can be dragged.
     * @private {?Element}
     */
    this.currentDragElement_ = null;
  
    /** @private {?goog.math.Coordinate} */
    this.startPosition_;
  
    if (!this.element) {
      throw new Error('Invalid argument');
    }
  };
  goog.inherits(goog.fx.DragDropItem, goog.events.EventTarget);
  
  
  /**
   * Get the data associated with the source/target.
   * @return {Object|null|undefined} Data associated with the source/target.
   */
  goog.fx.DragDropItem.prototype.getData = function() {
    return this.data;
  };
  
  
  /**
   * Gets the element that is actually draggable given that the given target was
   * attempted to be dragged. This should be overridden when the element that was
   * given actually contains many items that can be dragged. From the target, you
   * can determine what element should actually be dragged.
   *
   * @param {Element} target The target that was attempted to be dragged.
   * @return {Element} The element that is draggable given the target. If
   *     none are draggable, this will return null.
   */
  goog.fx.DragDropItem.prototype.getDraggableElement = function(target) {
    return target;
  };
  
  
  /**
   * Gets the element that is currently being dragged.
   *
   * @return {Element} The element that is currently being dragged.
   */
  goog.fx.DragDropItem.prototype.getCurrentDragElement = function() {
    return this.currentDragElement_;
  };
  
  
  /**
   * Gets all the elements of this item that are potentially draggable/
   *
   * @return {!Array<Element>} The draggable elements.
   */
  goog.fx.DragDropItem.prototype.getDraggableElements = function() {
    return [this.element];
  };
  
  
  /**
   * Event handler for mouse down.
   *
   * @param {goog.events.BrowserEvent} event Mouse down event.
   * @private
   */
  goog.fx.DragDropItem.prototype.mouseDown_ = function(event) {
    if (!event.isMouseActionButton()) {
      return;
    }
  
    // Get the draggable element for the target.
    var element = this.getDraggableElement(/** @type {Element} */ (event.target));
    if (element) {
      this.maybeStartDrag_(event, element);
    }
  };
  
  
  /**
   * Sets the dragdrop to which this item belongs.
   * @param {goog.fx.AbstractDragDrop} parent The parent dragdrop.
   */
  goog.fx.DragDropItem.prototype.setParent = function(parent) {
    this.parent_ = parent;
  };
  
  
  /**
   * Adds mouse move, mouse out and mouse up handlers.
   *
   * @param {goog.events.BrowserEvent} event Mouse down event.
   * @param {Element} element Element.
   * @private
   */
  goog.fx.DragDropItem.prototype.maybeStartDrag_ = function(event, element) {
    var eventType = goog.events.EventType;
    this.eventHandler_
        .listen(element, eventType.MOUSEMOVE, this.mouseMove_, false)
        .listen(element, eventType.MOUSEOUT, this.mouseMove_, false);
  
    // Capture the MOUSEUP on the document to ensure that we cancel the start
    // drag handlers even if the mouse up occurs on some other element. This can
    // happen for instance when the mouse down changes the geometry of the element
    // clicked on (e.g. through changes in activation styling) such that the mouse
    // up occurs outside the original element.
    var doc = goog.dom.getOwnerDocument(element);
    this.eventHandler_.listen(doc, eventType.MOUSEUP, this.mouseUp_, true);
  
    this.currentDragElement_ = element;
  
    this.startPosition_ = new goog.math.Coordinate(event.clientX, event.clientY);
  };
  
  
  /**
   * Event handler for mouse move. Starts drag operation if moved more than the
   * threshold value.
   *
   * @param {goog.events.BrowserEvent} event Mouse move or mouse out event.
   * @private
   */
  goog.fx.DragDropItem.prototype.mouseMove_ = function(event) {
    var distance = Math.abs(event.clientX - this.startPosition_.x) +
        Math.abs(event.clientY - this.startPosition_.y);
    // Fire dragStart event if the drag distance exceeds the threshold or if the
    // mouse leave the dragged element.
    // TODO(user): Consider using the goog.fx.Dragger to track the distance
    // even after the mouse leaves the dragged element.
    var currentDragElement = this.currentDragElement_;
    var distanceAboveThreshold =
        distance > goog.fx.AbstractDragDrop.initDragDistanceThreshold;
    var mouseOutOnDragElement = event.type == goog.events.EventType.MOUSEOUT &&
        event.target == currentDragElement;
    if (distanceAboveThreshold || mouseOutOnDragElement) {
      this.eventHandler_.removeAll();
      this.parent_.startDrag(event, this);
    }
  
    // Prevent text selection while dragging an element.
    event.preventDefault();
  };
  
  
  /**
   * Event handler for mouse up. Removes mouse move, mouse out and mouse up event
   * handlers.
   *
   * @param {goog.events.BrowserEvent} event Mouse up event.
   * @private
   */
  goog.fx.DragDropItem.prototype.mouseUp_ = function(event) {
    this.eventHandler_.removeAll();
    delete this.startPosition_;
    this.currentDragElement_ = null;
  };
  
  
  
  /**
   * Class representing an active drop target
   *
   * @param {goog.math.Box} box Box describing the position and dimension of the
   *     target item.
   * @param {goog.fx.AbstractDragDrop=} opt_target Target that contains the item
         associated with position.
   * @param {goog.fx.DragDropItem=} opt_item Item associated with position.
   * @param {Element=} opt_element Element of item associated with position.
   * @constructor
   * @struct
   * @private
   */
  goog.fx.ActiveDropTarget_ = function(box, opt_target, opt_item, opt_element) {
  
    /**
     * Box describing the position and dimension of the target item
     * @type {goog.math.Box}
     * @private
     */
    this.box_ = box;
  
    /**
     * Target that contains the item associated with position
     * @type {goog.fx.AbstractDragDrop|undefined}
     * @private
     */
    this.target_ = opt_target;
  
    /**
     * Item associated with position
     * @type {goog.fx.DragDropItem|undefined}
     * @private
     */
    this.item_ = opt_item;
  
    /**
     * The draggable element of the item associated with position.
     * @type {Element}
     * @private
     */
    this.element_ = opt_element || null;
  
    /**
     * If this target is in a scrollable container this is it.
     * @private {?goog.fx.ScrollableContainer_}
     */
    this.scrollableContainer_ = null;
  };
  
  
  
  /**
   * Class for representing a scrollable container
   * @param {Element} element the scrollable element.
   * @constructor
   * @private
   */
  goog.fx.ScrollableContainer_ = function(element) {
  
    /**
     * The targets that lie within this container.
     * @type {Array<goog.fx.ActiveDropTarget_>}
     * @private
     */
    this.containedTargets_ = [];
  
    /**
     * The element that is this container
     * @type {Element}
     * @private
     */
    this.element_ = element;
  
    /**
     * The saved scroll left location for calculating deltas.
     * @type {number}
     * @private
     */
    this.savedScrollLeft_ = 0;
  
    /**
     * The saved scroll top location for calculating deltas.
     * @type {number}
     * @private
     */
    this.savedScrollTop_ = 0;
  
    /**
     * The space occupied by the container.
     * @type {?goog.math.Box}
     * @private
     */
    this.box_ = null;
  };
  