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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@get-set/gs-sortable

v1.1.1

Published

Get-Set Sortable

Downloads

706

Readme

GSSortable

A dependency-free, drag-and-drop sortable plugin available in two flavours from one codebase:

  • Native / vanilla JS — a window.GSSortable(element, params) factory.
  • React — a <GSSortable /> component.

Both share the exact same engine (actions/, constants/, helpers/, types/), so behaviour is identical across the two. CSS is injected automatically in React (no stylesheet import needed) and shipped as a plain stylesheet for native use.

Features

  • Three layouts: column (vertical list), row (horizontal strip), grid (N-column grid via count)
  • Smooth pointer-driven dragging with animated sibling reflow and drop-settle
  • Cross-list drag — let one instance accept items dropped from named other instances (acceptFrom)
  • Drag handles — use your own (handler selector) or auto-render a styled one (handleStyle: bar / dots / grip / lines)
  • Placeholder / ghost variants while dragging (ghostStyle: solid / dashed / outline / glow)
  • Optional lift effect (drop-shadow on the grabbed item) and drop-zone highlight on the active container
  • Colour theme (light / dark / auto) and opt-in polished cardStyle
  • Tunable reflow animation duration + easing curve, all reduced-motion aware
  • Drag a clone instead of the original (takeClone), constrain to bounds (allowOutOfBox)
  • Responsive per-breakpoint overrides (responsive)
  • Auto re-layout on item resize (built-in ResizeObserver) and on scroll
  • afterSort callback with the live item list; imperative calculate / refresh / destroy
  • React-only gsx prop for scoped inline (CSS-in-JS) styles

Compatibility

| Target | Requirement | |---|---| | React (the <GSSortable> component) | React 16.8+ (Hooks are required), and 17 / 18 / 19. React is an optional peer dependency — you only need it for the component. | | Native / vanilla (window.GSSortable) | No framework. Any modern evergreen browser. | | TypeScript | First-class — type declarations (.d.ts) ship in the package. | | SSR / Next.js | Safe to import server-side (no DOM access at module load). The sortable itself is browser-only, so render it inside a Client Component ('use client'). |

The peer range is ^16.8.0 || ^17.0 || ^18.0 || ^19.0, with react/react-dom marked optional so the native build has zero framework dependencies.

Installation

npm i @get-set/gs-sortable

The package exposes both builds via conditional exports:

| Condition | Entry | |---|---| | import (React / bundlers) | dist/components/GSSortable.js (types: dist/components/GSSortable.d.ts) | | require (native bundle) | dist-js/bundle.js |

Project layout

GSSortable.ts             # native entry  (webpack -> dist-js/bundle.js, window global)
components/GSSortable.tsx  # React component (tsc -> dist/, npm entry)
actions/                  # shared engine (init, calculate, drag, sort-end, motion, …)
constants/                # shared constants + default params
helpers/uihelpers.ts      # shared helpers (GUID, CSS inject/remove, scss->css)
types/                    # Params / Ref / Window augmentation
components/styles/         # SCSS + compiled CSS + CSS-as-TS (runtime injection for React)
styles/                   # SCSS + compiled CSS (for <link> use by the native build)

Build

npm install
npm run build          # builds both targets
npm run build:js       # native bundle -> dist-js/bundle.js
npm run build:react    # React + types  -> dist/

Tests

Unit tests use Vitest + jsdom:

npm test         # run once
npm run test:watch

Usage — Native JS

Include the stylesheet and the bundle, then construct the sortable over a container whose direct children are the sortable items:

<link rel="stylesheet" href="styles/GSSortable.css" />

<div class="list">
  <div>Item 1</div>
  <div>Item 2</div>
  <div>Item 3</div>
</div>

<script src="dist-js/bundle.js"></script>
<script>
  new GSSortable(document.querySelector('.list'), {
    type: 'column',
    gap: '10px',
    afterSort: (items) => console.log('sorted', items),
  });
</script>

window.GSSortable is a constructor-style factory: new GSSortable(element, params). The first argument is a real HTMLElement (not a selector). Each instance is registered under its reference key (auto-generated if omitted).

The native bundle also ships two convenience adapters (injected as a banner into dist-js/bundle.js):

HTMLElement.prototype adapter — call .GSSortable(params) directly on any element:

document.querySelector('.list').GSSortable({
  type: 'column',
  gap: '10px',
});

jQuery adapter — registered as $.fn.GSSortable when jQuery is present on the page; it constructs one instance per matched element:

$('.list').GSSortable({
  type: 'grid',
  count: 3,
  gap: '12px',
});

All three forms (new GSSortable(el, params), el.GSSortable(params), $(sel).GSSortable(params)) create the same instance and register it in window.GSSortableConfigue.

