Skip to content

Commit 42de6a2

Browse files
crisbetojelbourn
authored andcommittedNov 28, 2018
feat(drag-drop): indicate in dropped event whether item was dropped outside of container (#14140)
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 acaed95 commit 42de6a2

File tree

6 files changed

+103
-38
lines changed

6 files changed

+103
-38
lines changed
 

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

+2
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

+69-15
Original file line numberDiff line numberDiff line change
@@ -776,13 +776,55 @@ describe('CdkDrag', () => {
776776
currentIndex: 2,
777777
item: firstItem,
778778
container: fixture.componentInstance.dropInstance,
779-
previousContainer: fixture.componentInstance.dropInstance
779+
previousContainer: fixture.componentInstance.dropInstance,
780+
isPointerOverContainer: true
780781
});
781782

782783
expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim()))
783784
.toEqual(['One', 'Two', 'Zero', 'Three']);
784785
}));
785786

787+
it('should expose whether an item was dropped over a container', fakeAsync(() => {
788+
const fixture = createComponent(DraggableInDropZone);
789+
fixture.detectChanges();
790+
const dragItems = fixture.componentInstance.dragItems;
791+
const firstItem = dragItems.first;
792+
const thirdItemRect = dragItems.toArray()[2].element.nativeElement.getBoundingClientRect();
793+
794+
dragElementViaMouse(fixture, firstItem.element.nativeElement,
795+
thirdItemRect.left + 1, thirdItemRect.top + 1);
796+
flush();
797+
fixture.detectChanges();
798+
799+
expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1);
800+
801+
const event: CdkDragDrop<any> =
802+
fixture.componentInstance.droppedSpy.calls.mostRecent().args[0];
803+
804+
expect(event.isPointerOverContainer).toBe(true);
805+
}));
806+
807+
it('should expose whether an item was dropped outside of a container', fakeAsync(() => {
808+
const fixture = createComponent(DraggableInDropZone);
809+
fixture.detectChanges();
810+
const dragItems = fixture.componentInstance.dragItems;
811+
const firstItem = dragItems.first;
812+
const containerRect = fixture.componentInstance.dropInstance.element
813+
.nativeElement.getBoundingClientRect();
814+
815+
dragElementViaMouse(fixture, firstItem.element.nativeElement,
816+
containerRect.right + 10, containerRect.bottom + 10);
817+
flush();
818+
fixture.detectChanges();
819+
820+
expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1);
821+
822+
const event: CdkDragDrop<any> =
823+
fixture.componentInstance.droppedSpy.calls.mostRecent().args[0];
824+
825+
expect(event.isPointerOverContainer).toBe(false);
826+
}));
827+
786828
it('should dispatch the `sorted` event as an item is being sorted', fakeAsync(() => {
787829
const fixture = createComponent(DraggableInDropZone);
788830
fixture.detectChanges();
@@ -841,7 +883,8 @@ describe('CdkDrag', () => {
841883
currentIndex: 0,
842884
item: firstItem,
843885
container: fixture.componentInstance.dropInstance,
844-
previousContainer: fixture.componentInstance.dropInstance
886+
previousContainer: fixture.componentInstance.dropInstance,
887+
isPointerOverContainer: false
845888
});
846889

847890
expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim()))
@@ -898,7 +941,8 @@ describe('CdkDrag', () => {
898941
currentIndex: 2,
899942
item: firstItem,
900943
container: fixture.componentInstance.dropInstance,
901-
previousContainer: fixture.componentInstance.dropInstance
944+
previousContainer: fixture.componentInstance.dropInstance,
945+
isPointerOverContainer: true
902946
});
903947

904948
expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim()))
@@ -937,7 +981,8 @@ describe('CdkDrag', () => {
937981
currentIndex: 2,
938982
item: firstItem,
939983
container: fixture.componentInstance.dropInstance,
940-
previousContainer: fixture.componentInstance.dropInstance
984+
previousContainer: fixture.componentInstance.dropInstance,
985+
isPointerOverContainer: true
941986
});
942987

