Skip to content

feat(drag-drop): add the ability to disable dragging #13722

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
Nov 14, 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
8 changes: 8 additions & 0 deletions src/cdk/drag-drop/drag-drop.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,11 @@ new container. Depending on whether the predicate returns `true` or `false`, the
be allowed into the new container.

<!-- example(cdk-drag-drop-enter-predicate) -->

### 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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,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();
}));

it('should stop propagation for the drag sequence start event', fakeAsync(() => {
const fixture = createComponent(StandaloneDraggable);
fixture.detectChanges();
Expand Down Expand Up @@ -467,6 +479,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 @@ -1551,6 +1589,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 @@ -2006,6 +2068,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
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
ViewContainerRef,
} from '@angular/core';
import {normalizePassiveListenerOptions} from '@angular/cdk/platform';
import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {Observable, Subject, Subscription, Observer} from 'rxjs';
import {startWith, take} from 'rxjs/operators';
import {DragDropRegistry} from './drag-drop-registry';
Expand Down Expand Up @@ -200,6 +201,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 @@ -327,10 +338,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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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 @@ -115,6 +115,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
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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);
}
}