Enhancement
Swappable
Drag one item onto another to exchange their places
- Import
@parallelogram-js/core/components/Swappable- Selector
[data-swappable]- Stylesheet
@parallelogram-js/core/styles/draggable.css
Playground
Tiles
Drag a tile onto another to swap them.
<div class="swappable-grid" data-swappable>
<div class="card">One</div>
<div class="card">Two</div>
<div class="card">Three</div>
<div class="card">Four</div>
</div>
Use the example to see the events it sends.
About
Works as Sortable does, with a mouse, a finger or the keyboard, except that an item dropped on another swaps the two rather than shifting the ones between: the right shape for a grid of tiles or a set of slots, where every other item should stay put.
The container's children are the items, or the elements data-swappable-items selects, and data-swappable-handle limits where a drag can start. From the keyboard, Space or Enter picks up the focused item, the arrow keys swap it with the previous or next one, Space or Enter drops it and Escape undoes the swap. Containers with the same data-swappable-group swap with each other. swappable:change fires once a drop has swapped two items.
Usage
Register the component and the framework loads it the first time a page contains [data-swappable].
import { Parallelogram } from '@parallelogram-js/core';
const app = Parallelogram.create();
app.components.add('[data-swappable]', () => import('@parallelogram-js/core/components/Swappable'));
app.run();
The framework is optional. Swappable.enhanceAll() mounts the component on every matching element by itself, for a page that wants one enhancement rather than a framework:
import Swappable from '@parallelogram-js/core/components/Swappable';
Swappable.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
Swappable 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-swappablerequired | flag | none | Marks the container |
data-swappable-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, Swappable.defaults.group. |
data-swappable-items | selector | none | The elements that can be dragged; the container's children when not set For every instance, Swappable.defaults.items. |
data-swappable-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, Swappable.defaults.handle. |
data-swappable-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, Swappable.defaults.distance. |
data-swappable-touch-delay | number | 100 | Milliseconds a finger rests on an item before a drag starts; moving sooner scrolls the page instead For every instance, Swappable.defaults.touchDelay. |
data-swappable-axis | both | x | y | both | Which way the copy under the pointer may move For every instance, Swappable.defaults.axis. |
data-swappable-disabled | boolean | false | Stops new drags from starting in this container while set |
data-swappable-picked-up | string | Picked up {item}. | Announced when an item is picked up; {item} is its label For every instance, Swappable.defaults.pickedUp. |
data-swappable-keyboard-hint | string | Arrow keys move it, Space drops it, Escape cancels. | Announced after the pick-up message when the keyboard picked the item up For every instance, Swappable.defaults.keyboardHint. |
data-swappable-moved | string | Swapped with {other}. | Announced after each move; {item} and {other}, the item it swapped with For every instance, Swappable.defaults.moved. |
data-swappable-dropped | string | Dropped {item}. | Announced when an item is dropped For every instance, Swappable.defaults.dropped. |
data-swappable-cancelled | string | Cancelled. {item} is back where it started. | Announced when a drag is cancelled with Escape For every instance, Swappable.defaults.cancelled. |
data-swappable-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-swappable-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. |
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 |
|---|---|---|
swappable: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. |
swappable:swap | { element: HTMLElement; item: HTMLElement; placeholder: HTMLElement; reverted: boolean; pointerType: string | null; over: HTMLElement } | The item is about to swap with another; preventDefault() stops it DOM event and event bus Cancelable. |
swappable:swapped | swappedElement: HTMLElement | Two items have swapped places; the drop is still to come DOM event and event bus |
swappable:stop | { element: HTMLElement; item: HTMLElement; placeholder: HTMLElement; reverted: boolean; pointerType: string | null; swappedElement: HTMLElement | null; 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 |
swappable:change | { element: HTMLElement; item: HTMLElement; placeholder: HTMLElement; reverted: boolean; pointerType: string | null; swappedElement: HTMLElement | null; changed: boolean; items: HTMLElement[] } | As swappable:stop, only when two items swapped: 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 |