Skip to content

Commit e4c64dd

Browse files
committed
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. (cherry picked from commit 45fae71)
1 parent d1476ec commit e4c64dd

File tree

2 files changed

+59
-12
lines changed

2 files changed

+59
-12
lines changed

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

+34
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', () => {
@@ -1613,6 +1626,27 @@ describe('CdkDrag', () => {
16131626
dragElementViaMouse(fixture, handleChild, 50, 100);
16141627
expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)');
16151628
}));
1629+
1630+
it('should prevent default dragStart on handle, not on entire draggable', fakeAsync(() => {
1631+
const fixture = createComponent(StandaloneDraggableWithHandle);
1632+
fixture.detectChanges();
1633+
1634+
const draggableEvent = dispatchFakeEvent(
1635+
fixture.componentInstance.dragElement.nativeElement,
1636+
'dragstart',
1637+
);
1638+
fixture.detectChanges();
1639+
1640+
const handleEvent = dispatchFakeEvent(
1641+
fixture.componentInstance.handleElement.nativeElement,
1642+
'dragstart',
1643+
true,
1644+
);
1645+
fixture.detectChanges();
1646+
1647+
expect(draggableEvent.defaultPrevented).toBe(false);
1648+
expect(handleEvent.defaultPrevented).toBe(true);
1649+
}));
16161650
});
16171651

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

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

+25-12
Original file line numberDiff line numberDiff line change
@@ -442,9 +442,7 @@ export class DragRef<T = any> {
442442
this._ngZone.runOutsideAngular(() => {
443443
element.addEventListener('mousedown', this._pointerDown, activeEventListenerOptions);
444444
element.addEventListener('touchstart', this._pointerDown, passiveEventListenerOptions);
445-
// Usually this isn't necessary since the we prevent the default action in `pointerDown`,
446-
// but some cases like dragging of links can slip through (see #24403).
447-
element.addEventListener('dragstart', preventDefault, activeEventListenerOptions);
445+
element.addEventListener('dragstart', this._nativeDragStart, activeEventListenerOptions);
448446
});
449447
this._initialTransform = undefined;
450448
this._rootElement = element;
@@ -631,9 +629,7 @@ export class DragRef<T = any> {
631629

632630
// Delegate the event based on whether it started from a handle or the element itself.
633631
if (this._handles.length) {
634-
const targetHandle = this._handles.find(handle => {
635-
return event.target && (event.target === handle || handle.contains(event.target as Node));
636-
});
632+
const targetHandle = this._getTargetHandle(event);
637633

638634
if (targetHandle && !this._disabledHandles.has(targetHandle) && !this.disabled) {
639635
this._initializeDragSequence(targetHandle, event);
@@ -1287,7 +1283,7 @@ export class DragRef<T = any> {
12871283
private _removeRootElementListeners(element: HTMLElement) {
12881284
element.removeEventListener('mousedown', this._pointerDown, activeEventListenerOptions);
12891285
element.removeEventListener('touchstart', this._pointerDown, passiveEventListenerOptions);
1290-
element.removeEventListener('dragstart', preventDefault, activeEventListenerOptions);
1286+
element.removeEventListener('dragstart', this._nativeDragStart, activeEventListenerOptions);
12911287
}
12921288

12931289
/**
@@ -1512,6 +1508,28 @@ export class DragRef<T = any> {
15121508

15131509
return this._previewRect;
15141510
}
1511+
1512+
/** Handles a native `dragstart` event. */
1513+
private _nativeDragStart = (event: DragEvent) => {
1514+
if (this._handles.length) {
1515+
const targetHandle = this._getTargetHandle(event);
1516+
1517+
if (targetHandle && !this._disabledHandles.has(targetHandle) && !this.disabled) {
1518+
event.preventDefault();
1519+
}
1520+
} else if (!this.disabled) {
1521+
// Usually this isn't necessary since the we prevent the default action in `pointerDown`,
1522+
// but some cases like dragging of links can slip through (see #24403).
1523+
event.preventDefault();
1524+
}
1525+
};
1526+
1527+
/** Gets a handle that is the target of an event. */
1528+
private _getTargetHandle(event: Event): HTMLElement | undefined {
1529+
return this._handles.find(handle => {
1530+
return event.target && (event.target === handle || handle.contains(event.target as Node));
1531+
});
1532+
}
15151533
}
15161534

15171535
/**
@@ -1564,8 +1582,3 @@ function matchElementSize(target: HTMLElement, sourceRect: ClientRect): void {
15641582
target.style.height = `${sourceRect.height}px`;
15651583
target.style.transform = getTransform(sourceRect.left, sourceRect.top);
15661584
}
1567-
1568-
/** Utility to prevent the default action of an event. */
1569-
function preventDefault(event: Event): void {
1570-
event.preventDefault();
1571-
}

0 commit comments

Comments
 (0)