mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(drag-drop): add directive to connect drop lists automatically (#13754)
Currently the only way to connect drop lists is via the `cdkDropListConnectedTo` input which works if the consumer knows the amount of drop lists that they're going to have. For dynamic lists they can pass a list of ids, however that can be a little boilerplate-y. These changes introduce the `cdkDropListGroup` directive which can be used to connect a dynamic list of drop containers automatically. Fixes #13750.
This commit is contained in:
committed by
vivian-hu
parent
3842c8c633
commit
962dbeb1a6
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
<!-- example(cdk-drag-drop-connected-sorting) -->
|
||||
|
||||
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
|
||||
<!-- This is valid -->
|
||||
@@ -39,6 +39,19 @@ referencing the `id` of another drop container:
|
||||
<div cdkDropList id="list-two" [cdkDropListConnectedTo]="['list-one']"></div>
|
||||
```
|
||||
|
||||
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
|
||||
<div cdkDropListGroup>
|
||||
<!-- All lists in here will be connected. -->
|
||||
<div cdkDropList *ngFor="let list of lists"></div>
|
||||
</div>
|
||||
```
|
||||
|
||||
<!-- example(cdk-drag-drop-connected-sorting-group) -->
|
||||
|
||||
### 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
|
||||
|
||||
@@ -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: `
|
||||
<div cdkDropListGroup>
|
||||
<div
|
||||
cdkDropList
|
||||
[cdkDropListData]="todo"
|
||||
(cdkDropListDropped)="droppedSpy($event)">
|
||||
<div [cdkDragData]="item" *ngFor="let item of todo" cdkDrag>{{item}}</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
cdkDropList
|
||||
[cdkDropListData]="done"
|
||||
(cdkDropListDropped)="droppedSpy($event)">
|
||||
<div [cdkDragData]="item" *ngFor="let item of done" cdkDrag>{{item}}</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
class ConnectedDropZonesViaGroupDirective extends ConnectedDropZones {}
|
||||
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
|
||||
@@ -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<T> implements OnDestroy {
|
||||
/** Drop lists registered inside the group. */
|
||||
readonly _items = new Set<T>();
|
||||
|
||||
ngOnDestroy() {
|
||||
this._items.clear();
|
||||
}
|
||||
}
|
||||
@@ -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<T = any> implements OnInit, OnDestroy {
|
||||
public element: ElementRef<HTMLElement>,
|
||||
private _dragDropRegistry: DragDropRegistry<CdkDrag, CdkDropList<T>>,
|
||||
private _changeDetectorRef: ChangeDetectorRef,
|
||||
@Optional() private _dir?: Directionality) {}
|
||||
@Optional() private _dir?: Directionality,
|
||||
@Optional() private _group?: CdkDropListGroup<CdkDropList>) {}
|
||||
|
||||
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<T = any> 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<T = any> 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<T = any> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div>
|
||||
<div cdkDropListGroup>
|
||||
<div class="demo-list">
|
||||
|
||||
<h2>To do</h2>
|
||||
@@ -7,11 +7,9 @@
|
||||
</mat-slide-toggle>
|
||||
<div
|
||||
cdkDropList
|
||||
#one="cdkDropList"
|
||||
(cdkDropListDropped)="drop($event)"
|
||||
[cdkDropListLockAxis]="axisLock"
|
||||
[cdkDropListData]="todo"
|
||||
[cdkDropListConnectedTo]="[two]">
|
||||
[cdkDropListData]="todo">
|
||||
<div *ngFor="let item of todo" cdkDrag>
|
||||
{{item}}
|
||||
<mat-icon cdkDragHandle svgIcon="dnd-move"></mat-icon>
|
||||
@@ -23,11 +21,9 @@
|
||||
<h2>Done</h2>
|
||||
<div
|
||||
cdkDropList
|
||||
#two="cdkDropList"
|
||||
(cdkDropListDropped)="drop($event)"
|
||||
[cdkDropListLockAxis]="axisLock"
|
||||
[cdkDropListData]="done"
|
||||
[cdkDropListConnectedTo]="[one]">
|
||||
[cdkDropListData]="done">
|
||||
<div *ngFor="let item of done" cdkDrag>
|
||||
{{item}}
|
||||
<mat-icon cdkDragHandle svgIcon="dnd-move"></mat-icon>
|
||||
|
||||
Reference in New Issue
Block a user