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

@rbuljan/jsort

v1.10.3

Published

A JavaScript library for sorting and reordering grid or list items.

Readme

JSort logo

JSort

Small yet powerful drag and drop sortable library with touch support, smooth animations, for a great UX.

NPM   JSort on GitHub   JSort GitHub Stars   Full-JS | JSort Homepage NPM: @rbuljan/jsort
Demo & examples: JSort — Homepage

Features

  • [ ] Multiple items select (coming soon)
  • [x] No external dependencies
  • [x] Smooth animations for a better UE/UX
  • [x] Touch & Mouse support (with Pointer events)
  • [x] Dual customization options (via instance argument or data-jsort HTML attribute)
  • [x] Grab handler
  • [x] Linked groups
  • [x] Nested groups
  • [x] Swap group items
  • [x] Scroll-intent on touch devices
  • [x] Dynamic items support (delegated events)
  • [x] Scroll parent on near-edge drag-move
  • [x] Respects selection on inner action elements
  • [x] In-place animated sorting beta

Installation

npm install @rbuljan/jsort

Syntax

const JSortInstance = new JSort(HTMLElement, { /* Options */ });

Usage example

<ul id="list">
    <li>1</li>
    <li>2</li>
    <li>
        <div class="jsort-handler">✥</div>
        3 Grab me by the handler
    </li>
    <li>4</li>
</ul>

<script type="module">
    import JSort from '@rbuljan/jsort';
    const jsortList = new JSort(document.querySelector("#list"), {
        duration: 360,
        // Other Options here
        onGrab(data) {
            console.log(this, data);
        }
        onMove(data) {
            console.log(this, data);
        }
        onDrop(data) {
            console.log(this, data);
        }
    });
</script>

Tip:
Works with any parent Tag

Options

JSort(HTMLElement, options)

The second parameter accepts an options object to customize the sorting behavior and appearance.

| Option | Type | Default | Description | | ---------------------- | ---------- | -------------------------- | ----------------------------------------------- | | Event Callbacks | | | | | onBeforeGrab(data) | function | | Called before item grab | | onGrab(data) | function | | Called on item grab | | onMove(data) | function | | Called on item move | | onBeforeDrop(data) | function | | Called before itme drop | | onDrop(data) | function | | Called on drop | | onAnimationEnd() | function | | Called on drop animation end | | Behavior | | | | | group | string | "" | Group sortable parents | | swap | boolean | false | Swap elements (instead of reordering) | | parentDrop | boolean | true | Allow drop onto parent container | | moveThreshold | number | 0 | Px before it's considered a pointer move | | grabTimeout | number | 140 | Grab delay in ms (touch devices only) | | Selectors | | | | | selectorParent | string | ".jsort" | Parent container | | selectorItems | string | "*" | Sortable items (parent's immediate children | | selectorItemsIgnore | string | ".jsort-ignore" | Ignored sortable's immediate children | | selectorIgnoreTarget | string | "" | Prevent grab on item descendant selectors | | selectorIgnoreFields | string | Read Tip below | Prevent grab on item descendant action elements | | selectorHandler | string | ".jsort-handler" | Drag handle selector | | CSS Classes | | | | | classGrab | string | "is-jsort-grab" | Grabbed item | | classGhost | string | "is-jsort-ghost" | Ghost element | | classTarget | string | "is-jsort-target" | Hovered target element | | classAnimated | string | "is-jsort-animated" | All affected and animated items | | classAnimatedDrop | string | "is-jsort-animated-drop" | Grabbed item during drop animation | | classInvalid | string | "is-jsort-invalid" | Ghost element over invalid drop zones | | classTouch | string | "is-jsort-touch" | Grabbed item if Event is touchstart (touch) | | Animation | | | | | duration | number | 420 | Sort animation duration in ms | | easing | string | "cubic-bezier()" | Animation easing function | | Ghost Styles | | | | | scale | number | 1.1 | Ghost element scale | | opacity | number | 0.8 | Ghost element opacity (0-1) | | zIndex | number | 2147483647 | Ghost element z-index | | Scroll | | | | | scrollThreshold | number | 8 | Px before considering auto-scroll | | edgeThreshold | number | 50 | Autoscroll distance to edge | | scrollSpeed | number | 10 | Auto-scroll speed in pixels per step |

Tip:
By defafult, the selectorIgnoreFields provides a good sane selector in order to prevent grabbing an item if the direct target is an action element, like: Anchor, Button, Input, Contenteditable, etc:

/* DO NOT GRAB ITEM IF POINTER TARGET IS AN ACTION FIELD: */
:is(input, select, textarea, button, label, [contenteditable=""], [contenteditable="true"], [tabindex]:not([tabindex^="-"]), a[href]:not(a[href=""]), area[href]):not(:disabled)

Tip:
Internally, the Options selectors will be concatenated like selectorParent > selectorItems:not( selectorItemsIgnore ) into i.e: .jsort > *:not(.jsort-ignore) (or like: .jsort > * if you pass an empty string to the selectorItemsIgnore). The > immediate child selector will always be injected by default.

Options example

const sortable = new JSort(document.querySelector("#mySortable"), {
  selectorItems: ".item",
  group: "shared-group",
  swap: true,
  duration: 300,
  scale: 1.05,
  onDrop: (data) => {
    console.log(this, data); // JSort, data{}
  },
  // More options here...
});

Tip:
Options (that are not callbacks) can be assigned directly in your HTML markup using the data-jsort attribute in this format option: value; option: value

<div id="mySortable" data-jsort="
    group: a;
    selectorItems: .item;
    selectorHandler: .my-handler;
    swap: true;
    duration: 300;
    easing: ease-out;
    zIndex: 999;
    parentDrop: false;
  ">
  <div class="item"><div class="my-handler">✥</div>Item 1</div>
  <div class="item"><div class="my-handler">✥</div>Item 2</div>
