Drag & Drop

Type-safe drag and drop with typed payloads and accept predicates.

Kraft's DnD system uses two components: a DndDragHandle that makes an element draggable, and a DndDropTarget that accepts drops. Both are generic over a payload type, so the compiler ensures you only drop compatible items.

DnD is part of the kraft-semanticui module.

Drag handles

Wrap any element in a DndDragHandle to make it draggable. The payload parameter is the data object that travels with the drag:

data class Task(val id: String, val title: String)

// Inside your component's render:
val task = Task("1", "Write docs")

DndDragHandle(payload = task) {
    ui.segment {
        icon.bars.render()
        +task.title
    }
}

When the user starts dragging, a visual clone follows the cursor. The payload type (Task) is inferred from the argument.

Drop targets

Define a drop zone with DndDropTarget. The type parameter must match the drag handle's payload type:

DndDropTarget<Task> {
    accepts = { task -> task.id != currentTask.id }  // filter what's accepted
    onDrop = { task ->
        // handle the dropped task
        moveTask(task, targetColumn)
    }
}

The accepts predicate controls which payloads this target will highlight for and accept. If it returns false, the target ignores the drag.

Visual feedback

Use the built-in highlight helpers for standard visual feedback during drag operations:

DndDropTarget<Task> {
    greenHighlights()  // green border on valid hover
    onDrop = { task -> moveTask(task) }
}

// Or blue:
DndDropTarget<Task> {
    blueHighlights()
    onDrop = { task -> moveTask(task) }
}

For custom feedback, use the individual callbacks:

DndDropTarget<Task> {
    onDragStart = { target ->
        // a compatible drag started somewhere — highlight this zone
    }
    onDragEnd = { target ->
        // drag ended (dropped or cancelled) — remove highlight
    }
    onMouseOver = { target ->
        // dragged item is hovering over this target
    }
    onMouseOut = { target ->
        // dragged item left this target
    }
    onDrop = { task ->
        // item was dropped here
    }
}

Full example

A Kanban-style board with draggable cards and two columns:

data class Card(val id: String, val title: String)

class KanbanBoard(ctx: NoProps) : PureComponent(ctx) {

    private var todo by value(listOf(Card("1", "Design"), Card("2", "Implement")))
    private var done by value(listOf<Card>())

    override fun VDom.render() {
        ui.two.column.grid {
            noui.column {
                h3 { +"To Do" }
                DndDropTarget<Card> {
                    greenHighlights()
                    accepts = { card -> card !in todo }
                    onDrop = { card ->
                        done = done - card
                        todo = todo + card
                    }
                }
                todo.forEach { card ->
                    DndDragHandle(payload = card) {
                        ui.segment { +card.title }
                    }
                }
            }
            noui.column {
                h3 { +"Done" }
                DndDropTarget<Card> {
                    blueHighlights()
                    accepts = { card -> card !in done }
                    onDrop = { card ->
                        todo = todo - card
                        done = done + card
                    }
                }
                done.forEach { card ->
                    DndDragHandle(payload = card) {
                        ui.segment { +card.title }
                    }
                }
            }
        }
    }
}

See it in action