Skip to content

Commit

Permalink
fix(droppable): only allow dropping of events when the cursor is inside
Browse files Browse the repository at this point in the history
BREAKING CHANGE: the drag enter, leave and drop events will not fire until cursor is inside the droppable element. This is to mimic how native drag and drop works

Closes #5
  • Loading branch information
Matt Lewis committed Dec 10, 2016
1 parent f0624a8 commit 652d632
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 31 deletions.
2 changes: 1 addition & 1 deletion demo/demo.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {Component} from '@angular/core';
Drag me!
</div>
<div mwlDraggable dropData="bar" [dragSnapGrid]="{x: 100, y: 100}">
I snap to a 100x100 grid
I snap to a 100 x 100 grid
</div>
<div
[class.dropOverActive]="dropOverActive"
Expand Down
41 changes: 25 additions & 16 deletions src/draggable.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ export class Draggable implements OnInit, OnDestroy {
return {
currentDrag,
x: mouseMoveEvent.clientX - mouseDownEvent.clientX,
y: mouseMoveEvent.clientY - mouseDownEvent.clientY
y: mouseMoveEvent.clientY - mouseDownEvent.clientY,
clientX: mouseMoveEvent.clientX,
clientY: mouseMoveEvent.clientY
};

})
Expand Down Expand Up @@ -113,24 +115,31 @@ export class Draggable implements OnInit, OnDestroy {

});

mouseDrag.subscribe(({x, y, currentDrag}) => {
mouseDrag.subscribe(({x, y, currentDrag, clientX, clientY}) => {
this.setCssTransform(`translate(${x}px, ${y}px)`);
currentDrag.next({rectangle: this.element.nativeElement.getBoundingClientRect(), dropData: this.dropData});
currentDrag.next({
clientX,
clientY,
dropData: this.dropData
});
});

Observable.merge(
mouseDrag.take(1).map(value => [, value]),
mouseDrag.pairwise()
).filter(([previous, next]) => {
if (!previous) {
return true;
}
return previous.x !== next.x || previous.y !== next.y;
})
.map(([previous, next]) => next)
.subscribe(({x, y}) => {
this.dragging.next({x, y});
});
Observable
.merge(
mouseDrag.take(1).map(value => [, value]),
mouseDrag.pairwise()
)
.filter(([previous, next]) => {
if (!previous) {
return true;
}
return previous.x !== next.x || previous.y !== next.y;
})
.map(([previous, next]) => next)
.filter(({x, y}) => x !== 0 || y !== 0)
.subscribe(({x, y}) => {
this.dragging.next({x, y});
});

}

Expand Down
21 changes: 11 additions & 10 deletions src/droppable.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,8 @@ import 'rxjs/add/operator/pairwise';
import 'rxjs/add/operator/filter';
import {DraggableHelper} from './draggableHelper.provider';

function isOverlapping(rect1: ClientRect, rect2: ClientRect): boolean {
return !(
rect1.right < rect2.left ||
rect1.left > rect2.right ||
rect1.bottom < rect2.top ||
rect1.top > rect2.bottom
);
function isCoordinateWithinRectangle(clientX: number, clientY: number, rect: ClientRect): boolean {
return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom;
}

export type DropData = {dropData: any};
Expand All @@ -37,14 +32,20 @@ export class Droppable implements OnInit, OnDestroy {

ngOnInit(): void {

this.currentDragSubscription = this.draggableHelper.currentDrag.subscribe((drag: Subject<{rectangle: ClientRect, dropData: any}>) => {
interface CurrentDragData {
clientX: number;
clientY: number;
dropData: any;
}

this.currentDragSubscription = this.draggableHelper.currentDrag.subscribe((drag: Subject<CurrentDragData>) => {

const droppableRectangle: ClientRect = this.element.nativeElement.getBoundingClientRect();

let currentDragDropData: any;
const overlaps: Observable<boolean> = drag.map(({rectangle: draggableRectangle, dropData}) => {
const overlaps: Observable<boolean> = drag.map(({clientX, clientY, dropData}) => {
currentDragDropData = dropData;
return isOverlapping(draggableRectangle, droppableRectangle);
return isCoordinateWithinRectangle(clientX, clientY, droppableRectangle);
});

const overlapsChanged: Observable<boolean> = overlaps.distinctUntilChanged();
Expand Down
16 changes: 12 additions & 4 deletions test/draggable.directive.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,6 @@ describe('draggable directive', () => {
fixture.detectChanges();
const draggableElement: HTMLElement = fixture.componentInstance.draggable.element.nativeElement;
triggerDomEvent('mousedown', draggableElement, {clientX: 10, clientY: 5});
triggerDomEvent('mousemove', draggableElement, {clientX: 12, clientY: 7});
expect(fixture.componentInstance.dragging).to.have.been.calledWith({x: 0, y: 0});
triggerDomEvent('mousemove', draggableElement, {clientX: 18, clientY: 14});
expect(fixture.componentInstance.dragging).to.have.been.calledWith({x: 0, y: 0});
triggerDomEvent('mousemove', draggableElement, {clientX: 20, clientY: 15});
expect(fixture.componentInstance.dragging).to.have.been.calledWith({x: 10, y: 10});
triggerDomEvent('mousemove', draggableElement, {clientX: 22, clientY: 16});
Expand Down Expand Up @@ -219,4 +215,16 @@ describe('draggable directive', () => {
expect(fixture.componentInstance.dragging).to.have.been.calledOnce;
});

it('should not call the dragging event with {x: 0, y: 0}', () => {
fixture.componentInstance.dragSnapGrid = {y: 10, x: 10};
fixture.detectChanges();
const draggableElement: HTMLElement = fixture.componentInstance.draggable.element.nativeElement;
triggerDomEvent('mousedown', draggableElement, {clientX: 10, clientY: 5});
triggerDomEvent('mousemove', draggableElement, {clientX: 12, clientY: 7});
expect(fixture.componentInstance.dragging).not.to.have.been.called;
triggerDomEvent('mousemove', draggableElement, {clientX: 12, clientY: 15});
expect(fixture.componentInstance.dragging).to.have.been.calledOnce;
expect(fixture.componentInstance.dragging).to.have.been.calledWith({x: 0, y: 10});
});

});
13 changes: 13 additions & 0 deletions test/droppable.directive.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,17 @@ describe('droppable directive', () => {
expect(fixture.componentInstance.drop).to.have.been.calledWith({dropData: {foo: 'bar'}});
});

it('should not fire the drag enter event until the mouse cursor is within the element', () => {
const draggableElement: HTMLElement = fixture.componentInstance.draggable.element.nativeElement;
triggerDomEvent('mousedown', draggableElement, {clientX: 5, clientY: 10});
expect(fixture.componentInstance.dragEvent).not.to.have.been.called;
triggerDomEvent('mousemove', draggableElement, {clientX: 5, clientY: 50});
expect(fixture.componentInstance.dragEvent).not.to.have.been.called;
triggerDomEvent('mousemove', draggableElement, {clientX: 5, clientY: 99});
expect(fixture.componentInstance.dragEvent).not.to.have.been.called;
triggerDomEvent('mousemove', draggableElement, {clientX: 5, clientY: 100});
expect(fixture.componentInstance.dragEvent.getCall(0).args).to.deep.equal(['enter', {dropData: {foo: 'bar'}}]);
expect(fixture.componentInstance.dragEvent.getCall(1).args).to.deep.equal(['over', {dropData: {foo: 'bar'}}]);
});

});

0 comments on commit 652d632

Please sign in to comment.