| import Handler from '../Handler'; | 
| import Element, { ElementEvent } from '../Element'; | 
| import Displayable from '../graphic/Displayable'; | 
|   | 
| class Param { | 
|   | 
|     target: Element | 
|     topTarget: Element | 
|   | 
|     constructor(target: Element, e?: ElementEvent) { | 
|         this.target = target; | 
|         this.topTarget = e && e.topTarget; | 
|     } | 
| } | 
|   | 
| // FIXME Draggable on element which has parent rotation or scale | 
| export default class Draggable { | 
|   | 
|     handler: Handler | 
|   | 
|     _draggingTarget: Element | 
|     _dropTarget: Element | 
|   | 
|     _x: number | 
|     _y: number | 
|   | 
|     constructor(handler: Handler) { | 
|         this.handler = handler; | 
|   | 
|         handler.on('mousedown', this._dragStart, this); | 
|         handler.on('mousemove', this._drag, this); | 
|         handler.on('mouseup', this._dragEnd, this); | 
|         // `mosuemove` and `mouseup` can be continue to fire when dragging. | 
|         // See [DRAG_OUTSIDE] in `Handler.js`. So we do not need to trigger | 
|         // `_dragEnd` when globalout. That would brings better user experience. | 
|         // this.on('globalout', this._dragEnd, this); | 
|   | 
|         // this._dropTarget = null; | 
|         // this._draggingTarget = null; | 
|   | 
|         // this._x = 0; | 
|         // this._y = 0; | 
|     } | 
|   | 
|     _dragStart(e: ElementEvent) { | 
|         let draggingTarget = e.target; | 
|         // Find if there is draggable in the ancestor | 
|         while (draggingTarget && !draggingTarget.draggable) { | 
|             draggingTarget = draggingTarget.parent || draggingTarget.__hostTarget; | 
|         } | 
|         if (draggingTarget) { | 
|             this._draggingTarget = draggingTarget; | 
|             draggingTarget.dragging = true; | 
|             this._x = e.offsetX; | 
|             this._y = e.offsetY; | 
|   | 
|             this.handler.dispatchToElement( | 
|                 new Param(draggingTarget, e), 'dragstart', e.event | 
|             ); | 
|         } | 
|     } | 
|   | 
|     _drag(e: ElementEvent) { | 
|         const draggingTarget = this._draggingTarget; | 
|         if (draggingTarget) { | 
|   | 
|             const x = e.offsetX; | 
|             const y = e.offsetY; | 
|   | 
|             const dx = x - this._x; | 
|             const dy = y - this._y; | 
|             this._x = x; | 
|             this._y = y; | 
|   | 
|             draggingTarget.drift(dx, dy, e); | 
|             this.handler.dispatchToElement( | 
|                 new Param(draggingTarget, e), 'drag', e.event | 
|             ); | 
|   | 
|             const dropTarget = this.handler.findHover( | 
|                 x, y, draggingTarget as Displayable // PENDING | 
|             ).target; | 
|             const lastDropTarget = this._dropTarget; | 
|             this._dropTarget = dropTarget; | 
|   | 
|             if (draggingTarget !== dropTarget) { | 
|                 if (lastDropTarget && dropTarget !== lastDropTarget) { | 
|                     this.handler.dispatchToElement( | 
|                         new Param(lastDropTarget, e), 'dragleave', e.event | 
|                     ); | 
|                 } | 
|                 if (dropTarget && dropTarget !== lastDropTarget) { | 
|                     this.handler.dispatchToElement( | 
|                         new Param(dropTarget, e), 'dragenter', e.event | 
|                     ); | 
|                 } | 
|             } | 
|         } | 
|     } | 
|   | 
|     _dragEnd(e: ElementEvent) { | 
|         const draggingTarget = this._draggingTarget; | 
|   | 
|         if (draggingTarget) { | 
|             draggingTarget.dragging = false; | 
|         } | 
|   | 
|         this.handler.dispatchToElement(new Param(draggingTarget, e), 'dragend', e.event); | 
|   | 
|         if (this._dropTarget) { | 
|             this.handler.dispatchToElement(new Param(this._dropTarget, e), 'drop', e.event); | 
|         } | 
|   | 
|         this._draggingTarget = null; | 
|         this._dropTarget = null; | 
|     } | 
|   | 
| } |