Skip to content

Commit 45fae71

Browse files
authored
fix(cdk/drag-drop): only block dragstart event on event targets (#24581)
Currently we block the `dragstart` event on the entire drag element which doesn't account for its disabled state and for any existing handles. Fixes #24533.
1 parent d8b5665 commit 45fae71

File tree

2 files changed

+59
-12
lines changed

2 files changed

+59
-12
lines changed

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,19 @@ describe('CdkDrag', () => {
271271

272272
expect(event.defaultPrevented).toBe(true);
273273
}));
274+
275+
it('should not prevent the default dragstart action when dragging is disabled', fakeAsync(() => {
276+
const fixture = createComponent(StandaloneDraggable);
277+
fixture.detectChanges();
278+
fixture.componentInstance.dragInstance.disabled = true;
279+
const event = dispatchFakeEvent(
280+
fixture.componentInstance.dragElement.nativeElement,
281+
'dragstart',
282+
);
283+
fixture.detectChanges();
284+
285+
expect(event.defaultPrevented).toBe(false);
286+
}));
274287
});
275288

276289
describe('touch dragging', () => {
@@ -1619,6 +1632,27 @@ describe('CdkDrag', () => {
16191632
dragElementViaMouse(fixture, handleChild, 50, 100);
16201633
expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)');
16211634
}));
1635+
1636+
it('should prevent default dragStart on handle, not on entire draggable', fakeAsync(() => {
1637+
const fixture = createComponent(StandaloneDraggableWithHandle);
1638+
fixture.detectChanges();
1639+
1640+
const draggableEvent = dispatchFakeEvent(
1641+
fixture.componentInstance.dragElement.nativeElement,
1642+
'dragstart',
1643+
);
1644+
fixture.detectChanges();
1645+
1646+
const handleEvent = dispatchFakeEvent(
1647+
fixture.componentInstance.handleElement.nativeElement,
1648+
'dragstart',
1649+
true,
1650+
);
1651+
fixture.detectChanges();
1652+
1653+
expect(draggableEvent.defaultPrevented).toBe(false);
1654+
expect(handleEvent.defaultPrevented).toBe(true);
1655+
}));
16221656
});
16231657

16241658
describe('in a drop container', () => {

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

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -448,9 +448,7 @@ export class DragRef<T = any> {
448448
this._ngZone.runOutsideAngular(() => {
449449
element.addEventListener('mousedown', this._pointerDown, activeEventListenerOptions);
450450
element.addEventListener('touchstart', this._pointerDown, passiveEventListenerOptions);
451-
// Usually this isn't necessary since the we prevent the default action in `pointerDown`,
452-
// but some cases like dragging of links can slip through (see #24403).
453-
element.addEventListener('dragstart', preventDefault, activeEventListenerOptions);
451+
element.addEventListener('dragstart', this._nativeDragStart, activeEventListenerOptions);
454452
});
455453
this._initialTransform = undefined;
456454
this._rootElement = element;
@@ -637,9 +635,7 @@ export class DragRef<T = any> {
637635

638636
// Delegate the event based on whether it started from a handle or the element itself.
639637
if (this._handles.length) {
640-
const targetHandle = this._handles.find(handle => {
641-
return event.target && (event.target === handle || handle.contains(event.target as Node));
642-
});
638+
const targetHandle = this._getTargetHandle(event);
643639

644640
if (targetHandle && !this._disabledHandles.has(targetHandle) && !this.disabled) {
645641
this._initializeDragSequence(targetHandle, event);
@@ -1295,7 +1291,7 @@ export class DragRef<T = any> {
12951291
private _removeRootElementListeners(element: HTMLElement) {
12961292
element.removeEventListener('mousedown', this._pointerDown, activeEventListenerOptions);
12971293
element.removeEventListener('touchstart', this._pointerDown, passiveEventListenerOptions);
1298-
element.removeEventListener('dragstart', preventDefault, activeEventListenerOptions);
1294+
element.removeEventListener('dragstart', this._nativeDragStart, activeEventListenerOptions);
12991295
}
13001296

13011297
/**
@@ -1520,6 +1516,28 @@ export class DragRef<T = any> {
15201516

15211517
return this._previewRect;
15221518
}
1519+
1520+
/** Handles a native `dragstart` event. */
1521+
private _nativeDragStart = (event: DragEvent) => {
1522+
if (this._handles.length) {
1523+
const targetHandle = this._getTargetHandle(event);
1524+
1525+
if (targetHandle && !this._disabledHandles.has(targetHandle) && !this.disabled) {
1526+
event.preventDefault();
1527+
}
1528+
} else if (!this.disabled) {
1529+
// Usually this isn't necessary since the we prevent the default action in `pointerDown`,
1530+
// but some cases like dragging of links can slip through (see #24403).
1531+
event.preventDefault();
1532+
}
1533+
};
1534+
1535+
/** Gets a handle that is the target of an event. */
1536+
private _getTargetHandle(event: Event): HTMLElement | undefined {
1537+
return this._handles.find(handle => {
1538+
return event.target && (event.target === handle || handle.contains(event.target as Node));
1539+
});
1540+
}
15231541
}
15241542

15251543
/**
@@ -1572,8 +1590,3 @@ function matchElementSize(target: HTMLElement, sourceRect: ClientRect): void {
15721590
target.style.height = `${sourceRect.height}px`;
15731591
target.style.transform = getTransform(sourceRect.left, sourceRect.top);
15741592
}
1575-
1576-
/** Utility to prevent the default action of an event. */
1577-
function preventDefault(event: Event): void {
1578-
event.preventDefault();
1579-
}

0 commit comments

Comments
 (0)