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.
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.
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.
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';
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'
},
});
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.
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
onGenerateDragPreview
fires on the drag source (eg adraggable
)onGenerateDragPreview
on the any parent drop targets of the drag sources in bubble order (innermost upwards)onGenerateDragPreview
on monitors
A drag operation has started. You can make changes to the DOM and those changes won't be reflected in your drag preview.
Flow
onDragStart
fires on the drag source (eg adraggable
)onDragStart
on the any parent drop targets of the drag sources in bubble order (innermost upwards)onDragStart
on monitors
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
onDrag
fires on the drag source (eg adraggable
)onDrag
on 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 allonDrag
events from the initial drop targets, you can use a monitor.onDrag
on monitors
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
dropTarget
during a drag - although you are unlikely to run into this one!)
Flow (high level)
onDropTargetChange
fires on the drag source (eg adraggable
)onDropTargetChange
fires on all previous drop targets in bubble order (inside out)onDropTargetChange
fires on any new current drop targets in bubble order
Scenario: [B, A] → [C, A]
onDropTargetChange
fires ondraggable
onDropTargetChange
fires ondropTarget(B)
onDropTargetChange
fires ondropTarget(A)
- now going to fire on newly added drop targets in bubble order
onDropTargetChange
fires ondropTarget(C)
Scenario: [B, A] → [D, C]
onDropTargetChange
fires ondraggable
onDropTargetChange
fires ondropTarget(B)
onDropTargetChange
fires ondropTarget(A)
- now going to fire on newly added drop targets in bubble order
onDropTargetChange
fires ondropTarget(D)
onDropTargetChange
fires ondropTarget(C)
onDropTargetChange
fires on all monitors
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]
onDropTargetChange
fires ondraggable
onDropTargetChange
fires ondropTarget(B)
onDragLeave
fires ondropTarget(B)
→ derived event
onDropTargetChange
fires ondropTarget(A)
onDropTargetChange
fires ondropTarget(C)
onDragEnter
fires ondropTarget(C)
→ derived event
onDropTargetChange
fires on all monitors
Scenario: [B, A] → [D, C]
onDropTargetChange
fires ondraggable
onDropTargetChange
fires ondropTarget(B)
onDragLeave
fires ondropTarget(B)
→ derived event
onDropTargetChange
fires ondropTarget(A)
onDragLeave
fires ondropTarget(A)
→ derived event
- now going to fire on newly added drop targets in bubble order
onDropTargetChange
fires ondropTarget(D)
onDragEnter
fires ondropTarget(D)
→ derived event
onDropTargetChange
fires ondropTarget(C)
onDragEnter
fires ondropTarget(C)
→ derived event
onDropTargetChange
fires on monitors
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
onDrop
fires on the drag source (eg adraggable
)onDrop
fires on alldropTarget
s currently being dragged over (all those inlocation.current.dropTargets
) in bubble event ordering (inner mostdropTarget
upwards)onDrop
fires 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
.
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:
onDropTargetChange
onDropTargetChange
fires on the source (eg adraggable
)onDropTargetChange
fires onB
onDragLeave
fires onB
(derived event)
onDropTargetChange
fires onA
onDragLeave
fires onA
(derived event)
onDropTargetChange
fires on monitors
- Phase 2:
onDrop
onDrop
fires on the source (eg adraggable
)onDrop
fires on monitors- note:
onDrop
does not fire onB
orA
as they are no longer being dragged over