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

gravity-dnd

v1.1.6

Published

A Vue 3 drag-and-drop component library.

Readme

Gravity - Stardust UI's drag-n-drop component.

Vercel Deploy Dynamic JSON Badge >> Check the Demo

Originally created back in 2019 for Pollux.gg's Medal Picker from the profile editor dashboard (Original source). Original was done in pure frontend Vue2 and Pug using vuedraggable.

This package ports it over for more general use as a lightweight, composable drag-and-drop system for Vue 3. It has been redesigned for use in component libraries and applications where you want customizable drop targets, drag sources, and flexible collision behavior.

Component structural styles are bundled with the Vue components. Visual color and border treatments are opt-in and come from the package stylesheet.

import 'gravity-dnd/styles.css';

Renamed to Gravity to keep on-theme with all of other Pollux's side-libs and elements. Mainly its parent component lib: Stardust UI


Core Concepts

GravityProvider

Wrap any part of your app that uses drag/drop in a single GravityProvider. It provides context for all draggables, slots, and pools.

<template>
  <GravityProvider>
    <!-- drag/drop components here -->
  </GravityProvider>
</template>

<script setup>
import 'gravity-dnd/styles.css';
import { GravityProvider } from '@/ui/gravity';
</script>

GravityDraggable

Represents a draggable item.

Key props:

  • draggable-id (string): unique id for the drag instance
  • item (any): the data payload carried during drag
  • source-id (string): identifies the container the drag originates from
  • source-kind ('pool' | 'slot' | 'custom'): used for identifying what type of container a drag comes from
  • source-index (number): index within the container
  • drop-mode ('target' | 'floating'): controls whether drop is evaluated by hover targets or by pointer release

Example:

<GravityDraggable
  draggable-id="draggable-1"
  :item="item"
  source-id="my-pool"
  source-kind="pool"
  :source-index="index"
>
  <template #default="{ dragging }">
    <div :style="{ opacity:  dragging ? .9 : 1 }">{{ item.label }}</div>
  </template>
</GravityDraggable>

GravitySlot

A drop target that accepts one item at a time. It emits drop events when an item is dropped.

Key props:

  • slot-id (string): unique identifier for the slot
  • item (any): current item in the slot (optional)
  • onDropCollision ('replace' | 'swap' | 'reject'): how to handle collisions when slot already contains an item
  • accepts (function): optional predicate to allow/reject drops based on item + source
<GravitySlot
  slot-id="my-slot"
  :item="currentItem"
  onDropCollision="swap"
  :accepts="(item) => item.type === 'allowed'"
  @drop="handleDrop"
>
  <template #default="{ hovering, accepting }">
    <div :class="{ hover: hovering, accept: accepting }">
      {{ currentItem ? currentItem.label : 'Drop here' }}
    </div>
  </template>
</GravitySlot>

GravityPool

A container that supports reordering items within the pool and receiving items from other sources.

Key events:

  • @reorder when items inside the pool are reordered
  • @receive when an item is dropped into the pool from another source
<GravityPool pool-id="my-pool" :items="items" @reorder="onReorder" @receive="onReceive">
  <template #item="{ item, index }">
    <GravityDraggable
      :draggable-id="`pool-${item.id}`"
      :item="item"
      source-id="my-pool"
      source-kind="pool"
      :source-index="index"
    >
      <template #default="{ dragging }">
        <div :style="{ opacity:  dragging ? .9 : 1 }">{{ item.label }}</div>
      </template>
    </GravityDraggable>
  </template>
</GravityPool>

Collision Modes (onDropCollision)

GravitySlot supports three collision behaviors when a drop occurs and the slot is already occupied:

| Mode | Behavior | |---------|----------| | replace | Overwrites the current slot item with the dropped item. The existing item is discarded. | | swap | Swaps the dropped item with the existing slot item. The existing item is returned to the drag source. | | reject | Prevents the drop. The dragged item is returned to its source container. |

If onDropCollision is not configured, the component uses the older swap behavior when swap=true, otherwise replace.


Drop event payload (for slots)

Drop handlers receive a GravitySlotDropEvent<TItem> with the following shape:

