Skip to content

Commit 220e388

Browse files
crisbetovivian-hu-zz
authored andcommittedDec 12, 2018
fix(drag-drop): prevent text selection while dragging on Safari (#14405)
Fixes being able to select text while dragging an item on Safari. Fixes #14403.
1 parent 53d2b58 commit 220e388

File tree

3 files changed

+30
-12
lines changed

3 files changed

+30
-12
lines changed
 

‎src/cdk/drag-drop/drag-drop-registry.spec.ts

+10
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,16 @@ describe('DragDropRegistry', () => {
190190
expect(dispatchFakeEvent(document, 'wheel').defaultPrevented).toBe(true);
191191
});
192192

193+
it('should not prevent the default `selectstart` actions when nothing is being dragged', () => {
194+
expect(dispatchFakeEvent(document, 'selectstart').defaultPrevented).toBe(false);
195+
});
196+
197+
it('should prevent the default `selectstart` action when an item is being dragged', () => {
198+
registry.startDragging(testComponent.dragItems.first, createMouseEvent('mousedown'));
199+
expect(dispatchFakeEvent(document, 'selectstart').defaultPrevented).toBe(true);
200+
});
201+
202+
193203
});
194204

195205
@Component({

‎src/cdk/drag-drop/drag-drop-registry.ts

+18-12
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@ const activeCapturingEventOptions = normalizePassiveListenerOptions({
1717
capture: true
1818
});
1919

20-
/** Handler for a pointer event callback. */
21-
type PointerEventHandler = (event: TouchEvent | MouseEvent) => void;
22-
2320
/**
2421
* Service that keeps track of all the drag item and drop container
2522
* instances, and manages global event listeners on the `document`.
@@ -42,8 +39,8 @@ export class DragDropRegistry<I, C extends {id: string}> implements OnDestroy {
4239
private _activeDragInstances = new Set<I>();
4340

4441
/** Keeps track of the event listeners that we've bound to the `document`. */
45-
private _globalListeners = new Map<'touchmove' | 'mousemove' | 'touchend' | 'mouseup' | 'wheel', {
46-
handler: PointerEventHandler,
42+
private _globalListeners = new Map<string, {
43+
handler: (event: Event) => void,
4744
options?: AddEventListenerOptions | boolean
4845
}>();
4946

@@ -87,7 +84,7 @@ export class DragDropRegistry<I, C extends {id: string}> implements OnDestroy {
8784
this._ngZone.runOutsideAngular(() => {
8885
// The event handler has to be explicitly active,
8986
// because newer browsers make it passive by default.
90-
this._document.addEventListener('touchmove', this._preventScrollListener,
87+
this._document.addEventListener('touchmove', this._preventDefaultWhileDragging,
9188
activeCapturingEventOptions);
9289
});
9390
}
@@ -104,7 +101,7 @@ export class DragDropRegistry<I, C extends {id: string}> implements OnDestroy {
104101
this.stopDragging(drag);
105102

106103
if (this._dragInstances.size === 0) {
107-
this._document.removeEventListener('touchmove', this._preventScrollListener,
104+
this._document.removeEventListener('touchmove', this._preventDefaultWhileDragging,
108105
activeCapturingEventOptions);
109106
}
110107
}
@@ -127,19 +124,27 @@ export class DragDropRegistry<I, C extends {id: string}> implements OnDestroy {
127124
// use `preventDefault` to prevent the page from scrolling while the user is dragging.
128125
this._globalListeners
129126
.set(moveEvent, {
130-
handler: e => this.pointerMove.next(e),
127+
handler: (e: Event) => this.pointerMove.next(e as TouchEvent | MouseEvent),
131128
options: activeCapturingEventOptions
132129
})
133130
.set(upEvent, {
134-
handler: e => this.pointerUp.next(e),
131+
handler: (e: Event) => this.pointerUp.next(e as TouchEvent | MouseEvent),
135132
options: true
133+
})
134+
// Preventing the default action on `mousemove` isn't enough to disable text selection
135+
// on Safari so we need to prevent the selection event as well. Alternatively this can
136+
// be done by setting `user-select: none` on the `body`, however it has causes a style
137+
// recalculation which can be expensive on pages with a lot of elements.
138+
.set('selectstart', {
139+
handler: this._preventDefaultWhileDragging,
140+
options: activeCapturingEventOptions
136141
});
137142

138143
// TODO(crisbeto): prevent mouse wheel scrolling while
139144
// dragging until we've set up proper scroll handling.
140145
if (!isTouchEvent) {
141146
this._globalListeners.set('wheel', {
142-
handler: this._preventScrollListener,
147+
handler: this._preventDefaultWhileDragging,
143148
options: activeCapturingEventOptions
144149
});
145150
}
@@ -184,9 +189,10 @@ export class DragDropRegistry<I, C extends {id: string}> implements OnDestroy {
184189
}
185190

186191
/**
187-
* Listener used to prevent `touchmove` and `wheel` events while the element is being dragged.
192+
* Event listener that will prevent the default browser action while the user is dragging.
193+
* @param event Event whose default action should be prevented.
188194
*/
189-
private _preventScrollListener = (event: Event) => {
195+
private _preventDefaultWhileDragging = (event: Event) => {
190196
if (this._activeDragInstances.size) {
191197
event.preventDefault();
192198
}

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

+2
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,8 @@ export class DragRef<T = any> {
720720
zIndex: '1000'
721721
});
722722

723+
toggleNativeDragInteractions(preview, false);
724+
723725
preview.classList.add('cdk-drag-preview');
724726
preview.setAttribute('dir', this._dir ? this._dir.value : 'ltr');
725727

0 commit comments

Comments
 (0)
Please sign in to comment.