mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(drag-drop): add API to get/set current position of a standalone draggable (#14696)
Adds an API that allows the consumer to get the current position of a standalone draggable and to set it. This is useful for cases where the dragged position should be preserved when the user navigates away and then restored when they return. Fixes #14420. Fixes #14674.
This commit is contained in:
committed by
mmalerba
parent
ae32092c22
commit
2f009d00da
@@ -788,6 +788,47 @@ describe('CdkDrag', () => {
|
||||
'Expected element to be dragged after all the time has passed.');
|
||||
}));
|
||||
|
||||
it('should be able to get the current position', fakeAsync(() => {
|
||||
const fixture = createComponent(StandaloneDraggable);
|
||||
fixture.detectChanges();
|
||||
|
||||
const dragElement = fixture.componentInstance.dragElement.nativeElement;
|
||||
const dragInstance = fixture.componentInstance.dragInstance;
|
||||
|
||||
expect(dragInstance.getFreeDragPosition()).toEqual({x: 0, y: 0});
|
||||
|
||||
dragElementViaMouse(fixture, dragElement, 50, 100);
|
||||
expect(dragInstance.getFreeDragPosition()).toEqual({x: 50, y: 100});
|
||||
|
||||
dragElementViaMouse(fixture, dragElement, 100, 200);
|
||||
expect(dragInstance.getFreeDragPosition()).toEqual({x: 150, y: 300});
|
||||
}));
|
||||
|
||||
it('should be able to set the current position', fakeAsync(() => {
|
||||
const fixture = createComponent(StandaloneDraggable);
|
||||
fixture.componentInstance.freeDragPosition = {x: 50, y: 100};
|
||||
fixture.detectChanges();
|
||||
|
||||
const dragElement = fixture.componentInstance.dragElement.nativeElement;
|
||||
const dragInstance = fixture.componentInstance.dragInstance;
|
||||
|
||||
expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)');
|
||||
expect(dragInstance.getFreeDragPosition()).toEqual({x: 50, y: 100});
|
||||
}));
|
||||
|
||||
it('should be able to continue dragging after the current position was set', fakeAsync(() => {
|
||||
const fixture = createComponent(StandaloneDraggable);
|
||||
fixture.componentInstance.freeDragPosition = {x: 50, y: 100};
|
||||
fixture.detectChanges();
|
||||
const dragElement = fixture.componentInstance.dragElement.nativeElement;
|
||||
|
||||
expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)');
|
||||
|
||||
dragElementViaMouse(fixture, dragElement, 100, 200);
|
||||
|
||||
expect(dragElement.style.transform).toBe('translate3d(150px, 300px, 0px)');
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
describe('draggable with a handle', () => {
|
||||
@@ -3187,6 +3228,7 @@ describe('CdkDrag', () => {
|
||||
[cdkDragBoundary]="boundarySelector"
|
||||
[cdkDragStartDelay]="dragStartDelay"
|
||||
[cdkDragConstrainPosition]="constrainPosition"
|
||||
[cdkDragFreeDragPosition]="freeDragPosition"
|
||||
(cdkDragStarted)="startedSpy($event)"
|
||||
(cdkDragReleased)="releasedSpy($event)"
|
||||
(cdkDragEnded)="endedSpy($event)"
|
||||
@@ -3204,6 +3246,7 @@ class StandaloneDraggable {
|
||||
boundarySelector: string;
|
||||
dragStartDelay: number | string;
|
||||
constrainPosition: (point: Point) => Point;
|
||||
freeDragPosition?: {x: number, y: number};
|
||||
}
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -116,6 +116,12 @@ export class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {
|
||||
*/
|
||||
@Input('cdkDragStartDelay') dragStartDelay: number = 0;
|
||||
|
||||
/**
|
||||
* Sets the position of a `CdkDrag` that is outside of a drop container.
|
||||
* Can be used to restore the element's position for a returning user.
|
||||
*/
|
||||
@Input('cdkDragFreeDragPosition') freeDragPosition: {x: number, y: number};
|
||||
|
||||
/** Whether starting to drag this element is disabled. */
|
||||
@Input('cdkDragDisabled')
|
||||
get disabled(): boolean {
|
||||
@@ -229,6 +235,13 @@ export class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {
|
||||
this._dragRef.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the pixel coordinates of the draggable outside of a drop container.
|
||||
*/
|
||||
getFreeDragPosition(): {readonly x: number, readonly y: number} {
|
||||
return this._dragRef.getFreeDragPosition();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
// We need to wait for the zone to stabilize, in order for the reference
|
||||
// element to be in the proper place in the DOM. This is mostly relevant
|
||||
@@ -260,17 +273,27 @@ export class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {
|
||||
const handle = handleInstance.element.nativeElement;
|
||||
handleInstance.disabled ? dragRef.disableHandle(handle) : dragRef.enableHandle(handle);
|
||||
});
|
||||
|
||||
if (this.freeDragPosition) {
|
||||
this._dragRef.setFreeDragPosition(this.freeDragPosition);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
const rootSelectorChange = changes['rootElementSelector'];
|
||||
const positionChange = changes['positionChange'];
|
||||
|
||||
// We don't have to react to the first change since it's being
|
||||
// handled in `ngAfterViewInit` where it needs to be deferred.
|
||||
if (rootSelectorChange && !rootSelectorChange.firstChange) {
|
||||
this._updateRootElement();
|
||||
}
|
||||
|
||||
// Skip the first change since it's being handled in `ngAfterViewInit`.
|
||||
if (positionChange && !positionChange.firstChange && this.freeDragPosition) {
|
||||
this._dragRef.setFreeDragPosition(this.freeDragPosition);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
|
||||
@@ -434,6 +434,29 @@ export class DragRef<T = any> {
|
||||
this._dropContainer = container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current position in pixels the draggable outside of a drop container.
|
||||
*/
|
||||
getFreeDragPosition(): Readonly<Point> {
|
||||
return {x: this._passiveTransform.x, y: this._passiveTransform.y};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current position in pixels the draggable outside of a drop container.
|
||||
* @param value New position to be set.
|
||||
*/
|
||||
setFreeDragPosition(value: Point): this {
|
||||
this._activeTransform = {x: 0, y: 0};
|
||||
this._passiveTransform.x = value.x;
|
||||
this._passiveTransform.y = value.y;
|
||||
|
||||
if (!this._dropContainer) {
|
||||
this._applyRootElementTransform(value.x, value.y);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Unsubscribes from the global subscriptions. */
|
||||
private _removeSubscriptions() {
|
||||
this._pointerMoveSubscription.unsubscribe();
|
||||
@@ -527,13 +550,8 @@ export class DragRef<T = any> {
|
||||
constrainedPointerPosition.x - this._pickupPositionOnPage.x + this._passiveTransform.x;
|
||||
activeTransform.y =
|
||||
constrainedPointerPosition.y - this._pickupPositionOnPage.y + this._passiveTransform.y;
|
||||
const transform = getTransform(activeTransform.x, activeTransform.y);
|
||||
|
||||
// Preserve the previous `transform` value, if there was one. Note that we apply our own
|
||||
// transform before the user's, because things like rotation can affect which direction
|
||||
// the element will be translated towards.
|
||||
this._rootElement.style.transform = this._initialTransform ?
|
||||
transform + ' ' + this._initialTransform : transform;
|
||||
this._applyRootElementTransform(activeTransform.x, activeTransform.y);
|
||||
|
||||
// Apply transform as attribute if dragging and svg element to work for IE
|
||||
if (typeof SVGElement !== 'undefined' && this._rootElement instanceof SVGElement) {
|
||||
@@ -660,12 +678,6 @@ export class DragRef<T = any> {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache the previous transform amount only after the first drag sequence, because
|
||||
// we don't want our own transforms to stack on top of each other.
|
||||
if (this._initialTransform == null) {
|
||||
this._initialTransform = this._rootElement.style.transform || '';
|
||||
}
|
||||
|
||||
// If we've got handles, we need to disable the tap highlight on the entire root element,
|
||||
// otherwise iOS will still add it, even though all the drag interactions on the handle
|
||||
// are disabled.
|
||||
@@ -990,6 +1002,26 @@ export class DragRef<T = any> {
|
||||
element.removeEventListener('touchstart', this._pointerDown, passiveEventListenerOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a `transform` to the root element, taking into account any existing transforms on it.
|
||||
* @param x New transform value along the X axis.
|
||||
* @param y New transform value along the Y axis.
|
||||
*/
|
||||
private _applyRootElementTransform(x: number, y: number) {
|
||||
const transform = getTransform(x, y);
|
||||
|
||||
// Cache the previous transform amount only after the first drag sequence, because
|
||||
// we don't want our own transforms to stack on top of each other.
|
||||
if (this._initialTransform == null) {
|
||||
this._initialTransform = this._rootElement.style.transform || '';
|
||||
}
|
||||
|
||||
// Preserve the previous `transform` value, if there was one. Note that we apply our own
|
||||
// transform before the user's, because things like rotation can affect which direction
|
||||
// the element will be translated towards.
|
||||
this._rootElement.style.transform = this._initialTransform ?
|
||||
transform + ' ' + this._initialTransform : transform;
|
||||
}
|
||||
}
|
||||
|
||||
/** Point on the page or within an element. */
|
||||
|
||||
+10
@@ -22,6 +22,10 @@ export declare class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDes
|
||||
ended: EventEmitter<CdkDragEnd>;
|
||||
entered: EventEmitter<CdkDragEnter<any>>;
|
||||
exited: EventEmitter<CdkDragExit<any>>;
|
||||
freeDragPosition: {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
lockAxis: 'x' | 'y';
|
||||
moved: Observable<CdkDragMove<T>>;
|
||||
released: EventEmitter<CdkDragRelease>;
|
||||
@@ -31,6 +35,10 @@ export declare class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDes
|
||||
element: ElementRef<HTMLElement>,
|
||||
dropContainer: CdkDropList, _document: any, _ngZone: NgZone, _viewContainerRef: ViewContainerRef, viewportRuler: ViewportRuler, dragDropRegistry: DragDropRegistry<DragRef, DropListRef>, config: DragRefConfig, _dir: Directionality,
|
||||
dragDrop?: DragDrop, _changeDetectorRef?: ChangeDetectorRef | undefined);
|
||||
getFreeDragPosition(): {
|
||||
readonly x: number;
|
||||
readonly y: number;
|
||||
};
|
||||
getPlaceholderElement(): HTMLElement;
|
||||
getRootElement(): HTMLElement;
|
||||
ngAfterViewInit(): void;
|
||||
@@ -252,10 +260,12 @@ export declare class DragRef<T = any> {
|
||||
disableHandle(handle: HTMLElement): void;
|
||||
dispose(): void;
|
||||
enableHandle(handle: HTMLElement): void;
|
||||
getFreeDragPosition(): Readonly<Point>;
|
||||
getPlaceholderElement(): HTMLElement;
|
||||
getRootElement(): HTMLElement;
|
||||
isDragging(): boolean;
|
||||
reset(): void;
|
||||
setFreeDragPosition(value: Point): this;
|
||||
withBoundaryElement(boundaryElement: ElementRef<HTMLElement> | HTMLElement | null): this;
|
||||
withDirection(direction: Direction): this;
|
||||
withHandles(handles: (HTMLElement | ElementRef<HTMLElement>)[]): this;
|
||||
|
||||
Reference in New Issue
Block a user