@molecule/app-drag-drop-dndkit
Provider bond · drag-drop · App (browser) · v1.0.2 · Apache-2.0
Headless dnd-kit-style drag-drop state provider for @molecule/app-drag-drop (no @dnd-kit dependency; app owns the drag listeners)
npm install @molecule/app-drag-drop-dndkitnpm · Source on GitHub · Implements @molecule/app-drag-drop
How it works
@molecule/app-drag-drop-dndkit is a provider bond on the app (browser) side: it implements the drag-drop core interface (@molecule/app-drag-drop) with a concrete vendor or library behind it.
Your code calls the core; you wire this provider once at startup. Swapping vendors later is one line in that wiring, not a rewrite.
import { provider } from '@molecule/app-drag-drop-dndkit'
import { setProvider } from '@molecule/app-drag-drop'
setProvider(provider)Works with: @molecule/app-drag-drop
Live demos
Running apps built with Molecule that install this package — open the app or study the template.
Reference
Real @dnd-kit drag-drop provider for molecule.dev.
Implements DragDropProvider from @molecule/app-drag-drop against
@dnd-kit/*. Two layers ship here:
- An imperative order store (
provider/createDndKitProvider) —createSortable/createDraggable/createDroppable— whose reorders use@dnd-kit's ownarrayMove. - A real React binding —
SortableList+useSortableItem— that wrapsDndContext/SortableContext/useSortablewith pointer and keyboard sensors and, on drop, reorders and invokes the coreonReorderwith the new order. This is the shipped DOM-event bridge; a drag actually reorders the list (mouse, touch, or keyboard).
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Quick Start
import { provider } from '@molecule/app-drag-drop-dndkit'
import { setProvider } from '@molecule/app-drag-drop'
setProvider(provider)
import { SortableList, useSortableItem } from '@molecule/app-drag-drop-dndkit'
// Dropping a row calls onReorder(newOrder); Space + arrow keys reorder too.
;<SortableList items={items} onReorder={setItems}>
<ul>
{items.map((item) => (
<Row key={item.id} item={item} />
))}
</ul>
</SortableList>
Type
provider
Installation
npm install @molecule/app-drag-drop-dndkit @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities @molecule/app-drag-drop react react-dom
npm install -D @types/react
API
Interfaces
SortableDragEndContext
Inputs for handleSortableDragEnd.
interface SortableDragEndContext<T extends { id: string }> {
/** The current ordered items. */
items: T[]
/** Core reorder callback, invoked with the new order after a real move. */
onReorder: (items: T[]) => void
/** Whether sorting is disabled — suppresses the reorder. Defaults to `false`. */
disabled?: boolean
/** Optional passthrough of the core drag-end event. */
onDragEnd?: (event: DragEndEvent) => void
}
SortableListProps
Props for SortableList.
interface SortableListProps<T extends { id: string }> {
/** Ordered items to render. Each must have a stable string `id`. */
items: T[]
/**
* Called with the full reordered array when a drag completes a move. This is
* the core `SortableOptions['onReorder']` callback — persist the new order.
*
* @param items - The reordered items.
*/
onReorder: (items: T[]) => void
/** Sort axis. Selects the default sorting strategy. Defaults to `'vertical'`. */
axis?: SortableAxis
/** Explicit sorting strategy override. */
strategy?: SortableStrategy
/** Disable all drag interactions. Defaults to `false`. */
disabled?: boolean
/** Sensor activation thresholds (pointer delay / distance). */
config?: DndKitConfig
/** Optional passthrough: fired when a drag starts. */
onDragStart?: (event: DragStartEvent) => void
/** Optional passthrough: fired while dragging over a target. */
onDragOver?: (event: DragOverEvent) => void
/** Optional passthrough: fired when a drag ends (before/besides `onReorder`). */
onDragEnd?: (event: DragEndEvent) => void
/** The rendered list — items using `useSortableItem` for their rows. */
children: ReactNode
}
UseSortableItemOptions
Options for useSortableItem.
interface UseSortableItemOptions {
/** The sortable item's id — must match the `id` of the corresponding item. */
id: string
/** Arbitrary data carried with the drag (surfaced on drag events). */
data?: Record<string, unknown>
/** Whether this specific item is undraggable. Defaults to `false`. */
disabled?: boolean
}
SortableItemState
The wiring returned by useSortableItem for a single sortable row.
interface SortableItemState {
/** Ref for the row element (the sortable node). */
setNodeRef: (node: HTMLElement | null) => void
/** Ref for an optional drag handle — spread `listeners` on this element for handle-only dragging. */
setActivatorNodeRef: (element: HTMLElement | null) => void
/** Accessibility attributes (role/tabindex/aria) — spread onto the row for keyboard support. */
attributes: DraggableAttributes
/** Pointer/keyboard drag listeners — spread onto the row body or the handle. */
listeners: DraggableSyntheticListeners
/** Inline transform/transition styles that animate the row while sorting. */
style: CSSProperties
/** `true` while this row is being dragged. */
isDragging: boolean
/** `true` while another dragged row is over this one. */
isOver: boolean
}
DndKitConfig
Configuration options for the dnd-kit drag-drop provider. activationDelay
and activationDistance are honored by the React binding's pointer sensor
(see SortableList / createSortableSensors).
interface DndKitConfig {
/**
* Activation delay in milliseconds before a drag starts. A positive value
* makes the pointer sensor use a delay+tolerance constraint — useful for
* distinguishing between click/tap and drag on touch devices. Takes
* precedence over `activationDistance`. Defaults to `0` (immediate).
*/
activationDelay?: number
/**
* Distance in pixels a pointer must travel before a drag starts. A positive
* value makes the pointer sensor use a distance constraint (ignored when
* `activationDelay` is set). Defaults to `0` (immediate).
*/
activationDistance?: number
/**
* Whether Escape cancels a drag. `@dnd-kit`'s keyboard sensor cancels on Escape
* by default, so this is effectively `true`. Defaults to `true`.
*/
cancelOnEscape?: boolean
}
DndKitSortableInstance
Extended sortable instance with internal methods for framework bindings.
interface DndKitSortableInstance<T extends { id: string }> extends SortableInstance<T> {
/**
* Sets the active drag id. Called by framework bindings when a drag starts.
*
* @param id - The id of the item being dragged, or `null` when drag ends.
*/
_setActiveId(id: string | null): void
/**
* Completes a reorder from one index to another. Called by framework
* bindings on drag end.
*
* @param fromIndex - The source index.
* @param toIndex - The destination index.
*/
_reorder(fromIndex: number, toIndex: number): void
/**
* Returns whether sorting is disabled.
*
* @returns `true` if disabled.
*/
_isDisabled(): boolean
}
DndKitDraggableInstance
Extended draggable instance with internal methods for framework bindings.
interface DndKitDraggableInstance extends DraggableInstance {
/**
* Sets the dragging state. Called by framework bindings.
*
* @param value - Whether the item is being dragged.
*/
_setDragging(value: boolean): void
/**
* Returns the current data payload.
*
* @returns The data associated with this draggable.
*/
_getData(): unknown
/**
* Returns whether dragging is disabled.
*
* @returns `true` if disabled.
*/
_isDisabled(): boolean
}
DndKitDroppableInstance
Extended droppable instance with internal methods for framework bindings.
interface DndKitDroppableInstance extends DroppableInstance {
/**
* Sets the hover state. Called by framework bindings.
*
* @param value - Whether a draggable is over this zone.
*/
_setOver(value: boolean): void
/**
* Checks if this zone accepts a given type.
*
* @param type - The draggable type to check.
* @returns `true` if the type is accepted.
*/
_accepts(type?: string): boolean
/**
* Invokes the onDrop handler. Called by framework bindings on drop.
*
* @param data - The data payload from the dropped draggable.
* @param draggableId - The identifier of the dropped draggable.
*/
_handleDrop(data: unknown, draggableId: string): void
/**
* Returns whether the droppable is disabled.
*
* @returns `true` if disabled.
*/
_isDisabled(): boolean
}
Functions
createDndKitProvider(_config)
Creates a dnd-kit-based drag-drop provider.
function createDndKitProvider(_config?: DndKitConfig): DragDropProvider
_config— Optional dnd-kit-specific configuration.
Returns: A DragDropProvider backed by dnd-kit state management.
createSortableSensors(config)
Builds the sensor descriptor list used by SortableList: a pointer
sensor (mouse/touch) and a keyboard sensor wired to
sortableKeyboardCoordinates so a sortable list is keyboard-accessible.
Exported so callers/tests can assert both sensors are present.
function createSortableSensors(
config?: DndKitConfig,
): (SensorDescriptor<PointerSensorOptions> | SensorDescriptor<KeyboardSensorOptions>)[]
config— dnd-kit provider configuration (activation thresholds).
Returns: The pointer + keyboard sensor descriptors.
resolveSortingStrategy(axis, strategy)
Maps a molecule SortableAxis/SortableStrategy to a concrete
@dnd-kit sorting strategy. An explicit strategy wins; otherwise the axis
selects it (horizontal → horizontal list, both → grid, else vertical).
function resolveSortingStrategy(axis?: SortableAxis, strategy?: SortableStrategy): SortingStrategy
axis— The sort axis. Defaults to'vertical'.strategy— An explicit strategy override.
Returns: The @dnd-kit sorting strategy to pass to SortableContext.
handleSortableDragEnd(event, context)
Bridges a @dnd-kit drag-end to the core drag-drop contract. Emits the core
onDragEnd event, then — unless sorting is disabled, the drop landed nowhere,
or the item was dropped in place — reorders items with @dnd-kit's
arrayMove and invokes onReorder with the new order. This is the exact
handler SortableList passes to DndContext.onDragEnd, so a real drop
runs this code and onReorder fires with the reordered array.
function handleSortableDragEnd(event: DndKitDragEndEvent, context: SortableDragEndContext<T>): void
event— The@dnd-kitdrag-end event (active/over).context— Items, callbacks, and disabled flag.
SortableList(props)
A real @dnd-kit sortable list. Wraps DndContext + SortableContext with a
pointer and a keyboard sensor and, on drop, reorders items and calls
onReorder with the new order (via handleSortableDragEnd). Render
the list itself as children, using useSortableItem for each row.
function SortableList(props: SortableListProps<T>): JSX.Element
props—SortableListProps.
Returns: The drag-and-drop context wrapping the sortable list.
useSortableItem(options)
Wraps @dnd-kit's useSortable for a single row inside a SortableList.
Spread attributes + listeners onto the row (or listeners onto a handle
bound with setActivatorNodeRef), attach setNodeRef, and apply style.
function useSortableItem(options: UseSortableItemOptions): SortableItemState
options—UseSortableItemOptions.
Returns: The refs, attributes, listeners, and transform style for the row.
Constants
provider
Default dnd-kit drag-drop provider instance.
const provider: DragDropProvider
Core Interface
Implements @molecule/app-drag-drop interface.
Bond Wiring
Setup function to register this provider with the core interface:
import { setProvider } from '@molecule/app-drag-drop'
import { provider } from '@molecule/app-drag-drop-dndkit'
export function setupDragDropDndkit(): void {
setProvider(provider)
}
Injection Notes
Requirements
Peer dependencies:
@molecule/app-drag-drop>=1.0.1react^18.0.0 || ^19.0.0react-dom^18.0.0 || ^19.0.0
Runtime Dependencies
@dnd-kit/core@dnd-kit/sortable@dnd-kit/utilities@molecule/app-drag-dropreactreact-dom
The React binding requires react / react-dom (peer dependencies) since
@dnd-kit is React-only. The imperative store's extended instance types
(_-prefixed methods) remain available for consumers wiring their own
(non-@dnd-kit) drag events. DndKitConfig.activationDelay /
activationDistance are honored by the React binding's pointer sensor.
E2E Tests
Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual sortable lists / drop zones, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:
- Dragging an item to a new position REORDERS the list: the item lands in the drop slot, the surrounding items shift, and the rendered order now matches getItems() — reordering along the configured axis (a horizontal or grid list moves left/right, not just top/bottom). The instance is a headless order store, so a drag that visibly "does nothing" means the wiring is missing, not that the feature works.
- The reorder fires onReorder with the NEW item array and the app SAVES it: after a full reload the list keeps the new order (proving onReorder persisted it, not just shuffled local state). If the save fails, the UI restores the previous order via setItems() instead of lying about it.
- Dragging an item BETWEEN two containers moves it: it leaves the source list and appears in the target, BOTH lists update, and the target's onDrop fires identifying the item that moved (data + draggableId say which item went where) — the move survives a reload if the app persists it.
- A drop zone with
acceptset takes only the draggable types it lists and rejects the rest — a disallowed item does not land there and no onDrop fires for it. - A disabled item can't be dragged: with
disabled: true(on the item or the whole sortable) a drag attempt does nothing, the order is unchanged, and no reorder/drop callback fires. - When
handle: true, only the drag-handle element starts a drag — grabbing the item body anywhere else does not move it. - Dropping outside any valid zone CANCELS: the item snaps back to its origin, the order is unchanged, and no callback claims a move — the item is never lost or left in limbo.