</div>

just remember that data-jsort options will override any instance options you passed to the constructor (analogue to stylesheet vs. style="" attribute). Having that in consideration, you can define some JSort global, shared options from JavaScript, and customize specific elements using the data-jsort="" attribute, if needed.

Custom validation

To manually abort some actions depending on a condition, you can use the onBeforeGrab() and onBeforeDrop() callbacks

new JSort(myListElement, {
    onBeforeGrab(data) {
        if (data.indexGrab === 0) {
            console.error("Cannot grab first item");
            return false;
        }
        if (data.elGrab.closest(".no-grab")) {
            console.error("Grabbed an invalid item");
            return false;
        }
    },
    onGrab(data) {
        console.log(`Grabbed index ${data.indexGrab}`);
    },
    onBeforeDrop(data) {
        if (data.indexDrop === 0) {
            console.error("Cannot drop into first item");
            return false;
        }
        if (data.elDrop.closest(".no-drop")) {
            console.error("Cannot drop here");
            return false;
        }
    },
    onDrop(data) {
        console.log(`Dropped index ${data.indexGrab} into ${data.indexDrop}`, this);
    }
})

If you returned false from one of the callbacks, the respective onGrab or onDrop actions will not be called.

Methods

| Method | Description | | ------------------------- | ----------------------------------------------------- | | init({/*options*/}) | Re-initialize the instance with updated Options | | destroy() | Destroys the instance and removes the event listeners | | insert(Element, Target) | Insert item at Target (parent or item) | | sort(fn) | (Beta) In-place sort parent items (with animation) |

Properties

JSort

| Property | Type | Default | Description | | ------------------ | --------------- | ------- | ------------------------------------------ | | indexGrab | number | -1 | The index of the grabbed item | | indexDrop | number | -1 | The new index on drop | | elGrab | HTMLElement | null | The grabbed item | | elGrabParent | HTMLElement | null | The grabbed item's parent | | elGhost | HTMLElement | null | Element that follows the pointer | | elTarget | HTMLElement | null | The hovered target (item or parent) | | elDrop | HTMLElement | null | Same as elTarget but on drop | | elDropParent | HTMLElement | null | The drop (target) item's parent on drop | | affectedElements | HTMLElement[] | [] | Array of drop-affected (animated) elements |

Static Properties

JSort

| Property | Type | Description | | --------- | -------- | ----------------------- | | version | string | Current library version |

Linked Groups

JSort allows to drag & drop into a linked group by defining a group property

<div class="shared">
    <div>A 1</div>
    <div>A 2</div>
    <div>A 3</div>
</div>
<div class="shared">
    <div>B 1</div>
    <div>B 2</div>
</div>

<script type="module">
    import JSort from '@rbuljan/jsort';
    document.querySelectorAll(".shared").forEach((el) => {
        new JSort(el, { 
            group: "shared"
        });
    });
</script>

or by adding the data-jsort attribute in HTML:

<div class="shared" data-jsort="group:shared">
    <div>A 1</div>
    <div>A 2</div>
    <div>A 3</div>
</div>
<div class="shared" data-jsort="group:shared">
    <div>B 1</div>
    <div>B 2</div>
</div>

<script type="module">
    import JSort from '@rbuljan/jsort';
    document.querySelectorAll(".shared").forEach((el) => new JSort(el));
</script>

Swap items

By default JSort reorders the items on drop. If instead you want to swap, you can set the swap option to true to your element or grouped elements:

<div class="players">
    <div>Mark</div>
    <div>Jack</div>
    <div>Theo</div>
</div>
<div class="players">
    <div>Luke</div>
    <div>John</div>
    <div>Roko</div>
</div>

<script type="module">
    import JSort from '@rbuljan/jsort';
    document.querySelectorAll(".players").forEach((el) => {
        new JSort(el, {
            group: "team",
            swap: true,
        })
    });
</script>

PS: Instead of using the constructor options, you can use the data-jsort:

<div class="players" data-jsort="group:team; swap:true">
    <div>Mark</div>
    <div>Jack</div>
    <div>Theo</div>
</div>
<div class="players" data-jsort="group:team; swap:true">
    <div>Luke</div>
    <div>John</div>
    <div>Roko</div>
</div>

<script type="module">
    import JSort from '@rbuljan/jsort';
    document.querySelectorAll(".players").forEach((el) => new JSort(el));
</script>

Find more examples on JSort Homepage.

Styling

Is there a minimal CSS styling I might want to use to get started?

Yes! This is the minimal CSS styling you might want to use to get the best from JSort defaults:

/* JSort — Minimal suggested styles */

.is-jsort-active.is-jsort-touch {
    outline: 2px solid currentColor; /* Useful hint on touch devices */
}

.is-jsort-grab {
    opacity: 0; /* Dim the original grabbed element */
}

.is-jsort-target {
    z-index: 1;
    outline: 2px dashed currentColor;
}

.is-jsort-invalid {
    outline: 2px solid red;
}

The above was not hardcoded into the library since everyone wants to style their UI differently, i.e: set the grabbed element's opacity at a different value, change the targeted element styles, etc.

For custom styling JSort provides several classes you could use in your CSS to further style your UI: (See: Options → CSS Classes)


See the JSort Homepage for examples and inspiration.

Motivation

I needed a sortable library. After reviewing some popular ones like SortableJS, Dragula, jQueryUI/Sortable, and others, I found that they do not work the way I want, fast, smoothly, touch/mobile friendly. An important factor was also to minimize motion, only when necessary (on drop) and to animate all affected elements naturally and smoothly to support visual cognitive feedback and make the experience overall more natural and pleasant.
JSort was born to fill this necessity.


Licence: MIT