Skip to content

Commit

Permalink
Stop propagation of click events on draggable node once activation co…
Browse files Browse the repository at this point in the history
…nstraints are met (#377)
  • Loading branch information
clauderic authored Jul 22, 2021
1 parent aede2cc commit 422d083
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 27 deletions.
5 changes: 5 additions & 0 deletions .changeset/prevent-click-propagation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@dnd-kit/core": minor
---

Pointer, Mouse and Touch sensors now stop propagation of click events on the draggable node once activation constraints are met
9 changes: 9 additions & 0 deletions packages/core/src/sensors/events.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
export enum EventName {
Click = 'click',
Keydown = 'keydown',
ContextMenu = 'contextmenu',
Resize = 'resize',
VisibilityChange = 'visibilitychange',
}

export function preventDefault(event: Event) {
event.preventDefault();
}

export function stopPropagation(event: Event) {
event.stopPropagation();
}
31 changes: 18 additions & 13 deletions packages/core/src/sensors/pointer/AbstractPointerSensor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from '../utilities';

import {getOwnerDocument, getWindow} from '../../utilities';
import {EventName} from '../events';
import {EventName, preventDefault, stopPropagation} from '../events';
import {KeyboardCode} from '../keyboard';
import type {SensorInstance, SensorProps, SensorOptions} from '../types';
import type {Coordinates, DistanceMeasurement} from '../../types';
Expand Down Expand Up @@ -61,21 +61,26 @@ export class AbstractPointerSensor implements SensorInstance {
private initialCoordinates: Coordinates;
private timeoutId: NodeJS.Timeout | null = null;
private listeners: Listeners;
private nodeListeners: Listeners;
private documentListeners: Listeners;
private windowListeners: Listeners;
private ownerDocument: Document;

constructor(
private props: AbstractPointerSensorProps,
private events: PointerEventHandlers,
listenerTarget = getEventListenerTarget(props.event.target)
) {
const {event} = props;
const {
event,
activeNode: {node},
} = props;
const {target} = event;

this.props = props;
this.events = events;
this.ownerDocument = getOwnerDocument(target);
this.documentListeners = new Listeners(getOwnerDocument(target));
this.listeners = new Listeners(listenerTarget);
this.nodeListeners = new Listeners(node.current);
this.windowListeners = new Listeners(getWindow(target));
this.initialCoordinates = getEventCoordinates(event);
this.handleStart = this.handleStart.bind(this);
Expand All @@ -99,7 +104,7 @@ export class AbstractPointerSensor implements SensorInstance {
this.windowListeners.add(EventName.Resize, this.handleCancel);
this.windowListeners.add(EventName.VisibilityChange, this.handleCancel);
this.windowListeners.add(EventName.ContextMenu, preventDefault);
this.ownerDocument.addEventListener(EventName.Keydown, this.handleKeydown);
this.documentListeners.add(EventName.Keydown, this.handleKeydown);

if (activationConstraint) {
if (isDistanceConstraint(activationConstraint)) {
Expand All @@ -121,10 +126,10 @@ export class AbstractPointerSensor implements SensorInstance {
private detach() {
this.listeners.removeAll();
this.windowListeners.removeAll();
this.ownerDocument.removeEventListener(
EventName.Keydown,
this.handleKeydown
);
this.documentListeners.removeAll();

// Wait until the next event loop before removing click listeners
setTimeout(this.nodeListeners.removeAll);

if (this.timeoutId !== null) {
clearTimeout(this.timeoutId);
Expand All @@ -138,6 +143,10 @@ export class AbstractPointerSensor implements SensorInstance {

if (initialCoordinates) {
this.activated = true;
// Stop propagation of click events once activation constraints are met
this.nodeListeners.add(EventName.Click, stopPropagation, {
capture: true,
});

onStart(initialCoordinates);
}
Expand Down Expand Up @@ -203,7 +212,3 @@ export class AbstractPointerSensor implements SensorInstance {
}
}
}

function preventDefault(event: Event) {
event.preventDefault();
}
29 changes: 15 additions & 14 deletions packages/core/src/sensors/utilities/Listeners.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
export class Listeners {
private listeners: {
eventName: string;
handler: EventListenerOrEventListenerObject;
}[] = [];
private listeners: [
string,
EventListenerOrEventListenerObject,
AddEventListenerOptions | boolean | undefined
][] = [];

constructor(private target: EventTarget) {}
constructor(private target: EventTarget | null) {}

public add(
public add<T extends Event>(
eventName: string,
handler: EventListenerOrEventListenerObject,
options?: AddEventListenerOptions | false
handler: (event: T) => void,
options?: AddEventListenerOptions | boolean
) {
this.target.addEventListener(eventName, handler, options);
this.listeners.push({eventName, handler});
this.target?.addEventListener(eventName, handler as EventListener, options);
this.listeners.push([eventName, handler as EventListener, options]);
}

public removeAll() {
this.listeners.forEach(({eventName, handler}) =>
this.target.removeEventListener(eventName, handler)
public removeAll = () => {
this.listeners.forEach((listener) =>
this.target?.removeEventListener(...listener)
);
}
};
}

0 comments on commit 422d083

Please sign in to comment.