mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(drag-drop): add the ability to disable dragging (#13722)
Adds inputs to `cdkDrag`, `cdkDropList` and `cdkDragHandle` that allows for dragging to be disabled through those specific elements. Fixes #13651.
This commit is contained in:
committed by
Andrew Seguin
parent
cb56df9c5b
commit
b02a72eb70
@@ -158,3 +158,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) -->
|
||||
|
||||
@@ -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<HTMLElement>,
|
||||
@Inject(CDK_DRAG_PARENT) @Optional() parentDrag?: any) {
|
||||
|
||||
@@ -452,6 +452,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();
|
||||
@@ -557,6 +569,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);
|
||||
|
||||
@@ -1698,6 +1736,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', () => {
|
||||
@@ -2178,6 +2240,8 @@ class StandaloneDraggable {
|
||||
class StandaloneDraggableWithHandle {
|
||||
@ViewChild('dragElement') dragElement: ElementRef<HTMLElement>;
|
||||
@ViewChild('handleElement') handleElement: ElementRef<HTMLElement>;
|
||||
@ViewChild(CdkDrag) dragInstance: CdkDrag;
|
||||
@ViewChild(CdkDragHandle) handleInstance: CdkDragHandle;
|
||||
}
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -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';
|
||||
@@ -217,6 +218,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>();
|
||||
|
||||
@@ -351,10 +362,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -116,6 +116,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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user