943988
expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim()))
@@ -972,7 +1017,8 @@ describe('CdkDrag', () => {
9721017
currentIndex: 0,
9731018
item: firstItem,
9741019
container: fixture.componentInstance.dropInstance,
975-
previousContainer: fixture.componentInstance.dropInstance
1020+
previousContainer: fixture.componentInstance.dropInstance,
1021+
isPointerOverContainer: false
9761022
});
9771023

9781024
expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim()))
@@ -1808,7 +1854,8 @@ describe('CdkDrag', () => {
18081854
currentIndex: 3,
18091855
item,
18101856
container: fixture.componentInstance.dropInstances.toArray()[1],
1811-
previousContainer: fixture.componentInstance.dropInstances.first
1857+
previousContainer: fixture.componentInstance.dropInstances.first,
1858+
isPointerOverContainer: true
18121859
});
18131860
}));
18141861

@@ -1909,7 +1956,8 @@ describe('CdkDrag', () => {
19091956
currentIndex: 3,
19101957
item: groups[0][1],
19111958
container: dropInstances[1],
1912-
previousContainer: dropInstances[0]
1959+
previousContainer: dropInstances[0],
1960+
isPointerOverContainer: true
19131961
});
19141962
}));
19151963

@@ -1938,7 +1986,8 @@ describe('CdkDrag', () => {
19381986
currentIndex: 1,
19391987
item: groups[0][1],
19401988
container: dropInstances[0],
1941-
previousContainer: dropInstances[0]
1989+
previousContainer: dropInstances[0],
1990+
isPointerOverContainer: false
19421991
});
19431992
}));
19441993

@@ -1967,7 +2016,8 @@ describe('CdkDrag', () => {
19672016
currentIndex: 1,
19682017
item: groups[0][1],
19692018
container: dropInstances[0],
1970-
previousContainer: dropInstances[0]
2019+
previousContainer: dropInstances[0],
2020+
isPointerOverContainer: false
19712021
});
19722022
}));
19732023

@@ -2089,7 +2139,8 @@ describe('CdkDrag', () => {
20892139
currentIndex: 3,
20902140
item: groups[0][1],
20912141
container: dropInstances[1],
2092-
previousContainer: dropInstances[0]
2142+
previousContainer: dropInstances[0],
2143+
isPointerOverContainer: true
20932144
});
20942145
}));
20952146

@@ -2114,7 +2165,8 @@ describe('CdkDrag', () => {
21142165
currentIndex: 3,
21152166
item: groups[0][1],
21162167
container: dropInstances[1],
2117-
previousContainer: dropInstances[0]
2168+
previousContainer: dropInstances[0],
2169+
isPointerOverContainer: true
21182170
});
21192171
}));
21202172

@@ -2144,7 +2196,8 @@ describe('CdkDrag', () => {
21442196
currentIndex: 3,
21452197
item: groups[0][1],
21462198
container: dropInstances[1],
2147-
previousContainer: dropInstances[0]
2199+
previousContainer: dropInstances[0],
2200+
isPointerOverContainer: true
21482201
});
21492202
}));
21502203

@@ -2178,7 +2231,8 @@ describe('CdkDrag', () => {
21782231
currentIndex: 0,
21792232
item,
21802233
container: fixture.componentInstance.dropInstances.toArray()[1],
2181-
previousContainer: fixture.componentInstance.dropInstances.first
2234+
previousContainer: fixture.componentInstance.dropInstances.first,
2235+
isPointerOverContainer: true
21822236
});
21832237

