Skip to content

Commit 4347f22

Browse files
committed
feat(drag-drop): indicate in dropped event whether item was dropped outside of container
Adds an extra flag on the `CdkDragDrop` event that indicates whether the user's pointer was over the container when they dropped an item. Fixes #14136.
1 parent 89bb692 commit 4347f22

File tree

5 files changed

+98
-34
lines changed

5 files changed

+98
-34
lines changed

src/cdk/drag-drop/drag-events.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ export interface CdkDragDrop<T, O = T> {
5353
container: CdkDropListContainer<T>;
5454
/** Container from which the item was picked up. Can be the same as the `container`. */
5555
previousContainer: CdkDropListContainer<O>;
56+
/** Whether the user's pointer was over the container when the item was dropped. */
57+
isPointerOverContainer: boolean;
5658
}
5759

5860
/** Event emitted as the user is dragging a draggable item. */

src/cdk/drag-drop/drag.spec.ts

Lines changed: 69 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -691,13 +691,55 @@ describe('CdkDrag', () => {
691691
currentIndex: 2,
692692
item: firstItem,
693693
container: fixture.componentInstance.dropInstance,
694-
previousContainer: fixture.componentInstance.dropInstance
694+
previousContainer: fixture.componentInstance.dropInstance,
695+
isPointerOverContainer: true
695696
});
696697

697698
expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim()))
698699
.toEqual(['One', 'Two', 'Zero', 'Three']);
699700
}));
700701

702+
it('should expose whether an item was dropped over a container', fakeAsync(() => {
703+
const fixture = createComponent(DraggableInDropZone);
704+
fixture.detectChanges();
705+
const dragItems = fixture.componentInstance.dragItems;
706+
const firstItem = dragItems.first;
707+
const thirdItemRect = dragItems.toArray()[2].element.nativeElement.getBoundingClientRect();
708+
709+
dragElementViaMouse(fixture, firstItem.element.nativeElement,
710+
thirdItemRect.left + 1, thirdItemRect.top + 1);
711+
flush();
712+
fixture.detectChanges();
713+
714+
expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1);
715+
716+
const event: CdkDragDrop<any> =
717+
fixture.componentInstance.droppedSpy.calls.mostRecent().args[0];
718+
719+
expect(event.isPointerOverContainer).toBe(true);
720+
}));
721+
722+
it('should expose whether an item was dropped outside of a container', fakeAsync(() => {
723+
const fixture = createComponent(DraggableInDropZone);
724+
fixture.detectChanges();
725+
const dragItems = fixture.componentInstance.dragItems;
726+
const firstItem = dragItems.first;
727+
const containerRect = fixture.componentInstance.dropInstance.element
728+
.nativeElement.getBoundingClientRect();
729+
730+
dragElementViaMouse(fixture, firstItem.element.nativeElement,
731+
containerRect.right + 10, containerRect.bottom + 10);
732+
flush();
733+
fixture.detectChanges();
734+
735+
expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1);
736+
737+
const event: CdkDragDrop<any> =
738+
fixture.componentInstance.droppedSpy.calls.mostRecent().args[0];
739+
740+
expect(event.isPointerOverContainer).toBe(false);
741+
}));
742+
701743
it('should dispatch the `sorted` event as an item is being sorted', fakeAsync(() => {
702744
const fixture = createComponent(DraggableInDropZone);
703745
fixture.detectChanges();
@@ -756,7 +798,8 @@ describe('CdkDrag', () => {
756798
currentIndex: 0,
757799
item: firstItem,
758800
container: fixture.componentInstance.dropInstance,
759-
previousContainer: fixture.componentInstance.dropInstance
801+
previousContainer: fixture.componentInstance.dropInstance,
802+
isPointerOverContainer: false
760803
});
761804

762805
expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim()))
@@ -813,7 +856,8 @@ describe('CdkDrag', () => {
813856
currentIndex: 2,
814857
item: firstItem,
815858
container: fixture.componentInstance.dropInstance,
816-
previousContainer: fixture.componentInstance.dropInstance
859+
previousContainer: fixture.componentInstance.dropInstance,
860+
isPointerOverContainer: true
817861
});
818862

819863
expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim()))
@@ -852,7 +896,8 @@ describe('CdkDrag', () => {
852896
currentIndex: 2,
853897
item: firstItem,
854898
container: fixture.componentInstance.dropInstance,
855-
previousContainer: fixture.componentInstance.dropInstance
899+
previousContainer: fixture.componentInstance.dropInstance,
900+
isPointerOverContainer: true
856901
});
857902

858903
expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim()))
@@ -887,7 +932,8 @@ describe('CdkDrag', () => {
887932
currentIndex: 0,
888933
item: firstItem,
889934
container: fixture.componentInstance.dropInstance,
890-
previousContainer: fixture.componentInstance.dropInstance
935+
previousContainer: fixture.componentInstance.dropInstance,
936+
isPointerOverContainer: false
891937
});
892938

