Skip to content

feat(drag-drop): convert cdk-drop into a directive #13441

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions src/cdk/drag-drop/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ ng_module(
name = "drag-drop",
srcs = glob(["**/*.ts"], exclude=["**/*.spec.ts"]),
module_name = "@angular/cdk/drag-drop",
assets = [":drop.css"],
deps = [
"@rxjs",
"//src/cdk/platform",
Expand All @@ -32,11 +31,6 @@ ts_library(
tsconfig = "//src/cdk:tsconfig-build.json",
)

sass_binary(
name = "drop_scss",
src = "drop.scss",
)

ts_web_test(
name = "unit_tests",
bootstrap = [
Expand Down
16 changes: 3 additions & 13 deletions src/cdk/drag-drop/drag-drop-registry.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,25 +155,15 @@ describe('DragDropRegistry', () => {
expect(dispatchTouchEvent(document, 'touchmove').defaultPrevented).toBe(true);
});

it('should disable the native interactions on the body while dragging', () => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've dropped this test and I haven't added new ones for the other elements that need to get these styles at runtime, because not all of the properties that are added are supported on the browsers that we test against, which will lead to flakes.

const firstItem = testComponent.dragItems.first;

registry.startDragging(firstItem, createMouseEvent('mousedown'));
expect(document.body.classList).toContain('cdk-drag-drop-disable-native-interactions');

registry.stopDragging(firstItem);
expect(document.body.classList).not.toContain('cdk-drag-drop-disable-native-interactions');
});

});

@Component({
template: `
<cdk-drop id="items" [data]="items">
<div cdkDrop id="items" [cdkDropData]="items">
<div *ngFor="let item of items" cdkDrag>{{item}}</div>
</cdk-drop>
</div>

<cdk-drop id="items" *ngIf="showDuplicateContainer"></cdk-drop>
<div cdkDrop id="items" *ngIf="showDuplicateContainer"></div>
`
})
class SimpleDropZone {
Expand Down
5 changes: 3 additions & 2 deletions src/cdk/drag-drop/drag-drop-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {Injectable, NgZone, OnDestroy, Inject} from '@angular/core';
import {DOCUMENT} from '@angular/common';
import {supportsPassiveEventListeners} from '@angular/cdk/platform';
import {Subject} from 'rxjs';
import {toggleNativeDragInteractions} from './drag-styling';

/** Event options that can be used to bind an active event. */
const activeEventOptions = supportsPassiveEventListeners() ? {passive: false} : false;
Expand Down Expand Up @@ -118,7 +119,7 @@ export class DragDropRegistry<I, C extends {id: string}> implements OnDestroy {

// We need to disable the native interactions on the entire body, because
// the user can start marking text if they drag too far in Safari.
this._document.body.classList.add('cdk-drag-drop-disable-native-interactions');
toggleNativeDragInteractions(this._document.body, false);

// We explicitly bind __active__ listeners here, because newer browsers will default to
// passive ones for `mousemove` and `touchmove`. The events need to be active, because we
Expand All @@ -140,7 +141,7 @@ export class DragDropRegistry<I, C extends {id: string}> implements OnDestroy {

if (this._activeDragInstances.size === 0) {
this._clearGlobalListeners();
this._document.body.classList.remove('cdk-drag-drop-disable-native-interactions');
toggleNativeDragInteractions(this._document.body, true);
}
}

Expand Down
46 changes: 23 additions & 23 deletions src/cdk/drag-drop/drag-drop.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,62 +6,62 @@ in addition to horizontal lists and locking along an axis.
### Getting started
Start by importing `DragDropModule` into the `NgModule` where you want to use drag-and-drop
features. You can now add the `cdkDrag` directive to elements to make them draggable. When
outside of a `<cdk-drop>` element, draggable elements can be freely moved around the page.
You can add `<cdk-drop>` elements to constrain where elements may be dropped.
outside of a `cdkDrop` element, draggable elements can be freely moved around the page.
You can add `cdkDrop` elements to constrain where elements may be dropped.

<!-- example(cdk-drag-drop-overview) -->

### Reordering lists
Adding `<cdk-drop>` around a set of `cdkDrag` elements groups the draggables into a
Adding `cdkDrop` around a set of `cdkDrag` elements groups the draggables into a
reorderable collection. Items will automatically rearrange as an element moves. Note
that this will *not* update your data model; you can listen to the `dropped` event to
that this will *not* update your data model; you can listen to the `cdkDropDropped` event to
update the data model once the user finishes dragging.

<!-- example(cdk-drag-drop-sorting) -->

### Transferring items between lists
The `<cdk-drop>` component supports transferring dragged items between connected drop zones.
You can connect one or more `<cdk-drop>` instances together by setting the `connectedTo`
The `cdkDrop` directive supports transferring dragged items between connected drop zones.
You can connect one or more `cdkDrop` instances together by setting the `cdkDropConnectedTo`
property.

<!-- example(cdk-drag-drop-connected-sorting) -->

Note that `connectedTo` works both with a direct reference to another `<cdk-drop>`, or by
Note that `cdkDropConnectedTo` works both with a direct reference to another `cdkDrop`, or by
referencing the `id` of another drop container:

```html
<!-- This is valid -->
<cdk-drop #listOne [connectedTo]="[listTwo]"></cdk-drop>
<cdk-drop #listTwo [connectedTo]="[listOne]"></cdk-drop>
<div cdkDrop #listOne="cdkDrop" [cdkDropConnectedTo]="[listTwo]"></div>
<div cdkDrop #listTwo="cdkDrop" [cdkDropConnectedTo]="[listOne]"></div>

<!-- This is valid as well -->
<cdk-drop id="list-one" [connectedTo]="['list-two']"></cdk-drop>
<cdk-drop id="list-two" [connectedTo]="['list-one']"></cdk-drop>
<div cdkDrop id="list-one" [cdkDropConnectedTo]="['list-two']"></div>
<div cdkDrop id="list-two" [cdkDropConnectedTo]="['list-one']"></div>
```

### Attaching data
You can associate some arbitrary data with both `cdkDrag` and `<cdk-drop>` by setting
`cdkDragData` or `data`, respectively. Events fired from both directives include this data,
allowing you to easily identify the origin of the drag or drop interaction.
You can associate some arbitrary data with both `cdkDrag` and `cdkDrop` by setting `cdkDragData`
or `cdkDropData`, respectively. Events fired from both directives include this data, allowing
you to easily identify the origin of the drag or drop interaction.

```html
<cdk-drop [data]="list" *ngFor="let list of lists" (dropped)="drop($event)">
<div cdkDrop [cdkDropData]="list" *ngFor="let list of lists" (cdkDropDropped)="drop($event)">
<div cdkDrag [cdkDragData]="item" *ngFor="let item of list"></div>
</cdk-drop>
</div>
```

### Styling
The `cdkDrag` and `<cdk-drop>` directive include only those styles strictly necessary for
The `cdkDrag` and `cdkDrop` directive include only those styles strictly necessary for
functionality. The application can then customize the elements by styling CSS classes added
by the directives:

| Selector | Description |
|---------------------|--------------------------------------------------------------------------|
| `.cdk-drop` | Corresponds to the `<cdk-drop>` container. |
| `.cdk-drop` | Corresponds to the `cdkDrop` container. |
| `.cdk-drag` | Corresponds to a `cdkDrag` instance. |
| `.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 `<cdk-drop>`. By default this will look exactly like the element that is being sorted. |
| `.cdk-drop-dragging` | A class that is added to `<cdk-drop>` while the user is dragging an item. |
| `.cdk-drag-placeholder` | This is element that will be shown instead of the real element as it's being dragged inside a `cdkDrop`. By default this will look exactly like the element that is being sorted. |
| `.cdk-drop-dragging` | A class that is added to `cdkDrop` while the user is dragging an item. |

### Animations
The drag-and-drop module supports animations both while sorting an element inside a list, as well as
Expand All @@ -73,7 +73,7 @@ following classes can be used for animations:
through a list.
* `.cdk-drag-animating` - This class is added to a `cdkDrag` when the user has stopped dragging.
If you add a `transition` to it, the CDK will animate the element from its drop position to
the final position inside the `<cdk-drop>` container.
the final position inside the `cdkDrop` container.

Example animations:

Expand Down Expand Up @@ -105,14 +105,14 @@ This preview can be customized, though, by providing a custom template via `*cdk
<!-- example(cdk-drag-drop-custom-preview) -->

### List orientation
The `cdk-drop` component assumes that lists are vertical by default. This can be
The `cdkDrop` directive assumes that lists are vertical by default. This can be
changed by setting the `orientation` property to `"horizontal".

<!-- example(cdk-drag-drop-horizontal-sorting) -->

### Restricting movement along an axis
By default, `cdkDrag` allows free movement in all directions. To restrict dragging to a
specific axis, you can set `cdkDragLockAxis` on `cdkDrag` or `lockAxis` on `<cdk-drop>`
specific axis, you can set `cdkDragLockAxis` on `cdkDrag` or `lockAxis` on `cdkDrop`
to either `"x"` or `"y"`.

<!-- example(cdk-drag-drop-axis-lock) -->
Expand Down
5 changes: 4 additions & 1 deletion src/cdk/drag-drop/drag-handle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import {Directive, ElementRef} from '@angular/core';
import {toggleNativeDragInteractions} from './drag-styling';

/** Handle that can be used to drag and CdkDrag instance. */
@Directive({
Expand All @@ -16,5 +17,7 @@ import {Directive, ElementRef} from '@angular/core';
}
})
export class CdkDragHandle {
constructor(public element: ElementRef<HTMLElement>) {}
constructor(public element: ElementRef<HTMLElement>) {
toggleNativeDragInteractions(element.nativeElement, false);
}
}
51 changes: 51 additions & 0 deletions src/cdk/drag-drop/drag-styling.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* @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
*/

/**
* Extended CSSStyleDeclaration that includes a couple of drag-related
* properties that aren't in the built-in TS typings.
*/
interface DragCSSStyleDeclaration extends CSSStyleDeclaration {
webkitUserDrag: string;
MozUserSelect: string; // For some reason the Firefox property is in PascalCase.
}

/**
* Shallow-extends a stylesheet object with another stylesheet object.
* @docs-private
*/
export function extendStyles(dest: CSSStyleDeclaration, source: Partial<DragCSSStyleDeclaration>) {
for (let key in source) {
if (source.hasOwnProperty(key)) {
dest[key!] = source[key];
}
}

return dest;
}


/**
* Toggles whether the native drag interactions should be enabled for an element.
* @param element Element on which to toggle the drag interactions.
* @param enable Whether the drag interactions should be enabled.
* @docs-private
*/
export function toggleNativeDragInteractions(element: HTMLElement, enable: boolean) {
const userSelect = enable ? '' : 'none';

extendStyles(element.style, {
touchAction: enable ? '' : 'none',
webkitUserDrag: enable ? '' : 'none',
webkitTapHighlightColor: enable ? '' : 'transparent',
userSelect: userSelect,
msUserSelect: userSelect,
webkitUserSelect: userSelect,
MozUserSelect: userSelect
});
}
72 changes: 42 additions & 30 deletions src/cdk/drag-drop/drag.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1726,17 +1726,18 @@ class StandaloneDraggableWithMultipleHandles {

@Component({
template: `
<cdk-drop
style="display: block; width: 100px; background: pink;"
<div
cdkDrop
style="width: 100px; background: pink;"
[id]="dropZoneId"
[data]="items"
(dropped)="droppedSpy($event)">
[cdkDropData]="items"
(cdkDropDropped)="droppedSpy($event)">
<div
*ngFor="let item of items"
cdkDrag
[cdkDragData]="item"
style="width: 100%; height: ${ITEM_HEIGHT}px; background: red;">{{item}}</div>
</cdk-drop>
</div>
`
})
class DraggableInDropZone {
Expand Down Expand Up @@ -1770,12 +1771,13 @@ class DraggableInDropZone {
}
`],
template: `
<cdk-drop
orientation="horizontal"
[data]="items"
(dropped)="droppedSpy($event)">
<div
cdkDrop
cdkDropOrientation="horizontal"
[cdkDropData]="items"
(cdkDropDropped)="droppedSpy($event)">
<div *ngFor="let item of items" cdkDrag>{{item}}</div>
</cdk-drop>
</div>
`
})
class DraggableInHorizontalDropZone {
Expand All @@ -1789,13 +1791,13 @@ class DraggableInHorizontalDropZone {

@Component({
template: `
<cdk-drop style="display: block; width: 100px; background: pink;">
<div cdkDrop style="width: 100px; background: pink;">
<div *ngFor="let item of items" cdkDrag
style="width: 100%; height: ${ITEM_HEIGHT}px; background: red;">
{{item}}
<div class="custom-preview" *cdkDragPreview>Custom preview</div>
</div>
</cdk-drop>
</div>
`
})
class DraggableInDropZoneWithCustomPreview {
Expand All @@ -1807,13 +1809,13 @@ class DraggableInDropZoneWithCustomPreview {

@Component({
template: `
<cdk-drop style="display: block; width: 100px; background: pink;">
<div cdkDrop style="width: 100px; background: pink;">
<div *ngFor="let item of items" cdkDrag
style="width: 100%; height: ${ITEM_HEIGHT}px; background: red;">
{{item}}
<div class="custom-placeholder" *cdkDragPlaceholder>Custom placeholder</div>
</div>
</cdk-drop>
</div>
`
})
class DraggableInDropZoneWithCustomPlaceholder {
Expand All @@ -1839,21 +1841,23 @@ class DraggableInDropZoneWithCustomPlaceholder {
}
`],
template: `
<cdk-drop
#todoZone
[data]="todo"
[connectedTo]="[doneZone]"
(dropped)="droppedSpy($event)">
<div
cdkDrop
#todoZone="cdkDrop"
[cdkDropData]="todo"
[cdkDropConnectedTo]="[doneZone]"
(cdkDropDropped)="droppedSpy($event)">
<div [cdkDragData]="item" *ngFor="let item of todo" cdkDrag>{{item}}</div>
</cdk-drop>
</div>

<cdk-drop
#doneZone
[data]="done"
[connectedTo]="[todoZone]"
(dropped)="droppedSpy($event)">
<div
cdkDrop
#doneZone="cdkDrop"
[cdkDropData]="done"
[cdkDropConnectedTo]="[todoZone]"
(cdkDropDropped)="droppedSpy($event)">
<div [cdkDragData]="item" *ngFor="let item of done" cdkDrag>{{item}}</div>
</cdk-drop>
</div>
`
})
class ConnectedDropZones implements AfterViewInit {
Expand Down Expand Up @@ -1912,13 +1916,21 @@ class DraggableWithAlternateRoot {
}
`],
template: `
<cdk-drop #todoZone [connectedTo]="[doneZone]" (dropped)="droppedSpy($event)">
<div
cdkDrop
#todoZone="cdkDrop"
[cdkDropConnectedTo]="[doneZone]"
(cdkDropDropped)="droppedSpy($event)">
<div cdkDrag>One</div>
</cdk-drop>
</div>

<cdk-drop #doneZone [connectedTo]="[todoZone]" (dropped)="droppedSpy($event)">
<div
cdkDrop
#doneZone="cdkDrop"
[cdkDropConnectedTo]="[todoZone]"
(cdkDropDropped)="droppedSpy($event)">
<div cdkDrag>Two</div>
</cdk-drop>
</div>
`
})
class ConnectedDropZonesWithSingleItems {
Expand Down
Loading