From 792d5365807c17c72057270120105ee8cbd0b7a6 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Tue, 18 Dec 2018 16:23:12 +0100 Subject: [PATCH] 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. --- src/cdk/drag-drop/directives/drag.spec.ts | 37 +++++++++++++++++++++++ src/cdk/drag-drop/directives/drag.ts | 9 ++++++ src/cdk/drag-drop/drag-events.ts | 6 ++++ src/cdk/drag-drop/drag-ref.ts | 6 ++++ tools/public_api_guard/cdk/drag-drop.d.ts | 5 +++ 5 files changed, 63 insertions(+) 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;