893939
expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim()))
@@ -1699,7 +1745,8 @@ describe('CdkDrag', () => {
16991745
currentIndex: 3,
17001746
item,
17011747
container: fixture.componentInstance.dropInstances.toArray()[1],
1702-
previousContainer: fixture.componentInstance.dropInstances.first
1748+
previousContainer: fixture.componentInstance.dropInstances.first,
1749+
isPointerOverContainer: true
17031750
});
17041751
}));
17051752

@@ -1800,7 +1847,8 @@ describe('CdkDrag', () => {
18001847
currentIndex: 3,
18011848
item: groups[0][1],
18021849
container: dropInstances[1],
1803-
previousContainer: dropInstances[0]
1850+
previousContainer: dropInstances[0],
1851+
isPointerOverContainer: true
18041852
});
18051853
}));
18061854

@@ -1829,7 +1877,8 @@ describe('CdkDrag', () => {
18291877
currentIndex: 1,
18301878
item: groups[0][1],
18311879
container: dropInstances[0],
1832-
previousContainer: dropInstances[0]
1880+
previousContainer: dropInstances[0],
1881+
isPointerOverContainer: false
18331882
});
18341883
}));
18351884

@@ -1858,7 +1907,8 @@ describe('CdkDrag', () => {
18581907
currentIndex: 1,
18591908
item: groups[0][1],
18601909
container: dropInstances[0],
1861-
previousContainer: dropInstances[0]
1910+
previousContainer: dropInstances[0],
1911+
isPointerOverContainer: false
18621912
});
18631913
}));
18641914

@@ -1980,7 +2030,8 @@ describe('CdkDrag', () => {
19802030
currentIndex: 3,
19812031
item: groups[0][1],
19822032
container: dropInstances[1],
1983-
previousContainer: dropInstances[0]
2033+
previousContainer: dropInstances[0],
2034+
isPointerOverContainer: true
19842035
});
19852036
}));
19862037

@@ -2005,7 +2056,8 @@ describe('CdkDrag', () => {
20052056
currentIndex: 3,
20062057
item: groups[0][1],
20072058
container: dropInstances[1],
2008-
previousContainer: dropInstances[0]
2059+
previousContainer: dropInstances[0],
2060+
isPointerOverContainer: true
20092061
});
20102062
}));
20112063

@@ -2035,7 +2087,8 @@ describe('CdkDrag', () => {
20352087
currentIndex: 3,
20362088
item: groups[0][1],
20372089
container: dropInstances[1],
2038-
previousContainer: dropInstances[0]
2090+
previousContainer: dropInstances[0],
2091+
isPointerOverContainer: true
20392092
});
20402093
}));
20412094

@@ -2069,7 +2122,8 @@ describe('CdkDrag', () => {
20692122
currentIndex: 0,
20702123
item,
20712124
container: fixture.componentInstance.dropInstances.toArray()[1],
2072-
previousContainer: fixture.componentInstance.dropInstances.first
2125+
previousContainer: fixture.componentInstance.dropInstances.first,
2126+
isPointerOverContainer: true
20732127
});
20742128

20752129
expect(dropContainers[0].contains(item.element.nativeElement)).toBe(true,
@@ -2516,7 +2570,7 @@ function dragElementViaMouse(fixture: ComponentFixture<any>,
25162570
dispatchMouseEvent(document, 'mousemove', x, y);
25172571
fixture.detectChanges();
25182572

2519-
dispatchMouseEvent(document, 'mouseup');
2573+
dispatchMouseEvent(document, 'mouseup', x, y);
25202574
fixture.detectChanges();
25212575
}
25222576

@@ -2555,7 +2609,7 @@ function dragElementViaTouch(fixture: ComponentFixture<any>,
25552609
dispatchTouchEvent(document, 'touchmove', x, y);
25562610
fixture.detectChanges();
25572611

2558-
dispatchTouchEvent(document, 'touchend');
2612+
dispatchTouchEvent(document, 'touchend', x, y);
25592613
fixture.detectChanges();
25602614
}
25612615

src/cdk/drag-drop/drag.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
474474
}
475475

476476
/** Handler that is invoked when the user lifts their pointer up, after initiating a drag. */
477-
private _pointerUp = () => {
477+
private _pointerUp = (event: MouseEvent | TouchEvent) => {
478478
if (!this._isDragging()) {
479479
return;
480480
}
@@ -496,11 +496,11 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
496496
return;
497497
}
498498

499-
this._animatePreviewToPlaceholder().then(() => this._cleanupDragArtifacts());
499+
this._animatePreviewToPlaceholder().then(() => this._cleanupDragArtifacts(event));
500500
}
501501

