DndContext
The DndContext
class tracks collisions between draggables and droppables and dispatches events during the drag and drop lifecycle.
Example
import { DndContext, Draggable, Droppable, CollisionDetector } from 'dragdoll';
import { PointerSensor } from 'dragdoll';
// Create a DndContext instance.
const dndContext = new DndContext();
// Create a draggable
const draggableElement = document.querySelector('.draggable') as HTMLElement;
const pointerSensor = new PointerSensor(draggableElement);
const draggable = new Draggable([pointerSensor], {
elements: () => [draggableElement],
group: 'groupA',
});
// Create a droppable
const dropZoneElement = document.querySelector('.drop-zone') as HTMLElement;
const droppable = new Droppable(dropZoneElement, {
accept: ['groupA'],
});
// Add draggables and droppables to the context.
dndContext.addDraggables([draggable]);
dndContext.addDroppables([droppable]);
// Listen to events.
dndContext.on('start', ({ draggable, targets }) => {
console.log('Drag Started', draggable, targets);
});
dndContext.on('move', ({ draggable, targets }) => {
console.log('Drag Moved', draggable, targets);
});
dndContext.on('enter', ({ draggable, targets, collisions, contacts, addedContacts }) => {
console.log('Enter', { draggable, targets, collisions, contacts, addedContacts });
});
dndContext.on('leave', ({ draggable, targets, collisions, contacts, removedContacts }) => {
console.log('Leave', { draggable, targets, collisions, contacts, removedContacts });
});
dndContext.on(
'collide',
({
draggable,
targets,
collisions,
contacts,
addedContacts,
removedContacts,
persistedContacts,
}) => {
console.log('Collide', {
draggable,
targets,
collisions,
contacts,
addedContacts,
removedContacts,
persistedContacts,
});
},
);
dndContext.on('end', ({ canceled, draggable, targets, collisions, contacts }) => {
console.log('End', { canceled, draggable, targets, collisions, contacts });
});
Constructor
class DndContext {
constructor(options?: DndContextOptions) {}
}
Parameters
- options
- An optional configuration object with the following properties:
collisionDetector
- A factory function that receives the
DndContext
instance and returns aCollisionDetector
. - If not provided, a default
CollisionDetector
will be created. - See the CollisionDetector docs for subclassing examples.
- A factory function that receives the
- An optional configuration object with the following properties:
Properties
draggables
readonly draggables: ReadonlyMap<DraggableId, Draggable<any>>;
A read-only map containing all registered draggable instances, keyed by their unique ID.
droppables
readonly droppables: ReadonlyMap<DroppableId, Droppable>;
A read-only map containing all registered droppable instances, keyed by their unique ID.
drags
readonly drags: ReadonlyMap<
Draggable<any>,
Readonly<{ isEnded: boolean; data: { [key: string]: any } }>
>;
A read-only map of all currently dragged draggables and their public dnd context state. The key is the draggable instance and the value is the readonly drag data object. The drag data object is read-only so you can't mutate it, but you can mutate the nested data
object and store per-drag state while keeping the reference stable.
// Get all draggables that are currently being dragged.
const draggedDraggables = dndContext.drags.keys();
// Get the drag data object for a specific draggable.
const dragData = dndContext.drags.get(draggable);
Methods
on
// Type
type on = <T extends keyof DndContextEventCallbacks>(
type: T,
listener: DndContextEventCallbacks[T],
listenerId?: EventListenerId,
) => EventListenerId;
// Usage
const id = dndContext.on('drop', ({ draggable, collisions }) => {
console.log('Dropped on', collisions);
});
Adds an event listener to the DndContext for the specified event type.
The method returns a listener id, which can be used to remove this specific listener. By default this will always be a symbol unless manually provided.
off
// Type
type off = <T extends keyof DndContextEventCallbacks>(type: T, listenerId: EventListenerId) => void;
// Usage
dndContext.off('drop', id);
Removes an event listener from the DndContext based on its listener id.
addDraggables
// Type
type addDraggables = (draggables: Draggable<any>[] | Set<Draggable<any>>) => void;
// Usage
dndContext.addDraggables([draggable]);
Registers one or more draggable instances with the context. This adds the draggables to the internal registry, binds to their events, and emits the addDraggables
event.
If any of the draggables are already being dragged, dnd context will start the drag process for them manually.
removeDraggables
// Type
type removeDraggables = (draggables: Draggable<any>[] | Set<Draggable<any>>) => void;
// Usage
dndContext.removeDraggables([draggable]);
Deregisters one or more draggable instances from the context. This removes all bound event listeners, cleans up drag data, and emits the appropriate events.
addDroppables
// Type
type addDroppables = (droppables: Droppable[] | Set<Droppable>) => void;
// Usage
dndContext.addDroppables(droppables);
Registers one or more droppables with the context. This adds them to the internal registry, binds their destroy event, and updates any active draggables with the new droppables as potential targets.
If any active draggable now matches the newly added droppables, a collision check is queued automatically.
removeDroppables
// Type
type removeDroppables = (droppables: Droppable[] | Set<Droppable>) => void;
// Usage
dndContext.removeDroppables(droppables);
Deregisters one or more droppables from the context. This removes them from the internal registry, unbinds their destroy event, and updates affected draggables by removing the droppables from their targets.
If any active draggable had an ongoing collision with the removed droppables, a collision check is queued automatically, which may emit leave
events on the next tick.
updateDroppableClientRects
// Type
type updateDroppableClientRects = () => void;
// Usage
dndContext.updateDroppableClientRects();
Updates the cached client rectangles for all registered droppables. This is automatically called on scroll events and when drag starts, but can be manually triggered if needed.
detectCollisions
// Type
type detectCollisions = (draggable?: Draggable<any>) => void;
// Usage: specific draggable
dndContext.detectCollisions(draggable);
// Usage: all active drags
dndContext.detectCollisions();
Queues collision detection for either a specific draggable, or for all currently active draggables when called without an argument. This compares current and new collisions and emits the appropriate events (enter
, leave
, collide
).
// Removed: getDragData // Use the drags
map instead: dndContext.drags.get(draggable)
clearTargets
// Type
type clearTargets = (draggable?: Draggable<any>) => void;
// Usage: specific draggable
dndContext.clearTargets(draggable);
// Usage: all active draggables
dndContext.clearTargets();
Clears cached target information for the specified draggable (or all active draggables when called without an argument), forcing re-evaluation on the next detection. Call this if the draggable's group changes or if any droppable's accept
criteria changes during a drag. Targets are computed on drag start and cached for performance.
destroy
// Type
type destroy = () => void;
// Usage
dndContext.destroy();
Destroys the DndContext by emitting the destroy
event, unbinding all event listeners, clearing all internal data structures, and destroying the collision detector.
Events
To keep memory allocations to a minimum most of the event data objects are reused/pooled between events. Treat all event data objects as read-only and assume that they are mutated between events.
In practice this means that you should not store the event data objects in your own variables for later use, but rather use them directly in your event or store the specific primitive values that you need. You can also clone the event data objects if you need to store them for later use.
start
type start = (data: {
draggable: Draggable<any>;
targets: ReadonlyMap<DroppableId, Droppable>;
}) => void;
Emitted when a draggable starts dragging.
Event Data:
draggable
- The draggable instance that started dragging.targets
- Map (read-only) of all droppable instances that accept this draggable (based on the droppable'saccept
criteria and the draggable'sgroup
). Keyed bydroppable.id
.
move
type move = (data: {
draggable: Draggable<any>;
targets: ReadonlyMap<DroppableId, Droppable>;
}) => void;
Emitted when a draggable moves during dragging.
Event Data:
draggable
- The draggable instance that is moving.targets
- Map (read-only) of all droppable instances that accept this draggable (based on the droppable'saccept
criteria and the draggable'sgroup
). Keyed bydroppable.id
.
enter
type enter = (data: {
draggable: Draggable<any>;
targets: ReadonlyMap<DroppableId, Droppable>;
collisions: ReadonlyArray<CollisionData>;
contacts: ReadonlySet<Droppable>;
addedContacts: ReadonlySet<Droppable>;
}) => void;
Emitted when a draggable first collides with one or more droppables.
Event Data:
draggable
- The draggable instance that entered collision with droppables.targets
- Map (read-only) of all droppable instances that accept this draggable. Keyed bydroppable.id
.collisions
- Array (read-only) of collision data for all current collisions (includes the newly added ones). Each collision containsdroppableId
.contacts
- Set (read-only) of droppable instances currently in collision.addedContacts
- Set (read-only) of droppable instances that newly entered collision in this cycle.
leave
type leave = (data: {
draggable: Draggable<any>;
targets: ReadonlyMap<DroppableId, Droppable>;
collisions: ReadonlyArray<CollisionData>;
contacts: ReadonlySet<Droppable>;
removedContacts: ReadonlySet<Droppable>;
}) => void;
Emitted when a draggable stops colliding with one or more droppables.
Event Data:
draggable
- The draggable instance that left collision with droppables.targets
- Map (read-only) of all droppable instances that accept this draggable. Keyed bydroppable.id
.collisions
- Array (read-only) of collision data for current collisions (excludes removed ones). Each collision containsdroppableId
.contacts
- Set (read-only) of droppable instances currently in collision.removedContacts
- Set (read-only) of droppables that exited collision in this cycle.
collide
type collide = (data: {
draggable: Draggable<any>;
targets: ReadonlyMap<DroppableId, Droppable>;
collisions: ReadonlyArray<CollisionData>;
contacts: ReadonlySet<Droppable>;
addedContacts: ReadonlySet<Droppable>;
removedContacts: ReadonlySet<Droppable>;
persistedContacts: ReadonlySet<Droppable>;
}) => void;
Emitted each collision cycle if there are any current collisions or removed collisions. Use this event to process all contact changes transactionally in one hook.
Event Data:
draggable
- The draggable instance for this cycle.targets
- Map (read-only) of all droppable instances that accept this draggable. Keyed bydroppable.id
.collisions
- Array (read-only) of collision data for current collisions.contacts
- Set (read-only) of droppable instances currently in collision.addedContacts
- Set (read-only) of droppables newly entered this cycle.removedContacts
- Set (read-only) of droppables left this cycle.persistedContacts
- Set (read-only) of droppables that remained in collision from the previous cycle.
end
type end = (data: {
canceled: boolean;
draggable: Draggable<any>;
targets: ReadonlyMap<DroppableId, Droppable>;
collisions: ReadonlyArray<CollisionData>;
contacts: ReadonlySet<Droppable>;
}) => void;
Emitted when the drag ends, regardless of whether there are active collisions. If canceled
is true
, the drag ended due to cancellation. When ending during an ongoing collision emission, the end
event is still emitted synchronously; cleanup is deferred to a microtask to keep event data intact for the current cycle.
Event Data:
canceled
- Whether the drag ended due to cancellation.draggable
- The draggable instance that ended dragging.targets
- Map (read-only) of all droppable instances that accept this draggable. Keyed bydroppable.id
.collisions
- Array (read-only) of collision data captured at the time of end.contacts
- Set (read-only) of droppable instances currently in collision at the time of end.
addDraggables
type addDraggables = (data: { draggables: ReadonlySet<Draggable<any>> }) => void;
Emitted when one or more draggables are registered with the context.
Event Data:
draggable
- The draggable instance that was added to the context.
removeDraggables
type removeDraggables = (data: { draggables: ReadonlySet<Draggable<any>> }) => void;
Emitted when one or more draggables are deregistered from the context.
Event Data:
draggable
- The draggable instance that was removed from the context.
addDroppables
type addDroppables = (data: { droppables: ReadonlySet<Droppable> }) => void;
Emitted when one or more droppables are registered with the context.
Event Data:
droppables
- Set (read-only) of droppable instances that were added to the context.
removeDroppables
type removeDroppables = (data: { droppables: ReadonlySet<Droppable> }) => void;
Emitted when one or more droppables are deregistered from the context.
Event Data:
droppables
- Set (read-only) of droppable instances that were removed from the context.
destroy
type destroy = () => void;
Emitted when the DndContext is destroyed.