feat(drag-drop): add the ability to lock dragging along an axis (#12604)

Adds inputs that allow consumers to lock dragging of a particular drag item, or all items in a drag container, along an axis.
This commit is contained in:
Kristiyan Kostadinov
2018-08-15 00:52:57 +02:00
committed by mmalerba
parent 3a8425968d
commit 4d430bd146
6 changed files with 137 additions and 8 deletions
@@ -246,6 +246,38 @@ describe('CdkDrag', () => {
subscription.unsubscribe();
});
it('should be able to lock dragging along the x axis', fakeAsync(() => {
const fixture = createComponent(StandaloneDraggable);
fixture.detectChanges();
fixture.componentInstance.dragInstance.lockAxis = 'x';
const dragElement = fixture.componentInstance.dragElement.nativeElement;
expect(dragElement.style.transform).toBeFalsy();
dragElementViaMouse(fixture, dragElement, 50, 100);
expect(dragElement.style.transform).toBe('translate3d(50px, 0px, 0px)');
dragElementViaMouse(fixture, dragElement, 100, 200);
expect(dragElement.style.transform).toBe('translate3d(150px, 0px, 0px)');
}));
it('should be able to lock dragging along the y axis', fakeAsync(() => {
const fixture = createComponent(StandaloneDraggable);
fixture.detectChanges();
fixture.componentInstance.dragInstance.lockAxis = 'y';
const dragElement = fixture.componentInstance.dragElement.nativeElement;
expect(dragElement.style.transform).toBeFalsy();
dragElementViaMouse(fixture, dragElement, 50, 100);
expect(dragElement.style.transform).toBe('translate3d(0px, 100px, 0px)');
dragElementViaMouse(fixture, dragElement, 100, 200);
expect(dragElement.style.transform).toBe('translate3d(0px, 300px, 0px)');
}));
});
describe('draggable with a handle', () => {
@@ -690,6 +722,65 @@ describe('CdkDrag', () => {
expect(preview.style.transform).toBe('translate3d(50px, 50px, 0px)');
}));
it('should lock position inside a drop container along the x axis', fakeAsync(() => {
const fixture = createComponent(DraggableInDropZoneWithCustomPreview);
fixture.detectChanges();
const item = fixture.componentInstance.dragItems.toArray()[1];
const element = item.element.nativeElement;
item.lockAxis = 'x';
dispatchMouseEvent(element, 'mousedown', 50, 50);
fixture.detectChanges();
dispatchMouseEvent(element, 'mousemove', 100, 100);
fixture.detectChanges();
const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement;
expect(preview.style.transform).toBe('translate3d(100px, 50px, 0px)');
}));
it('should lock position inside a drop container along the y axis', fakeAsync(() => {
const fixture = createComponent(DraggableInDropZoneWithCustomPreview);
fixture.detectChanges();
const item = fixture.componentInstance.dragItems.toArray()[1];
const element = item.element.nativeElement;
item.lockAxis = 'y';
dispatchMouseEvent(element, 'mousedown', 50, 50);
fixture.detectChanges();
dispatchMouseEvent(element, 'mousemove', 100, 100);
fixture.detectChanges();
const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement;
expect(preview.style.transform).toBe('translate3d(50px, 100px, 0px)');
}));
it('should inherit the position locking from the drop container', fakeAsync(() => {
const fixture = createComponent(DraggableInDropZoneWithCustomPreview);
fixture.detectChanges();
const element = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement;
fixture.componentInstance.dropInstance.lockAxis = 'x';
dispatchMouseEvent(element, 'mousedown', 50, 50);
fixture.detectChanges();
dispatchMouseEvent(element, 'mousemove', 100, 100);
fixture.detectChanges();
const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement;
expect(preview.style.transform).toBe('translate3d(100px, 50px, 0px)');
}));
it('should be able to customize the placeholder', fakeAsync(() => {
const fixture = createComponent(DraggableInDropZoneWithCustomPlaceholder);
fixture.detectChanges();
@@ -1103,6 +1194,7 @@ export class DraggableInHorizontalDropZone {
`
})
export class DraggableInDropZoneWithCustomPreview {
@ViewChild(CdkDrop) dropInstance: CdkDrop;
@ViewChildren(CdkDrag) dragItems: QueryList<CdkDrag>;
items = ['Zero', 'One', 'Two', 'Three'];
}
+20 -5
View File
@@ -119,14 +119,15 @@ export class CdkDrag<T = any> implements OnDestroy {
/** Element that will be used as a template to create the draggable item's preview. */
@ContentChild(CdkDragPreview) _previewTemplate: CdkDragPreview;
/**
* Template for placeholder element rendered to show where a draggable would be dropped.
*/
/** Template for placeholder element rendered to show where a draggable would be dropped. */
@ContentChild(CdkDragPlaceholder) _placeholderTemplate: CdkDragPlaceholder;
/** Arbitrary data to attach to this drag instance. */
@Input() data: T;
/** Locks the position of the dragged element along the specified axis. */
@Input('cdkDragLockAxis') lockAxis: 'x' | 'y';
/** Emits when the user starts dragging the item. */
@Output('cdkDragStarted') started: EventEmitter<CdkDragStart> = new EventEmitter<CdkDragStart>();
@@ -276,7 +277,7 @@ export class CdkDrag<T = any> implements OnDestroy {
this._hasMoved = true;
event.preventDefault();
const pointerPosition = this._getPointerPositionOnPage(event);
const pointerPosition = this._getConstrainedPointerPosition(event);
if (this.dropContainer) {
this._updateActiveDropContainer(pointerPosition);
@@ -361,7 +362,7 @@ export class CdkDrag<T = any> implements OnDestroy {
* Updates the item's position in its drop container, or moves it
* into a new one, depending on its current drag position.
*/
private _updateActiveDropContainer({x, y}: Point) {
private _updateActiveDropContainer({x, y}) {
// Drop container that draggable has been moved into.
const newContainer = this.dropContainer._getSiblingContainerFromPosition(x, y);
@@ -531,6 +532,20 @@ export class CdkDrag<T = any> implements OnDestroy {
};
}
/** Gets the pointer position on the page, accounting for any position constraints. */
private _getConstrainedPointerPosition(event: MouseEvent | TouchEvent): Point {
const point = this._getPointerPositionOnPage(event);
const dropContainerLock = this.dropContainer ? this.dropContainer.lockAxis : null;
if (this.lockAxis === 'x' || dropContainerLock === 'x') {
point.y = this._pickupPositionOnPage.y;
} else if (this.lockAxis === 'y' || dropContainerLock === 'y') {
point.x = this._pickupPositionOnPage.x;
}
return point;
}
/** Determines whether an event is a touch event. */
private _isTouchEvent(event: MouseEvent | TouchEvent): event is TouchEvent {
return event.type.startsWith('touch');
@@ -19,6 +19,9 @@ export interface CdkDropContainer<T = any> {
/** Direction in which the list is oriented. */
orientation: 'horizontal' | 'vertical';
/** Locks the position of the draggable elements inside the container along the specified axis. */
lockAxis: 'x' | 'y';
/** Starts dragging an item. */
start(): void;
+3
View File
@@ -69,6 +69,9 @@ export class CdkDrop<T = any> implements OnInit, OnDestroy {
*/
@Input() id: string = `cdk-drop-${_uniqueIdCounter++}`;
/** Locks the position of the draggable elements inside the container along the specified axis. */
@Input() lockAxis: 'x' | 'y';
/** Emits when the user drops an item inside the container. */
@Output() dropped: EventEmitter<CdkDragDrop<T, any>> = new EventEmitter<CdkDragDrop<T, any>>();
+18 -3
View File
@@ -4,6 +4,7 @@
<cdk-drop
#one
(dropped)="drop($event)"
[lockAxis]="axisLock"
[data]="todo"
[connectedTo]="[two]">
<div *ngFor="let item of todo" cdkDrag>
@@ -18,6 +19,7 @@
<cdk-drop
#two
(dropped)="drop($event)"
[lockAxis]="axisLock"
[data]="done"
[connectedTo]="[one]">
<div *ngFor="let item of done" cdkDrag>
@@ -34,6 +36,7 @@
<cdk-drop
orientation="horizontal"
(dropped)="drop($event)"
[lockAxis]="axisLock"
[data]="horizontalData">
<div *ngFor="let item of horizontalData" cdkDrag>
{{item}}
@@ -43,6 +46,11 @@
</div>
</div>
<div class="list">
<h2>Free dragging</h2>
<div cdkDrag class="free-draggable" [cdkDragLockAxis]="axisLock">Drag me around</div>
</div>
<div>
<h2>Data</h2>
<pre>{{todo.join(', ')}}</pre>
@@ -50,7 +58,14 @@
<pre>{{horizontalData.join(', ')}}</pre>
</div>
<div class="list">
<h2>Free dragging</h2>
<div cdkDrag class="free-draggable">Drag me around</div>
<div>
<h2>Axis locking</h2>
<mat-form-field>
<mat-label>Lock position along axis</mat-label>
<mat-select [(ngModel)]="axisLock">
<mat-option>None</mat-option>
<mat-option value="x">X axis</mat-option>
<mat-option value="y">Y axis</mat-option>
</mat-select>
</mat-form-field>
</div>
+1
View File
@@ -19,6 +19,7 @@ import {CdkDragDrop, moveItemInArray, transferArrayItem} from '@angular/cdk-expe
encapsulation: ViewEncapsulation.None,
})
export class DragAndDropDemo {
axisLock: 'x' | 'y';
todo = [
'Come up with catchy start-up name',
'Add "blockchain" to name',