502502
/** Cleans up the DOM artifacts that were added to facilitate the element being dragged. */
503-
private _cleanupDragArtifacts() {
503+
private _cleanupDragArtifacts(event: MouseEvent | TouchEvent) {
504504
// Restore the element's visibility and insert it at its old position in the DOM.
505505
// It's important that we maintain the position, because moving the element around in the DOM
506506
// can throw off `NgFor` which does smart diffing and re-creates elements only when necessary,
@@ -519,16 +519,19 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
519519
// Re-enter the NgZone since we bound `document` events on the outside.
520520
this._ngZone.run(() => {
521521
const currentIndex = this.dropContainer.getItemIndex(this);
522+
const {x, y} = this._getPointerPositionOnPage(event);
523+
const isPointerOverContainer = this.dropContainer._isOverContainer(x, y);
522524

523525
this.ended.emit({source: this});
524526
this.dropped.emit({
525527
item: this,
526528
currentIndex,
527529
previousIndex: this._initialContainer.getItemIndex(this),
528530
container: this.dropContainer,
529-
previousContainer: this._initialContainer
531+
previousContainer: this._initialContainer,
532+
isPointerOverContainer
530533
});
531-
this.dropContainer.drop(this, currentIndex, this._initialContainer);
534+
this.dropContainer.drop(this, currentIndex, this._initialContainer, isPointerOverContainer);
532535
this.dropContainer = this._initialContainer;
533536
});
534537
}
@@ -542,11 +545,11 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
542545
let newContainer = this.dropContainer._getSiblingContainerFromPosition(this, x, y);
543546

544547
// If we couldn't find a new container to move the item into, and the item has left it's
545-
// initial container, check whether the it's allowed to return into its original container.
546-
// This handles the case where two containers are connected one way and the user tries to
547-
// undo dragging an item into a new container.
548+
// initial container, check whether the it's over the initial container. This handles the
549+
// case where two containers are connected one way and the user tries to undo dragging an
550+
// item into a new container.
548551
if (!newContainer && this.dropContainer !== this._initialContainer &&
549-
this._initialContainer._canReturnItem(x, y)) {
552+
this._initialContainer._isOverContainer(x, y)) {
550553
newContainer = this._initialContainer;
551554
}
552555

src/cdk/drag-drop/drop-list-container.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,11 @@ export interface CdkDropListContainer<T = any> {
3434
* @param item Item being dropped into the container.
3535
* @param currentIndex Index at which the item should be inserted.
3636
* @param previousContainer Container from which the item got dragged in.
37+
* @param isPointerOverContainer Whether the user's pointer was over the
38+
* container when the item was dropped.
3739
*/
38-
drop(item: CdkDrag, currentIndex: number, previousContainer?: CdkDropListContainer): void;
40+
drop(item: CdkDrag, currentIndex: number, previousContainer: CdkDropListContainer,
41+
isPointerOverContainer: boolean): void;
3942

4043
/**
4144
* Emits an event to indicate that the user moved an item into the container.
@@ -60,7 +63,7 @@ export interface CdkDropListContainer<T = any> {
6063
_draggables: QueryList<CdkDrag>;
6164
_getSiblingContainerFromPosition(item: CdkDrag, x: number, y: number):
6265
CdkDropListContainer | null;
63-
_canReturnItem(x: number, y: number): boolean;
66+
_isOverContainer(x: number, y: number): boolean;
6467
}
6568

6669
/**

src/cdk/drag-drop/drop-list.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -199,16 +199,19 @@ export class CdkDropList<T = any> implements OnInit, OnDestroy {
199199
* @param item Item being dropped into the container.
200200
* @param currentIndex Index at which the item should be inserted.
201201
* @param previousContainer Container from which the item got dragged in.
202+
* @param isPointerOverContainer Whether the user's pointer was over the
203+
* container when the item was dropped.
202204
*/
203-
drop(item: CdkDrag, currentIndex: number, previousContainer: CdkDropList): void {
205+
drop(item: CdkDrag, currentIndex: number, previousContainer: CdkDropList,
206+
isPointerOverContainer: boolean): void {
204207
this._reset();
205208
this.dropped.emit({
206209
item,
207210
currentIndex,
208211
previousIndex: previousContainer.getItemIndex(item),
209212
container: this,
210-
// TODO(crisbeto): reconsider whether to make this null if the containers are the same.
211-
previousContainer
213+
previousContainer,
214+
isPointerOverContainer
212215
});
213216
}
214217

@@ -375,12 +378,11 @@ export class CdkDropList<T = any> implements OnInit, OnDestroy {
375378
}
376379

377380
/**
378-
* Checks whether an item that started in this container can be returned to it,
379-
* after it was moved out into another container.
380-
* @param x Position of the item along the X axis.
381-
* @param y Position of the item along the Y axis.
381+
* Checks whether the user's pointer is positioned over the container.
382+
* @param x Pointer position along the X axis.
383+
* @param y Pointer position along the Y axis.
382384
*/
383-
_canReturnItem(x: number, y: number): boolean {
385+
_isOverContainer(x: number, y: number): boolean {
384386
return isInsideClientRect(this._positionCache.self, x, y);
385387
}
386388

0 commit comments

Comments
 (0)