21842238
expect(dropContainers[0].contains(item.element.nativeElement)).toBe(true,
@@ -2667,7 +2721,7 @@ function dragElementViaMouse(fixture: ComponentFixture<any>,
26672721
dispatchMouseEvent(document, 'mousemove', x, y);
26682722
fixture.detectChanges();
26692723

2670-
dispatchMouseEvent(document, 'mouseup');
2724+
dispatchMouseEvent(document, 'mouseup', x, y);
26712725
fixture.detectChanges();
26722726
}
26732727

@@ -2706,7 +2760,7 @@ function dragElementViaTouch(fixture: ComponentFixture<any>,
27062760
dispatchTouchEvent(document, 'touchmove', x, y);
27072761
fixture.detectChanges();
27082762

2709-
dispatchTouchEvent(document, 'touchend');
2763+
dispatchTouchEvent(document, 'touchend', x, y);
27102764
fixture.detectChanges();
27112765
}
27122766

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

+12-9
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
515515
}
516516

517517
/** Handler that is invoked when the user lifts their pointer up, after initiating a drag. */
518-
private _pointerUp = () => {
518+
private _pointerUp = (event: MouseEvent | TouchEvent) => {
519519
if (!this._isDragging()) {
520520
return;
521521
}
@@ -539,13 +539,13 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
539539
}
540540

541541
this._animatePreviewToPlaceholder().then(() => {
542-
this._cleanupDragArtifacts();
542+
this._cleanupDragArtifacts(event);
543543
this._dragDropRegistry.stopDragging(this);
544544
});
545545
}
546546

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

568570
this.ended.emit({source: this});
569571
this.dropped.emit({
570572
item: this,
571573
currentIndex,
572574
previousIndex: this._initialContainer.getItemIndex(this),
573575
container: this.dropContainer,
574-
previousContainer: this._initialContainer
576+
previousContainer: this._initialContainer,
577+
isPointerOverContainer
575578
});
576-
this.dropContainer.drop(this, currentIndex, this._initialContainer);
579+
this.dropContainer.drop(this, currentIndex, this._initialContainer, isPointerOverContainer);
577580
this.dropContainer = this._initialContainer;
578581
});
579582
}
@@ -587,11 +590,11 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
587590
let newContainer = this.dropContainer._getSiblingContainerFromPosition(this, x, y);
588591

589592
// If we couldn't find a new container to move the item into, and the item has left it's
590-
// initial container, check whether the it's allowed to return into its original container.
591-
// This handles the case where two containers are connected one way and the user tries to
592-
// undo dragging an item into a new container.
593+
// initial container, check whether the it's over the initial container. This handles the
594+
// case where two containers are connected one way and the user tries to undo dragging an
595+
// item into a new container.
593596
if (!newContainer && this.dropContainer !== this._initialContainer &&
594-
this._initialContainer._canReturnItem(x, y)) {
597+
this._initialContainer._isOverContainer(x, y)) {
595598
newContainer = this._initialContainer;
596599
}
597600

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

