TheFreeform
The main container component that manages all drag & drop state.
Basic Usage
vue
<TheFreeform v-model="items" @drop-into="onDropInto">
<FreeformItem v-for="item in items" :key="item.id" :item="item" />
<FreeformPlaceholder />
</TheFreeform>Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | FreeformItemData[] | required | Items array (v-model) |
disabled | boolean | false | Disable all interactions |
manualReorder | boolean | false | Don't auto-reorder, handle @reorder manually |
dropZoneId | string | - | Id of the surrounding FreeformDropZone, enabling drops from other lists |
Events
| Event | Payload | Description |
|---|---|---|
update:modelValue | items[] | Items array changed (reorder) |
select | items[] | Selection changed |
drag-start | items[] | Drag operation started |
drag-move | items[], position | Dragging (with cursor position) |
drag-end | items[] | Drag operation ended |
drop-into | items[], container, accepted | Items dropped into a container |
reorder | fromIndex, toIndex | Items reordered |
drop | DropEventPayload | Any completed drop - carries dropType, indices and target |
drop-to-zone | items[], zoneId, index, containerId | Items dropped into another zone |
Slots
Default Slot
Receives state information:
vue
<TheFreeform v-model="items">
<template #default="{ items, selected, isDragging, dragItems, dropIndex }">
<!-- Your content -->
</template>
</TheFreeform>| Prop | Type | Description |
|---|---|---|
items | array | Current items |
selected | array | Currently selected items |
isDragging | boolean | Drag in progress |
dragItems | array | Items being dragged |
dropIndex | number | Where items will drop |
dragSourceIndex | number | Index the drag started from (-1 for incoming external drops) |
isLassoActive | boolean | Lasso selection in progress |
drag-ghost
Custom ghost displayed while dragging:
vue
<template #drag-ghost="{ items, count, position }">
<div class="my-ghost">
{{ items[0]?.name }}
<span v-if="count > 1">+{{ count - 1 }}</span>
</div>
</template>CSS Variables
Set these on the container to customize colors:
css
.my-freeform {
--freeform-color-primary: #3b82f6;
--freeform-color-primary-light: #dbeafe;
--freeform-color-success: #22c55e;
--freeform-color-success-light: #dcfce7;
--freeform-color-danger: #ef4444;
--freeform-color-danger-light: #fee2e2;
--freeform-color-neutral: #f3f4f6;
--freeform-color-text: #374151;
}Manual Reorder
Set manualReorder to handle reordering yourself:
vue
<TheFreeform
v-model="items"
:manual-reorder="true"
@reorder="handleReorder"
>ts
function handleReorder(fromIndex: number, toIndex: number) {
// Custom reorder logic
const item = items.value.splice(fromIndex, 1)[0]
items.value.splice(toIndex, 0, item)
}The drop event
@drop fires for every completed drop and tells you which kind it was, so you can handle reordering, folders and cross-list drops in one place:
ts
function onDrop(payload: DropEventPayload) {
switch (payload.dropType) {
case 'reorder':
// payload.fromIndex / payload.toIndex
break
case 'container':
// payload.targetContainer
break
case 'zone':
// payload.targetZoneId / payload.targetContainerId
break
}
}Event order
@drag-end fires before @drop. If you need state that gets cleared when the drag ends - a hovered grid cell, for example - stash it in @drag-end and read it in @drop.
Note that @drop does not fire for every gesture: a drag that ends without a valid target only emits @drag-end. Reset drag-related UI state there, not in @drop.
