Skip to content

Commit c6dc070

Browse files
crisbetojelbourn
authored andcommitted
feat(drag-drop): add class to indicate whether a container can receive an item (#14532)
Adds the `cdk-drop-list-receiving` class to drop containers that are able to receive the item that is currently being dragged. This class can be used to indicate to the user where they can drop an item. Fixes #14439.
1 parent 9485aff commit c6dc070

File tree

6 files changed

+80
-2
lines changed

6 files changed

+80
-2
lines changed

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

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2536,6 +2536,56 @@ describe('CdkDrag', () => {
25362536
});
25372537
}));
25382538

2539+
it('should set a class when a container can receive an item', fakeAsync(() => {
2540+
const fixture = createComponent(ConnectedDropZones);
2541+
fixture.detectChanges();
2542+
2543+
const dropZones = fixture.componentInstance.dropInstances.map(d => d.element.nativeElement);
2544+
const item = fixture.componentInstance.groupedDragItems[0][1];
2545+
2546+
expect(dropZones.every(c => !c.classList.contains('cdk-drop-list-receiving')))
2547+
.toBe(true, 'Expected neither of the containers to have the class.');
2548+
2549+
startDraggingViaMouse(fixture, item.element.nativeElement);
2550+
fixture.detectChanges();
2551+
2552+
expect(dropZones[0].classList).not.toContain('cdk-drop-list-receiving',
2553+
'Expected source container not to have the receiving class.');
2554+
2555+
expect(dropZones[1].classList).toContain('cdk-drop-list-receiving',
2556+
'Expected target container to have the receiving class.');
2557+
}));
2558+
2559+
it('should toggle the `receiving` class when the item enters a new list', fakeAsync(() => {
2560+
const fixture = createComponent(ConnectedDropZones);
2561+
fixture.detectChanges();
2562+
2563+
const groups = fixture.componentInstance.groupedDragItems;
2564+
const dropZones = fixture.componentInstance.dropInstances.map(d => d.element.nativeElement);
2565+
const item = groups[0][1];
2566+
const targetRect = groups[1][2].element.nativeElement.getBoundingClientRect();
2567+
2568+
expect(dropZones.every(c => !c.classList.contains('cdk-drop-list-receiving')))
2569+
.toBe(true, 'Expected neither of the containers to have the class.');
2570+
2571+
startDraggingViaMouse(fixture, item.element.nativeElement);
2572+
2573+
expect(dropZones[0].classList).not.toContain('cdk-drop-list-receiving',
2574+
'Expected source container not to have the receiving class.');
2575+
2576+
expect(dropZones[1].classList).toContain('cdk-drop-list-receiving',
2577+
'Expected target container to have the receiving class.');
2578+
2579+
dispatchMouseEvent(document, 'mousemove', targetRect.left + 1, targetRect.top + 1);
2580+
fixture.detectChanges();
2581+
2582+
expect(dropZones[0].classList).toContain('cdk-drop-list-receiving',
2583+
'Expected old container not to have the receiving class after exiting.');
2584+
2585+
expect(dropZones[1].classList).not.toContain('cdk-drop-list-receiving',
2586+
'Expected new container not to have the receiving class after entering.');
2587+
}));
2588+
25392589
});
25402590