+5-2
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,11 @@ export interface CdkDropListContainer<T = any> {
3737
* @param item Item being dropped into the container.
3838
* @param currentIndex Index at which the item should be inserted.
3939
* @param previousContainer Container from which the item got dragged in.
40+
* @param isPointerOverContainer Whether the user's pointer was over the
41+
* container when the item was dropped.
4042
*/
41-
drop(item: CdkDrag, currentIndex: number, previousContainer?: CdkDropListContainer): void;
43+
drop(item: CdkDrag, currentIndex: number, previousContainer: CdkDropListContainer,
44+
isPointerOverContainer: boolean): void;
4245

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

6972
/**

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

+10-8
Original file line numberDiff line numberDiff line change
@@ -210,16 +210,19 @@ export class CdkDropList<T = any> implements OnInit, OnDestroy {
210210
* @param item Item being dropped into the container.
211211
* @param currentIndex Index at which the item should be inserted.
212212
* @param previousContainer Container from which the item got dragged in.
213+
* @param isPointerOverContainer Whether the user's pointer was over the
214+
* container when the item was dropped.
213215
*/
214-
drop(item: CdkDrag, currentIndex: number, previousContainer: CdkDropList): void {
216+
drop(item: CdkDrag, currentIndex: number, previousContainer: CdkDropList,
217+
isPointerOverContainer: boolean): void {
215218
this._reset();
216219
this.dropped.emit({
217220
item,
218221
currentIndex,
219222
previousIndex: previousContainer.getItemIndex(item),
220223
container: this,
221-
// TODO(crisbeto): reconsider whether to make this null if the containers are the same.
222-
previousContainer
224+
previousContainer,
225+
isPointerOverContainer
223226
});
224227
}
225228

@@ -388,12 +391,11 @@ export class CdkDropList<T = any> implements OnInit, OnDestroy {
388391
}
389392

390393
/**
391-
* Checks whether an item that started in this container can be returned to it,
392-
* after it was moved out into another container.
393-
* @param x Position of the item along the X axis.
394-
* @param y Position of the item along the Y axis.
394+
* Checks whether the user's pointer is positioned over the container.
395+
* @param x Pointer position along the X axis.
396+
* @param y Pointer position along the Y axis.
395397
*/
396-
_canReturnItem(x: number, y: number): boolean {
398+
_isOverContainer(x: number, y: number): boolean {
397399
return isInsideClientRect(this._positionCache.self, x, y);
398400
}
399401

‎tools/public_api_guard/cdk/drag-drop.d.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export interface CdkDragConfig {
4141
export interface CdkDragDrop<T, O = T> {
4242
container: CdkDropListContainer<T>;
4343
currentIndex: number;
44+
isPointerOverContainer: boolean;
4445
item: CdkDrag;
4546
previousContainer: CdkDropListContainer<O>;
4647
previousIndex: number;
@@ -119,13 +120,13 @@ export declare class CdkDropList<T = any> implements OnInit, OnDestroy {
119120
orientation: 'horizontal' | 'vertical';
120121
sorted: EventEmitter<CdkDragSortEvent<T>>;
121122
constructor(element: ElementRef<HTMLElement>, _dragDropRegistry: DragDropRegistry<CdkDrag, CdkDropList<T>>, _changeDetectorRef: ChangeDetectorRef, _dir?: Directionality | undefined, _group?: CdkDropListGroup<CdkDropList<any>> | undefined);
122-
_canReturnItem(x: number, y: number): boolean;
123123
_getSiblingContainerFromPosition(item: CdkDrag, x: number, y: number): CdkDropList | null;
124+
_isOverContainer(x: number, y: number): boolean;
124125
_sortItem(item: CdkDrag, pointerX: number, pointerY: number, pointerDelta: {
125126
x: number;
126127
y: number;
127128
}): void;
128-
drop(item: CdkDrag, currentIndex: number, previousContainer: CdkDropList): void;
129+
drop(item: CdkDrag, currentIndex: number, previousContainer: CdkDropList, isPointerOverContainer: boolean): void;
129130
enter(item: CdkDrag, pointerX: number, pointerY: number): void;
130131
exit(item: CdkDrag): void;
131132
getItemIndex(item: CdkDrag): number;
@@ -142,13 +143,13 @@ export interface CdkDropListContainer<T = any> {
142143
id: string;
143144
lockAxis: 'x' | 'y';
144145
orientation: 'horizontal' | 'vertical';
145-
_canReturnItem(x: number, y: number): boolean;
146146
_getSiblingContainerFromPosition(item: CdkDrag, x: number, y: number): CdkDropListContainer | null;
147+
_isOverContainer(x: number, y: number): boolean;
147148
_sortItem(item: CdkDrag, pointerX: number, pointerY: number, delta: {
148149
x: number;
149150
y: number;
150151
}): void;
151-
drop(item: CdkDrag, currentIndex: number, previousContainer?: CdkDropListContainer): void;
152+
drop(item: CdkDrag, currentIndex: number, previousContainer: CdkDropListContainer, isPointerOverContainer: boolean): void;
152153
enter(item: CdkDrag, pointerX: number, pointerY: number): void;
153154
exit(item: CdkDrag): void;
154155
getItemIndex(item: CdkDrag): number;

0 commit comments

Comments
 (0)
Please sign in to comment.