Events
Events allow you to respond to a drag and drop operation.Events fire based on specific user actions and the part of Pragmatic drag and drop involved in the
interaction (such as a draggable and drop target).
To listen for all events for a specific type of draggable item (element or file), you can use a monitor.
Available events
onGenerateDragPreview- Drag is about to start. Make changes you want to see in the drag preview.onDragStart- Something has started dragging. You can make visual changes and they will not be captured in the drag preview.onDropTargetChange- The drop target hierarchy has changed in some way.onDrag- (throttled) - High fidelity updates throughout a drag with the latest details about the drag and user input.onDrop- Drag operation completed.
Derived events on drop targets
onDragEnter(derived fromonDropTargetChange) - a drop target is being entered into.onDragLeave(derived fromonDropTargetChange) - a drop target is being exited from.
Derived drop target events are not their own events in the system; rather they are derived from
other events. If a drop target wants to know about child drop target changes, then please use the
onDropTargetChange event.
Event ordering
All events flow through the system in the same way:
- drag source (e.g.
draggable) if relevant - drop targets - inner most upwards (bubble ordering)
grandChild → child → parent - monitors (in the order that they were created)
const unbind = combine(
monitorForElements({
onDragStart: () => 'monitor:start',
}),
draggable({
element: myElement,
onDragStart: () => 'draggable:start',
}),
dropTargetForElements({
element: myElement,
onDragStart: () => 'dropTarget:start',
}),
);
// After `onDragStart`
// console.log:
// - 'draggable:start'
// - 'dropTarget:start',
// - 'monitor:start',All events are provided the following base data. Particular event types add additional data.
Additionally, event listeners (e.g. dropTargetForExternal({ onDragStart })) can be given
additional localized data for convenience.
import { Input, DragLocation, DragLocationHistory } from '@atlaskit/pragmatic-drag-and-drop/types';export type Input = {
// user input
altKey: boolean;
button: number;
buttons: number;
ctrlKey: boolean;
metaKey: boolean;
shiftKey: boolean;
// coordinates
clientX: number;
clientY: number;
pageX: number;
pageY: number;
};
export type DragLocation = {
/**
* A users input at a point in time
*/
input: Input;
/**
* A _bubble_ ordered (innermost upwards) list of active drop targets
*
* @example
* [grandChildRecord, childRecord, parentRecord]
*
*/
dropTargets: DropTargetRecord[];
};
export type DragLocationHistory = {
/**
* Where the drag operation started
*/
initial: DragLocation;
/**
* Where the user currently is
*/
current: DragLocation;
/**
* Where the user was previously.
* `previous` points to what `current` was in the last dispatched event
*
* `previous` is particularly useful for `onDropTargetChange`
* (and the derived `onDragEnter` and `onDragLeave`)
* as you can know what the delta of the change
*
* Exception: `onGenerateDragPreview` and `onDragStart` will have the
* same `current` and `previous` values. This is done so that the data
* received in `onDragStart` feels logical
* (`location.previous` should be `[]` in `onDragStart`)
*/
previous: Pick<DragLocation, 'dropTargets'>;
};
// Each drag type (eg files) has their own base payload type
// This allows different types of drags to have different source data
type BaseEventPayload = {
location: DragLocationHistory;
// source is different for different drag types
source: {
element: Element;
dragHandle: Element | null;
data: Record<string, unknown>;
};
};
import { ElementBaseEventPayload } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';Convenience data for dropTargets (of all types)
All drop targets add a self property (type: DropTargetRecord) that contains a convenience
object with information about the drop target that the event is firing on.
You could get all this information from the outer scope (or from location.current.dropTargets),
but having it available inline is convenient.
type AllowedDropTargetDropEffect = Exclude<DataTransfer['dropEffect'], 'none'>;
type DropTargetRecord = {
element: Element;
// data provided using .getData()
data: Record<string | symbol, unknown>;
// dropEffect provided by using getDropEffect()
dropEffect: AllowedDropTargetDropEffect;
};dropTargetForExternal({
element: myElement,
getData: () => ({ name: 'Alex' }),
getDropEffect: () => 'move',
onDragStart: ({ self }) => {
console.log(self.element); // myElement
console.log(self.data); // {name: 'Alex'}
console.log(self.dropEffect); // 'move'
},
});External drag sources
Sometimes a user is dragging something that started outside the current browser window, for example a local file.
When a user first drags an external entity into the webpage, @atlaskit/pragmatic-drag-and-drop
considers this the start of the drag operation. The drag operation will finish if the user drags
the external entity out of the browser.
By following this model, all entity types follow the same event lifecycle.
Event: onGenerateDragPreview
You can make changes to the DOM that you want to be reflected in your drag preview.
BaseEventPayload & {
// Allows people to use the native set drag image function if they want
// https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/setDragImage
// Although, we recommend using alternative techniques (see element source docs)
nativeSetDragImage: DataTransfer['setDragImage'] | null;
};Flow
onGenerateDragPreviewfires on the drag source (eg adraggable)onGenerateDragPreviewon the any parent drop targets of the drag sources in bubble order (innermost upwards)onGenerateDragPreviewon monitors
Event: onDragStart
A drag operation has started. You can make changes to the DOM and those changes won't be reflected in your drag preview.
Flow
onDragStartfires on the drag source (eg adraggable)onDragStarton the any parent drop targets of the drag sources in bubble order (innermost upwards)onDragStarton monitors
Event: onDrag
A throttled update of where the the user is currently dragging. Useful if you want to create a high
fidelity experience such as drawing. @atlaskit/pragmatic-drag-and-drop throttles native drag
events using requestAnimationFrame so onDrag events will fire around 60 times a second.
Flow
onDragfires on the drag source (eg adraggable)onDragon the current parent drop targets of the drag sources in bubble order (innermost outwards). The current drop targets are the drop targets that the user is currently dragging over. If you want to listen for allonDragevents from the initial drop targets, you can use a monitor.onDragon monitors
Event: onDropTargetChange
The onDropTargetChange event fires when the dropTarget hierarchy changes during a drag.
In a single native drag event:
- drop targets can be exited
- drop targets can be entered into
- the hierarchy of drop targets can change (such as by dynamically adding a new parent
dropTargetduring a drag - although you are unlikely to run into this one!)
Flow (high level)
onDropTargetChangefires on the drag source (eg adraggable)onDropTargetChangefires on all previous drop targets in bubble order (inside out)onDropTargetChangefires on any new current drop targets in bubble order
Scenario: [B, A] → [C, A]
onDropTargetChangefires ondraggableonDropTargetChangefires ondropTarget(B)onDropTargetChangefires ondropTarget(A)- now going to fire on newly added drop targets in bubble order
onDropTargetChangefires ondropTarget(C)
Scenario: [B, A] → [D, C]
onDropTargetChangefires ondraggableonDropTargetChangefires ondropTarget(B)onDropTargetChangefires ondropTarget(A)- now going to fire on newly added drop targets in bubble order
onDropTargetChangefires ondropTarget(D)onDropTargetChangefires ondropTarget(C)onDropTargetChangefires on all monitors
Derived events: onDragEnter and onDragLeave
The onDropTargetChange event allows you to know a lot of information about drop target hierarchy
changes. One of the most common use cases for a dropTarget is to know when it starts being dragged
over, and when it is no longer being dragged over. To support this common use case, the
onDragEnter and onDragLeave callbacks are executed on a dropTarget as required when the
dropTarget receives a onDropTargetChange event.
The onDragEnter and onDragLeave events are not strictly their own events, but travel along
side the bubbling of onDropTargetChange.
Scenario: [B, A] → [C, A]
onDropTargetChangefires ondraggableonDropTargetChangefires ondropTarget(B)onDragLeavefires ondropTarget(B)→ derived event
onDropTargetChangefires ondropTarget(A)onDropTargetChangefires ondropTarget(C)onDragEnterfires ondropTarget(C)→ derived event
onDropTargetChangefires on all monitors
Scenario: [B, A] → [D, C]
onDropTargetChangefires ondraggableonDropTargetChangefires ondropTarget(B)onDragLeavefires ondropTarget(B)→ derived event
onDropTargetChangefires ondropTarget(A)onDragLeavefires ondropTarget(A)→ derived event
- now going to fire on newly added drop targets in bubble order
onDropTargetChangefires ondropTarget(D)onDragEnterfires ondropTarget(D)→ derived event
onDropTargetChangefires ondropTarget(C)onDragEnterfires ondropTarget(C)→ derived event
onDropTargetChangefires on monitors
Event: onDrop
The onDrop event occurs when a user has finished a drag and drop operation. The onDrop event
will fire when the drag operation finishes, regardless of how the drag operation finished (e.g. due
to an explicit drop, the drag being canceled, recovering from an error and so on).
Using the information that the web platform provides, we cannot distinguish between dropping on no
drop targets, an explicit cancel, or dropping externally, so we do not publish any information about
how the drag ended, only that it ended. The location.current property will accurately contain
the final drop targets.
Flow
onDropfires on the drag source (eg adraggable)onDropfires on alldropTargets currently being dragged over (all those inlocation.current.dropTargets) in bubble event ordering (inner mostdropTargetupwards)onDropfires on monitors
For drags that start within the current window
(element adapter,
text selection adapter),
the drag finishes when the drag operation finish, which might be while the user is outside of the
current window. For drags that start outside of the current windows
(external adapter), a drag
finishes when the user leaves the window.
Cancelling
If a user is currently over drop target(s) when a drag is cancelled, then the exiting current drop
targets(s) are cleared with an onDropTargetChange event. This matches how cancelling works with
the browser's drag and drop API.
Flow
- User drags over two drop targets:
[B, A](bubble ordered) - User cancels drag
- Phase 1:
onDropTargetChangeonDropTargetChangefires on the source (eg adraggable)onDropTargetChangefires onBonDragLeavefires onB(derived event)
onDropTargetChangefires onAonDragLeavefires onA(derived event)
onDropTargetChangefires on monitors
- Phase 2:
onDroponDropfires on the source (eg adraggable)onDropfires on monitors- note:
onDropdoes not fire onBorAas they are no longer being dragged over