25412591
});

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ export interface CdkDropListInternal extends CdkDropList {}
5858
host: {
5959
'class': 'cdk-drop-list',
6060
'[id]': 'id',
61-
'[class.cdk-drop-list-dragging]': '_dropListRef.isDragging()'
61+
'[class.cdk-drop-list-dragging]': '_dropListRef.isDragging()',
62+
'[class.cdk-drop-list-receiving]': '_dropListRef.isReceiving()',
6263
}
6364
})
6465
export class CdkDropList<T = any> implements CdkDropListContainer, OnDestroy {

src/cdk/drag-drop/drag-drop.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ by the directives:
7575
| `.cdk-drag-preview` | This is the element that will be rendered next to the user's cursor as they're dragging an item in a sortable list. By default the element looks exactly like the element that is being dragged. |
7676
| `.cdk-drag-placeholder` | This is element that will be shown instead of the real element as it's being dragged inside a `cdkDropList`. By default this will look exactly like the element that is being sorted. |
7777
| `.cdk-drop-list-dragging` | A class that is added to `cdkDropList` while the user is dragging an item. |
78+
| `.cdk-drop-list-receiving` | A class that is added to `cdkDropList` when it can receive an item that is being dragged inside a connected drop list. |
7879

7980
### Animations
8081
The drag-and-drop module supports animations both while sorting an element inside a list, as well as

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ export class DropListRef<T = any> {
158158
/** Direction in which the list is oriented. */
159159
private _orientation: 'horizontal' | 'vertical' = 'vertical';
160160

161+
/** Amount of connected siblings that currently have a dragged item. */
162+
private _activeSiblings = 0;
163+
161164
constructor(
162165
public element: ElementRef<HTMLElement>,
163166
private _dragDropRegistry: DragDropRegistry<DragRef, DropListRef>,
@@ -188,6 +191,7 @@ export class DropListRef<T = any> {
188191
this._isDragging = true;
189192
this._activeDraggables = this._draggables.slice();
190193
this._cachePositions();
194+
this._positionCache.siblings.forEach(sibling => sibling.drop._toggleIsReceiving(true));
191195
}
192196

193197
/**
@@ -308,6 +312,14 @@ export class DropListRef<T = any> {
308312
return findIndex(items, currentItem => currentItem.drag === item);
309313
}
310314

315+
/**
316+
* Whether the list is able to receive the item that
317+
* is currently being dragged inside a connected drop list.
318+
*/
319+
isReceiving(): boolean {
320+
return this._activeSiblings > 0;
321+
}
322+
311323
/**
312324
* Sorts an item inside the container based on its position.
313325
* @param item Item to be sorted.
@@ -431,12 +443,21 @@ export class DropListRef<T = any> {
431443
}));
432444
}
433445

446+
/**
447+
* Toggles whether the list can receive the item that is currently being dragged.
448+
* Usually called by a sibling that initiated the dragging.
449+
*/
450+
_toggleIsReceiving(isDragging: boolean) {
451+
this._activeSiblings = Math.max(0, this._activeSiblings + (isDragging ? 1 : -1));
452+
}
453+
434454
/** Resets the container to its initial state. */
435455
private _reset() {
436456
this._isDragging = false;
437457

438458
// TODO(crisbeto): may have to wait for the animations to finish.
439459
this._activeDraggables.forEach(item => item.getRootElement().style.transform = '');
460+
this._positionCache.siblings.forEach(sibling => sibling.drop._toggleIsReceiving(false));
440461
this._activeDraggables = [];
441462
this._positionCache.items = [];
442463
this._positionCache.siblings = [];

src/dev-app/drag-drop/drag-drop-demo.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
}
3030
}
3131

32+
.cdk-drop-list-receiving {
33+
border-style: dashed;
34+
}
35+
3236
.cdk-drag {
3337
padding: 20px 10px;
3438
border-bottom: solid 1px #ccc;

src/dev-app/drag-drop/drag-drop-demo.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Component, ViewEncapsulation} from '@angular/core';
9+
import {Component, ViewEncapsulation, ChangeDetectionStrategy} from '@angular/core';
1010
import {MatIconRegistry} from '@angular/material/icon';
1111
import {DomSanitizer} from '@angular/platform-browser';
1212
import {CdkDragDrop, moveItemInArray, transferArrayItem} from '@angular/cdk/drag-drop';
@@ -17,6 +17,7 @@ import {CdkDragDrop, moveItemInArray, transferArrayItem} from '@angular/cdk/drag
1717
templateUrl: 'drag-drop-demo.html',
1818
styleUrls: ['drag-drop-demo.css'],
1919
encapsulation: ViewEncapsulation.None,
20+
changeDetection: ChangeDetectionStrategy.OnPush,
2021
})
2122
export class DragAndDropDemo {
2223
axisLock: 'x' | 'y';

0 commit comments

Comments
 (0)