Files
lucent-laravel/front/js/common/Sortable.svelte
T
2026-01-08 15:19:08 +02:00

151 lines
4.6 KiB
Svelte

<script>
import { arrayMoveElement } from "../helpers";
import { flip } from "svelte/animate";
let {
sortableClass,
onUpdate,
items,
itemView,
itemCssClass,
itemKey = "id",
type = "list",
handleClass = null,
disabled = false,
} = $props();
// let sortableInstance = $state();
let sortableContainer = $state();
let draggedItem = $state();
function handleDragStart(event, item) {
if (disabled) {
return false;
}
draggedItem = item;
// Set data required for drag operation
event.dataTransfer.effectAllowed = "move";
event.dataTransfer.setData("text/plain", item.id);
// Set a ghost drag image
event.currentTarget.classList.add("dragging");
}
// Handle drag over another item
function handleDragOver(event) {
event.preventDefault();
event.target.closest(".draggable-item").classList.add("dragover");
event.dataTransfer.dropEffect = "move";
}
// Handle dropping the item
function handleDrop(event, targetItem) {
event.preventDefault();
if (draggedItem === targetItem) return;
// Find positions of dragged and target items
const draggedIndex = items.findIndex(
(item) => getItem(item, itemKey) === getItem(draggedItem, itemKey),
);
const targetIndex = items.findIndex(
(item) => getItem(item, itemKey) === getItem(targetItem, itemKey),
);
onUpdate(
arrayMoveElement([...items], draggedIndex, targetIndex),
draggedIndex,
targetIndex,
);
}
function getItem(item, key) {
if (key === null) {
return item;
}
if (key.includes(".")) {
return key.split(".").reduce((a, b) => a[b] ?? null, item);
}
return item[key];
}
function handleDragEnd(event) {
event.target.classList.remove("dragging");
sortableContainer
.querySelector(".dragover")
?.classList.remove("dragover");
draggedItem = null;
const draggableItem = event.target.closest(".draggable-item");
draggableItem.draggable = false;
}
function handleDragLeave(event) {
// event.target.classList.remove("dragover");
const draggableItem = event.target.closest(".draggable-item");
draggableItem.classList.remove("dragover");
draggableItem.draggable = false;
}
function handleMouseDown(e) {
const handleEl = e.target.closest("." + handleClass);
if (!handleEl) {
return true;
}
const draggableItem = e.target.closest(".draggable-item");
draggableItem.draggable = true;
}
function handleMouseUp(e) {
const handleEl = e.target.closest("." + handleClass);
if (!handleEl) {
return true;
}
const draggableItem = e.target.closest(".draggable-item");
draggableItem.draggable = false;
}
</script>
{#if type === "table"}
<tbody
class="sortable-container {sortableClass}"
bind:this={sortableContainer}
>
{#each items as item (getItem(item, itemKey))}
<tr
animate:flip={{ duration: 100 }}
class="draggable-item {itemCssClass}"
draggable="false"
onmousedown={handleMouseDown}
onmouseup={handleMouseUp}
ondragstart={(e) => handleDragStart(e, item)}
ondragover={handleDragOver}
ondragleave={handleDragLeave}
ondrop={(e) => handleDrop(e, item)}
ondragend={handleDragEnd}
>
{@render itemView(item)}
</tr>
{/each}
</tbody>
{:else}
<div
class="sortable-container {sortableClass}"
bind:this={sortableContainer}
>
{#each items as item (getItem(item, itemKey))}
<div
animate:flip={{ duration: 100 }}
role="listitem"
class="draggable-item {itemCssClass}"
draggable="false"
onmousedown={handleMouseDown}
onmouseup={handleMouseUp}
ondragstart={(e) => handleDragStart(e, item)}
ondragover={handleDragOver}
ondragleave={handleDragLeave}
ondrop={(e) => handleDrop(e, item)}
ondragend={handleDragEnd}
>
{@render itemView(item)}
</div>
{/each}
</div>
{/if}