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 from onDropTargetChange) - a drop target is being entered into.
  • onDragLeave (derived from onDropTargetChange) - 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:

  1. drag source (e.g. draggable) if relevant
  2. drop targets - inner most upwards (bubble ordering) grandChild → child → parent
  3. 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',

Shared event payload

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

  • onGenerateDragPreview fires on the drag source (eg a draggable)
  • onGenerateDragPreview on the any parent drop targets of the drag sources in bubble order (innermost upwards)
  • onGenerateDragPreview on 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

  • onDragStart fires on the drag source (eg a draggable)
  • onDragStart on the any parent drop targets of the drag sources in bubble order (innermost upwards)
  • onDragStart on 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

  • onDrag fires on the drag source (eg a draggable)
  • 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 all onDrag events from the initial drop targets, you can use a monitor.
  • onDrag on 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 dropTarget during a drag - although you are unlikely to run into this one!)

Flow (high level)

  • onDropTargetChange fires on the drag source (eg a draggable)
  • 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 on draggable
  • onDropTargetChange fires on dropTarget(B)
  • onDropTargetChange fires on dropTarget(A)
  • now going to fire on newly added drop targets in bubble order
  • onDropTargetChange fires on dropTarget(C)

Scenario: [B, A] → [D, C]

  • onDropTargetChange fires on draggable
  • onDropTargetChange fires on dropTarget(B)
  • onDropTargetChange fires on dropTarget(A)
  • now going to fire on newly added drop targets in bubble order
  • onDropTargetChange fires on dropTarget(D)
  • onDropTargetChange fires on dropTarget(C)
  • onDropTargetChange fires 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]

  • onDropTargetChange fires on draggable
  • onDropTargetChange fires on dropTarget(B)
    • onDragLeave fires on dropTarget(B) → derived event
  • onDropTargetChange fires on dropTarget(A)
  • onDropTargetChange fires on dropTarget(C)
    • onDragEnter fires on dropTarget(C) → derived event
  • onDropTargetChange fires on all monitors

Scenario: [B, A] → [D, C]

  • onDropTargetChange fires on draggable
  • onDropTargetChange fires on dropTarget(B)
    • onDragLeave fires on dropTarget(B) → derived event
  • onDropTargetChange fires on dropTarget(A)
    • onDragLeave fires on dropTarget(A) → derived event
  • now going to fire on newly added drop targets in bubble order
  • onDropTargetChange fires on dropTarget(D)
    • onDragEnter fires on dropTarget(D) → derived event
  • onDropTargetChange fires on dropTarget(C)
    • onDragEnter fires on dropTarget(C) → derived event
  • onDropTargetChange fires 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

  • onDrop fires on the drag source (eg a draggable)
  • onDrop fires on all dropTargets currently being dragged over (all those in location.current.dropTargets) in bubble event ordering (inner most dropTarget 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.

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: onDropTargetChange
    • onDropTargetChange fires on the source (eg a draggable)
    • onDropTargetChange fires on B
      • onDragLeave fires on B (derived event)
    • onDropTargetChange fires on A
      • onDragLeave fires on A (derived event)
    • onDropTargetChange fires on monitors
  • Phase 2: onDrop
    • onDrop fires on the source (eg a draggable)
    • onDrop fires on monitors
    • note: onDrop does not fire on B or A as they are no longer being dragged over

Was this page helpful?

We use this feedback to improve our documentation.