feat(drag-drop): add released event (#14513)

Adds the `cdkDragReleased` event, in addition to `cdkDragEnded`. The difference between released and ended is that released will fire as soon as the user has released the item, whereas ended will fire once all animations are done. The former is useful to customize the animation on drop, based on where the item is being dropped.

Fixes #14498.
This commit is contained in:
Kristiyan Kostadinov
2018-12-18 16:23:12 +01:00
committed by Jeremy Elbourn
parent c94812de2c
commit 792d536580
5 changed files with 63 additions and 0 deletions
+37
View File
@@ -354,6 +354,7 @@ describe('CdkDrag', () => {
dragElementViaMouse(fixture, fixture.componentInstance.dragElement.nativeElement, 2, 2);
expect(fixture.componentInstance.startedSpy).not.toHaveBeenCalled();
expect(fixture.componentInstance.releasedSpy).not.toHaveBeenCalled();
expect(fixture.componentInstance.endedSpy).not.toHaveBeenCalled();
expect(moveSpy).not.toHaveBeenCalled();
subscription.unsubscribe();
@@ -1221,6 +1222,40 @@ describe('CdkDrag', () => {
.toBeFalsy('Expected preview to be removed from the DOM if the transition timed out');
}));
it('should emit the released event as soon as the item is released', fakeAsync(() => {
const fixture = createComponent(DraggableInDropZone);
fixture.detectChanges();
const item = fixture.componentInstance.dragItems.toArray()[1];
const endedSpy = jasmine.createSpy('ended spy');
const releasedSpy = jasmine.createSpy('released spy');
const endedSubscription = item.ended.subscribe(endedSpy);
const releasedSubscription = item.released.subscribe(releasedSpy);
startDraggingViaMouse(fixture, item.element.nativeElement);
const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement;
// Add a duration since the tests won't include one.
preview.style.transitionDuration = '500ms';
// Move somewhere so the draggable doesn't exit immediately.
dispatchMouseEvent(document, 'mousemove', 50, 50);
fixture.detectChanges();
dispatchMouseEvent(document, 'mouseup');
fixture.detectChanges();
// Expected the released event to fire immediately upon release.
expect(releasedSpy).toHaveBeenCalled();
tick(1000);
// Expected the ended event to fire once the entire sequence is done.
expect(endedSpy).toHaveBeenCalled();
endedSubscription.unsubscribe();
releasedSubscription.unsubscribe();
}));
it('should reset immediately when failed drag happens after a successful one', fakeAsync(() => {
const fixture = createComponent(DraggableInDropZone);
fixture.detectChanges();
@@ -2597,6 +2632,7 @@ describe('CdkDrag', () => {
cdkDrag
[cdkDragBoundary]="boundarySelector"
(cdkDragStarted)="startedSpy($event)"
(cdkDragReleased)="releasedSpy($event)"
(cdkDragEnded)="endedSpy($event)"
#dragElement
style="width: 100px; height: 100px; background: red;"></div>
@@ -2608,6 +2644,7 @@ class StandaloneDraggable {
@ViewChild(CdkDrag) dragInstance: CdkDrag;
startedSpy = jasmine.createSpy('started spy');
endedSpy = jasmine.createSpy('ended spy');
releasedSpy = jasmine.createSpy('released spy');
boundarySelector: string;
}
+9
View File
@@ -38,6 +38,7 @@ import {
CdkDragExit,
CdkDragMove,
CdkDragStart,
CdkDragRelease,
} from '../drag-events';
import {CdkDragHandle} from './drag-handle';
import {CdkDragPlaceholder} from './drag-placeholder';
@@ -118,6 +119,10 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
/** Emits when the user starts dragging the item. */
@Output('cdkDragStarted') started: EventEmitter<CdkDragStart> = new EventEmitter<CdkDragStart>();
/** Emits when the user has released a drag item, before any animations have started. */
@Output('cdkDragReleased') released: EventEmitter<CdkDragRelease> =
new EventEmitter<CdkDragRelease>();
/** Emits when the user stops dragging an item in the container. */
@Output('cdkDragEnded') ended: EventEmitter<CdkDragEnd> = new EventEmitter<CdkDragEnd>();
@@ -254,6 +259,10 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
this.started.emit({source: this});
});
ref.released.subscribe(() => {
this.released.emit({source: this});
});
ref.ended.subscribe(() => {
this.ended.emit({source: this});
});
+6
View File
@@ -15,6 +15,12 @@ export interface CdkDragStart<T = any> {
source: CdkDrag<T>;
}
/** Event emitted when the user releases an item, before any animations have started. */
export interface CdkDragRelease<T = any> {
/** Draggable that emitted the event. */
source: CdkDrag<T>;
}
/** Event emitted when the user stops dragging a draggable. */
export interface CdkDragEnd<T = any> {
/** Draggable that emitted the event. */
+6
View File
@@ -213,6 +213,9 @@ export class DragRef<T = any> {
/** Emits when the user starts dragging the item. */
started = new Subject<{source: DragRef}>();
/** Emits when the user has released a drag item, before any animations have started. */
released = new Subject<{source: DragRef}>();
/** Emits when the user stops dragging an item in the container. */
ended = new Subject<{source: DragRef}>();
@@ -355,6 +358,7 @@ export class DragRef<T = any> {
this._removeSubscriptions();
this.beforeStarted.complete();
this.started.complete();
this.released.complete();
this.ended.complete();
this.entered.complete();
this.exited.complete();
@@ -516,6 +520,8 @@ export class DragRef<T = any> {
return;
}
this.released.next({source: this});
if (!this.dropContainer) {
// Convert the active transform into a passive one. This means that next time
// the user starts dragging the item, its position will be calculated relatively
+5
View File
@@ -22,6 +22,7 @@ export declare class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
exited: EventEmitter<CdkDragExit<any>>;
lockAxis: 'x' | 'y';
moved: Observable<CdkDragMove<T>>;
released: EventEmitter<CdkDragRelease>;
rootElementSelector: string;
started: EventEmitter<CdkDragStart>;
constructor(
@@ -92,6 +93,10 @@ export declare class CdkDragPreview<T = any> {
constructor(templateRef: TemplateRef<T>);
}
export interface CdkDragRelease<T = any> {
source: CdkDrag<T>;
}
export interface CdkDragSortEvent<T = any, I = T> {
container: CdkDropList<T>;
currentIndex: number;