diff --git a/src/cdk/drag-drop/directives/drag.spec.ts b/src/cdk/drag-drop/directives/drag.spec.ts index d9ffc82f12b..2e51549f6d1 100644 --- a/src/cdk/drag-drop/directives/drag.spec.ts +++ b/src/cdk/drag-drop/directives/drag.spec.ts @@ -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;"> @@ -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; } diff --git a/src/cdk/drag-drop/directives/drag.ts b/src/cdk/drag-drop/directives/drag.ts index c3539087e84..b857318c78a 100644 --- a/src/cdk/drag-drop/directives/drag.ts +++ b/src/cdk/drag-drop/directives/drag.ts @@ -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 implements AfterViewInit, OnDestroy { /** Emits when the user starts dragging the item. */ @Output('cdkDragStarted') started: EventEmitter = new EventEmitter(); + /** Emits when the user has released a drag item, before any animations have started. */ + @Output('cdkDragReleased') released: EventEmitter = + new EventEmitter(); + /** Emits when the user stops dragging an item in the container. */ @Output('cdkDragEnded') ended: EventEmitter = new EventEmitter(); @@ -254,6 +259,10 @@ export class CdkDrag implements AfterViewInit, OnDestroy { this.started.emit({source: this}); }); + ref.released.subscribe(() => { + this.released.emit({source: this}); + }); + ref.ended.subscribe(() => { this.ended.emit({source: this}); }); diff --git a/src/cdk/drag-drop/drag-events.ts b/src/cdk/drag-drop/drag-events.ts index 57ffc86e9e1..a613dfe0154 100644 --- a/src/cdk/drag-drop/drag-events.ts +++ b/src/cdk/drag-drop/drag-events.ts @@ -15,6 +15,12 @@ export interface CdkDragStart { source: CdkDrag; } +/** Event emitted when the user releases an item, before any animations have started. */ +export interface CdkDragRelease { + /** Draggable that emitted the event. */ + source: CdkDrag; +} + /** Event emitted when the user stops dragging a draggable. */ export interface CdkDragEnd { /** Draggable that emitted the event. */ diff --git a/src/cdk/drag-drop/drag-ref.ts b/src/cdk/drag-drop/drag-ref.ts index 2bf7b6d60b8..f3b59ea1f3e 100644 --- a/src/cdk/drag-drop/drag-ref.ts +++ b/src/cdk/drag-drop/drag-ref.ts @@ -213,6 +213,9 @@ export class DragRef { /** 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 { 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 { 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 diff --git a/tools/public_api_guard/cdk/drag-drop.d.ts b/tools/public_api_guard/cdk/drag-drop.d.ts index 12655937157..6dc3498ec12 100644 --- a/tools/public_api_guard/cdk/drag-drop.d.ts +++ b/tools/public_api_guard/cdk/drag-drop.d.ts @@ -22,6 +22,7 @@ export declare class CdkDrag implements AfterViewInit, OnDestroy { exited: EventEmitter>; lockAxis: 'x' | 'y'; moved: Observable>; + released: EventEmitter; rootElementSelector: string; started: EventEmitter; constructor( @@ -92,6 +93,10 @@ export declare class CdkDragPreview { constructor(templateRef: TemplateRef); } +export interface CdkDragRelease { + source: CdkDrag; +} + export interface CdkDragSortEvent { container: CdkDropList; currentIndex: number;