Instance registry & methods

Look up a registered instance by its reference key via the global registry, then call its methods:

new GSSortable(document.querySelector('.list'), { reference: 'tasks' });

const instance = window.GSSortableConfigue.instance('tasks');
instance.calculate(); // re-measure + re-lay-out the items
instance.refresh();   // tear down + re-initialise (re-binds drag handlers)
instance.destroy();   // remove all GSSortable styling/behaviour

window.GSSortableConfigue is the shared registry:

| Member | Description | |---|---| | references | Array of { key, ref } for every live instance. | | instance(key) | Returns the Ref for a given reference key (or undefined). |

Usage — React

CSS is injected automatically when the component mounts — no stylesheet import required. Each direct child becomes a sortable item.

import GSSortable from '@get-set/gs-sortable';

export default function Tasks() {
  return (
    <GSSortable type="column" gap="10px" afterSort={(items) => console.log(items)}>
      <div>Item 1</div>
      <div>Item 2</div>
      <div>Item 3</div>
    </GSSortable>
  );
}

Next.js (App Router)

The sortable is browser-only, so use it from a Client Component:

'use client';
import GSSortable from '@get-set/gs-sortable';

export default function Tasks() {
  return (
    <GSSortable type="grid" count={3} cardStyle theme="auto" handleStyle="grip">
      <div>One</div>
      <div>Two</div>
      <div>Three</div>
    </GSSortable>
  );
}

Cross-container drag (acceptFrom)

Give each list a reference, then let a target list accept items dragged out of named sources:

<GSSortable reference="list-a" type="column">
  <div>A1</div>
  <div>A2</div>
</GSSortable>

<GSSortable reference="list-b" type="column" acceptFrom={['list-a']}>
  <div>B1</div>
  <div>B2</div>
</GSSortable>

acceptFrom lists the reference keys of other instances whose items may be dropped into this one.

The gsx prop (React only)

gsx is a scoped CSS-in-JS map injected for this instance only (matched via a data-key attribute). It supports nested selectors:

<GSSortable
  type="grid"
  count={2}
  gsx={{
    backgroundColor: '#0b1220',
    padding: '12px',
    ':hover': { outline: '1px solid #4f7cff' },
  }}
>
  <div>A</div>
  <div>B</div>
</GSSortable>

Ref / imperative API

Attach a ref to call the instance methods imperatively. The ref is typed as GSSortableHandle:

import { useRef } from 'react';
import GSSortable, { GSSortableHandle } from '@get-set/gs-sortable';

function Demo() {
  const sortable = useRef<GSSortableHandle>(null);

  return (
    <>
      <button onClick={() => sortable.current?.calculate()}>Re-layout</button>
      <button onClick={() => sortable.current?.refresh()}>Refresh</button>
      <button onClick={() => sortable.current?.destroy()}>Destroy</button>

      <GSSortable ref={sortable} type="grid" count={3}>
        <div>Item 1</div>
        <div>Item 2</div>
        <div>Item 3</div>
      </GSSortable>
    </>
  );
}

| Method | Signature | Description | |---|---|---| | calculate | () => void | Re-measure and re-lay-out the items (e.g. after the container resizes). | | refresh | () => void | Tear down and re-initialise the instance (re-binds drag handlers, re-lays-out). | | destroy | () => void | Remove all GSSortable styling/behaviour from the grid. |

All methods are no-ops until the instance has mounted. (In React, item changes are handled automatically — driving items via children re-inits on add/remove.)

Options

Every option below is also a React prop (plus the React-only gsx and children).

