npm package: @illusionalsagacity/rescript-dnd-kit
Description: ReScript bindings for [@dnd-kit/react](https://dndkit.com/) v0.2.x.
Latest version: 0.2.0

# rescript-dnd-kit

ReScript bindings for [@dnd-kit/react](https://dndkit.com/) v0.2.x.

## Installation

```sh
npm install @illusionalsagacity/rescript-dnd-kit @dnd-kit/react @dnd-kit/helpers
```

Add to your `rescript.json`:

```json
{
  "dependencies": ["@illusionalsagacity/rescript-dnd-kit", "@rescript/react"]
}
```

## Usage

All bindings are in the top-level `DndKit` module.

### Sortable List

```rescript
module SortableItem = {
  @react.component
  let make = (~id: string, ~index: int) => {
    let {ref, isDragSource} = DndKit.useSortable({
      id: String(id),
      index,
    })

    <div
      ref={ReactDOM.Ref.callbackDomRef(el => {ref(el); None})}
      style={{opacity: isDragSource ? "0.3" : "1"}}>
      {React.string(id)}
    </div>
  }
}

@react.component
let make = () => {
  let (items, setItems) = React.useState(() => ["Apple", "Banana", "Cherry"])

  let onDragOver = (event, _manager) => {
    setItems(prev => DndKit.move(prev, event))
  }

  <DndKit.DragDropProvider
    sensors={[DndKit.pointerSensor, DndKit.keyboardSensor]}
    onDragOver>
    {items
    ->Array.mapWithIndex((item, index) =>
      <SortableItem key={item} id={item} index />
    )
    ->React.array}
  </DndKit.DragDropProvider>
}
```

### Draggable + Droppable

```rescript
module DraggableCard = {
  @react.component
  let make = (~id: string) => {
    let {ref, isDragSource} = DndKit.useDraggable({id: String(id)})

    <div
      ref={ReactDOM.Ref.callbackDomRef(el => {ref(el); None})}
      style={{opacity: isDragSource ? "0.3" : "1", cursor: "grab"}}>
      {React.string(id)}
    </div>
  }
}

module DropZone = {
  @react.component
  let make = (~id: string, ~children: React.element) => {
    let {ref, isDropTarget} = DndKit.useDroppable({id: String(id)})

    <div
      ref={ReactDOM.Ref.callbackDomRef(el => {ref(el); None})}
      style={{background: isDropTarget ? "#e8f5e9" : "#f5f5f5"}}>
      children
    </div>
  }
}
```

### Event Handling

Events carry an `operation` with source/target accessible via getters:

```rescript
let onDragEnd = (event: DndKit.dragEndEvent, _manager) => {
  if !event.canceled {
    let sourceId =
      event.operation
      ->DndKit.operationSource
      ->Nullable.toOption
      ->Option.map(DndKit.draggableId)

    let targetId =
      event.operation
      ->DndKit.operationTarget
      ->Nullable.toOption
      ->Option.map(DndKit.droppableId)

    switch (sourceId, targetId) {
    | (Some(String(from)), Some(String(to_))) => handleDrop(from, to_)
    | _ => ()
    }
  }
}
```

### DragOverlay

Render a preview of the dragged item:

```rescript
<DndKit.DragOverlay>
  {switch activeItem {
  | Some(item) => <ItemPreview item />
  | None => React.null
  }}
</DndKit.DragOverlay>
```

### Monitoring Events

```rescript
DndKit.useDragDropMonitor({
  onDragStart: ?Some((event, _manager) => Console.log("started")),
  onDragEnd: ?Some((event, _manager) => Console.log("ended")),
})
```

### Current Drag State

```rescript
let {source, target} = DndKit.useDragOperation()
let isDragging = source->Nullable.toOption->Option.isSome
```

## Available Bindings

### Sensors

| Binding | Source |
|---|---|
| `DndKit.pointerSensor` | `@dnd-kit/react` |
| `DndKit.keyboardSensor` | `@dnd-kit/react` |

### Collision Detectors

| Binding | Source |
|---|---|
| `DndKit.defaultCollisionDetection` | `@dnd-kit/collision` |
| `DndKit.closestCenter` | `@dnd-kit/collision` |
| `DndKit.closestCorners` | `@dnd-kit/collision` |
| `DndKit.pointerIntersection` | `@dnd-kit/collision` |
| `DndKit.shapeIntersection` | `@dnd-kit/collision` |
| `DndKit.directionBiased` | `@dnd-kit/collision` |
| `DndKit.pointerDistance` | `@dnd-kit/collision` |

### Modifiers

| Binding | Source |
|---|---|
| `DndKit.restrictToVerticalAxis` | `@dnd-kit/abstract/modifiers` |
| `DndKit.restrictToHorizontalAxis` | `@dnd-kit/abstract/modifiers` |
| `DndKit.restrictToWindow` | `@dnd-kit/dom/modifiers` |
| `DndKit.restrictToElement` | `@dnd-kit/dom/modifiers` |

### Helpers

| Binding | Source |
|---|---|
| `DndKit.move` | `@dnd-kit/helpers` |
| `DndKit.swap` | `@dnd-kit/helpers` |
| `DndKit.arrayMove` | `@dnd-kit/helpers` |
| `DndKit.arraySwap` | `@dnd-kit/helpers` |

## Examples

See the [`example/`](example/) directory for working demos:

- **SortableDemo** — Vertical list reordering with `useSortable`, `DndKit.move`, modifiers, `DragOverlay`, and event monitoring
- **KanbanDemo** — Multi-column board with `useDraggable`/`useDroppable` for moving cards between columns

Run the examples:

```sh
cd example
yarn install
yarn run res:build
yarn run dev
```