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

dnd-master

v1.0.2

Published

Limitless, extensible drag and drop for Svelte

Readme

DnD Master

Lightweight, powerful drag-and-drop for Svelte.

Please see bottom of page for information about mobile support.

Installation

npm install dnd-master

Quickstart

If you just want to get started with a simple drag and drop, you can import the dnd singleton. This has no middleware.

import { dnd } from 'dnd-master'

const data = { name: 'Alice' }

Data Attachments

To pass data, create a data attachment using dnd.draggable, like this:

const dataAttachment = dnd.draggable(data)

Then attach it to an element:

<div {@attach dataAttachment}>{data.name}</div>

Now, we're ready to drop.

Dropzone Attachments

The same way we do with data, here we create a dropzone attachment, this time passing a callback that receives a data parameter:

let lastDropped = $state()

const dropzone = dnd.dropzone(data => {
   lastDropped = data
})

Then attach the dropzone to an element:

<div {@attach dropzone}>Drop Here</div>

Hooks

In addition to passing and receiving data, you might want to run side effects of your drag and drop operations. For instance, maybe you want to keep a count of how many drops have happened, but on the data side. For this, you can set up hooks like this:

let timesDropped = $state(0)

const dataAttachment = dnd.draggable(data, {
   drop: () => timesDropped++
})

This lets you run logic on either the drag or drop side of the operation cleanly.

There are hooks for each drag event: dragstart, dragover, dragenter, dragexit, dragend, and drop. There are also: stop, and cancel. (More on those in a moment.)

All hooks run before middleware runs, with the exception of the Data Attachment drop hook. This will only run on a successful drop, which happens after the middleware. For cancellations, use the cancel hook, and for interrupted drops, (i.e. validation refused the drop) use stop.

If you want to run logic on the dropzone side only on a successful drop, put that logic into your drop callback itself, not into the drop hook.

Middleware

You can extend dnd-master with middleware, and it ships with two included: validate and ghost. Middlewares work by attaching extension functions to the dnd instance, and can also provide new hooks that you can add to you your data and dropzone attachments.

In order to use middleware, you need to import createDnd and create a dnd instance, then import the middleware you'd like and pass it into dnd.use, like this:

import { createDnd } from 'dnd-master'
import { validate, ghost } from 'dnd-master/middleware'

const dnd = createDnd()
   .use(ghost)
   .use(validate) // you can chain .use calls!

Validate

Validate lets you set up validation logic on both the drag and drop sides of the operation, so that you can control when data is allowed to drop.

import { createDnd } from 'dnd-master'
import { validate } from 'dnd-master/middleware'

const dnd = createDnd().use(validate)

CSS Classes

The default css classes are valid and invalid. If you'd like to override them, you can call dnd.classes. Note that the validate middleware needs to have been added to dnd first for the classes function to be available.

const dnd = createDnd().use(validate)

dnd.classes('myValidClass', 'myInvalidClass')

Validating Data & Dropzones

To use validation, you can use the functions assertData and assertZone. These functions receive a predicate (which can also be a type assertion) and return a validator function which also has a builder on it. That's a bit confusing, so let's see it in action and then explain what's happening.

let lastDropped = $state('')

// the type assertion here is optional but highly encouraged
const isString = dnd.assertData((data): data is string =>
   typeof data === "string"
)

isString("Hello there") // returns true
isString(1) // returns false

// soDrop is a property on isString, that we can use to create a dropzone:
const dropzone = isString.soDrop(data => lastDropped = data)
// data will be correctly typed as a string from the type assertion!

Internally, soDrop just calls dnd.dropzone, so you can also pass hooks if you like.

Now let's see the same for creating the draggable. For that we use assertZone, which receives an HTMLElement:

const isPremiumZone = dnd.assertZone(element =>
   element.dataset.zone === "premium"
)

const premiumItem = isPremiumZone.soGive("Premium Item")

soGive uses dnd.draggable internally, so again, you can use hooks.

There's no need to make the zone predicate a type assertion.

Ghost

Ghost sets up a ghost image to replace the browser default, and even allows you to dynamically update it during the drag. We'll look at a simple ghost image here, and later in the advanced usage examples there's a dynamic ghost implementation.

To do this, you can either create an element programmatically or bind one from inside a template. Then use the dragstart hook to attach the ghost:

import { createDnd } from 'dnd-master'
import { ghost } from 'dnd-master/middleware'

const dnd = createDnd().use(ghost)

const ghostElement = document.createElement('div')
ghostElement.textContent = "👻 Ghost Item"
ghostElement.style.cssText = `
   background: #ff5722;
   color: white;
   padding: 0.5rem;
   border-radius: 4px;
   box-shadow: 0 2px 8px rgba(0,0,0,0.3);
`

const itemWithGhost = dnd.draggable("My Item", {
   dragstart: () => dnd.setGhost(ghostElement)
})

Or alternatively:

import { createDnd } from 'dnd-master'
import { ghost } from 'dnd-master/middleware'

const dnd = createDnd().use(ghost)

let ghostElement = $state<HTMLDivElement>()

const itemWithGhost = dnd.draggable("My Item", {
   dragstart: () => dnd.setGhost(ghostElement)
})
<template>
   <div class="ghost" bind:this={ghostElement}>👻 Ghost Item</div>
</template>
div.ghost {
   background: #7022ff;
   color: white;
   padding: 0.5rem;
   border-radius: 4px;
   box-shadow: 0 2px 8px rgba(0,0,0,0.3);
}

Advanced Usage Examples

Dynamic Ghosts

If you want to have a dynamic ghost that updates on valid or invalid dropzones, you can do the following:

let defaultGhost = $state<HTMLDivElement>()
let validGhost = $state<HTMLDivElement>()
let invalidGhost = $state<HTMLDivElement>()

const isValidZone = dnd.assertZone(element =>
   element.dataset.zone === "valid"
)

const dynamicItem = isValidZone.soGive("Item with Dynamic Ghost", {
   dragstart: () => dnd.setGhost(defaultGhost),
   dragleave: () => dnd.setGhost(defaultGhost),
   dragover: (_event, element) => {
      isValidZone(element) ? dnd.setGhost(validGhost) : dnd.setGhost(invalidGhost)
   }
})

This allows you to dynamically update your ghost image, and without the ghost and validate middlewares needing to talk to each other at all!

Mobile Support

From v1.0.2, there's a touch middleware included for mobile support. I've not developed for mobile before so there are probably edge cases (multi-touch, scrolling etc.) that need to be addressed. Please let me know if you run into issues!

You can use it like this:

import { touch } from '$lib/middleware/touch.js';

const dnd = createDnd().use(touch) // Clean instance with no middleware

or:

import { createTouch } from '$lib/middleware/touch.js'

const dnd = createDnd().use(createTouch({
   longPressDuration: 200,
   scrollThreshold: 10,
   autoGhost: true
}))