mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
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:
committed by
mmalerba
parent
3a8425968d
commit
4d430bd146
@@ -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'];
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>>();
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user