| Option | Type | Default | Description | |---|---|---|---| | reference | string | auto GUID | Unique key. Used by the registry and by acceptFrom. Auto-generated if omitted. | | type | 'column' \| 'row' \| 'grid' | 'column' | Layout direction. See the catalog below. | | count | number | 3 | Number of columns (grid type only). | | gap | string | '' | CSS gap between items, e.g. '10px' or '10px 20px' (row col). | | handler | string | '' | CSS selector for a drag handle inside each item. When set, only the handle starts a drag (and auto handleStyle rendering is skipped). | | cancel | string | undefined | CSS selector; a pointer-down on a matching element (or its descendants) does not start a drag — so buttons/inputs/links stay interactive inside a whole-item (or handle) draggable. Works for mouse + touch. Example: 'button, input, textarea, select, a, [contenteditable], [data-no-drag]'. | | width | string | 'auto' | Item width override hint. (Effective item width is normally computed from the layout; column/grid size items to fit the container.) | | className | string | '' | Extra class added to the container element (React). | | takeClone | boolean | false | Drag a clone; the original stays in place (no placeholder left behind). | | allowOutOfBox | boolean | true | Allow dragging the item beyond the container bounds. false constrains the drag. | | acceptFrom | string[] | [] | reference keys of other instances whose items may be dropped into this one. | | responsive | ResponsiveOption[] | [] | Per-breakpoint param overrides — { windowSize, params }[] (sorted automatically). | | animation | number | 400 | Sibling-reflow / drop-settle duration (ms). Respects reduced-motion (collapses to 0). | | easing | string | cubic-bezier(0.22, 1, 0.36, 1) | CSS easing curve for reflow / drop-settle transitions. | | theme | 'light' \| 'dark' \| 'auto' | 'light' | Colour theme for the container + default card UI. | | cardStyle | boolean | false | Apply the polished default item-card styling (padding, radius, surface, subtle border + shadow). | | handleStyle | 'none' \| 'bar' \| 'dots' \| 'grip' \| 'lines' | 'none' | Auto-render a styled drag handle on each item when no handler selector is set. none keeps whole-item dragging. | | ghostStyle | 'solid' \| 'dashed' \| 'outline' \| 'glow' | 'solid' | Visual variant of the placeholder / ghost left behind while dragging. | | lift | boolean | true | Lift the grabbed item with a drop-shadow + slight scale/tilt for tactile feedback. Respects reduced-motion. | | dropZoneHighlight | boolean | true | Highlight the active drop-zone container while an item hovers over it. | | dragClass | string | '' | Extra class added to the grabbed (in-move) element while dragging. | | ghostClass | string | '' | Extra class added to the placeholder / ghost element. | | beforeInit | () => void | — | Called at the start of each layout calculation (before items are positioned). | | afterInit | () => void | — | Reserved lifecycle hook (declared on the params type). | | afterSort | (items: NodeListOf<Element>) => void | () => {} | Called after a sort completes, with the container's current child item list. | | gsx | NestedCSS | — | React only — scoped inline (CSS-in-JS) styles for this instance; supports nested selectors. |

Variant catalogs

type — layout

| Value | Description | |---|---| | column | Vertical list. Items span the container width and stack top-to-bottom. | | row | Horizontal strip. Items keep their natural width and flow left-to-right; container height matches the tallest item. | | grid | N-column grid. Items are sized to (containerWidth − gaps) / count; rows wrap every count items. |

handleStyle — auto-rendered drag handle

Only applied when no custom handler selector is set. A .gs-sortable-handle span is injected as the first child of each item.

| Value | Description | |---|---| | none | No handle injected — the whole item is draggable (default / legacy behaviour). | | bar | A single horizontal bar / pill grip. | | dots | A dotted (grid-of-dots) grip. | | grip | A classic two-column dotted drag-grip. | | lines | Stacked horizontal lines (hamburger-style). |

ghostStyle — placeholder / drop-ghost

The placeholder is the gap left behind (or shown at the target slot) while dragging.

| Value | Description | |---|---| | solid | Filled placeholder surface (default). | | dashed | Dashed border outline. | | outline | Thin solid outline, transparent fill. | | glow | Accent-coloured glow / highlighted drop target. |

theme — colour theme

| Value | Description | |---|---| | light | Light surfaces and borders (default). | | dark | Dark surfaces and borders. | | auto | Follows the OS prefers-color-scheme setting. |

Examples

Native — grid with handles, dark theme, cross-list drop

<link rel="stylesheet" href="styles/GSSortable.css" />

<div id="backlog"><div>Task A</div><div>Task B</div></div>
<div id="done"></div>

<script src="dist-js/bundle.js"></script>
<script>
  new GSSortable(document.getElementById('backlog'), {
    reference: 'backlog',
    type: 'grid',
    count: 3,
    gap: '12px',
    theme: 'dark',
    cardStyle: true,
    handleStyle: 'grip',
    ghostStyle: 'glow',
  });

  new GSSortable(document.getElementById('done'), {
    reference: 'done',
    type: 'grid',
    count: 3,
    gap: '12px',
    acceptFrom: ['backlog'],
    afterSort: (items) => console.log('done now has', items.length),
  });

  // Imperative control later:
  window.GSSortableConfigue.instance('backlog').refresh();
</script>

React — column list with a custom handle and lift

'use client';
import GSSortable from '@get-set/gs-sortable';

export default function Playlist() {
  return (
    <GSSortable
      type="column"
      gap="8px"
      handler=".drag"
      lift
      dropZoneHighlight
      animation={300}
      easing="ease-out"
      afterSort={(items) => console.log('order changed:', items.length)}
    >
      <div className="track"><span className="drag">≡</span> Song 1</div>
      <div className="track"><span className="drag">≡</span> Song 2</div>
      <div className="track"><span className="drag">≡</span> Song 3</div>
    </GSSortable>
  );
}

License

ISC.