From db5f0faa1315e9f403eead5da327aa779ab66c0d Mon Sep 17 00:00:00 2001 From: crisbeto Date: Sat, 15 Dec 2018 16:25:30 +0100 Subject: [PATCH] feat(drag-drop): add class to indicate whether a container can receive an item 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. --- src/cdk/drag-drop/directives/drag.spec.ts | 50 +++++++++++++++++++++++ src/cdk/drag-drop/directives/drop-list.ts | 3 +- src/cdk/drag-drop/drag-drop.md | 1 + src/cdk/drag-drop/drop-list-ref.ts | 21 ++++++++++ src/dev-app/drag-drop/drag-drop-demo.scss | 4 ++ src/dev-app/drag-drop/drag-drop-demo.ts | 3 +- 6 files changed, 80 insertions(+), 2 deletions(-) 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';