{
	"$schema": "https://shadcn-svelte.com/schema/registry-item.json",
	"name": "sortable",
	"title": "Sortable",
	"type": "registry:ui",
	"description": "A sortable list for vertical, horizontal, and grid layouts.",
	"dependencies": [
		"@lucide/svelte@^0.554.0"
	],
	"devDependencies": [
		"@lucide/svelte@^0.544.0"
	],
	"files": [
		{
			"content": "<script lang=\"ts\">\r\n\timport { cn } from '$UTILS$';\r\n\timport { setSortableContext } from './ctx';\r\n\timport { tick, type Snippet } from 'svelte';\r\n\r\n\tlet {\r\n\t\titems = $bindable([]),\r\n\t\tclass: className,\r\n\t\tchildren,\r\n\t\tonSort,\r\n\t\tanimation = 200\r\n\t}: {\r\n\t\titems: any[];\r\n\t\tclass?: string;\r\n\t\tchildren: Snippet;\r\n\t\tonSort?: (items: any[]) => void;\r\n\t\tanimation?: number;\r\n\t} = $props();\r\n\r\n\tlet draggedId = $state<string | null>(null);\r\n\tlet itemNodes = new Map<string, HTMLElement>();\r\n\r\n\tasync function move(fromIdx: number, toIdx: number) {\r\n\t\tconst rects = new Map<string, DOMRect>();\r\n\t\titemNodes.forEach((node, id) => rects.set(id, node.getBoundingClientRect()));\r\n\r\n\t\tconst newItems = [...items];\r\n\t\tconst [item] = newItems.splice(fromIdx, 1);\r\n\t\tnewItems.splice(toIdx, 0, item);\r\n\t\titems = newItems;\r\n\t\tonSort?.(newItems);\r\n\r\n\t\tawait tick();\r\n\r\n\t\titemNodes.forEach((node, id) => {\r\n\t\t\tconst prev = rects.get(id);\r\n\t\t\tif (!prev) return;\r\n\t\t\tconst curr = node.getBoundingClientRect();\r\n\t\t\tconst dx = prev.left - curr.left;\r\n\t\t\tconst dy = prev.top - curr.top;\r\n\t\t\tif (dx || dy) {\r\n\t\t\t\tnode.style.transition = 'none';\r\n\t\t\t\tnode.style.transform = `translate(${dx}px, ${dy}px)`;\r\n\t\t\t\tnode.offsetHeight;\r\n\t\t\t\tnode.style.transition = `transform ${animation}ms cubic-bezier(0.2, 0, 0, 1)`;\r\n\t\t\t\tnode.style.transform = '';\r\n\t\t\t}\r\n\t\t});\r\n\t}\r\n\r\n\tsetSortableContext({\r\n\t\tgetDraggedId: () => draggedId,\r\n\t\tregisterItem: (id, node) => itemNodes.set(id, node),\r\n\t\tunregisterItem: (id) => itemNodes.delete(id),\r\n\t\tonDragStart: (e, id) => {\r\n\t\t\tdraggedId = id;\r\n\t\t\tif (e.dataTransfer) {\r\n\t\t\t\te.dataTransfer.effectAllowed = 'move';\r\n\t\t\t\tconst ghost = document.createElement('div');\r\n\t\t\t\te.dataTransfer.setDragImage(ghost, 0, 0);\r\n\t\t\t}\r\n\t\t},\r\n\t\tonDragOver: (e, targetId) => {\r\n\t\t\te.preventDefault();\r\n\t\t\tif (!draggedId || draggedId === targetId) return;\r\n\r\n\t\t\tconst fromIdx = items.findIndex((i) => i.id === draggedId);\r\n\t\t\tconst toIdx = items.findIndex((i) => i.id === targetId);\r\n\r\n\t\t\tif (fromIdx !== -1 && toIdx !== -1) {\r\n\t\t\t\tmove(fromIdx, toIdx);\r\n\t\t\t}\r\n\t\t},\r\n\t\tonDragEnd: () => {\r\n\t\t\tdraggedId = null;\r\n\t\t}\r\n\t});\r\n</script>\r\n\r\n<div class={cn(className)}>\r\n\t{@render children()}\r\n</div>\r\n",
			"type": "registry:ui",
			"target": "sortable/sortable.svelte"
		},
		{
			"content": "<script lang=\"ts\">\r\n\timport { cn } from '$UTILS$';\r\n\timport { getSortableContext } from './ctx';\r\n\timport { onMount, onDestroy, type Snippet } from 'svelte';\r\n\r\n\tlet {\r\n\t\tid,\r\n\t\tclass: className,\r\n\t\tchildren\r\n\t}: { id: string; class?: string; children: Snippet } = $props();\r\n\r\n\tconst ctx = getSortableContext();\r\n\tlet node = $state<HTMLElement>();\r\n\r\n\tonMount(() => {\r\n\t\tif (node) ctx.registerItem(id, node);\r\n\t});\r\n\tonDestroy(() => ctx.unregisterItem(id));\r\n\r\n\tlet isDragging = $derived(ctx?.getDraggedId() === id);\r\n</script>\r\n\r\n<div\r\n\tbind:this={node}\r\n\tclass={cn('relative transition-all', isDragging && 'opacity-30', className)}\r\n\tdraggable=\"false\"\r\n\tondragstart={(e) => ctx.onDragStart(e, id)}\r\n\tondragover={(e) => ctx.onDragOver(e, id)}\r\n\tondragend={(e) => ctx.onDragEnd(e)}\r\n>\r\n\t{@render children()}\r\n</div>\r\n",
			"type": "registry:ui",
			"target": "sortable/sortable-item.svelte"
		},
		{
			"content": "<script lang=\"ts\">\r\n\timport { cn } from '$UTILS$';\r\n\timport { GripVertical } from '@lucide/svelte';\r\n\timport type { Snippet } from 'svelte';\r\n\r\n\tlet { class: className, children }: { class?: string; children?: Snippet } = $props();\r\n\r\n\tfunction setDraggable(e: MouseEvent, state: boolean) {\r\n\t\tconst target = e.currentTarget as HTMLElement;\r\n\t\tconst item =\r\n\t\t\ttarget.closest('div[draggable=\"false\"]') || target.closest('div[draggable=\"true\"]');\r\n\t\tif (item instanceof HTMLElement) {\r\n\t\t\titem.draggable = state;\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<div\r\n\tclass={cn('cursor-grab active:cursor-grabbing p-1 touch-none', className)}\r\n\tonmousedown={(e) => setDraggable(e, true)}\r\n\tonmouseup={(e) => setDraggable(e, false)}\r\n\tonmouseleave={(e) => setDraggable(e, false)}\r\n>\r\n\t{#if children}\r\n\t\t{@render children()}\r\n\t{:else}\r\n\t\t<GripVertical class=\"size-4 opacity-50\" />\r\n\t{/if}\r\n</div>\r\n",
			"type": "registry:ui",
			"target": "sortable/sortable-handle.svelte"
		},
		{
			"content": "import { getContext, setContext } from 'svelte';\r\n\r\nconst SORTABLE_KEY = Symbol('sortable');\r\n\r\nexport type SortableContext = {\r\n\tonDragStart: (e: DragEvent, id: string) => void;\r\n\tonDragOver: (e: DragEvent, id: string) => void;\r\n\tonDragEnd: (e: DragEvent) => void;\r\n\tregisterItem: (id: string, node: HTMLElement) => void;\r\n\tunregisterItem: (id: string) => void;\r\n\tgetDraggedId: () => string | null;\r\n};\r\n\r\nexport function setSortableContext(props: SortableContext) {\r\n\tsetContext(SORTABLE_KEY, props);\r\n}\r\n\r\nexport function getSortableContext() {\r\n\treturn getContext<SortableContext>(SORTABLE_KEY);\r\n}\r\n",
			"type": "registry:ui",
			"target": "sortable/ctx.ts"
		},
		{
			"content": "import Sortable from './sortable.svelte';\nimport SortableItem from './sortable-item.svelte';\nimport SortableHandle from './sortable-handle.svelte';\n\nexport { Sortable as Root, SortableItem as Item, SortableHandle as Handle };\n",
			"type": "registry:ui",
			"target": "sortable/index.ts"
		}
	]
}