Skip to content

Commit

Permalink
feat(drag-drop): add the ability to disable dragging
Browse files Browse the repository at this point in the history
Adds inputs to `cdkDrag`, `cdkDropList` and `cdkDragHandle` that allows for dragging to be disabled through those specific elements.

Fixes angular#13651.
  • Loading branch information
crisbeto committed Oct 21, 2018
1 parent efeefd1 commit 858af25
Show file tree
Hide file tree
Showing 9 changed files with 184 additions and 4 deletions.
8 changes: 8 additions & 0 deletions src/cdk/drag-drop/drag-drop.md
Expand Up @@ -125,3 +125,11 @@ the element that is moved as the user is dragging. This is useful for cases like
draggable.

<!-- example(cdk-drag-drop-root-element) -->

### Disable dragging
If you want to disable dragging for a particular drag item, you can do so by setting the
`cdkDragDisabled` input on a `cdkDrag` item. Furthermore, you can disable an entire list
using the `cdkDropListDisabled` input on a `cdkDropList` or a particular handle via
`cdkDragHandleDisabled` on `cdkDragHandle`.

<!-- example(cdk-drag-drop-disabled) -->
11 changes: 10 additions & 1 deletion src/cdk/drag-drop/drag-handle.ts
Expand Up @@ -6,7 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Directive, ElementRef, Inject, Optional} from '@angular/core';
import {Directive, ElementRef, Inject, Optional, Input} from '@angular/core';
import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {CDK_DRAG_PARENT} from './drag-parent';
import {toggleNativeDragInteractions} from './drag-styling';

Expand All @@ -21,6 +22,14 @@ export class CdkDragHandle {
/** Closest parent draggable instance. */
_parentDrag: {} | undefined;

/** Whether starting to drag through this handle is disabled. */
@Input('cdkDragHandleDisabled')
get disabled(): boolean { return this._disabled; }
set disabled(value: boolean) {
this._disabled = coerceBooleanProperty(value);
}
private _disabled = false;

constructor(
public element: ElementRef<HTMLElement>,
@Inject(CDK_DRAG_PARENT) @Optional() parentDrag?: any) {
Expand Down
64 changes: 64 additions & 0 deletions src/cdk/drag-drop/drag.spec.ts
Expand Up @@ -412,6 +412,18 @@ describe('CdkDrag', () => {
expect(dragElement.style.transform).toBeFalsy();
}));

it('should not be able to drag the element if dragging is disabled', fakeAsync(() => {
const fixture = createComponent(StandaloneDraggable);
fixture.detectChanges();
const dragElement = fixture.componentInstance.dragElement.nativeElement;

fixture.componentInstance.dragInstance.disabled = true;

expect(dragElement.style.transform).toBeFalsy();
dragElementViaMouse(fixture, dragElement, 50, 100);
expect(dragElement.style.transform).toBeFalsy();
}));

});

describe('draggable with a handle', () => {
Expand All @@ -436,6 +448,32 @@ describe('CdkDrag', () => {
expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)');
}));

it('should not be able to drag the element if the handle is disabled', fakeAsync(() => {
const fixture = createComponent(StandaloneDraggableWithHandle);
fixture.detectChanges();
const dragElement = fixture.componentInstance.dragElement.nativeElement;
const handle = fixture.componentInstance.handleElement.nativeElement;

fixture.componentInstance.handleInstance.disabled = true;

expect(dragElement.style.transform).toBeFalsy();
dragElementViaMouse(fixture, handle, 50, 100);
expect(dragElement.style.transform).toBeFalsy();
}));

it('should not be able to drag using the handle if the element is disabled', fakeAsync(() => {
const fixture = createComponent(StandaloneDraggableWithHandle);
fixture.detectChanges();
const dragElement = fixture.componentInstance.dragElement.nativeElement;
const handle = fixture.componentInstance.handleElement.nativeElement;

fixture.componentInstance.dragInstance.disabled = true;

expect(dragElement.style.transform).toBeFalsy();
dragElementViaMouse(fixture, handle, 50, 100);
expect(dragElement.style.transform).toBeFalsy();
}));

it('should be able to use a handle that was added after init', fakeAsync(() => {
const fixture = createComponent(StandaloneDraggableWithDelayedHandle);

Expand Down Expand Up @@ -1329,6 +1367,30 @@ describe('CdkDrag', () => {
expect(thirdItem.style.transform).toBeFalsy();
}));

it('should not move the item if the list is disabled', fakeAsync(() => {
const fixture = createComponent(DraggableInDropZone);
fixture.detectChanges();
const dragItems = fixture.componentInstance.dragItems;

fixture.componentInstance.dropInstance.disabled = true;

expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim()))
.toEqual(['Zero', 'One', 'Two', 'Three']);

const firstItem = dragItems.first;
const thirdItemRect = dragItems.toArray()[2].element.nativeElement.getBoundingClientRect();

dragElementViaMouse(fixture, firstItem.element.nativeElement,
thirdItemRect.right + 1, thirdItemRect.top + 1);
flush();
fixture.detectChanges();

expect(fixture.componentInstance.droppedSpy).not.toHaveBeenCalled();

expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim()))
.toEqual(['Zero', 'One', 'Two', 'Three']);
}));

});