interface GravitySlotDropEvent<TItem> {
  draggableId: string;
  item: TItem;
  source: {
    containerId: string;
    kind: 'slot' | 'pool' | 'custom';
    index: number;
  };
  target: {
    kind: 'slot' | 'pool' | 'floating';
    containerId: string | null;
    index: number;
  };
  slotId: string;
  swap: boolean;
  collision: 'replace' | 'swap' | 'reject';
  replacedItem?: TItem;
}
  • collision: the configured collision rule in effect.
  • replacedItem: the item that was in the slot prior to the drop (available for swap/replace).

Extending / Customizing Behavior

Custom Accept Logic

Use accepts to fine-tune which items can be dropped into a slot.

<GravitySlot
  slot-id="custom-slot"
  :accepts="(item, { sourceContainerId }) => sourceContainerId === 'trusted-pool'"
  @drop="onDrop"
/>

Custom Slot Rendering

Use the slot scope values to render feedback:

  • hovering: whether the pointer is hovering the slot
  • accepting: whether the current drag is accepted
<GravitySlot slot-id="styled-slot" @drop="onDrop">
  <template #default="{ hovering, accepting }">
    <div :class="{ 'hovering': hovering, 'accepting': accepting }">
      <!-- custom status UI -->
    </div>
  </template>
</GravitySlot>

Extending drop logic in parent components

Use event handlers to implement complex behavior (e.g., persistence, undo, analytics).

function handleSlotDrop(event: GravitySlotDropEvent<MyItem>) {
  // apply app-specific rules
  if (event.collision === 'swap') {
    // maybe persist both items
  }
  updateLocalState(event);
}

Use Cases

1) Drag-to-slot with swap behavior

  • Use case: a single slot that accepts an item, but dropping another item should return the original back to its source.
  • Configure:
    • onDropCollision="swap"

2) Drag-to-slot with replace behavior

  • Use case: a slot that always accepts the newest item and discards the previous.
  • Configure:
    • onDropCollision="replace" (or swap=false)

3) Drop rejection based on slot fullness

  • Use case: only allow a drop when the slot is empty.
  • Configure:
    • onDropCollision="reject"

4) Ordered pool with receive/reorder

  • Use case: a list of draggable items where items can be reordered and new items can be dropped in.
  • Use GravityPool + @reorder + @receive.

Quick Start (smallest sample)

<template>
  <GravityProvider>
    <GravitySlot slot-id="target" onDropCollision="swap" @drop="onDrop">
      <template #default="{ hovering, accepting }">
        <div :style="{ background: hovering ? (accepting ? '#e0f7ff' : '#ffeaea') : '#fff' }">
          Drop an item here
        </div>
      </template>
    </GravitySlot>

    <GravityPool pool-id="pool" :items="items" @receive="onReceive" @reorder="onReorder">
      <template #item="{ item, index }">
        <GravityDraggable
          :draggable-id="`item-${item.id}`"
          :item="item"
          source-id="pool"
          source-kind="pool"
          :source-index="index"
        >
          <template #default="{ dragging }">
            <div :style="{ opacity: dragging ? 0.3 : 1 }">{{ item.label }}</div>
          </template>
        </GravityDraggable>
      </template>
    </GravityPool>
  </GravityProvider>
</template>

<script setup lang="ts">
import 'gravity-dnd/styles.css';
import { ref } from 'vue';
import { GravityProvider, GravityPool, GravitySlot, GravityDraggable } from '@/ui/gravity';

const items = ref([{ id: 'a', label: 'A' }, { id: 'b', label: 'B' }]);

function onDrop(event) {
  // handle drop
}
function onReceive(event) {
  // handle received item
}
function onReorder(event) {
  // handle reorder
}
</script>

Notes

  • This drag/drop system is intentionally lightweight and does not have built-in keyboard accessibility.
  • For production use, wrap drag-drop logic in safe state updates (avoid mutating arrays directly in complex UIs).
  • The onDropCollision API is designed for specific use-cases from Pollux.gg's dashboard, its implementation might be a bit stiff.
  • Better mobile support is planned for future iterations
  • React support is being considered.