# Drag and Drop Enable drag-and-drop interactions between cells using `ct-drag-source` and `ct-drop-zone` components. ## Components ### ct-drag-source Wraps content that can be dragged. The dragged cell is passed to any drop zone that accepts it. | Attribute | Type | Description | |-----------|------|-------------| | `$cell` | `Cell` | The cell being dragged (required) | | `type` | `string` | Type identifier for filtering which drop zones accept this source | | `disabled` | `boolean` | Disable dragging | ### ct-drop-zone Marks a region where items can be dropped. Provides visual feedback (dashed outline) when a valid drag is over it. | Attribute | Type | Description | |-----------|------|-------------| | `accept` | `string` | Comma-separated types to accept (empty = accept all) | ## Events | Event | Payload | Description | |-------|---------|-------------| | `onct-drop` | `{ detail: { sourceCell: Writable, type?: string } }` | Fired when a valid drop occurs | | `onct-drag-enter` | `{ detail: { sourceCell: Cell, type?: string } }` | Fired when drag enters the zone | | `onct-drag-leave` | `{ detail: {} }` | Fired when drag leaves the zone | ## Example ```tsx /// import { Default, equals, handler, NAME, pattern, UI, Writable, } from "commontools"; interface Item { title: string; } interface DragDropDemoInput { availableItems: Default; droppedItems: Writable; } interface DragDropDemoOutput { availableItems: Item[]; droppedItems: Writable; } // Handler to remove an item from the dropped list const removeItem = handler< unknown, { droppedItems: Writable; item: Writable } >((_, { droppedItems, item }) => { const current = droppedItems.get(); const index = current.findIndex((el) => equals(item, el)); if (index >= 0) { droppedItems.set(current.toSpliced(index, 1)); } }); export default pattern( ({ availableItems, droppedItems }) => { return { [NAME]: "Drag Drop Demo", [UI]: (
{/* Drag Sources */}

Available Items

{availableItems.map((item) => (
{item.title}
))}
{/* Drop Zone */} } }) => { droppedItems.push(e.detail.sourceCell); }} >

Drop Zone

{droppedItems.get().length === 0 ? (

Drop items here

) : (
{droppedItems.map((item) => (
{item.title} x
))}
)}
), availableItems, droppedItems, }; }, ); ``` ## Best Practices 1. **Use `equals()` for Cell identity** - When finding items in arrays, use `equals(cellA, cellB)` instead of `===`. This is critical for multi-tab scenarios where the same logical cell may have different object references. 2. **Get fresh array data** - Always call `.get()` on the array before searching/modifying. Don't rely on stale references. 3. **Use type filtering** - Set `type` on drag sources and `accept` on drop zones to control which items can be dropped where. 4. **Handle missing items gracefully** - Check if `findIndex` returns `-1` before modifying arrays. Another tab may have already removed the item.