describe('in a connected drop container', () => {
Expand Down Expand Up @@ -1744,6 +1806,8 @@ class StandaloneDraggable {
class StandaloneDraggableWithHandle {
@ViewChild('dragElement') dragElement: ElementRef<HTMLElement>;
@ViewChild('handleElement') handleElement: ElementRef<HTMLElement>;
@ViewChild(CdkDrag) dragInstance: CdkDrag;
@ViewChild(CdkDragHandle) handleInstance: CdkDragHandle;
}

@Component({
Expand Down
15 changes: 13 additions & 2 deletions src/cdk/drag-drop/drag.ts
Expand Up @@ -29,6 +29,7 @@ import {
ViewContainerRef,
} from '@angular/core';
import {supportsPassiveEventListeners} from '@angular/cdk/platform';
import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {Observable, Subject, Subscription, Observer} from 'rxjs';
import {take} from 'rxjs/operators';
import {DragDropRegistry} from './drag-drop-registry';
Expand Down Expand Up @@ -198,6 +199,16 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
*/
@Input('cdkDragRootElement') rootElementSelector: string;

/** Whether starting to drag this element is disabled. */
@Input('cdkDragDisabled')
get disabled(): boolean {
return this._disabled || (this.dropContainer && this.dropContainer.disabled);
}
set disabled(value: boolean) {
this._disabled = coerceBooleanProperty(value);
}
private _disabled = false;

/** Emits when the user starts dragging the item. */
@Output('cdkDragStarted') started: EventEmitter<CdkDragStart> = new EventEmitter<CdkDragStart>();

Expand Down Expand Up @@ -314,10 +325,10 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
return !!target && (target === element || element.contains(target as HTMLElement));
});

if (targetHandle) {
if (targetHandle && !targetHandle.disabled && !this.disabled) {
this._initializeDragSequence(targetHandle.element.nativeElement, event);
}
} else {
} else if (!this.disabled) {
this._initializeDragSequence(this._rootElement, event);
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/cdk/drag-drop/drop-list-container.ts
Expand Up @@ -26,6 +26,9 @@ export interface CdkDropListContainer<T = any> {
/** Locks the position of the draggable elements inside the container along the specified axis. */
lockAxis: 'x' | 'y';

/** Whether starting a dragging sequence from this container is disabled. */
disabled: boolean;

/** Starts dragging an item. */
start(): void;

Expand Down
10 changes: 9 additions & 1 deletion src/cdk/drag-drop/drop-list.ts
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {coerceArray} from '@angular/cdk/coercion';
import {coerceArray, coerceBooleanProperty} from '@angular/cdk/coercion';
import {
ContentChildren,
ElementRef,
Expand Down Expand Up @@ -77,6 +77,14 @@ export class CdkDropList<T = any> implements OnInit, OnDestroy {
/** Locks the position of the draggable elements inside the container along the specified axis. */
@Input('cdkDropListLockAxis') lockAxis: 'x' | 'y';

/** Whether starting a dragging sequence from this container is disabled. */
@Input('cdkDropListDisabled')
get disabled(): boolean { return this._disabled; }
set disabled(value: boolean) {
this._disabled = coerceBooleanProperty(value);
}
private _disabled = false;

/**
* Function that is used to determine whether an item
* is allowed to be moved into a drop container.
Expand Down
@@ -0,0 +1,48 @@
.example-list {
width: 500px;
max-width: 100%;
border: solid 1px #ccc;
min-height: 60px;
display: block;
background: white;
border-radius: 4px;
overflow: hidden;
}

.example-box {
padding: 20px 10px;
border-bottom: solid 1px #ccc;
color: rgba(0, 0, 0, 0.87);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: move;
background: white;
font-size: 14px;
}

.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
0 8px 10px 1px rgba(0, 0, 0, 0.14),
0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.cdk-drag-placeholder {
opacity: 0;
}

.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

.example-box:last-child {
border: none;
}

.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
@@ -0,0 +1,7 @@
<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
<div
class="example-box"
*ngFor="let item of items"
cdkDrag
[cdkDragDisabled]="item.disabled">{{item.value}}</div>
</div>
@@ -0,0 +1,22 @@
import {Component} from '@angular/core';
import {CdkDragDrop, moveItemInArray} from '@angular/cdk/drag-drop';

/**
* @title Drag&Drop disabled
*/
@Component({
selector: 'cdk-drag-drop-disabled-example',
templateUrl: 'cdk-drag-drop-disabled-example.html',
styleUrls: ['cdk-drag-drop-disabled-example.css'],
})
export class CdkDragDropDisabledExample {
items = [
{value: 'I can be dragged', disabled: false},
{value: 'I cannot be dragged', disabled: true},
{value: 'I can also be dragged', disabled: false}
];

drop(event: CdkDragDrop<string[]>) {
moveItemInArray(this.items, event.previousIndex, event.currentIndex);
}
}

0 comments on commit 858af25

Please sign in to comment.