Skip to content

Commit 0f21ac0

Browse files
committed
feat(drag-drop): add released event
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.
1 parent e5efd0a commit 0f21ac0

File tree

5 files changed

+63
-0
lines changed

5 files changed

+63
-0
lines changed

src/cdk/drag-drop/directives/drag.spec.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,7 @@ describe('CdkDrag', () => {
354354
dragElementViaMouse(fixture, fixture.componentInstance.dragElement.nativeElement, 2, 2);
355355

356356
expect(fixture.componentInstance.startedSpy).not.toHaveBeenCalled();
357+
expect(fixture.componentInstance.releasedSpy).not.toHaveBeenCalled();
357358
expect(fixture.componentInstance.endedSpy).not.toHaveBeenCalled();
358359
expect(moveSpy).not.toHaveBeenCalled();
359360
subscription.unsubscribe();
@@ -1169,6 +1170,40 @@ describe('CdkDrag', () => {
11691170
.toBeFalsy('Expected preview to be removed from the DOM if the transition timed out');
11701171
}));
11711172

1173+
it('should emit the released event as soon as the item is released', fakeAsync(() => {
1174+
const fixture = createComponent(DraggableInDropZone);
1175+
fixture.detectChanges();
1176+
const item = fixture.componentInstance.dragItems.toArray()[1];
1177+
const endedSpy = jasmine.createSpy('ended spy');
1178+
const releasedSpy = jasmine.createSpy('released spy');
1179+
const endedSubscription = item.ended.subscribe(endedSpy);
1180+
const releasedSubscription = item.released.subscribe(releasedSpy);
1181+
1182+
startDraggingViaMouse(fixture, item.element.nativeElement);
1183+
1184+
const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement;
1185+
1186+
// Add a duration since the tests won't include one.
1187+
preview.style.transitionDuration = '500ms';
1188+
1189+
// Move somewhere so the draggable doesn't exit immediately.
1190+
dispatchMouseEvent(document, 'mousemove', 50, 50);
1191+
fixture.detectChanges();
1192+
1193+
dispatchMouseEvent(document, 'mouseup');
1194+
fixture.detectChanges();
1195+
1196+
// Expected the released event to fire immediately upon release.
1197+
expect(releasedSpy).toHaveBeenCalled();
1198+
tick(1000);
1199+
1200+
// Expected the ended event to fire once the entire sequence is done.
1201+
expect(endedSpy).toHaveBeenCalled();
1202+
1203+
endedSubscription.unsubscribe();
1204+
releasedSubscription.unsubscribe();
1205+
}));
1206+
11721207
it('should reset immediately when failed drag happens after a successful one', fakeAsync(() => {
11731208
const fixture = createComponent(DraggableInDropZone);
11741209
fixture.detectChanges();
@@ -2459,6 +2494,7 @@ describe('CdkDrag', () => {
24592494
cdkDrag
24602495
[cdkDragBoundary]="boundarySelector"
24612496
(cdkDragStarted)="startedSpy($event)"
2497+
(cdkDragReleased)="releasedSpy($event)"
24622498
(cdkDragEnded)="endedSpy($event)"
24632499
#dragElement
24642500
style="width: 100px; height: 100px; background: red;"></div>
@@ -2470,6 +2506,7 @@ class StandaloneDraggable {
24702506
@ViewChild(CdkDrag) dragInstance: CdkDrag;
24712507
startedSpy = jasmine.createSpy('started spy');
24722508
endedSpy = jasmine.createSpy('ended spy');
2509+
releasedSpy = jasmine.createSpy('released spy');
24732510
boundarySelector: string;
24742511
}
24752512

src/cdk/drag-drop/directives/drag.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
CdkDragExit,
3939
CdkDragMove,
4040
CdkDragStart,
41+
CdkDragRelease,
4142
} from '../drag-events';
4243
import {CdkDragHandle} from './drag-handle';
4344
import {CdkDragPlaceholder} from './drag-placeholder';
@@ -118,6 +119,10 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
118119
/** Emits when the user starts dragging the item. */
119120
@Output('cdkDragStarted') started: EventEmitter<CdkDragStart> = new EventEmitter<CdkDragStart>();
120121

122+
/** Emits when the user has released a drag item, before any animations have started. */
123+
@Output('cdkDragReleased') released: EventEmitter<CdkDragRelease> =
124+
new EventEmitter<CdkDragRelease>();
125+
121126
/** Emits when the user stops dragging an item in the container. */
122127
@Output('cdkDragEnded') ended: EventEmitter<CdkDragEnd> = new EventEmitter<CdkDragEnd>();
123128

@@ -255,6 +260,10 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
255260
this.started.emit({source: this});
256261
});
257262

263+
ref.released.subscribe(() => {
264+
this.released.emit({source: this});
265+
});
266+
258267
ref.ended.subscribe(() => {
259268
this.ended.emit({source: this});
260269
});

src/cdk/drag-drop/drag-events.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ export interface CdkDragStart<T = any> {
1515
source: CdkDrag<T>;
1616
}
1717

18+
/** Event emitted when the user releases an item, before any animations have started. */
19+
export interface CdkDragRelease<T = any> {
20+
/** Draggable that emitted the event. */
21+
source: CdkDrag<T>;
22+
}
23+
1824
/** Event emitted when the user stops dragging a draggable. */
1925
export interface CdkDragEnd<T = any> {
2026
/** Draggable that emitted the event. */

src/cdk/drag-drop/drag-ref.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,9 @@ export class DragRef<T = any> {
207207
/** Emits when the user starts dragging the item. */
208208
started = new Subject<{source: DragRef}>();
209209

210+
/** Emits when the user has released a drag item, before any animations have started. */
211+
released = new Subject<{source: DragRef}>();
212+
210213
/** Emits when the user stops dragging an item in the container. */
211214
ended = new Subject<{source: DragRef}>();
212215

@@ -349,6 +352,7 @@ export class DragRef<T = any> {
349352
this._removeSubscriptions();
350353
this.beforeStarted.complete();
351354
this.started.complete();
355+
this.released.complete();
352356
this.ended.complete();
353357
this.entered.complete();
354358
this.exited.complete();
@@ -506,6 +510,8 @@ export class DragRef<T = any> {
506510
return;
507511
}
508512

513+
this.released.next({source: this});
514+
509515
if (!this.dropContainer) {
510516
// Convert the active transform into a passive one. This means that next time
511517
// the user starts dragging the item, its position will be calculated relatively

tools/public_api_guard/cdk/drag-drop.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export declare class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
2222
exited: EventEmitter<CdkDragExit<any>>;
2323
lockAxis: 'x' | 'y';
2424
moved: Observable<CdkDragMove<T>>;
25+
released: EventEmitter<CdkDragRelease>;
2526
rootElementSelector: string;
2627
started: EventEmitter<CdkDragStart>;
2728
constructor(
@@ -92,6 +93,10 @@ export declare class CdkDragPreview<T = any> {
9293
constructor(templateRef: TemplateRef<T>);
9394
}
9495

96+
export interface CdkDragRelease<T = any> {
97+
source: CdkDrag<T>;
98+
}
99+
95100
export interface CdkDragSortEvent<T = any, I = T> {
96101
container: CdkDropList<T>;
97102
currentIndex: number;

0 commit comments

Comments
 (0)