Enhancement
Droppable
Drag items between dropzones that hold one item each
- Import
@parallelogram-js/core/components/Droppable- Selector
[data-droppable]- Stylesheet
@parallelogram-js/core/styles/draggable.css
Playground
Dropzones
Drag the invoice between the columns.
<div class="droppable-zones" data-droppable>
<div class="droppable-zone" data-droppable-zone="inbox" aria-label="Inbox"><div class="card">Invoice 42</div></div>
<div class="droppable-zone" data-droppable-zone="review" aria-label="In review"></div>
<div class="droppable-zone" data-droppable-zone="paid" aria-label="Paid"></div>
</div>
Use the example to see the events it sends.
About
The dropzones are the elements marked data-droppable-zone inside the container, and the items are their children, or the elements data-droppable-items selects. An item dragged over an empty dropzone moves into it; dragged back over its own, or let go outside every dropzone, it goes home. A dropzone holding an item is occupied, so the markup needs no marker for it.
From the keyboard, Space or Enter picks up the focused item, the arrow keys move it to the previous or next free dropzone, Space or Enter drops it and Escape puts it back. Give each dropzone an aria-label so its name is announced; one without is announced by its data-droppable-zone value. Containers with the same data-droppable-group share their dropzones. droppable:change fires once an item has landed in a different dropzone.
Usage
Register the component and the framework loads it the first time a page contains [data-droppable].
import { Parallelogram } from '@parallelogram-js/core';
const app = Parallelogram.create();
app.components.add('[data-droppable]', () => import('@parallelogram-js/core/components/Droppable'));
app.run();
The framework is optional. Droppable.enhanceAll() mounts the component on every matching element by itself, for a page that wants one enhancement rather than a framework:
import Droppable from '@parallelogram-js/core/components/Droppable';
Droppable.enhanceAll();
It takes a selector and the component's options, and returns the instance it mounted them with. What it leaves out is what the framework adds: loading a component only on the pages that use it, mounting again after a page swap, and ordering with dependsOn.
Its styles come from the package stylesheet:
@import '@parallelogram-js/core/styles/draggable.css';
Acknowledgements
Droppable is built on Shopify Draggable, used under the MIT licence. Copyright (c) 2018-present Shopify.
The drag engine, its Mirror, Scrollable and Focusable plugins, and the Sortable, Swappable and Droppable behaviour are ported from Draggable 1.2. A single Pointer Events sensor replaces its mouse, touch and force-touch sensors, the keyboard sensor is new, and state is written to data attributes rather than classes. Thank you to Shopify and Draggable's contributors for the work this builds on.
Attributes
| Name | Type | Default | Description |
|---|---|---|---|
data-droppablerequired | flag | none | Marks the container |
data-droppable-group | string | none | Containers with the same group share one drag, so items move between them; the first one mounted sets the options For every instance, Droppable.defaults.group. |
data-droppable-items | selector | none | The elements that can be dragged; the dropzones' children when not set For every instance, Droppable.defaults.items. |
data-droppable-handle | selector | none | The part of an item that starts a drag, such as a grip; give it touch-action: none so a touch on it drags rather than scrolls For every instance, Droppable.defaults.handle. |
data-droppable-distance | number | 4 | Pixels a mouse or pen moves before a drag starts, so a click on a link or button inside an item still clicks it For every instance, Droppable.defaults.distance. |
data-droppable-touch-delay | number | 100 | Milliseconds a finger rests on an item before a drag starts; moving sooner scrolls the page instead For every instance, Droppable.defaults.touchDelay. |
data-droppable-axis | both | x | y | both | Which way the copy under the pointer may move For every instance, Droppable.defaults.axis. |
data-droppable-disabled | boolean | false | Stops new drags from starting in this container while set |
data-droppable-picked-up | string | Picked up {item}. | Announced when an item is picked up; {item} is its label For every instance, Droppable.defaults.pickedUp. |
data-droppable-keyboard-hint | string | Arrow keys move it between dropzones, Space drops it, Escape cancels. | Announced after the pick-up message when the keyboard picked the item up For every instance, Droppable.defaults.keyboardHint. |
data-droppable-moved | string | In {zone}. | Announced after each move; {item} and {zone}, the dropzone it is in For every instance, Droppable.defaults.moved. |
data-droppable-dropped | string | Dropped {item}. | Announced when an item is dropped For every instance, Droppable.defaults.dropped. |
data-droppable-cancelled | string | Cancelled. {item} is back where it started. | Announced when a drag is cancelled with Escape For every instance, Droppable.defaults.cancelled. |
data-droppable-stateset by the component | idle | active | over | none | active while a drag in its group is in progress, over while the item is over this container |
data-droppable-item-stateset by the component | placeholder | mirror | placed | none | On the placeholder showing where the item will land, the copy following the pointer, and an item just dropped On item. |
data-droppable-zonerequired | string | none | Marks a dropzone; the value names it in announcements when it has no aria-label On dropzone. |
data-droppable-zone-stateset by the component | empty | available | occupied | none | occupied while it holds an item; available when empty during a drag On dropzone. |
Methods
| Name | Signature | Description |
|---|---|---|
cancel | (element: HTMLElement) => void | Cancel a drag in progress in the element’s group, putting the item back |
Events
| Name | Detail | Description |
|---|---|---|
droppable:start | { element: HTMLElement; item: HTMLElement; placeholder: HTMLElement; reverted: boolean; pointerType: string | null } | An item is being picked up; preventDefault() leaves it where it is DOM event and event bus Cancelable. |
droppable:dropped | { element: HTMLElement; item: HTMLElement; placeholder: HTMLElement; reverted: boolean; pointerType: string | null; dropzone: HTMLElement } | The item is about to move into a dropzone; preventDefault() stops it DOM event and event bus Cancelable. |
droppable:returned | dropzone: HTMLElement | The item is about to go back to the dropzone it started in; preventDefault() keeps it where it is DOM event and event bus Cancelable. |
droppable:stop | { element: HTMLElement; item: HTMLElement; placeholder: HTMLElement; reverted: boolean; pointerType: string | null; dropzone: HTMLElement; initialDropzone: HTMLElement; changed: boolean; items: HTMLElement[] } | The drag has ended and the item is in place, dispatched on each container it changed; items are that container’s items in order DOM event and event bus |
droppable:change | { element: HTMLElement; item: HTMLElement; placeholder: HTMLElement; reverted: boolean; pointerType: string | null; dropzone: HTMLElement; initialDropzone: HTMLElement; changed: boolean; items: HTMLElement[] } | As droppable:stop, only when the item ended up in a different dropzone: the one to save on DOM event and event bus |
CSS custom properties
| Name | Default | Description |
|---|---|---|
--drag-over-bg | var(--color-hover) | Background of a container the item is over |
--drag-placeholder-opacity | 0.4 | Opacity of the placeholder where the item will land |
--drag-placeholder-outline | 2px dashed var(--color-accent) | Outline of the placeholder |
--drag-mirror-shadow | var(--surface-dropdown-shadow) | Shadow of the copy following the pointer |
--drag-mirror-opacity | 0.95 | Opacity of the copy following the pointer |
--drag-mirror-z-index | 2147483000 | Stacking of the copy following the pointer |
--drag-placed-color | rgba(37, 99, 235, 0.15) | Colour a dropped item fades from; none under prefers-reduced-motion |
--drag-placed-duration | 0.6s | How long a dropped item takes to fade back |
--drag-zone-outline | 2px dashed var(--color-border-strong) | Outline of a dropzone the item can go into, while dragging |