Skip to content

Commit 652d632

Browse files
author
Matt Lewis
committed
fix(droppable): only allow dropping of events when the cursor is inside
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
1 parent f0624a8 commit 652d632

5 files changed

+62
-31
lines changed

demo/demo.component.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {Component} from '@angular/core';
77
Drag me!
88
</div>
99
<div mwlDraggable dropData="bar" [dragSnapGrid]="{x: 100, y: 100}">
10-
I snap to a 100x100 grid
10+
I snap to a 100 x 100 grid
1111
</div>
1212
<div
1313
[class.dropOverActive]="dropOverActive"

src/draggable.directive.ts

+25-16
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ export class Draggable implements OnInit, OnDestroy {
7070
return {
7171
currentDrag,
7272
x: mouseMoveEvent.clientX - mouseDownEvent.clientX,
73-
y: mouseMoveEvent.clientY - mouseDownEvent.clientY
73+
y: mouseMoveEvent.clientY - mouseDownEvent.clientY,
74+
clientX: mouseMoveEvent.clientX,
75+
clientY: mouseMoveEvent.clientY
7476
};
7577

7678
})
@@ -113,24 +115,31 @@ export class Draggable implements OnInit, OnDestroy {
113115

114116
});
115117

116-
mouseDrag.subscribe(({x, y, currentDrag}) => {
118+
mouseDrag.subscribe(({x, y, currentDrag, clientX, clientY}) => {
117119
this.setCssTransform(`translate(${x}px, ${y}px)`);
118-
currentDrag.next({rectangle: this.element.nativeElement.getBoundingClientRect(), dropData: this.dropData});
120+
currentDrag.next({
121+
clientX,
122+
clientY,
123+
dropData: this.dropData
124+
});
119125
});
120126

121-
Observable.merge(
122-
mouseDrag.take(1).map(value => [, value]),
123-
mouseDrag.pairwise()
124-
).filter(([previous, next]) => {
125-
if (!previous) {
126-
return true;
127-
}
128-
return previous.x !== next.x || previous.y !== next.y;
129-
})
130-
.map(([previous, next]) => next)
131-
.subscribe(({x, y}) => {
132-
this.dragging.next({x, y});
133-
});
127+
Observable
128+
.merge(
129+
mouseDrag.take(1).map(value => [, value]),
130+
mouseDrag.pairwise()
131+
)
132+
.filter(([previous, next]) => {
133+
if (!previous) {
134+
return true;
135+
}
136+
return previous.x !== next.x || previous.y !== next.y;
137+
})
138+
.map(([previous, next]) => next)
139+
.filter(({x, y}) => x !== 0 || y !== 0)
140+
.subscribe(({x, y}) => {
141+
this.dragging.next({x, y});
142+
});
134143

135144
}
136145

src/droppable.directive.ts

+11-10
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,8 @@ import 'rxjs/add/operator/pairwise';
77
import 'rxjs/add/operator/filter';
88
import {DraggableHelper} from './draggableHelper.provider';
99

10-
function isOverlapping(rect1: ClientRect, rect2: ClientRect): boolean {
11-
return !(
12-
rect1.right < rect2.left ||
13-
rect1.left > rect2.right ||
14-
rect1.bottom < rect2.top ||
15-
rect1.top > rect2.bottom
16-
);
10+
function isCoordinateWithinRectangle(clientX: number, clientY: number, rect: ClientRect): boolean {
11+
return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom;
1712
}
1813

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

3833
ngOnInit(): void {
3934

40-
this.currentDragSubscription = this.draggableHelper.currentDrag.subscribe((drag: Subject<{rectangle: ClientRect, dropData: any}>) => {
35+
interface CurrentDragData {
36+
clientX: number;
37+
clientY: number;
38+
dropData: any;
39+
}
40+
41+
this.currentDragSubscription = this.draggableHelper.currentDrag.subscribe((drag: Subject<CurrentDragData>) => {
4142

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

4445
let currentDragDropData: any;
45-
const overlaps: Observable<boolean> = drag.map(({rectangle: draggableRectangle, dropData}) => {
46+
const overlaps: Observable<boolean> = drag.map(({clientX, clientY, dropData}) => {
4647
currentDragDropData = dropData;
47-
return isOverlapping(draggableRectangle, droppableRectangle);
48+
return isCoordinateWithinRectangle(clientX, clientY, droppableRectangle);
4849
});
4950

5051
const overlapsChanged: Observable<boolean> = overlaps.distinctUntilChanged();

test/draggable.directive.spec.ts

+12-4
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,6 @@ describe('draggable directive', () => {
162162
fixture.detectChanges();
163163
const draggableElement: HTMLElement = fixture.componentInstance.draggable.element.nativeElement;
164164
triggerDomEvent('mousedown', draggableElement, {clientX: 10, clientY: 5});
165-
triggerDomEvent('mousemove', draggableElement, {clientX: 12, clientY: 7});
166-
expect(fixture.componentInstance.dragging).to.have.been.calledWith({x: 0, y: 0});
167-
triggerDomEvent('mousemove', draggableElement, {clientX: 18, clientY: 14});
168-
expect(fixture.componentInstance.dragging).to.have.been.calledWith({x: 0, y: 0});
169165
triggerDomEvent('mousemove', draggableElement, {clientX: 20, clientY: 15});
170166
expect(fixture.componentInstance.dragging).to.have.been.calledWith({x: 10, y: 10});
171167
triggerDomEvent('mousemove', draggableElement, {clientX: 22, clientY: 16});
@@ -219,4 +215,16 @@ describe('draggable directive', () => {
219215
expect(fixture.componentInstance.dragging).to.have.been.calledOnce;
220216
});
221217

218+
it('should not call the dragging event with {x: 0, y: 0}', () => {
219+
fixture.componentInstance.dragSnapGrid = {y: 10, x: 10};
220+
fixture.detectChanges();
221+
const draggableElement: HTMLElement = fixture.componentInstance.draggable.element.nativeElement;
222+
triggerDomEvent('mousedown', draggableElement, {clientX: 10, clientY: 5});
223+
triggerDomEvent('mousemove', draggableElement, {clientX: 12, clientY: 7});
224+
expect(fixture.componentInstance.dragging).not.to.have.been.called;
225+
triggerDomEvent('mousemove', draggableElement, {clientX: 12, clientY: 15});
226+
expect(fixture.componentInstance.dragging).to.have.been.calledOnce;
227+
expect(fixture.componentInstance.dragging).to.have.been.calledWith({x: 0, y: 10});
228+
});
229+
222230
});

test/droppable.directive.spec.ts

+13
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,17 @@ describe('droppable directive', () => {
9999
expect(fixture.componentInstance.drop).to.have.been.calledWith({dropData: {foo: 'bar'}});
100100
});
101101

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

0 commit comments

Comments
 (0)