Skip to content

Commit 538f9b7

Browse files
committed
feat(dragCancel$): allow the drag to be cancelled
BREAKING CHANGE: The `dragStart` `$event.x` and `$event.y` values were removed as these were always `0` Closes #30
1 parent fa640c6 commit 538f9b7

File tree

2 files changed

+99
-47
lines changed

2 files changed

+99
-47
lines changed

src/draggable.directive.ts

+43-17
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
SimpleChanges,
1313
Inject
1414
} from '@angular/core';
15-
import { Subject, Observable, merge } from 'rxjs';
15+
import { Subject, Observable, merge, ReplaySubject } from 'rxjs';
1616
import {
1717
map,
1818
mergeMap,
@@ -21,7 +21,8 @@ import {
2121
takeLast,
2222
pairwise,
2323
share,
24-
filter
24+
filter,
25+
count
2526
} from 'rxjs/operators';
2627
import { CurrentDragData, DraggableHelper } from './draggable-helper.provider';
2728
import { DOCUMENT } from '@angular/common';
@@ -41,6 +42,14 @@ export interface SnapGrid {
4142
y?: number;
4243
}
4344

45+
export interface DragStart {
46+
cancelDrag$: ReplaySubject<void>;
47+
}
48+
49+
export interface DragEnd extends Coordinates {
50+
dragCancelled: boolean;
51+
}
52+
4453
export type ValidateDrag = (coordinates: Coordinates) => boolean;
4554

4655
export interface PointerEvent {
@@ -102,9 +111,10 @@ export class DraggableDirective implements OnInit, OnChanges, OnDestroy {
102111

103112
/**
104113
* Called when the element has started to be dragged.
105-
* Only called after at least one mouse or touch move event
114+
* Only called after at least one mouse or touch move event.
115+
* If you call $event.cancelDrag$.emit() it will cancel the current drag
106116
*/
107-
@Output() dragStart = new EventEmitter<Coordinates>();
117+
@Output() dragStart = new EventEmitter<DragStart>();
108118

109119
/**
110120
* Called when the element is being dragged
@@ -114,7 +124,7 @@ export class DraggableDirective implements OnInit, OnChanges, OnDestroy {
114124
/**
115125
* Called after the element is dragged
116126
*/
117-
@Output() dragEnd = new EventEmitter<Coordinates>();
127+
@Output() dragEnd = new EventEmitter<DragEnd>();
118128

119129
/**
120130
* @hidden
@@ -181,6 +191,7 @@ export class DraggableDirective implements OnInit, OnChanges, OnDestroy {
181191
this.document.head.appendChild(globalDragStyle);
182192

183193
const currentDrag$ = new Subject<CurrentDragData>();
194+
const cancelDrag$ = new ReplaySubject<void>();
184195

185196
this.zone.run(() => {
186197
this.dragPointerDown.next({ x: 0, y: 0 });
@@ -225,7 +236,7 @@ export class DraggableDirective implements OnInit, OnChanges, OnDestroy {
225236
filter(
226237
({ x, y }) => !this.validateDrag || this.validateDrag({ x, y })
227238
),
228-
takeUntil(merge(this.pointerUp, this.pointerDown)),
239+
takeUntil(merge(this.pointerUp, this.pointerDown, cancelDrag$)),
229240
share()
230241
);
231242

@@ -240,7 +251,7 @@ export class DraggableDirective implements OnInit, OnChanges, OnDestroy {
240251

241252
dragStarted$.subscribe(() => {
242253
this.zone.run(() => {
243-
this.dragStart.next({ x: 0, y: 0 });
254+
this.dragStart.next({ cancelDrag$ });
244255
});
245256

246257
this.renderer.addClass(
@@ -294,17 +305,32 @@ export class DraggableDirective implements OnInit, OnChanges, OnDestroy {
294305
this.draggableHelper.currentDrag.next(currentDrag$);
295306
});
296307

297-
dragEnded$.subscribe(({ x, y }) => {
298-
this.document.head.removeChild(globalDragStyle);
299-
currentDrag$.complete();
300-
this.zone.run(() => {
301-
this.dragEnd.next({ x, y });
308+
dragEnded$
309+
.pipe(
310+
mergeMap(dragEndData => {
311+
const dragEndData$ = cancelDrag$.pipe(
312+
count(),
313+
take(1),
314+
map(calledCount => ({
315+
...dragEndData,
316+
dragCancelled: calledCount > 0
317+
}))
318+
);
319+
cancelDrag$.complete();
320+
return dragEndData$;
321+
})
322+
)
323+
.subscribe(({ x, y, dragCancelled }) => {
324+
this.document.head.removeChild(globalDragStyle);
325+
currentDrag$.complete();
326+
this.zone.run(() => {
327+
this.dragEnd.next({ x, y, dragCancelled });
328+
});
329+
this.renderer.removeClass(
330+
this.element.nativeElement,
331+
this.dragActiveClass
332+
);
302333
});
303-
this.renderer.removeClass(
304-
this.element.nativeElement,
305-
this.dragActiveClass
306-
);
307-
});
308334

309335
return pointerMove;
310336
}),

test/draggable.directive.spec.ts

+56-30
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,7 @@ describe('draggable directive', () => {
7272
y: 0
7373
});
7474
triggerDomEvent('mousemove', draggableElement, { clientX: 7, clientY: 10 });
75-
expect(fixture.componentInstance.dragStart).to.have.been.calledWith({
76-
x: 0,
77-
y: 0
78-
});
75+
expect(fixture.componentInstance.dragStart).to.have.been.calledOnce;
7976
expect(fixture.componentInstance.dragging).to.have.been.calledWith({
8077
x: 2,
8178
y: 0
@@ -91,7 +88,8 @@ describe('draggable directive', () => {
9188
triggerDomEvent('mouseup', draggableElement, { clientX: 7, clientY: 8 });
9289
expect(fixture.componentInstance.dragEnd).to.have.been.calledWith({
9390
x: 2,
94-
y: -2
91+
y: -2,
92+
dragCancelled: false
9593
});
9694
});
9795

@@ -132,7 +130,8 @@ describe('draggable directive', () => {
132130
triggerDomEvent('mouseup', draggableElement, { clientX: 7, clientY: 12 });
133131
expect(fixture.componentInstance.dragEnd).to.have.been.calledWith({
134132
x: 0,
135-
y: 2
133+
y: 2,
134+
dragCancelled: false
136135
});
137136
});
138137

@@ -152,7 +151,8 @@ describe('draggable directive', () => {
152151
triggerDomEvent('mouseup', draggableElement, { clientX: 7, clientY: 12 });
153152
expect(fixture.componentInstance.dragEnd).to.have.been.calledWith({
154153
x: 2,
155-
y: 0
154+
y: 0,
155+
dragCancelled: false
156156
});
157157
});
158158

@@ -217,7 +217,8 @@ describe('draggable directive', () => {
217217
triggerDomEvent('mouseup', draggableElement, { clientX: 16, clientY: 22 });
218218
expect(fixture.componentInstance.dragEnd).to.have.been.calledWith({
219219
x: 10,
220-
y: 12
220+
y: 12,
221+
dragCancelled: false
221222
});
222223
});
223224

@@ -259,7 +260,8 @@ describe('draggable directive', () => {
259260
triggerDomEvent('mouseup', draggableElement, { clientX: 22, clientY: 16 });
260261
expect(fixture.componentInstance.dragEnd).to.have.been.calledWith({
261262
x: 12,
262-
y: 10
263+
y: 10,
264+
dragCancelled: false
263265
});
264266
});
265267

@@ -288,7 +290,8 @@ describe('draggable directive', () => {
288290
triggerDomEvent('mouseup', draggableElement, { clientX: 22, clientY: 16 });
289291
expect(fixture.componentInstance.dragEnd).to.have.been.calledWith({
290292
x: 10,
291-
y: 10
293+
y: 10,
294+
dragCancelled: false
292295
});
293296
});
294297

@@ -299,10 +302,7 @@ describe('draggable directive', () => {
299302
fixture.componentInstance.draggable.element.nativeElement;
300303
triggerDomEvent('mousedown', draggableElement, { clientX: 5, clientY: 10 });
301304
triggerDomEvent('mousemove', draggableElement, { clientX: 7, clientY: 10 });
302-
expect(fixture.componentInstance.dragStart).to.have.been.calledWith({
303-
x: 0,
304-
y: 0
305-
});
305+
expect(fixture.componentInstance.dragStart).to.have.been.calledOnce;
306306
expect(fixture.componentInstance.dragging).to.have.been.calledWith({
307307
x: 2,
308308
y: 0
@@ -317,7 +317,8 @@ describe('draggable directive', () => {
317317
triggerDomEvent('mouseup', draggableElement, { clientX: 7, clientY: 8 });
318318
expect(fixture.componentInstance.dragEnd).to.have.been.calledWith({
319319
x: 2,
320-
y: -2
320+
y: -2,
321+
dragCancelled: false
321322
});
322323
expect(draggableElement.nextSibling).not.to.ok;
323324
});
@@ -381,10 +382,7 @@ describe('draggable directive', () => {
381382
y: -2
382383
});
383384
triggerDomEvent('mousemove', draggableElement, { clientX: 7, clientY: 12 });
384-
expect(fixture.componentInstance.dragStart).to.have.been.calledWith({
385-
x: 0,
386-
y: 0
387-
});
385+
expect(fixture.componentInstance.dragStart).to.have.been.calledOnce;
388386
expect(fixture.componentInstance.dragging).to.have.been.calledWith({
389387
x: 2,
390388
y: 2
@@ -397,7 +395,8 @@ describe('draggable directive', () => {
397395
triggerDomEvent('mouseup', draggableElement, { clientX: 3, clientY: 10 });
398396
expect(fixture.componentInstance.dragEnd).to.have.been.calledWith({
399397
x: 2,
400-
y: 2
398+
y: 2,
399+
dragCancelled: false
401400
});
402401
});
403402

@@ -443,10 +442,7 @@ describe('draggable directive', () => {
443442
triggerDomEvent('touchmove', draggableElement, {
444443
targetTouches: [{ clientX: 7, clientY: 10 }]
445444
});
446-
expect(fixture.componentInstance.dragStart).to.have.been.calledWith({
447-
x: 0,
448-
y: 0
449-
});
445+
expect(fixture.componentInstance.dragStart).to.have.been.calledOnce;
450446
expect(fixture.componentInstance.dragging).to.have.been.calledWith({
451447
x: 2,
452448
y: 0
@@ -466,7 +462,8 @@ describe('draggable directive', () => {
466462
});
467463
expect(fixture.componentInstance.dragEnd).to.have.been.calledWith({
468464
x: 2,
469-
y: -2
465+
y: -2,
466+
dragCancelled: false
470467
});
471468
});
472469

@@ -479,10 +476,7 @@ describe('draggable directive', () => {
479476
triggerDomEvent('touchmove', draggableElement, {
480477
targetTouches: [{ clientX: 7, clientY: 10 }]
481478
});
482-
expect(fixture.componentInstance.dragStart).to.have.been.calledWith({
483-
x: 0,
484-
y: 0
485-
});
479+
expect(fixture.componentInstance.dragStart).to.have.been.calledOnce;
486480
expect(fixture.componentInstance.dragging).to.have.been.calledWith({
487481
x: 2,
488482
y: 0
@@ -502,7 +496,8 @@ describe('draggable directive', () => {
502496
});
503497
expect(fixture.componentInstance.dragEnd).to.have.been.calledWith({
504498
x: 2,
505-
y: -2
499+
y: -2,
500+
dragCancelled: false
506501
});
507502
});
508503

@@ -636,4 +631,35 @@ describe('draggable directive', () => {
636631
triggerDomEvent('mouseup', draggableElement, { clientX: 7, clientY: 8 });
637632
expect(getComputedStyle(tmp).userSelect).to.equal('auto');
638633
});
634+
635+
it('should cancel the drag', () => {
636+
const draggableElement: HTMLElement =
637+
fixture.componentInstance.draggable.element.nativeElement;
638+
triggerDomEvent('mousedown', draggableElement, { clientX: 5, clientY: 10 });
639+
expect(fixture.componentInstance.dragPointerDown).to.have.been.calledWith({
640+
x: 0,
641+
y: 0
642+
});
643+
triggerDomEvent('mousemove', draggableElement, { clientX: 7, clientY: 10 });
644+
expect(fixture.componentInstance.dragStart).to.have.been.calledOnce;
645+
expect(fixture.componentInstance.dragging).to.have.been.calledWith({
646+
x: 2,
647+
y: 0
648+
});
649+
const ghostElement = draggableElement.nextSibling as HTMLElement;
650+
expect(ghostElement.style.transform).to.equal('translate(2px, 0px)');
651+
triggerDomEvent('mousemove', draggableElement, { clientX: 7, clientY: 8 });
652+
expect(fixture.componentInstance.dragging).to.have.been.calledWith({
653+
x: 2,
654+
y: -2
655+
});
656+
expect(ghostElement.style.transform).to.equal('translate(2px, -2px)');
657+
fixture.componentInstance.dragStart.getCall(0).args[0].cancelDrag$.next();
658+
expect(fixture.componentInstance.dragEnd).to.have.been.calledWith({
659+
x: 2,
660+
y: -2,
661+
dragCancelled: true
662+
});
663+
expect(draggableElement.nextSibling).to.not.equal(ghostElement);
664+
});
639665
});

0 commit comments

Comments
 (0)