Skip to content

Commit e42d502

Browse files
authored
fix(cdk/drag-drop): don't stop event propagation unless nested (#21227)
Some time ago we added logic to stop event propagation so that nested drag items don't trigger multiple sequences. Stopping propagation for all events seems to interfere multiple other use cases so these changes add some logic so that we only stop propagation when items are nested. There was something similar in #19334, but I decided not to move forward with it, because it required consumers to know the internals of the `drag-drop` module, whereas this approach can do it automatically. Fixes #19333.
1 parent 74cd45a commit e42d502

File tree

4 files changed

+30
-11
lines changed

4 files changed

+30
-11
lines changed

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -691,7 +691,7 @@ describe('CdkDrag', () => {
691691
expect(styles.touchAction || (styles as any).webkitUserDrag).toBeFalsy();
692692
}));
693693

694-
it('should stop propagation for the drag sequence start event', fakeAsync(() => {
694+
it('should not stop propagation for the drag sequence start event by default', fakeAsync(() => {
695695
const fixture = createComponent(StandaloneDraggable);
696696
fixture.detectChanges();
697697
const dragElement = fixture.componentInstance.dragElement.nativeElement;
@@ -702,7 +702,7 @@ describe('CdkDrag', () => {
702702
dispatchEvent(dragElement, event);
703703
fixture.detectChanges();
704704

705-
expect(event.stopPropagation).toHaveBeenCalled();
705+
expect(event.stopPropagation).not.toHaveBeenCalled();
706706
}));
707707

708708
it('should not throw if destroyed before the first change detection run', fakeAsync(() => {
@@ -5520,7 +5520,6 @@ describe('CdkDrag', () => {
55205520

55215521
describe('with nested drags', () => {
55225522
it('should not move draggable container when dragging child (multitouch)', fakeAsync(() => {
5523-
55245523
const fixture = createComponent(NestedDragsComponent, [], 10);
55255524
fixture.detectChanges();
55265525

@@ -5568,7 +5567,20 @@ describe('CdkDrag', () => {
55685567
.toHaveBeenCalledTimes(containerDragMovedCount);
55695568
expect(fixture.componentInstance.containerDragReleasedSpy)
55705569
.toHaveBeenCalledTimes(containerDragReleasedCount);
5570+
}));
5571+
5572+
it('should stop event propagation when dragging a nested item', fakeAsync(() => {
5573+
const fixture = createComponent(NestedDragsComponent);
5574+
fixture.detectChanges();
5575+
const dragElement = fixture.componentInstance.item.nativeElement;
5576+
5577+
const event = createMouseEvent('mousedown');
5578+
spyOn(event, 'stopPropagation').and.callThrough();
5579+
5580+
dispatchEvent(dragElement, event);
5581+
fixture.detectChanges();
55715582

5583+
expect(event.stopPropagation).toHaveBeenCalled();
55725584
}));
55735585
});
55745586
});

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,13 +192,15 @@ export class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {
192192
@Optional() @Inject(CDK_DRAG_CONFIG) config: DragDropConfig,
193193
@Optional() private _dir: Directionality, dragDrop: DragDrop,
194194
private _changeDetectorRef: ChangeDetectorRef,
195-
@Optional() @Self() @Inject(CDK_DRAG_HANDLE) private _selfHandle?: CdkDragHandle) {
195+
@Optional() @Self() @Inject(CDK_DRAG_HANDLE) private _selfHandle?: CdkDragHandle,
196+
@Optional() @SkipSelf() @Inject(CDK_DRAG_PARENT) parentDrag?: CdkDrag) {
196197
this._dragRef = dragDrop.createDrag(element, {
197198
dragStartThreshold: config && config.dragStartThreshold != null ?
198199
config.dragStartThreshold : 5,
199200
pointerDirectionChangeThreshold: config && config.pointerDirectionChangeThreshold != null ?
200201
config.pointerDirectionChangeThreshold : 5,
201-
zIndex: config?.zIndex
202+
zIndex: config?.zIndex,
203+
parentDragRef: parentDrag?._dragRef
202204
});
203205
this._dragRef.data = this;
204206

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ export interface DragRefConfig {
3636

3737
/** `z-index` for the absolutely-positioned elements that are created by the drag item. */
3838
zIndex?: number;
39+
40+
/** Ref that the current drag item is nested in. */
41+
parentDragRef?: DragRef;
3942
}
4043

4144
/** Options that can be used to bind a passive event listener. */
@@ -783,10 +786,11 @@ export class DragRef<T = any> {
783786
* @param event Browser event object that started the sequence.
784787
*/
785788
private _initializeDragSequence(referenceElement: HTMLElement, event: MouseEvent | TouchEvent) {
786-
// Always stop propagation for the event that initializes
787-
// the dragging sequence, in order to prevent it from potentially
788-
// starting another sequence for a draggable parent somewhere up the DOM tree.
789-
event.stopPropagation();
789+
// Stop propagation if the item is inside another
790+
// draggable so we don't start multiple drag sequences.
791+
if (this._config.parentDragRef) {
792+
event.stopPropagation();
793+
}
790794

791795
const isDragging = this.isDragging();
792796
const isTouchSequence = isTouchEvent(event);

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export declare class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDes
4242
constructor(
4343
element: ElementRef<HTMLElement>,
4444
dropContainer: CdkDropList,
45-
_document: any, _ngZone: NgZone, _viewContainerRef: ViewContainerRef, config: DragDropConfig, _dir: Directionality, dragDrop: DragDrop, _changeDetectorRef: ChangeDetectorRef, _selfHandle?: CdkDragHandle | undefined);
45+
_document: any, _ngZone: NgZone, _viewContainerRef: ViewContainerRef, config: DragDropConfig, _dir: Directionality, dragDrop: DragDrop, _changeDetectorRef: ChangeDetectorRef, _selfHandle?: CdkDragHandle | undefined, parentDrag?: CdkDrag);
4646
getFreeDragPosition(): {
4747
readonly x: number;
4848
readonly y: number;
@@ -55,7 +55,7 @@ export declare class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDes
5555
reset(): void;
5656
static ngAcceptInputType_disabled: BooleanInput;
5757
static ɵdir: i0.ɵɵDirectiveDefWithMeta<CdkDrag<any>, "[cdkDrag]", ["cdkDrag"], { "data": "cdkDragData"; "lockAxis": "cdkDragLockAxis"; "rootElementSelector": "cdkDragRootElement"; "boundaryElement": "cdkDragBoundary"; "dragStartDelay": "cdkDragStartDelay"; "freeDragPosition": "cdkDragFreeDragPosition"; "disabled": "cdkDragDisabled"; "constrainPosition": "cdkDragConstrainPosition"; "previewClass": "cdkDragPreviewClass"; }, { "started": "cdkDragStarted"; "released": "cdkDragReleased"; "ended": "cdkDragEnded"; "entered": "cdkDragEntered"; "exited": "cdkDragExited"; "dropped": "cdkDragDropped"; "moved": "cdkDragMoved"; }, ["_previewTemplate", "_placeholderTemplate", "_handles"]>;
58-
static ɵfac: i0.ɵɵFactoryDef<CdkDrag<any>, [null, { optional: true; skipSelf: true; }, null, null, null, { optional: true; }, { optional: true; }, null, null, { optional: true; self: true; }]>;
58+
static ɵfac: i0.ɵɵFactoryDef<CdkDrag<any>, [null, { optional: true; skipSelf: true; }, null, null, null, { optional: true; }, { optional: true; }, null, null, { optional: true; self: true; }, { optional: true; skipSelf: true; }]>;
5959
}
6060

6161
export interface CdkDragDrop<T, O = T> {
@@ -321,6 +321,7 @@ export declare class DragRef<T = any> {
321321

322322
export interface DragRefConfig {
323323
dragStartThreshold: number;
324+
parentDragRef?: DragRef;
324325
pointerDirectionChangeThreshold: number;
325326
zIndex?: number;
326327
}

0 commit comments

Comments
 (0)