{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "data-table-reorder-grip",
  "title": "Data Table Reorder Grip",
  "description": "Slot-mounted drag grip for column reordering.",
  "dependencies": [
    "@virtuoso.dev/data-table",
    "lucide-react"
  ],
  "registryDependencies": [
    "petyosi/react-virtuoso/data-table"
  ],
  "files": [
    {
      "path": "registry/new-york/data-table/column-reorder/index.ts",
      "content": "export { DraggableGroupHeader } from './draggable-group-header'\nexport { ReorderDropZone } from './reorder-drop-zone'\nexport { ReorderGrip } from './reorder-grip'\nexport type { DraggableGroupHeaderProps } from './draggable-group-header'\n",
      "type": "registry:component",
      "target": "@ui/data-table/column-reorder/index.ts"
    },
    {
      "path": "registry/new-york/data-table/column-reorder/draggable-group-header.tsx",
      "content": "'use client'\n\nimport { useCellValue, usePublisher } from '@virtuoso.dev/data-table'\nimport {\n  beginColumnDrag$,\n  columnDragState$,\n  endColumnDrag$,\n  reorderColumnGroup$,\n  setColumnDropTarget$,\n} from '@virtuoso.dev/data-table/column-reorder'\n\nimport { cn } from '@/lib/utils'\n\ninterface DraggableGroupHeaderProps {\n  label: string\n  columnKeys: string[]\n  stickyGroup?: 'left' | 'right'\n}\n\nfunction DraggableGroupHeader({ label, columnKeys, stickyGroup }: DraggableGroupHeaderProps) {\n  const dragState = useCellValue(columnDragState$)\n  const beginColumnDrag = usePublisher(beginColumnDrag$)\n  const endColumnDrag = usePublisher(endColumnDrag$)\n  const setColumnDropTarget = usePublisher(setColumnDropTarget$)\n  const reorderColumnGroup = usePublisher(reorderColumnGroup$)\n\n  const isGroupDrag = (dragState.sourceKeys?.length ?? 0) > 1\n  const isSelfDrag = isGroupDrag && dragState.sourceKeys?.[0] === columnKeys[0]\n  const groupAnchorKey = dragState.dropTarget?.key\n  const dropPosition =\n    groupAnchorKey && (groupAnchorKey === columnKeys[0] || groupAnchorKey === columnKeys.at(-1)) ? dragState.dropTarget!.position : null\n\n  return (\n    <div\n      className={cn(\n        'relative flex items-center border-b border-border bg-muted px-2 py-1 text-xs font-semibold whitespace-nowrap',\n        stickyGroup && 'bg-muted/80'\n      )}\n      onDragOver={(ev) => {\n        if (!isGroupDrag) {\n          return\n        }\n        if (isSelfDrag) {\n          return\n        }\n        if (dragState.sourceSticky !== stickyGroup) {\n          return\n        }\n        ev.preventDefault()\n        ev.dataTransfer.dropEffect = 'move'\n        const rect = ev.currentTarget.getBoundingClientRect()\n        const position = ev.clientX < rect.left + rect.width / 2 ? 'before' : 'after'\n        const anchorKey = position === 'before' ? columnKeys[0]! : columnKeys.at(-1)!\n        if (dragState.dropTarget?.key === anchorKey && dragState.dropTarget.position === position) {\n          return\n        }\n        setColumnDropTarget({ key: anchorKey, position })\n      }}\n      onDragLeave={(ev) => {\n        const next = ev.relatedTarget as Node | null\n        if (next && ev.currentTarget.contains(next)) {\n          return\n        }\n        setColumnDropTarget(null)\n      }}\n      onDrop={(ev) => {\n        ev.preventDefault()\n        if (!isGroupDrag || isSelfDrag || !dragState.sourceKeys) {\n          setColumnDropTarget(null)\n          return\n        }\n        const rect = ev.currentTarget.getBoundingClientRect()\n        const position = ev.clientX < rect.left + rect.width / 2 ? 'before' : 'after'\n        const anchorKey = position === 'before' ? columnKeys[0]! : columnKeys.at(-1)!\n        setColumnDropTarget(null)\n        reorderColumnGroup({ sourceKeys: dragState.sourceKeys, targetKey: anchorKey, position })\n        endColumnDrag()\n      }}\n    >\n      {dropPosition === 'before' && (\n        <div className=\"pointer-events-none absolute top-1/2 left-0 z-[1] h-4 w-[2px] -translate-x-1/2 -translate-y-1/2 bg-primary\" />\n      )}\n      {dropPosition === 'after' && (\n        <div className=\"pointer-events-none absolute top-1/2 right-0 z-[1] h-4 w-[2px] translate-x-1/2 -translate-y-1/2 bg-primary\" />\n      )}\n      <span\n        draggable\n        onDragStart={(ev) => {\n          beginColumnDrag({ sourceKeys: columnKeys, sourceSticky: stickyGroup })\n          ev.dataTransfer.effectAllowed = 'move'\n          ev.dataTransfer.setData('text/plain', 'group')\n          const header = ev.currentTarget.parentElement!\n          const rect = header.getBoundingClientRect()\n          ev.dataTransfer.setDragImage(header, ev.clientX - rect.left, ev.clientY - rect.top)\n        }}\n        onDragEnd={() => {\n          endColumnDrag()\n        }}\n        className=\"mr-2 cursor-grab text-sm opacity-50 select-none\"\n      >\n        ⠿\n      </span>\n      {label}\n    </div>\n  )\n}\n\nexport { DraggableGroupHeader }\nexport type { DraggableGroupHeaderProps }\n",
      "type": "registry:component",
      "target": "@ui/data-table/column-reorder/draggable-group-header.tsx"
    },
    {
      "path": "registry/new-york/data-table/column-reorder/reorder-drop-zone.tsx",
      "content": "'use client'\n\nimport { useEffect } from 'react'\n\nimport { useCellValue, usePublisher } from '@virtuoso.dev/data-table'\nimport {\n  columnDragState$,\n  endColumnDrag$,\n  reorderColumnGroup$,\n  reorderColumns$,\n  setColumnDropTarget$,\n} from '@virtuoso.dev/data-table/column-reorder'\n\nimport type { HeaderSlotCustomComponent } from '@virtuoso.dev/data-table'\n\nexport const ReorderDropZone: HeaderSlotCustomComponent = ({ columnKey, columnState, headerRef }) => {\n  const dragState = useCellValue(columnDragState$)\n  const endColumnDrag = usePublisher(endColumnDrag$)\n  const setColumnDropTarget = usePublisher(setColumnDropTarget$)\n  const reorderColumns = usePublisher(reorderColumns$)\n  const reorderColumnGroup = usePublisher(reorderColumnGroup$)\n\n  const isSourceForThisColumn = dragState.sourceKeys?.includes(columnKey) ?? false\n  const isGroupDrag = (dragState.sourceKeys?.length ?? 0) > 1\n  const dropPosition = dragState.dropTarget?.key === columnKey ? dragState.dropTarget.position : null\n\n  useEffect(() => {\n    const header = headerRef.current\n    if (!header) {\n      return\n    }\n\n    const handleDragOver = (event: DragEvent) => {\n      if (!dragState.sourceKeys) {\n        return\n      }\n      if (isSourceForThisColumn) {\n        return\n      }\n      if (dragState.sourceSticky !== columnState.sticky) {\n        return\n      }\n\n      event.preventDefault()\n      event.dataTransfer!.dropEffect = 'move'\n      const rect = header.getBoundingClientRect()\n      const position = event.clientX < rect.left + rect.width / 2 ? 'before' : 'after'\n      if (dragState.dropTarget?.key === columnKey && dragState.dropTarget.position === position) {\n        return\n      }\n      setColumnDropTarget({ key: columnKey, position })\n    }\n\n    const handleDragLeave = (event: DragEvent) => {\n      const next = event.relatedTarget as Node | null\n      if (next && header.contains(next)) {\n        return\n      }\n      if (dragState.dropTarget?.key === columnKey) {\n        setColumnDropTarget(null)\n      }\n    }\n\n    const handleDrop = (event: DragEvent) => {\n      event.preventDefault()\n      if (!dragState.sourceKeys || isSourceForThisColumn) {\n        setColumnDropTarget(null)\n        return\n      }\n\n      const rect = header.getBoundingClientRect()\n      const position = event.clientX < rect.left + rect.width / 2 ? 'before' : 'after'\n      setColumnDropTarget(null)\n      if (isGroupDrag) {\n        reorderColumnGroup({ sourceKeys: dragState.sourceKeys, targetKey: columnKey, position })\n      } else {\n        reorderColumns({ sourceKey: dragState.sourceKeys[0]!, targetKey: columnKey, position })\n      }\n      endColumnDrag()\n    }\n\n    header.addEventListener('dragover', handleDragOver)\n    header.addEventListener('dragleave', handleDragLeave)\n    header.addEventListener('drop', handleDrop)\n\n    return () => {\n      header.removeEventListener('dragover', handleDragOver)\n      header.removeEventListener('dragleave', handleDragLeave)\n      header.removeEventListener('drop', handleDrop)\n    }\n  }, [\n    columnKey,\n    columnState.sticky,\n    dragState,\n    endColumnDrag,\n    headerRef,\n    isGroupDrag,\n    isSourceForThisColumn,\n    reorderColumnGroup,\n    reorderColumns,\n    setColumnDropTarget,\n  ])\n\n  if (!dragState.sourceKeys || !dropPosition) {\n    return null\n  }\n\n  return (\n    <div\n      className={\n        dropPosition === 'before'\n          ? 'pointer-events-none absolute top-1/2 left-0 z-[1] h-4 w-[2px] -translate-x-1/2 -translate-y-1/2 bg-primary'\n          : 'pointer-events-none absolute top-1/2 right-0 z-[1] h-4 w-[2px] translate-x-1/2 -translate-y-1/2 bg-primary'\n      }\n    />\n  )\n}\n",
      "type": "registry:component",
      "target": "@ui/data-table/column-reorder/reorder-drop-zone.tsx"
    },
    {
      "path": "registry/new-york/data-table/column-reorder/reorder-grip.tsx",
      "content": "'use client'\n\nimport { usePublisher } from '@virtuoso.dev/data-table'\nimport { beginColumnDrag$, endColumnDrag$ } from '@virtuoso.dev/data-table/column-reorder'\nimport { GripVertical } from 'lucide-react'\n\nimport type { HeaderSlotCustomComponent } from '@virtuoso.dev/data-table'\n\nexport const ReorderGrip: HeaderSlotCustomComponent = ({ columnKey, columnState, headerRef }) => {\n  const beginColumnDrag = usePublisher(beginColumnDrag$)\n  const endColumnDrag = usePublisher(endColumnDrag$)\n\n  return (\n    <span\n      draggable\n      onDragStart={(event) => {\n        beginColumnDrag({ sourceKeys: [columnKey], sourceSticky: columnState.sticky })\n        event.dataTransfer.effectAllowed = 'move'\n        event.dataTransfer.setData('text/plain', columnKey)\n        const header = headerRef.current\n        if (!header) {\n          return\n        }\n\n        const rect = header.getBoundingClientRect()\n        event.dataTransfer.setDragImage(header, event.clientX - rect.left, event.clientY - rect.top)\n      }}\n      onDragEnd={() => {\n        endColumnDrag()\n      }}\n      className=\"mr-2 inline-flex shrink-0 cursor-grab items-center text-muted-foreground/60 select-none hover:text-muted-foreground\"\n      aria-label=\"Drag to reorder column\"\n    >\n      <GripVertical className=\"size-4\" />\n    </span>\n  )\n}\n",
      "type": "registry:component",
      "target": "@ui/data-table/column-reorder/reorder-grip.tsx"
    }
  ],
  "type": "registry:component"
}