From bd56a49dda5b5d2f0f0f2feac829afbe3752f5d5 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Sun, 21 Oct 2018 12:36:34 +0200 Subject: [PATCH] feat(drag-drop): add the ability to disable dragging Adds inputs to `cdkDrag`, `cdkDropList` and `cdkDragHandle` that allows for dragging to be disabled through those specific elements. Fixes #13651. --- src/cdk/drag-drop/drag-drop.md | 8 +++ src/cdk/drag-drop/drag-handle.ts | 11 +++- src/cdk/drag-drop/drag.spec.ts | 64 +++++++++++++++++++ src/cdk/drag-drop/drag.ts | 15 ++++- src/cdk/drag-drop/drop-list-container.ts | 3 + src/cdk/drag-drop/drop-list.ts | 10 ++- .../cdk-drag-drop-disabled-example.css | 48 ++++++++++++++ .../cdk-drag-drop-disabled-example.html | 7 ++ .../cdk-drag-drop-disabled-example.ts | 22 +++++++ 9 files changed, 184 insertions(+), 4 deletions(-) create mode 100644 src/material-examples/cdk-drag-drop-disabled/cdk-drag-drop-disabled-example.css create mode 100644 src/material-examples/cdk-drag-drop-disabled/cdk-drag-drop-disabled-example.html create mode 100644 src/material-examples/cdk-drag-drop-disabled/cdk-drag-drop-disabled-example.ts diff --git a/src/cdk/drag-drop/drag-drop.md b/src/cdk/drag-drop/drag-drop.md index 3a5764949fcb..d17c3267a6a7 100644 --- a/src/cdk/drag-drop/drag-drop.md +++ b/src/cdk/drag-drop/drag-drop.md @@ -142,3 +142,11 @@ new container. Depending on whether the predicate returns `true` or `false`, the be allowed into the new container. + +### 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`. + + diff --git a/src/cdk/drag-drop/drag-handle.ts b/src/cdk/drag-drop/drag-handle.ts index cca7bee3836e..9d8cf3b1fd17 100644 --- a/src/cdk/drag-drop/drag-handle.ts +++ b/src/cdk/drag-drop/drag-handle.ts @@ -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'; @@ -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, @Inject(CDK_DRAG_PARENT) @Optional() parentDrag?: any) { diff --git a/src/cdk/drag-drop/drag.spec.ts b/src/cdk/drag-drop/drag.spec.ts index d131461ed73e..94aed6825579 100644 --- a/src/cdk/drag-drop/drag.spec.ts +++ b/src/cdk/drag-drop/drag.spec.ts @@ -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(); @@ -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); @@ -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', () => { @@ -2006,6 +2068,8 @@ class StandaloneDraggable { class StandaloneDraggableWithHandle { @ViewChild('dragElement') dragElement: ElementRef; @ViewChild('handleElement') handleElement: ElementRef; + @ViewChild(CdkDrag) dragInstance: CdkDrag; + @ViewChild(CdkDragHandle) handleInstance: CdkDragHandle; } @Component({ diff --git a/src/cdk/drag-drop/drag.ts b/src/cdk/drag-drop/drag.ts index 35c9a5f29eac..ad3b9e36ea27 100644 --- a/src/cdk/drag-drop/drag.ts +++ b/src/cdk/drag-drop/drag.ts @@ -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'; @@ -200,6 +201,16 @@ export class CdkDrag 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 = new EventEmitter(); @@ -327,10 +338,10 @@ export class CdkDrag 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); } } diff --git a/src/cdk/drag-drop/drop-list-container.ts b/src/cdk/drag-drop/drop-list-container.ts index 5fd02d414087..296438017536 100644 --- a/src/cdk/drag-drop/drop-list-container.ts +++ b/src/cdk/drag-drop/drop-list-container.ts @@ -26,6 +26,9 @@ export interface CdkDropListContainer { /** 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; diff --git a/src/cdk/drag-drop/drop-list.ts b/src/cdk/drag-drop/drop-list.ts index 7e4add8e0f3e..65f03c92409f 100644 --- a/src/cdk/drag-drop/drop-list.ts +++ b/src/cdk/drag-drop/drop-list.ts @@ -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, @@ -115,6 +115,14 @@ export class CdkDropList 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. diff --git a/src/material-examples/cdk-drag-drop-disabled/cdk-drag-drop-disabled-example.css b/src/material-examples/cdk-drag-drop-disabled/cdk-drag-drop-disabled-example.css new file mode 100644 index 000000000000..8e55bdbb9a65 --- /dev/null +++ b/src/material-examples/cdk-drag-drop-disabled/cdk-drag-drop-disabled-example.css @@ -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); +} diff --git a/src/material-examples/cdk-drag-drop-disabled/cdk-drag-drop-disabled-example.html b/src/material-examples/cdk-drag-drop-disabled/cdk-drag-drop-disabled-example.html new file mode 100644 index 000000000000..d258da1b47de --- /dev/null +++ b/src/material-examples/cdk-drag-drop-disabled/cdk-drag-drop-disabled-example.html @@ -0,0 +1,7 @@ +
+
{{item.value}}
+
diff --git a/src/material-examples/cdk-drag-drop-disabled/cdk-drag-drop-disabled-example.ts b/src/material-examples/cdk-drag-drop-disabled/cdk-drag-drop-disabled-example.ts new file mode 100644 index 000000000000..679795063256 --- /dev/null +++ b/src/material-examples/cdk-drag-drop-disabled/cdk-drag-drop-disabled-example.ts @@ -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) { + moveItemInArray(this.items, event.previousIndex, event.currentIndex); + } +}