diff --git a/src/cdk/drag-drop/directives/drag.spec.ts b/src/cdk/drag-drop/directives/drag.spec.ts index 8bb9e04e88ac..25809559cebe 100644 --- a/src/cdk/drag-drop/directives/drag.spec.ts +++ b/src/cdk/drag-drop/directives/drag.spec.ts @@ -2448,6 +2448,56 @@ describe('CdkDrag', () => { }); })); + it('should set a class when a container can receive an item', fakeAsync(() => { + const fixture = createComponent(ConnectedDropZones); + fixture.detectChanges(); + + const dropZones = fixture.componentInstance.dropInstances.map(d => d.element.nativeElement); + const item = fixture.componentInstance.groupedDragItems[0][1]; + + expect(dropZones.every(c => !c.classList.contains('cdk-drop-list-receiving'))) + .toBe(true, 'Expected neither of the containers to have the class.'); + + startDraggingViaMouse(fixture, item.element.nativeElement); + fixture.detectChanges(); + + expect(dropZones[0].classList).not.toContain('cdk-drop-list-receiving', + 'Expected source container not to have the receiving class.'); + + expect(dropZones[1].classList).toContain('cdk-drop-list-receiving', + 'Expected target container to have the receiving class.'); + })); + + it('should toggle the `receiving` class when the item enters a new list', fakeAsync(() => { + const fixture = createComponent(ConnectedDropZones); + fixture.detectChanges(); + + const groups = fixture.componentInstance.groupedDragItems; + const dropZones = fixture.componentInstance.dropInstances.map(d => d.element.nativeElement); + const item = groups[0][1]; + const targetRect = groups[1][2].element.nativeElement.getBoundingClientRect(); + + expect(dropZones.every(c => !c.classList.contains('cdk-drop-list-receiving'))) + .toBe(true, 'Expected neither of the containers to have the class.'); + + startDraggingViaMouse(fixture, item.element.nativeElement); + + expect(dropZones[0].classList).not.toContain('cdk-drop-list-receiving', + 'Expected source container not to have the receiving class.'); + + expect(dropZones[1].classList).toContain('cdk-drop-list-receiving', + 'Expected target container to have the receiving class.'); + + dispatchMouseEvent(document, 'mousemove', targetRect.left + 1, targetRect.top + 1); + fixture.detectChanges(); + + expect(dropZones[0].classList).toContain('cdk-drop-list-receiving', + 'Expected old container not to have the receiving class after exiting.'); + + expect(dropZones[1].classList).not.toContain('cdk-drop-list-receiving', + 'Expected new container not to have the receiving class after entering.'); + })); + }); }); diff --git a/src/cdk/drag-drop/directives/drop-list.ts b/src/cdk/drag-drop/directives/drop-list.ts index a1f63f76518f..0c0549eaf401 100644 --- a/src/cdk/drag-drop/directives/drop-list.ts +++ b/src/cdk/drag-drop/directives/drop-list.ts @@ -58,7 +58,8 @@ export interface CdkDropListInternal extends CdkDropList {} host: { 'class': 'cdk-drop-list', '[id]': 'id', - '[class.cdk-drop-list-dragging]': '_dropListRef.isDragging()' + '[class.cdk-drop-list-dragging]': '_dropListRef.isDragging()', + '[class.cdk-drop-list-receiving]': '_dropListRef.isReceiving()', } }) export class CdkDropList implements CdkDropListContainer, OnDestroy { diff --git a/src/cdk/drag-drop/drag-drop.md b/src/cdk/drag-drop/drag-drop.md index 76d8550bc226..bc95a0802fb1 100644 --- a/src/cdk/drag-drop/drag-drop.md +++ b/src/cdk/drag-drop/drag-drop.md @@ -75,6 +75,7 @@ by the directives: | `.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. | | `.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. | | `.cdk-drop-list-dragging` | A class that is added to `cdkDropList` while the user is dragging an item. | +| `.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. | ### Animations The drag-and-drop module supports animations both while sorting an element inside a list, as well as diff --git a/src/cdk/drag-drop/drop-list-ref.ts b/src/cdk/drag-drop/drop-list-ref.ts index db544ea7d1d2..a99e89c9d850 100644 --- a/src/cdk/drag-drop/drop-list-ref.ts +++ b/src/cdk/drag-drop/drop-list-ref.ts @@ -158,6 +158,9 @@ export class DropListRef { /** Direction in which the list is oriented. */ private _orientation: 'horizontal' | 'vertical' = 'vertical'; + /** Amount of connected siblings that currently have a dragged item. */ + private _activeSiblings = 0; + constructor( public element: ElementRef, private _dragDropRegistry: DragDropRegistry, @@ -188,6 +191,7 @@ export class DropListRef { this._isDragging = true; this._activeDraggables = this._draggables.slice(); this._cachePositions(); + this._positionCache.siblings.forEach(sibling => sibling.drop._toggleIsReceiving(true)); } /** @@ -308,6 +312,14 @@ export class DropListRef { return findIndex(items, currentItem => currentItem.drag === item); } + /** + * Whether the list is able to receive the item that + * is currently being dragged inside a connected drop list. + */ + isReceiving(): boolean { + return this._activeSiblings > 0; + } + /** * Sorts an item inside the container based on its position. * @param item Item to be sorted. @@ -431,12 +443,21 @@ export class DropListRef { })); } + /** + * Toggles whether the list can receive the item that is currently being dragged. + * Usually called by a sibling that initiated the dragging. + */ + _toggleIsReceiving(isDragging: boolean) { + this._activeSiblings = Math.max(0, this._activeSiblings + (isDragging ? 1 : -1)); + } + /** Resets the container to its initial state. */ private _reset() { this._isDragging = false; // TODO(crisbeto): may have to wait for the animations to finish. this._activeDraggables.forEach(item => item.getRootElement().style.transform = ''); + this._positionCache.siblings.forEach(sibling => sibling.drop._toggleIsReceiving(false)); this._activeDraggables = []; this._positionCache.items = []; this._positionCache.siblings = []; diff --git a/src/dev-app/drag-drop/drag-drop-demo.scss b/src/dev-app/drag-drop/drag-drop-demo.scss index 59a38398dac3..bacf64bf8bf4 100644 --- a/src/dev-app/drag-drop/drag-drop-demo.scss +++ b/src/dev-app/drag-drop/drag-drop-demo.scss @@ -29,6 +29,10 @@ } } +.cdk-drop-list-receiving { + border-style: dashed; +} + .cdk-drag { padding: 20px 10px; border-bottom: solid 1px #ccc; diff --git a/src/dev-app/drag-drop/drag-drop-demo.ts b/src/dev-app/drag-drop/drag-drop-demo.ts index c8683432ba8b..40ddc027ac6d 100644 --- a/src/dev-app/drag-drop/drag-drop-demo.ts +++ b/src/dev-app/drag-drop/drag-drop-demo.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, ViewEncapsulation} from '@angular/core'; +import {Component, ViewEncapsulation, ChangeDetectionStrategy} from '@angular/core'; import {MatIconRegistry} from '@angular/material/icon'; import {DomSanitizer} from '@angular/platform-browser'; import {CdkDragDrop, moveItemInArray, transferArrayItem} from '@angular/cdk/drag-drop'; @@ -17,6 +17,7 @@ import {CdkDragDrop, moveItemInArray, transferArrayItem} from '@angular/cdk/drag templateUrl: 'drag-drop-demo.html', styleUrls: ['drag-drop-demo.css'], encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class DragAndDropDemo { axisLock: 'x' | 'y';