| // Copyright 2011 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * @fileoverview DragWrapper |
| * A class for simplifying HTML5 drag and drop. Classes should use this to |
| * handle the details of nested drag enters and leaves. |
| */ |
| export interface DragWrapperDelegate { |
| // TODO(devlin): The only method this "delegate" actually needs is |
| // shouldAcceptDrag(); the rest can be events emitted by the DragWrapper. |
| /** |
| * @return Whether the drag should be accepted. If false, |
| * subsequent methods (doDrag*) will not be called. |
| */ |
| shouldAcceptDrag(e: MouseEvent): boolean; |
| |
| doDragEnter(e: MouseEvent): void; |
| |
| doDragLeave(e: MouseEvent): void; |
| |
| doDragOver(e: MouseEvent): void; |
| |
| doDrop(e: MouseEvent): void; |
| } |
| |
| /** |
| * Creates a DragWrapper which listens for drag target events on |target| and |
| * delegates event handling to |delegate|. |
| */ |
| export class DragWrapper { |
| /** |
| * The number of un-paired dragenter events that have fired on |this|. |
| * This is incremented by |onDragEnter_| and decremented by |
| * |onDragLeave_|. This is necessary because dragging over child widgets |
| * will fire additional enter and leave events on |this|. A non-zero value |
| * does not necessarily indicate that |isCurrentDragTarget()| is true. |
| */ |
| private dragEnters_: number = 0; |
| private target_: HTMLElement; |
| private delegate_: DragWrapperDelegate; |
| |
| constructor(target: HTMLElement, delegate: DragWrapperDelegate) { |
| this.target_ = target; |
| this.delegate_ = delegate; |
| |
| target.addEventListener('dragenter', e => this.onDragEnter_(e)); |
| target.addEventListener('dragover', e => this.onDragOver_(e)); |
| target.addEventListener('drop', e => this.onDrop_(e)); |
| target.addEventListener('dragleave', e => this.onDragLeave_(e)); |
| } |
| |
| /** |
| * Whether the tile page is currently being dragged over with data it can |
| * accept. |
| */ |
| get isCurrentDragTarget(): boolean { |
| return this.target_.classList.contains('drag-target'); |
| } |
| |
| /** |
| * Delegate for dragenter events fired on |target_|. |
| */ |
| private onDragEnter_(e: MouseEvent) { |
| if (++this.dragEnters_ === 1) { |
| if (this.delegate_.shouldAcceptDrag(e)) { |
| this.target_.classList.add('drag-target'); |
| this.delegate_.doDragEnter(e); |
| } |
| } else { |
| // Sometimes we'll get an enter event over a child element without an |
| // over event following it. In this case we have to still call the |
| // drag over delegate so that we make the necessary updates (one visible |
| // symptom of not doing this is that the cursor's drag state will |
| // flicker during drags). |
| this.onDragOver_(e); |
| } |
| } |
| |
| /** |
| * Thunk for dragover events fired on |target_|. |
| */ |
| private onDragOver_(e: MouseEvent) { |
| if (!this.target_.classList.contains('drag-target')) { |
| return; |
| } |
| this.delegate_.doDragOver(e); |
| } |
| |
| /** |
| * Thunk for drop events fired on |target_|. |
| */ |
| private onDrop_(e: MouseEvent) { |
| this.dragEnters_ = 0; |
| if (!this.target_.classList.contains('drag-target')) { |
| return; |
| } |
| this.target_.classList.remove('drag-target'); |
| this.delegate_.doDrop(e); |
| } |
| |
| /** |
| * Thunk for dragleave events fired on |target_|. |
| */ |
| private onDragLeave_(e: MouseEvent) { |
| if (--this.dragEnters_ > 0) { |
| return; |
| } |
| |
| this.target_.classList.remove('drag-target'); |
| this.delegate_.doDragLeave(e); |
| } |
| } |