npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

swappable

v1.1.2

Published

Lightweight, high-performance drag and drop grid library with smooth animations and touch support. Create interactive, swappable grids with minimal overhead and maximum responsiveness.

Readme

Swappable

Swappable is a lightweight and performant JavaScript library for creating interactive, draggable, and swappable grid layouts. Built with modern web APIs, it offers smooth animations and a simple API for common use cases.

Features

  • Touch-first Draggable Items: Seamless drag-and-drop functionality on both desktop and mobile devices.
  • Smooth Animations: Utilizes the FLIP (First, Last, Invert, Play) animation technique for fluid layout transitions after a swap or item change.
  • CSS-driven Layout: Leverages CSS Grid for a flexible and responsive layout.
  • Lightweight & Performant: No external dependencies, ensuring a small footprint.
  • Customizable: Easily configure class names, animation durations, and drag behavior.
    codepen
    demo

Installation

$ npm i swappable
import Swappable from "swappable";
import "./node_modules/swappable/dist/swappable.css";

Usage

Basic Setup

Create a container with grid items and initialize Swappable:

<div id="grid" class="swappable-grid">
  <div class="grid-item">Item 1</div>
  <div class="grid-item">Item 2</div>
  <div class="grid-item">Item 3</div>
  <div class="grid-item">Item 4</div>
</div>

<script type="module">
  import Swappable from "swappable";

  const grid = new Swappable("#grid", {
    itemsPerRow: 4,
    dragHandle: ".grid-item",
    layoutDuration: 300,
    swapDuration: 300,
    longPressDelay: 100,
  });
</script>

Options

Customize Swappable with the following options:

| Option | Type | Default | Description | | ---------------- | ---------------- | -------------- | ------------------------------------------------ | | dragEnabled | boolean | true | Enable or disable dragging. | | dragHandle | string \| null | ".grid-item" | Selector for drag handle (null for entire item). | | classNames | ClassNames | See below | Custom class names for grid items and states. | | layoutDuration | number | 300 | Duration of layout animations (ms). | | swapDuration | number | 300 | Duration of swap animations (ms). | | layoutEasing | string | "ease" | Easing function for animations. | | itemsPerRow | number | 4 | Number of items per row in the grid. | | longPressDelay | number | 100 | Delay (ms) for initiating drag on touch devices. |

Default classNames:

{
  "item": "grid-item",
  "drag": "dragging",
  "placeholder": "placeholder",
  "ghost": "ghost",
  "hidden": "hidden"
}

Events

Swappable supports the following events:

| Event | Data Type | Description | | ------------- | ------------------------------------------------------------------------------------------ | ------------------------------------- | | add | { items: HTMLElement[] } | Fired when items are added. | | remove | { items: HTMLElement[] } | Fired when items are removed. | | dragStart | { item: HTMLElement, event: PointerEvent } | Fired when dragging starts. | | dragMove | { item: HTMLElement, event: PointerEvent } | Fired during dragging. | | swap | { fromIndex: number, toIndex: number, fromElement: HTMLElement, toElement: HTMLElement } | Fired when items are swapped. | | sort | { oldIndex: number, newIndex: number, items: SwappableItemData[] } | Fired after sorting items. | | dragEnd | { item: HTMLElement, event: PointerEvent } | Fired when dragging ends. | | layoutStart | void | Fired before layout animation starts. | | layoutEnd | void | Fired after layout animation ends. |

Example of event handling:

grid.on("dragStart", ({ item, event }) => {
  console.log("Dragging started on", item);
});

grid.on("swap", ({ fromIndex, toIndex }) => {
  console.log(`Swapped item from ${fromIndex} to ${toIndex}`);
});

Methods

| Method | Parameters | Description | | --------- | ---------------------------------------- | ----------------------------------- | | on | event: keyof SwappableEvents, callback | Attach an event listener. | | off | event: keyof SwappableEvents | Remove an event listener. | | layout | duration?: number | Recompute and animate layout. | | add | element: HTMLElement, index?: number | Add a new item to the grid. | | remove | target: HTMLElement \| number | Remove an item by element or index. | | select | target: HTMLElement \| number | Select an item by element or index. | | swap | fromIndex: number, toIndex: number | Swap two items by index. | | refresh | None | Refresh the grid layout. | | destroy | None | Destroy the instance and clean up. | | detach | None | Detach event listeners. | | enable | None | Enable dragging. | | disable | None | Disable dragging. |

Example

A complete example with a 4x2 grid and event logging:

<!DOCTYPE html>
<html>
  <head>
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/swappable/dist/swappable.css"
    >
    <style>
      .grid-item {
        background: #f0f0f0;
        padding: 20px;
        border: 1px solid #ccc;
        text-align: center;
      }
    </style>
  </head>
  <body>
    <div id="grid" class="swappable-grid">
      <div class="grid-item">Item 1</div>
      <div class="grid-item">Item 2</div>
      <div class="grid-item">Item 3</div>
      <div class="grid-item">Item 4</div>
      <div class="grid-item">Item 5</div>
      <div class="grid-item">Item 6</div>
      <div class="grid-item">Item 7</div>
      <div class="grid-item">Item 8</div>
    </div>

    <script type="module">
      import Swappable from "https://cdn.jsdelivr.net/npm/swappable/dist/index.umd.js";

      const grid = new Swappable("#grid", {
        itemsPerRow: 4,
        longPressDelay: 200,
      });

      grid.on("swap", ({ fromIndex, toIndex }) => {
        console.log(`Swapped item from ${fromIndex} to ${toIndex}`);
      });

      grid.on("layoutEnd", () => {
        console.log("Layout animation completed");
      });
    </script>
  </body>
</html>

Plugins

import Swappable, { withAutoResponsiveLayout, withHistory } from "swappable";

let swappableInstance = new Swappable("#my-grid");

// with the history plugin
let swappableWithHistory = withHistory(swappableInstance, {
  maxHistorySize: 20,
});

// with the responsive layout plugin
let finalSwappable = withAutoResponsiveLayout(swappableWithHistory, {
  breakpoints: [
    { breakpoint: 0, items: 1 }, // 1 column for smallest screens
    { breakpoint: 576, items: 2 }, // 2 columns for screens >= 576px
    { breakpoint: 768, items: 3 }, // 3 columns for screens >= 768px
    { breakpoint: 992, items: 4 }, // 4 columns for screens >= 992px
  ],
});

Contributing

We welcome contributions! Feel free to open issues or pull requests on the GitHub repository.


License

This project is licensed under the MIT License.