diff --git a/src/cdk/drag-drop/drag-drop-module.ts b/src/cdk/drag-drop/drag-drop-module.ts index c0f95b36014..b6237a9bb8c 100644 --- a/src/cdk/drag-drop/drag-drop-module.ts +++ b/src/cdk/drag-drop/drag-drop-module.ts @@ -8,6 +8,7 @@ import {NgModule} from '@angular/core'; import {CdkDropList} from './drop-list'; +import {CdkDropListGroup} from './drop-list-group'; import {CdkDrag} from './drag'; import {CdkDragHandle} from './drag-handle'; import {CdkDragPreview} from './drag-preview'; @@ -16,6 +17,7 @@ import {CdkDragPlaceholder} from './drag-placeholder'; @NgModule({ declarations: [ CdkDropList, + CdkDropListGroup, CdkDrag, CdkDragHandle, CdkDragPreview, @@ -23,6 +25,7 @@ import {CdkDragPlaceholder} from './drag-placeholder'; ], exports: [ CdkDropList, + CdkDropListGroup, CdkDrag, CdkDragHandle, CdkDragPreview, diff --git a/src/cdk/drag-drop/drag-drop.md b/src/cdk/drag-drop/drag-drop.md index 3a5764949fc..81144c75feb 100644 --- a/src/cdk/drag-drop/drag-drop.md +++ b/src/cdk/drag-drop/drag-drop.md @@ -22,12 +22,12 @@ update the data model once the user finishes dragging. ### Transferring items between lists The `cdkDropList` directive supports transferring dragged items between connected drop zones. You can connect one or more `cdkDropList` instances together by setting the `cdkDropListConnectedTo` -property. +property or by wrapping the elements in an element with the `cdkDropListGroup` attribute. -Note that `cdkDropListConnectedTo` works both with a direct reference to another `cdkDropList`, or by -referencing the `id` of another drop container: +Note that `cdkDropListConnectedTo` works both with a direct reference to another `cdkDropList`, or +by referencing the `id` of another drop container: ```html @@ -39,6 +39,19 @@ referencing the `id` of another drop container:
``` +If you have an unknown number of connected drop lists, you can use the `cdkDropListGroup` directive +to set up the connection automatically. Note that any new `cdkDropList` that is added under a group +will be connected to all other automatically. + +```html +
+ +
+
+``` + + + ### Attaching data You can associate some arbitrary data with both `cdkDrag` and `cdkDropList` by setting `cdkDragData` or `cdkDropListData`, respectively. Events fired from both directives include this data, allowing diff --git a/src/cdk/drag-drop/drag.spec.ts b/src/cdk/drag-drop/drag.spec.ts index a5b7b781b7b..971b4d7b31b 100644 --- a/src/cdk/drag-drop/drag.spec.ts +++ b/src/cdk/drag-drop/drag.spec.ts @@ -1927,6 +1927,31 @@ describe('CdkDrag', () => { }); })); + it('should be able to connect two drop zones using the drop list group', fakeAsync(() => { + const fixture = createComponent(ConnectedDropZonesViaGroupDirective); + fixture.detectChanges(); + + const dropInstances = fixture.componentInstance.dropInstances.toArray(); + const groups = fixture.componentInstance.groupedDragItems; + const element = groups[0][1].element.nativeElement; + const targetRect = groups[1][2].element.nativeElement.getBoundingClientRect(); + + dragElementViaMouse(fixture, element, targetRect.left + 1, targetRect.top + 1); + flush(); + fixture.detectChanges(); + + const event = fixture.componentInstance.droppedSpy.calls.mostRecent().args[0]; + + expect(event).toBeTruthy(); + expect(event).toEqual({ + previousIndex: 1, + currentIndex: 3, + item: groups[0][1], + container: dropInstances[1], + previousContainer: dropInstances[0] + }); + })); + it('should be able to pass a single id to `connectedTo`', fakeAsync(() => { const fixture = createComponent(ConnectedDropZones); fixture.detectChanges(); @@ -2311,6 +2336,42 @@ class ConnectedDropZones implements AfterViewInit { } } +@Component({ + encapsulation: ViewEncapsulation.None, + styles: [` + .cdk-drop-list { + display: block; + width: 100px; + min-height: ${ITEM_HEIGHT}px; + background: hotpink; + } + + .cdk-drag { + display: block; + height: ${ITEM_HEIGHT}px; + background: red; + } + `], + template: ` +
+
+
{{item}}
+
+ +
+
{{item}}
+
+
+ ` +}) +class ConnectedDropZonesViaGroupDirective extends ConnectedDropZones {} + @Component({ template: ` diff --git a/src/cdk/drag-drop/drop-list-group.ts b/src/cdk/drag-drop/drop-list-group.ts new file mode 100644 index 00000000000..b6a97bbf265 --- /dev/null +++ b/src/cdk/drag-drop/drop-list-group.ts @@ -0,0 +1,27 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Directive, OnDestroy} from '@angular/core'; + +/** + * Declaratively connects sibling `cdkDropList` instances together. All of the `cdkDropList` + * elements that are placed inside a `cdkDropListGroup` will be connected to each other + * automatically. Can be used as an alternative to the `cdkDropListConnectedTo` input + * from `cdkDropList`. + */ +@Directive({ + selector: '[cdkDropListGroup]' +}) +export class CdkDropListGroup implements OnDestroy { + /** Drop lists registered inside the group. */ + readonly _items = new Set(); + + ngOnDestroy() { + this._items.clear(); + } +} diff --git a/src/cdk/drag-drop/drop-list.ts b/src/cdk/drag-drop/drop-list.ts index 7e4add8e0f3..25a7437a2ce 100644 --- a/src/cdk/drag-drop/drop-list.ts +++ b/src/cdk/drag-drop/drop-list.ts @@ -27,6 +27,7 @@ import {DragDropRegistry} from './drag-drop-registry'; import {CdkDragDrop, CdkDragEnter, CdkDragExit} from './drag-events'; import {moveItemInArray} from './drag-utils'; import {CDK_DROP_LIST_CONTAINER} from './drop-list-container'; +import {CdkDropListGroup} from './drop-list-group'; /** Counter used to generate unique ids for drop zones. */ @@ -143,14 +144,23 @@ export class CdkDropList implements OnInit, OnDestroy { public element: ElementRef, private _dragDropRegistry: DragDropRegistry>, private _changeDetectorRef: ChangeDetectorRef, - @Optional() private _dir?: Directionality) {} + @Optional() private _dir?: Directionality, + @Optional() private _group?: CdkDropListGroup) {} ngOnInit() { this._dragDropRegistry.registerDropContainer(this); + + if (this._group) { + this._group._items.add(this); + } } ngOnDestroy() { this._dragDropRegistry.removeDropContainer(this); + + if (this._group) { + this._group._items.delete(this); + } } /** Whether an item in the container is being dragged. */ @@ -366,6 +376,8 @@ export class CdkDropList implements OnInit, OnDestroy { /** Refreshes the position cache of the items and sibling containers. */ private _cachePositions() { const isHorizontal = this.orientation === 'horizontal'; + + this._positionCache.self = this.element.nativeElement.getBoundingClientRect(); this._positionCache.items = this._activeDraggables .map(drag => { const elementToMeasure = this._dragDropRegistry.isDragging(drag) ? @@ -397,12 +409,10 @@ export class CdkDropList implements OnInit, OnDestroy { a.clientRect.top - b.clientRect.top; }); - this._positionCache.siblings = coerceArray(this.connectedTo) - .map(drop => typeof drop === 'string' ? this._dragDropRegistry.getDropContainer(drop)! : drop) - .filter(drop => drop && drop !== this) - .map(drop => ({drop, clientRect: drop.element.nativeElement.getBoundingClientRect()})); - - this._positionCache.self = this.element.nativeElement.getBoundingClientRect(); + this._positionCache.siblings = this._getConnectedLists().map(drop => ({ + drop, + clientRect: drop.element.nativeElement.getBoundingClientRect() + })); } /** Resets the container to its initial state. */ @@ -535,6 +545,23 @@ export class CdkDropList implements OnInit, OnDestroy { return siblingOffset; } + + /** Gets an array of unique drop lists that the current list is connected to. */ + private _getConnectedLists(): CdkDropList[] { + const siblings = coerceArray(this.connectedTo).map(drop => { + return typeof drop === 'string' ? this._dragDropRegistry.getDropContainer(drop)! : drop; + }); + + if (this._group) { + this._group._items.forEach(drop => { + if (siblings.indexOf(drop) === -1) { + siblings.push(drop); + } + }); + } + + return siblings.filter(drop => drop && drop !== this); + } } diff --git a/src/cdk/drag-drop/public-api.ts b/src/cdk/drag-drop/public-api.ts index 15623f5425a..a4f5b173002 100644 --- a/src/cdk/drag-drop/public-api.ts +++ b/src/cdk/drag-drop/public-api.ts @@ -7,6 +7,7 @@ */ export * from './drop-list'; +export * from './drop-list-group'; export * from './drop-list-container'; export * from './drag'; export * from './drag-handle'; diff --git a/src/demo-app/drag-drop/drag-drop-demo.html b/src/demo-app/drag-drop/drag-drop-demo.html index 810a8061475..342562ed031 100644 --- a/src/demo-app/drag-drop/drag-drop-demo.html +++ b/src/demo-app/drag-drop/drag-drop-demo.html @@ -1,4 +1,4 @@ -
+

To do

@@ -7,11 +7,9 @@
+ [cdkDropListData]="todo">
{{item}} @@ -23,11 +21,9 @@

Done

+ [cdkDropListData]="done">
{{item}}