Element adapter
Create, respond to and listen to element drag operationsThe element adapter enables you to create rich drag and drop experiences, such as lists, boards, grids, resizing and so on.
The element adapter contains the essential pieces for element operations:
- draggable: enable dragging of an element.
- dropTargetForElements: marking an element as a valid drop target
- monitorForElements: create a monitor to listen for element drag operation events anywhere.
- types: all types for this adapter.
There are also a number of optional element utilities:
- setCustomNativeDragPreview: use a new element as the native drag preview
- pointerOutsideOfPreview: native drag preview function to place the users pointer outside of the drag preview
- centerUnderPointer: native drag preview function to place the center of the ntaive drag preview under the users pointer
- preserveOffsetOnSource: native drag preview function to match the pointer position on a native drag preview as close as possible to the pointer position on the draggable element
- disableNativeDragPreview: disable the native drag preview (helpful if you want to use your own custom drag preview or have no drag preview)
- scrollJustEnoughIntoView: scroll an element just enough into view so it is visible (helpful when working with default native drag previews)
It is likely that some top level utilities will be helpful for your experience as well
Draggable
A draggable is an HTMLElement that can be dragged around by a user.
A draggable can be located:
- Outside of any drop targets
- Inside any amount of levels of nested drop targets
- So, anywhere!
While a drag operation is occurring:
- You can add new
draggables - You can remount a
draggable. See Reconciliation - You can change the dimensions of the dragging
draggableduring a drag. But keep in mind that won't change the drag preview image, as that is collected only at the start of the drag (inonGenerateDragPreview()) - You can remove the dragging
draggableduring a drag operation. When adraggableis removed it's event functions (egonDrag) will no longer be called. Being able to remove the draggingdraggableis a common requirement for virtual lists
Draggable argument overview
element: HTMLElement: aHTMLElementthat will be draggable (usingHTMLElementas that is the interface that allows the"draggable"attribute)dragHandle?: Element: an optionalElementthat can be used to designate the part of thedraggablethat can exclusively used to drag the wholedraggablecanDrag?: (args: GetFeedbackArgs) => boolean: used to conditionally allow dragging (see below)getInitialData?: (args: GetFeedbackArgs) => Record<string, unknown>: a one time attaching of data to a draggable as a drag is starting. If you want to understand the type of data attached to a drop target elsewhere in your application, see our typing data guide.getInitialDataForExternal?: (args: GetFeedbackArgs) => {[Key in NativeMediaType]?: string;}: used to attach native data (eg"text/plain") to otherwindows or applications.
type GetFeedbackArgs = {
/**
* The user input as a drag is trying to start (the `initial` input)
*/
input: Input;
/**
* The `draggable` element
*/
element: HTMLElement;
/**
* The `dragHandle` element for the `draggable`
*/
dragHandle: Element | null;
};Drag handles
A drag handle is the part of your draggable element that can be dragged in order to drag the
whole draggable. By default, the entire draggable acts as a drag handle. However, you can
optionally mark a child element of a draggable element as the drag handle.
draggable({
element: myElement,
dragHandle: myDragHandleElement,
});You can also implement a drag handle by making a small part of an element the draggable, and
then using
setCustomNativeDragPreview
to generate a preview for the entire entity.
Conditional dragging (canDrag())
A draggable can conditionally allow dragging by using the canDrag() function. Returning true
from canDrag() will allow the drag, and returning false will prevent a drag.
draggable({
element: myElement,
// disable dragging
canDrag: () => false,
});noneDisabling a drag by returning false from canDrag() will prevent any other draggable on the
page from being dragged. @atlaskit/pragmatic-drag-and-drop calls event.preventDefault() under
the hood when canDrag() returns false, which cancels the drag operation. Unfortunately, once a
drag event has started, a draggable element cannot individually opt out of dragging and allow
another element to be dragged.
If you want to disable dragging for a draggable, but still want a parent draggable to be able to
be dragged, then rather than using canDrag() you can conditionally apply draggable()
Here is example of what that could look like using react:
import { useEffect } from 'react';
import { draggable } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
function noop(){};
function Item({isDraggingEnabled}: {isDraggingEnabled: boolean}) {
const ref = useRef();
useEffect({
// when disabled, don't make the element draggable
// this will allow a parent draggable to still be dragged
if(!isDraggingEnabled) {
return noop;
}
return draggable({
element: ref.current,
});
}, [isDraggingEnabled]);
return <div ref={ref}>Draggable item</div>
};Data for external consumers (getInitialDataForExternal())
getInitialDataForExternal() allows you want to attach data to a drag operation that can be used by
other windowss or applications (externally)
draggable({
element: myElement,
getInitialData: () => ({ taskId: task.id }),
getInitialDataForExternal: () => ({
'text/plain': task.description,
'text/uri-list': task.url,
}),
});We also have a helper formatURLsForExternal(urls: string[]): string that allows you to attach
multiple urls for external consumers.
import { formatURLsForExternal } from '@atlaskit/pragmatic-drag-and-drop/element/format-urls-for-external';
draggable({
element: myElement,
getInitialData: () => ({ taskId: task.id }),
getInitialDataForExternal: () => ({
'text/plain': task.description,
'text/uri-list': formatURLsForExternal([task.url, task.anotherUrl]),
}),
});Data attached for external consumers can be accessed by any external consumer that the user drops on. It is important that you don't expose private data.
Attaching external data from a draggable will not trigger the
external adapter in the
window that the draggable started in, but it will trigger the external adapter in other
windows (eg in <iframe>s).
Drop target for elements
A drop target for elements.
The default dropEffect for this type of drop target is "move". This lines up with our
design guides. You can override this
default with getDropEffect().
import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
const cleanup = dropTargetForElements({
element: myElement,
onDragStart: () => console.log('Something started dragging in me!');
});Monitor for elements
A monitor for elements.
import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
const cleanup = monitorForElements({
onDragStart: () => console.log('Dragging an element');
});Types
Generally you won't need to explicitly use our provided types, but we expose a number of TypeScript types if you would like to use them.
All events on draggables, drop targets and monitors, are given the following base payload:
type ElementEventBasePayload = {
location: DragLocationHistory;
source: ElementDragPayload;
};
type ElementDragPayload = {
element: HTMLElement;
dragHandle: Element | null;
data: Record<string, unknown>;
};For all the arguments for all events, you can use our event map type:
type ElementEventPayloadMap = {
onDragStart: ElementEventBasePayload;
// .. the rest of the events
};Draggable feedback functions (canDrag, getInitialData, getInitialDataForExternal) are given
the following:
type ElementGetFeedbackArgs = {
/**
* The user input as a drag is trying to start (the `initial` input)
*/
input: Input;
/**
* The `draggable` element
*/
element: HTMLElement;
/**
* The `dragHandle` element for the `draggable`
*/
dragHandle: Element | null;
};Drop targets are given a little bit more information in each event:
type ElementDropTargetEventBasePayload = ElementEventBasePayload & {
/**
* A convenance pointer to this drop targets values
*/
self: DropTargetRecord;
};For all arguments for all events on drop targets, you can use our event map type:
type ElementDropTargetEventPayloadMap = {
onDragStart: ElementDropTargetEventBasePayload;
// .. the rest of the events
};Drop target feedback functions (canDrop, getData, getDropEffect, getIsSticky) are given the
following:
type ElementDropTargetGetFeedbackArgs = {
/**
* The users _current_ input
*/
input: Input;
/**
* The data associated with the entity being dragged
*/
source: ElementDragPayload;
/**
* This drop target's element
*/
element: Element;
};The monitor feedback function (canMonitor), is given the following:
type ElementMonitorGetFeedbackArgs = {
/**
* The users `initial` drag location
*/
initial: DragLocation;
/**
* The data associated with the entity being dragged
*/
source: ElementDragPayload;
};You can get these type from the element adapter import:
import type {
// Payload for the draggable being dragged
ElementDragPayload,
// Base events
ElementEventBasePayload,
ElementEventPayloadMap,
// Drop target events
ElementDropTargetEventBasePayload,
ElementDropTargetEventPayloadMap,
// Feedback types
ElementGetFeedbackArgs,
ElementDropTargetGetFeedbackArgs,
ElementMonitorGetFeedbackArgs,
} from '@atlaskit/pragmatic-drag-and-drop/element/adapter';There are also some types (eg DropTargetLocation) that can be used for all adapters which can be
found on our top level utilities page
Further reading
- Drag preview documentation → how to control what the user drags around during a drag
- Typing data → how to
improve the types for
"data" - Unregistered elements